GitHub Actions 可复用工作流与 Monorepo 策略:缓存优化、矩阵构建与发布自动化

GitHub Actions 高级实践:可复用工作流(Reusable Workflows)设计、跨仓库调用、Composite Action 组合、Monorepo CI/CD(Turborepo/pnpm workspace)策略、矩阵构建、Docker 多阶段缓存、Artifact 传递、语义化版本管理与 Changesets 自动发布。

在大型组织和 Monorepo 项目中,CI/CD 的 DRY(Don’t Repeat Yourself)原则至关重要。GitHub Actions 提供可复用工作流(Reusable Workflows)和 Composite Actions,让组织级别的工作流标准化成为可能。本文覆盖从可复用设计到 Monorepo CI 的完整实践。


一、可复用工作流(Reusable Workflows)

1.1 为什么需要可复用工作流?

多个项目的重复 CI 配置:
- Project A: install → lint → test → build
- Project B: install → lint → test → build
- Project C: install → lint → test → build

→ 提取到 shared-workflows → 所有项目调用同一模板

优势:集中维护、统一标准、减少重复、强制合规。

1.2 定义可复用工作流

# .github/workflows/reusable-test.yml
name: Reusable Node.js Test

on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version'
        type: string
        required: true
        default: '20'
      run-e2e:
        description: 'Run E2E tests'
        type: boolean
        required: false
        default: false
      working-directory:
        description: 'Working directory'
        type: string
        required: false
        default: '.'
    secrets:
      codecov-token:
        description: 'Codecov upload token'
        required: false
    outputs:
      test-passed:
        description: 'Whether tests passed'
        value: ${{ jobs.test.outputs.result }}

jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      result: ${{ steps.test.outputs.result }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'

      - run: npm ci
        working-directory: ${{ inputs.working-directory }}

      - name: Run Lint
        run: npm run lint
        working-directory: ${{ inputs.working-directory }}

      - name: Run Type Check
        run: npm run type-check
        working-directory: ${{ inputs.working-directory }}

      - name: Run Tests
        id: test
        run: |
          npm test -- --coverage
          echo "result=success" >> $GITHUB_OUTPUT
        working-directory: ${{ inputs.working-directory }}

      - name: Upload Coverage
        if: inputs.codecov-token != ''
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.codecov-token }}
          files: ${{ inputs.working-directory }}/coverage/lcov.info

      - name: Run E2E
        if: inputs.run-e2e
        run: npm run test:e2e
        working-directory: ${{ inputs.working-directory }}

1.3 调用可复用工作流

# .github/workflows/main.yml
name: CI

on: [push, pull_request]

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
      run-e2e: true
    secrets:
      codecov-token: ${{ secrets.CODECOV_TOKEN }}

1.4 跨仓库调用

jobs:
  call-shared:
    uses: my-org/shared-workflows/.github/workflows/test.yml@main
    with:
      node-version: '20'
    secrets:
      inherit  # 继承调用仓库的 secrets

二、Monorepo CI 策略

2.1 pnpm Workspace + Turborepo

name: Monorepo CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile

      - name: Build & Test affected
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
        run: pnpm turbo run lint type-check test build --filter=[origin/main...HEAD]

      - name: Build all (main branch)
        if: github.ref == 'refs/heads/main'
        run: pnpm turbo run build

2.2 基于路径的筛选

on:
  push:
    paths:
      - 'apps/web/**'
      - 'packages/ui/**'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - '.github/workflows/web.yml'

三、Composite Action 组合

# .github/actions/setup/action.yml
name: 'Setup Node + pnpm + Cache'
description: 'Sets up Node.js with pnpm and caching'

inputs:
  node-version:
    description: 'Node.js version'
    required: true
    default: '20'

runs:
  using: "composite"
  steps:
    - uses: pnpm/action-setup@v3
      with:
        version: 9
    - uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'pnpm'
    - run: pnpm install --frozen-lockfile
      shell: bash

四、发布自动化

4.1 Changesets 自动版本管理

name: Release

on:
  push:
    branches: [main]

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install

      - name: Create Release PR or Publish
        uses: changesets/action@v1
        with:
          version: pnpm changeset version
          publish: pnpm changeset publish
          commit: 'chore(release): version packages'
          title: 'chore(release): version packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

五、性能优化矩阵

优化手段效果适用
cache: 'npm'-30~60s通用
pnpm store 缓存-40%pnpm 项目
turbo run --filter只构建变更Monorepo
paths 过滤避免无意义触发大仓库
cancel-in-progress节省并发资源快速迭代

相关阅读

下一篇 →

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「saas」更多文章