monorepo-ci-optimizer

安装量: 38
排名: #18545

安装

npx skills add https://github.com/patricio0312rev/skills --skill monorepo-ci-optimizer

Monorepo CI Optimizer

Run only affected builds and tests in monorepos for faster CI.

Affected Detection Strategy Using Turborepo

.github/workflows/ci.yml

name: CI

on: pull_request: push: branches: [main]

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Need full history for affected detection

  - uses: actions/setup-node@v4
    with:
      node-version: "20"
      cache: "pnpm"

  - run: pnpm install

  - name: Build affected
    run: |
      pnpm turbo run build --filter='...[origin/main]'

  - name: Test affected
    run: |
      pnpm turbo run test --filter='...[origin/main]'

  - name: Lint affected
    run: |
      pnpm turbo run lint --filter='...[origin/main]'

Using Nx build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0

- uses: nrwl/nx-set-shas@v4

- uses: actions/setup-node@v4
  with:
    node-version: "20"
    cache: "npm"

- run: npm ci

- name: Build affected projects
  run: npx nx affected:build --base=$NX_BASE --head=$NX_HEAD

- name: Test affected projects
  run: npx nx affected:test --base=$NX_BASE --head=$NX_HEAD --parallel=3

- name: Lint affected projects
  run: npx nx affected:lint --base=$NX_BASE --head=$NX_HEAD

Remote Caching (Turborepo) - name: Setup Turborepo cache uses: actions/cache@v3 with: path: .turbo key: ${{ runner.os }}-turbo-${{ github.sha }} restore-keys: | ${{ runner.os }}-turbo-

  • name: Build with remote cache run: pnpm turbo run build env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

Nx Cloud Integration - name: Setup Nx Cloud run: | npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

  • name: Run affected run: | npx nx affected --target=build --parallel=3 npx nx affected --target=test --parallel=3 npx nx affected --target=lint --parallel=3

Manual Affected Detection // scripts/get-affected.ts import * as path from "path"; import { execSync } from "child_process"; import * as fs from "fs";

interface Package { name: string; path: string; dependencies: string[]; }

function getChangedFiles(base: string = "origin/main"): string[] { const output = execSync(git diff --name-only ${base}...HEAD, { encoding: "utf-8", }); return output.split("\n").filter(Boolean); }

function getPackages(): Package[] { const packagesDir = path.join(process.cwd(), "packages"); return fs.readdirSync(packagesDir).map((name) => { const packageJson = JSON.parse( fs.readFileSync(path.join(packagesDir, name, "package.json"), "utf-8") ); return { name: packageJson.name, path: packages/${name}, dependencies: [ ...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.devDependencies || {}), ], }; }); }

function getAffectedPackages(): string[] { const changedFiles = getChangedFiles(); const packages = getPackages(); const affected = new Set();

// Direct changes changedFiles.forEach((file) => { packages.forEach((pkg) => { if (file.startsWith(pkg.path)) { affected.add(pkg.name); } }); });

// Dependent packages let changed = true; while (changed) { changed = false; packages.forEach((pkg) => { if (!affected.has(pkg.name)) { const hasAffectedDep = pkg.dependencies.some((dep) => affected.has(dep) ); if (hasAffectedDep) { affected.add(pkg.name); changed = true; } } }); }

return Array.from(affected); }

// Output for GitHub Actions const affected = getAffectedPackages(); console.log(::set-output name=packages::${affected.join(",")}); console.log(Affected packages: ${affected.join(", ")});

Matrix Strategy for Affected Packages detect-affected: runs-on: ubuntu-latest outputs: packages: ${{ steps.affected.outputs.packages }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0

- id: affected
  run: |
    PACKAGES=$(node scripts/get-affected.ts)
    echo "packages=$PACKAGES" >> $GITHUB_OUTPUT

test-affected: needs: detect-affected if: needs.detect-affected.outputs.packages != '' runs-on: ubuntu-latest strategy: matrix: package: ${{ fromJSON(needs.detect-affected.outputs.packages) }} steps: - uses: actions/checkout@v4

- uses: actions/setup-node@v4
  with:
    node-version: "20"
    cache: "pnpm"

- run: pnpm install

- name: Test ${{ matrix.package }}
  run: pnpm --filter ${{ matrix.package }} test

Workspace-aware Caching - name: Cache workspace builds uses: actions/cache@v3 with: path: | packages//dist packages//node_modules/.cache key: ${{ runner.os }}-workspace-${{ hashFiles('/pnpm-lock.yaml') }}-${{ hashFiles('packages//.ts') }} restore-keys: | ${{ runner.os }}-workspace-${{ hashFiles('*/pnpm-lock.yaml') }}- ${{ runner.os }}-workspace-

Parallel Execution - name: Build all packages in parallel run: pnpm turbo run build --parallel

  • name: Test with controlled parallelism run: pnpm turbo run test --concurrency=3

Optimization Metrics

Before Optimization

  • Full build: 25 minutes
  • All tests: 15 minutes
  • Total: 40 minutes
  • Runs on every PR

After Affected Detection

  • Affected build: 5 minutes (80% reduction)
  • Affected tests: 3 minutes (80% reduction)
  • Total: 8 minutes (80% reduction)
  • Only runs necessary checks

With Remote Caching

  • Cache hit build: 30 seconds (98% reduction)
  • Cache hit tests: 1 minute (93% reduction)
  • Total: 1.5 minutes (96% reduction)

Best Practices Fetch full history: fetch-depth: 0 for accurate diffs Topological order: Build dependencies first Remote caching: Share cache across CI runs Parallel execution: Run independent tasks concurrently Incremental builds: Only rebuild what changed Dependency graph: Track package relationships Force full build: On main branch merges Output Checklist Affected detection implemented Turborepo or Nx configured Remote caching enabled Parallel execution optimized Matrix strategy for packages Workspace-aware caching Dependency graph documented Before/after metrics tracked

返回排行榜