--- name: cicd-gen description: 自动检测项目技术栈,生成 GitHub Actions workflow 及其他 CI/CD 配置 triggers: - "生成 ci" - "配置流水线" - "generate ci/cd" - "github actions" - "setup workflow" - "ci cd pipeline" --- # Skill: CI/CD Pipeline 智能生成 自动检测项目类型和技术栈,生成 GitHub Actions workflow 及其他 CI/CD 配置。 ## 触发条件 当用户请求以下操作时激活: - "生成 ci"、"配置流水线"、"pipeline" - "generate ci/cd"、"github actions" - "给项目加 ci"、"setup workflow" - 需要自动化构建、测试、部署流程 --- ## 执行步骤 ### 第一步:检测项目类型和技术栈 ```bash # Node.js 检测(验证 package.json 有效性) [ -f package.json ] && grep -q '"name"' package.json && echo "NODE_PROJECT" # Python 检测 [ -f pyproject.toml ] && grep -qE '\[project\]|\[tool\.' pyproject.toml && echo "PYTHON_PROJECT" [ -f requirements.txt ] && echo "PYTHON_PROJECT" # Go 检测 [ -f go.mod ] && grep -q 'module ' go.mod && echo "GO_PROJECT" # Rust 检测 [ -f Cargo.toml ] && grep -q '\[package\]' Cargo.toml && echo "RUST_PROJECT" # JS/TS runtime detection if [ -f bun.lockb ]; then echo "RUNTIME=bun" elif [ -f pnpm-lock.yaml ]; then echo "RUNTIME=pnpm" elif [ -f yarn.lock ]; then echo "RUNTIME=yarn" elif [ -f package.json ]; then echo "RUNTIME=npm" fi # Framework detection (JS/TS) — 匹配 dependencies/devDependencies 中的包名 # 框架优先级:Next.js > Nuxt > Remix > Astro > Vite > CRA grep -E '"(next|nuxt|remix|astro|vite|gatsby)":\s*"' package.json 2>/dev/null # Python framework detection grep -rE '"(fastapi|flask|django|streamlit)"' requirements.txt pyproject.toml setup.py 2>/dev/null # Go framework detection grep -E '^\s*(github\.com/gin-gonic/gin|github\.com/labstack/echo|github\.com/gofiber/fiber|github\.com/go-chi/chi)' go.mod 2>/dev/null # Rust framework detection grep -E '(actix-web|axum|rocket|warp)\s*=' Cargo.toml 2>/dev/null # Docker detection [ -f Dockerfile ] && echo "DOCKER_PROJECT" [ -f docker-compose.yml ] && echo "DOCKER_COMPOSE" # Monorepo detection [ -f lerna.json ] && echo "MONOREPO=lerna" [ -f turbo.json ] && echo "MONOREPO=turborepo" [ -f nx.json ] && echo "MONOREPO=nx" [ -f pnpm-workspace.yaml ] && echo "MONOREPO=pnpm-workspace" if [ -f "package.json" ] && grep -q '"workspaces"' package.json; then echo "MONOREPO=workspaces" fi ls packages/*/package.json apps/*/package.json 2>/dev/null ``` 根据检测结果确定: - 语言 + 框架 + 运行时 - 包管理器(bun/pnpm/yarn/npm/pip/cargo/go) - 是否为 monorepo - 是否使用 Docker - 现有 CI 配置(可能已有部分配置) ### 第二步:检查现有 CI 配置 ```bash # Check for existing CI/CD configurations ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null ls .gitlab-ci.yml .circleci/config.yml Jenkinsfile 2>/dev/null ls .github/dependabot.yml renovate.json .renovaterc 2>/dev/null ``` 若已有配置,读取并作为基础进行增量更新,而非从零生成。 ### 第三步:确定流水线模式 根据项目类型选择合适的流水线模式: | 项目类型 | 流水线阶段 | |---------|-----------| | 前端应用 (Next.js/Vite/React) | lint -> type-check -> test -> build -> deploy | | 后端 API (Express/FastAPI/Gin) | lint -> test -> build -> docker-push -> deploy | | 全栈 (Next.js full-stack) | lint -> type-check -> test -> build -> deploy | | 库/SDK (npm package) | lint -> test -> build -> publish | | CLI 工具 | lint -> test -> build -> release | | Monorepo | 路径过滤 -> 各子项目独立流水线 | | Docker 项目 | lint -> test -> docker-build -> docker-push -> deploy | | 静态站点 | lint -> build -> deploy | ### 第四步:生成 GitHub Actions Workflow 在 `.github/workflows/` 目录下生成 workflow 文件。 --- ## Workflow 模板 ### Node.js / Bun 项目 ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: oven-sh/setup-bun@735343b667d82a9b6e7e29e1a5a61cce62063358 # v2.0.1 (pinned SHA) with: bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile - name: Lint run: bun run lint - name: Type check run: bun run type-check - name: Test run: bun test - name: Build run: bun run build ``` **npm 变体(替换 bun 部分):** ```yaml - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.3 (pinned SHA) with: node-version: 20 cache: 'npm' - name: Install dependencies run: npm ci ``` **pnpm 变体:** ```yaml - uses: pnpm/action-setup@a7487c7e89a18df77c0e7081cb187228765167f8 # v4.0.0 (pinned SHA) with: version: 9 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.3 (pinned SHA) with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile ``` ### Python 项目 ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: ci: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.11', '3.12'] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v5.2.0 (pinned SHA) with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - name: Lint run: | ruff check . ruff format --check . - name: Type check # NOTE: adjust the path to match your project structure (e.g., mypy src/ tests/) run: mypy src/ - name: Test # NOTE: coverage path should match your project layout # Recommended: configure in pyproject.toml instead: # [tool.pytest.ini_options] # addopts = "--cov=src --cov-report=xml" # [tool.coverage.run] # source = ["src"] run: pytest --cov=src --cov-report=xml - name: Upload coverage uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 (pinned SHA) with: token: ${{ secrets.CODECOV_TOKEN }} ``` **uv 变体(替换 pip 部分):** ```yaml - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v4.1.0 (pinned SHA) - name: Install dependencies run: uv sync --frozen - name: Lint run: | uv run ruff check . uv run ruff format --check . ``` ### Go 项目 ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 (pinned SHA) with: go-version-file: go.mod cache: true - name: Lint uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 (pinned SHA) with: version: latest - name: Test run: go test -race -coverprofile=coverage.out ./... - name: Build run: go build -v ./... ``` ### Rust 项目 ```yaml name: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a # stable (pinned SHA) with: components: clippy, rustfmt - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.3 (pinned SHA) - name: Format check run: cargo fmt --all -- --check - name: Lint run: cargo clippy --all-targets --all-features -- -D warnings - name: Test run: cargo test --all-features - name: Build run: cargo build --release ``` ### Docker 项目 ```yaml name: Docker Build & Push on: push: branches: [main] tags: ['v*'] permissions: contents: read packages: write jobs: docker: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v3.6.1 (pinned SHA) # Registry options (uncomment the one you need): # # GHCR (GitHub Container Registry) — default, uses GITHUB_TOKEN # Docker Hub — set DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets # registry: (omit for Docker Hub) # username: ${{ secrets.DOCKERHUB_USERNAME }} # password: ${{ secrets.DOCKERHUB_TOKEN }} # AWS ECR — use aws-actions/amazon-ecr-login instead (see ECR section below) # Quay.io: # registry: quay.io # username: ${{ secrets.QUAY_USERNAME }} # password: ${{ secrets.QUAY_PASSWORD }} - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.3.0 (pinned SHA) with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 (pinned SHA) id: meta with: # Change the image name to match your registry: # Docker Hub: docker.io/${{ github.repository }} # Quay.io: quay.io/${{ github.repository }} images: ghcr.io/${{ github.repository }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=sha - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v6.9.0 (pinned SHA) with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max ``` --- ## 部署目标配置 ### Vercel 部署 ```yaml deploy: needs: ci runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' permissions: contents: read deployments: write steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) # 推荐方案:直接使用 Vercel CLI(而非第三方 Action) - uses: oven-sh/setup-bun@735343b667d82a9b6e7e29e1a5a61cce62063358 # v2.0.1 (pinned SHA) - name: Deploy to Vercel env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} run: bunx vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} ``` > ⚠️ Action 版本需定期更新。建议: > - 使用 Vercel 官方 CLI 而非第三方 Action(如 `amondnet/vercel-action`) > - 若仍使用第三方 Action,请 pin 到最新 commit SHA 并注释版本号 **所需 Secrets:** - `VERCEL_TOKEN` — Vercel API Token - `VERCEL_ORG_ID` — Vercel Organization ID - `VERCEL_PROJECT_ID` — Vercel Project ID ### Netlify 部署 ```yaml deploy: needs: ci runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' permissions: contents: read steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - name: Build run: bun run build - uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0 (pinned SHA) with: publish-dir: './dist' production-branch: main deploy-message: 'Deploy from GitHub Actions' env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} ``` **所需 Secrets:** - `NETLIFY_AUTH_TOKEN` — Netlify Personal Access Token - `NETLIFY_SITE_ID` — Netlify Site ID ### Docker Registry 部署(AWS ECR) ```yaml deploy: needs: ci runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' permissions: id-token: write contents: read steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 (pinned SHA) with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-east-1 - uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a0945b076 # v2.0.1 (pinned SHA) id: ecr - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v6.9.0 (pinned SHA) with: context: . push: true tags: ${{ steps.ecr.outputs.registry }}/${{ github.repository }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max ``` ### npm Publish ```yaml publish: needs: ci runs-on: ubuntu-latest # 添加 repository 检查防止 fork 意外发布 if: >- github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && # TODO: Replace with your actual repository name github.repository == 'OWNER/REPO_NAME' permissions: contents: read id-token: write steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.3 (pinned SHA) with: node-version: 20 registry-url: 'https://registry.npmjs.org' - name: Install & Build run: | npm ci npm run build - name: Preview publish contents run: npm pack --dry-run - name: Publish run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` > ⚠️ npm publish 安全建议: > - 添加 `github.repository` 检查,防止 fork 仓库误触发发布 > - 建议在 publish 前添加 `npm pack --dry-run` 预览发布内容 > - 使用 npm provenance(`--provenance`)增强供应链安全 > - 建议配合 GitHub Environment 的 manual approval 使用 ### GitHub Pages 部署 ```yaml deploy: needs: ci runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - name: Build run: bun run build - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 (pinned SHA) with: path: './dist' - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac553fd0d28 # v4.0.5 (pinned SHA) id: deployment ``` --- ## 缓存策略配置 ### 缓存策略速查表 | 技术栈 | 缓存方式 | 缓存路径 / Key | |-------|---------|---------------| | Bun | `setup-bun` 内置缓存 | `bun.lockb` 的内容哈希,路径由 bun 自动管理,使用 `setup-bun` action 的内置缓存 | | npm | `actions/setup-node` + `cache: 'npm'` | `package-lock.json` 的内容哈希,`~/.npm` | | pnpm | `actions/setup-node` + `cache: 'pnpm'` | `pnpm-lock.yaml` 的内容哈希,`setup-node` 的 `cache: pnpm` 自动处理 | | yarn | `actions/setup-node` + `cache: 'yarn'` | `yarn.lock` 的内容哈希,`setup-node` 的 `cache: yarn` 自动处理 | | pip | `actions/setup-python` + `cache: 'pip'` | 自动管理,key: `requirements*.txt` | | Go | `actions/setup-go` + `cache: true` | 自动管理,key: `go.sum` | | Rust | `Swatinem/rust-cache` (SHA pinned) | `target/`,key: `Cargo.lock` | | Docker | `docker/build-push-action` + `cache-from/to: type=gha` | GitHub Actions cache | | Gradle | `actions/setup-java` + `cache: 'gradle'` | `~/.gradle/caches` | | Maven | `actions/setup-java` + `cache: 'maven'` | `~/.m2/repository` | ### Bun 缓存配置示例 ```yaml - uses: oven-sh/setup-bun@735343b667d82a9b6e7e29e1a5a61cce62063358 # v2.0.1 (pinned SHA) with: bun-version: latest # bun install automatically uses lockfile-based caching - name: Install dependencies run: bun install --frozen-lockfile ``` ### 自定义缓存(通用) ```yaml - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 (pinned SHA) with: path: | ~/.cache/some-tool .build-cache key: ${{ runner.os }}-tool-${{ hashFiles('**/lockfile') }} restore-keys: | ${{ runner.os }}-tool- ``` --- ## Monorepo 检测和路径过滤 ### Monorepo 检测逻辑 ```bash # Turborepo [ -f turbo.json ] && echo "MONOREPO=turborepo" # Nx [ -f nx.json ] && echo "MONOREPO=nx" # Lerna [ -f lerna.json ] && echo "MONOREPO=lerna" # pnpm workspace [ -f pnpm-workspace.yaml ] && echo "MONOREPO=pnpm-workspace" # npm/yarn/bun workspaces (via package.json "workspaces" field) if [ -f "package.json" ] && grep -q '"workspaces"' package.json; then echo "MONOREPO=workspaces" fi # General multi-package detection (fallback) ls packages/*/package.json apps/*/package.json 2>/dev/null ``` ### 路径过滤 Workflow ```yaml name: CI - Web App on: push: branches: [main] paths: - 'apps/web/**' - 'packages/shared/**' - 'package.json' - 'bun.lockb' pull_request: branches: [main] paths: - 'apps/web/**' - 'packages/shared/**' jobs: ci-web: runs-on: ubuntu-latest steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 (pinned SHA) - uses: oven-sh/setup-bun@735343b667d82a9b6e7e29e1a5a61cce62063358 # v2.0.1 (pinned SHA) - run: bun install --frozen-lockfile # NOTE: --filter syntax differs by tool: # bun workspace: bun run --filter=web