|$ curl https://forge-ai.dev/api/markdown?path=docs/ci/monorepo
$cat docs/monorepo-ci.md
updated Recently·30 min read·published
Monorepo CI
Introduction
Monorepos (one repository, many packages) face a CI challenge: running all tests for every change is wasteful. The solution is incremental CI — only build, test, and deploy packages affected by a change, using dependency graphs and caching.
Turborepo
turbo.json
JSON
| 1 | // turbo.json — Turborepo pipeline configuration |
| 2 | { |
| 3 | "$schema": "https://turbo.build/schema.json", |
| 4 | "tasks": { |
| 5 | "build": { |
| 6 | "dependsOn": ["^build"], |
| 7 | "outputs": ["dist/**", ".next/**"], |
| 8 | "inputs": ["src/**", "package.json"] |
| 9 | }, |
| 10 | "test": { |
| 11 | "dependsOn": ["build"], |
| 12 | "inputs": ["src/**", "tests/**"], |
| 13 | "outputs": [] |
| 14 | }, |
| 15 | "lint": { |
| 16 | "inputs": ["src/**"], |
| 17 | "outputs": [] |
| 18 | }, |
| 19 | "dev": { |
| 20 | "cache": false, |
| 21 | "persistent": true |
| 22 | } |
| 23 | } |
| 24 | } |
turborepo-commands.sh
Bash
| 1 | # Turborepo commands |
| 2 | npx turbo run build # Build all packages |
| 3 | npx turbo run build --filter=web # Build only web and its dependencies |
| 4 | npx turbo run test --filter=./packages/ui # Test UI package |
| 5 | |
| 6 | # Only run tasks affected by changes (remote cache) |
| 7 | npx turbo run build test --since=main |
| 8 | |
| 9 | # Verbose output to see cache hits |
| 10 | npx turbo run build --verbose |
| 11 | |
| 12 | # Prune packages not needed for deployment |
| 13 | npx turbo prune web --docker # Output for Docker build |
| 14 | |
| 15 | # GitHub Actions with Turborepo |
| 16 | # - uses: actions/cache@v4 |
| 17 | # with: |
| 18 | # path: .turbo |
| 19 | # key: turbo-${{ hashFiles('**/turbo.json') }}-${{ github.sha }} |
| 20 | # restore-keys: turbo- |
ℹ
info
Turborepo's remote caching shares build artifacts across your team and CI. Configure it with npx turbo login && npx turbo link to get cache hits on CI runs triggered by other team members.
Nx
nx.json
TypeScript
| 1 | // nx.json — Nx workspace configuration |
| 2 | { |
| 3 | "tasksRunnerOptions": { |
| 4 | "default": { |
| 5 | "runner": "nx-cloud", |
| 6 | "options": { |
| 7 | "cacheDirectory": ".nx/cache", |
| 8 | "accessToken": "xxx" |
| 9 | } |
| 10 | } |
| 11 | }, |
| 12 | "targetDefaults": { |
| 13 | "build": { |
| 14 | "dependsOn": ["^build"], |
| 15 | "inputs": ["production", "^production"] |
| 16 | } |
| 17 | } |
| 18 | } |
| 19 | |
| 20 | # Nx affected commands — only process changed projects |
| 21 | npx nx affected --target=build # Build affected projects |
| 22 | npx nx affected --target=test # Test affected projects |
| 23 | npx nx affected --target=lint # Lint affected projects |
| 24 | npx nx affected --target=e2e # E2E test affected projects |
| 25 | |
| 26 | # Visualize dependency graph |
| 27 | npx nx graph |
| 28 | |
| 29 | # Run specific project |
| 30 | npx nx build web |
| 31 | npx nx test api |
| 32 | |
| 33 | # Cloud caching with Nx Cloud |
| 34 | npx nx connect-to-nx-cloud |
✓
best practice
Use nx affected in CI to skip unchanged packages. Nx uses a dependency graph and file hashes to determine what changed — only those packages are built and tested.
GitHub Actions Monorepo CI
monorepo-ci.yml
YAML
| 1 | # GitHub Actions: only CI for changed packages |
| 2 | name: CI |
| 3 | on: |
| 4 | pull_request: |
| 5 | branches: [main] |
| 6 | |
| 7 | jobs: |
| 8 | detect-changes: |
| 9 | runs-on: ubuntu-latest |
| 10 | outputs: |
| 11 | packages: ${{ steps.changes.outputs.packages }} |
| 12 | steps: |
| 13 | - uses: actions/checkout@v4 |
| 14 | - uses: dorny/paths-filter@v3 |
| 15 | id: changes |
| 16 | with: |
| 17 | filters: | |
| 18 | web: |
| 19 | - 'apps/web/**' |
| 20 | api: |
| 21 | - 'apps/api/**' |
| 22 | ui: |
| 23 | - 'packages/ui/**' |
| 24 | |
| 25 | build-and-test: |
| 26 | needs: detect-changes |
| 27 | runs-on: ubuntu-latest |
| 28 | strategy: |
| 29 | matrix: |
| 30 | package: ${{ fromJSON(needs.detect-changes.outputs.packages) }} |
| 31 | steps: |
| 32 | - uses: actions/checkout@v4 |
| 33 | - run: npm ci |
| 34 | - run: npm run build --workspace=${{ matrix.package }} |
| 35 | - run: npm test --workspace=${{ matrix.package }} |
$Blueprint — Engineering Documentation·Section ID: CI-MR-01·Revision: 1.0