From 95ede05fadf0d3f5b0ff50ac22c43357d48d1b85 Mon Sep 17 00:00:00 2001 From: saltbo Date: Wed, 5 Aug 2026 09:51:30 -0400 Subject: [PATCH] ci: enforce merged coverage thresholds --- .github/workflows/ci.yml | 58 +++++++++++++++++++++++++++++++--------- vitest.config.ts | 33 ++++++++++------------- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0facd190..20aed7cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,15 +84,17 @@ jobs: node-version: 24 cache: pnpm - run: pnpm install --frozen-lockfile - - run: pnpm exec vitest run --project unit --shard=${{ matrix.shard }}/2 --coverage --coverage.reportsDirectory=coverage/unit-${{ matrix.shard }} - - uses: codecov/codecov-action@v5 + - name: Run unit shard + env: + VITEST_BLOB_LABEL: unit-${{ matrix.shard }} + run: pnpm exec vitest run --project unit --shard=${{ matrix.shard }}/2 --coverage --coverage.reportsDirectory=coverage/unit-${{ matrix.shard }} --reporter=default --reporter=blob --outputFile.blob=.vitest-reports/unit-${{ matrix.shard }}.blob + - uses: actions/upload-artifact@v4 if: always() with: - files: coverage/unit-${{ matrix.shard }}/coverage-final.json - flags: unit - disable_search: true - fail_ci_if_error: false - handle_no_reports_found: true + name: vitest-coverage-unit-${{ matrix.shard }} + path: .vitest-reports/unit-${{ matrix.shard }}.blob + include-hidden-files: true + retention-days: 1 integration: name: Integration tests (${{ matrix.shard }}/2) @@ -110,12 +112,45 @@ jobs: node-version: 24 cache: pnpm - run: pnpm install --frozen-lockfile - - run: pnpm exec vitest run --project integration --shard=${{ matrix.shard }}/2 --coverage --coverage.reportsDirectory=coverage/integration-${{ matrix.shard }} + - name: Run integration shard + env: + VITEST_BLOB_LABEL: integration-${{ matrix.shard }} + run: pnpm exec vitest run --project integration --shard=${{ matrix.shard }}/2 --coverage --coverage.reportsDirectory=coverage/integration-${{ matrix.shard }} --reporter=default --reporter=blob --outputFile.blob=.vitest-reports/integration-${{ matrix.shard }}.blob + - uses: actions/upload-artifact@v4 + if: always() + with: + name: vitest-coverage-integration-${{ matrix.shard }} + path: .vitest-reports/integration-${{ matrix.shard }}.blob + include-hidden-files: true + retention-days: 1 + + coverage: + name: Coverage gate + runs-on: ubuntu-latest + if: github.repository == 'saltbo/zpan' + needs: [unit, integration] + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - uses: actions/download-artifact@v5 + with: + pattern: vitest-coverage-* + path: .vitest-reports + merge-multiple: true + - name: Merge coverage and enforce thresholds + env: + COVERAGE_ENFORCE: '1' + run: pnpm exec vitest --merge-reports=.vitest-reports --coverage --reporter=agent --coverage.reportsDirectory=coverage/merged - uses: codecov/codecov-action@v5 if: always() with: - files: coverage/integration-${{ matrix.shard }}/coverage-final.json - flags: integration + files: coverage/merged/coverage-final.json + flags: unit,integration disable_search: true fail_ci_if_error: false handle_no_reports_found: true @@ -144,8 +179,7 @@ jobs: needs: - downloader - static - - unit - - integration + - coverage - cloudflare - docker-smoke - docker-cli-smoke diff --git a/vitest.config.ts b/vitest.config.ts index 4ee81e57..0cc6f9bc 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -33,12 +33,25 @@ const coverageConfig = { 'src/**/*.integration.test.ts', 'src/i18n/index.ts', ], - reporter: ['text', 'json'] as const, + reporter: ['text-summary', 'json'] as const, } +const coverageGate = + process.env.COVERAGE_ENFORCE === '1' + ? { + thresholds: { + statements: 85, + branches: 80, + functions: 75, + lines: 90, + }, + } + : {} + export default defineConfig({ test: { globals: true, + coverage: { ...coverageConfig, ...coverageGate }, projects: [ { plugins: [react()], @@ -55,15 +68,6 @@ export default defineConfig({ ], exclude: ['**/*.integration.test.ts', '**/*.cf-test.ts', '**/e2e-*.test.ts'], setupFiles: ['./server/test/app-version.ts'], - coverage: { - ...coverageConfig, - thresholds: { - statements: 60, - branches: 50, - functions: 40, - lines: 60, - }, - }, }, }, { @@ -72,15 +76,6 @@ export default defineConfig({ name: 'integration', include: ['server/**/*.integration.test.ts', 'src/**/*.integration.test.ts'], setupFiles: ['./server/test/app-version.ts'], - coverage: { - ...coverageConfig, - thresholds: { - statements: 90, - branches: 80, - functions: 90, - lines: 90, - }, - }, }, }, {