Monorepo 按变更自动部署

Monorepo 项目的按变更自动部署

当一个仓库里放了多个子项目,它们共享同一套底层代码,每次推送如果全部重新构建和部署,既慢又浪费构建额度。更好的做法是:改了哪个项目,就只部署哪个项目


共享代码的困境

典型的 monorepo 目录结构:

my-project/
├── apps/
│   ├── web/          # 主站
│   ├── admin/        # 后台
│   └── docs/         # 文档站
├── packages/
│   ├── shared-ui/    # 公共组件
│   └── utils/        # 工具函数
└── package.json

apps/ 下的三个项目都依赖 packages/ 里的公共代码。公共代码一改,三个项目都可能受影响;但只改 admin 的业务页面,webdocs 就没必要动。


思路

在 GitHub Actions 里加一个"变更检测"步骤,判断这次提交改了哪些目录。如果只改了 apps/web,就只构建和部署 web;如果改了 packages/utils,所有依赖它的项目都触发部署。

推送代码 → 检测哪些目录变了 → 只构建和部署变更涉及的项目 → 未变更的跳过

示例

name: Deploy

on:
  push:
    branches: [main]

jobs:
  # 第一步:检测变更
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      web: ${{ steps.changes.outputs.web }}
      admin: ${{ steps.changes.outputs.admin }}
      docs: ${{ steps.changes.outputs.docs }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: dorny/paths-filter@v2
        id: changes
        with:
          filters: |
            web:
              - 'apps/web/**'
              - 'packages/**'
            admin:
              - 'apps/admin/**'
              - 'packages/**'
            docs:
              - 'apps/docs/**'
              - 'packages/**'

  # 第二步:按条件部署
  deploy-web:
    needs: detect-changes
    if: needs.detect-changes.outputs.web == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... 构建和部署 web

  deploy-admin:
    needs: detect-changes
    if: needs.detect-changes.outputs.admin == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... 构建和部署 admin

  deploy-docs:
    needs: detect-changes
    if: needs.detect-changes.outputs.docs == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... 构建和部署 docs

关键设计

变更规则怎么写

每个子项目的 filters 要同时包含自己的目录和公共包目录:

filters: |
  web:
    - 'apps/web/**'      # 自己的业务代码
    - 'packages/**'       # 公共依赖
  admin:
    - 'apps/admin/**'
    - 'packages/**'

这样公共包一改,所有子项目都会触发部署,不会遗漏。

如果公共包内部也可以区分为"仅 A 项目用"和"B、C 共用",可以写得更细:

filters: |
  web:
    - 'apps/web/**'
    - 'packages/shared-ui/**'
    - 'packages/utils/**'
  admin:
    - 'apps/admin/**'
    - 'packages/utils/**'

条件跳过

通过 needs 拿到上一步的输出结果,用 if 决定是否执行:

deploy-admin:
  needs: detect-changes
  if: needs.detect-changes.outputs.admin == 'true'

没变化的项目这个 Job 直接跳过,不浪费 CI 时间。

支持手动全量部署

加一个手动触发入口,允许绕开变更检测,强制部署所有项目:

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      deploy_all:
        description: '强制全量部署'
        type: boolean
        default: false

然后在 Job 的条件里加判断:

if: |
  needs.detect-changes.outputs.admin == 'true' ||
  github.event.inputs.deploy_all == 'true'