From 39e33eb634582b447015034c35439216ee64c213 Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Wed, 13 May 2026 14:12:02 +0200 Subject: [PATCH] ci(coverage): wire L1 + L2 + auto-snapshot summary.json on merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-workflow split per ADR-020: .github/workflows/ci.yml (existing, extended): - checkout now uses fetch-depth: 0 so coverage:diff can resolve origin/...HEAD against the PR's base branch - new step "Coverage — aggregate (L2)" runs after the test step (with `if: always()` so the artifact still captures partial state on test failures) - new step "Coverage — diff (L1)" runs only on pull_request events, diffing against origin/${{ github.base_ref }} - artifact upload extended to include the aggregated coverage/lcov.info and coverage/summary.json alongside the per-package files .github/workflows/coverage-snapshot.yml (new): - dedicated workflow with `permissions: contents: write` so it can commit the aggregated coverage/summary.json back to main after each merge — the committed trend store (ADR-020 L2) - runs full test + aggregate, then commits summary.json only if it actually changed (commit body marked [skip ci] so the snapshot doesn't recurse into itself) - concurrency: coverage-snapshot ensures only one snapshot at a time This closes the CI side of the coverage architecture. PRs now fail fast when changed lines are uncovered, and main's trend history accumulates automatically. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 20 ++++++- .github/workflows/coverage-snapshot.yml | 72 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/coverage-snapshot.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0a7fd8..5b3d937 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,10 @@ jobs: --health-retries 5 steps: - uses: actions/checkout@v4 + with: + # Full history so coverage:diff can resolve `origin/...HEAD` + # against the PR's base branch (ADR-020 L1). + fetch-depth: 0 - uses: pnpm/action-setup@v4 with: version: 9 @@ -58,12 +62,26 @@ jobs: DATABASE_URL: postgres://postgres:postgres@localhost:5432/cms_test PAYLOAD_SECRET: test-secret-do-not-use-in-prod run: pnpm test -- --coverage + # L2 — merge per-package lcovs to coverage/lcov.info + emit + # coverage/summary.json (ADR-020). Runs even on test failure so the + # artifact still captures what was produced. + - name: Coverage — aggregate (L2) + if: always() + run: pnpm coverage:aggregate + # L1 — cover-the-diff gate. Only meaningful on PRs (push-to-main has + # no base ref to diff against). Compares against the PR's base branch. + - name: Coverage — diff (L1) + if: github.event_name == 'pull_request' + run: pnpm coverage:diff -- --base origin/${{ github.base_ref }} - run: pnpm build - uses: actions/upload-artifact@v4 if: always() with: name: coverage - path: "**/coverage/lcov.info" + path: | + coverage/lcov.info + coverage/summary.json + **/coverage/lcov.info retention-days: 7 e2e: diff --git a/.github/workflows/coverage-snapshot.yml b/.github/workflows/coverage-snapshot.yml new file mode 100644 index 0000000..51924fc --- /dev/null +++ b/.github/workflows/coverage-snapshot.yml @@ -0,0 +1,72 @@ +# Coverage snapshot — commits coverage/summary.json back to main after each +# merge so the trend history accumulates in `git log -- coverage/summary.json` +# (ADR-020 L2). This is the only workflow that needs `contents: write`. +# +# Skipped if summary.json hasn't changed since the previous commit. + +name: Coverage snapshot + +on: + push: + branches: [main] + +# Avoid two snapshot runs racing each other on rapid-fire merges. +concurrency: + group: coverage-snapshot + cancel-in-progress: false + +permissions: + contents: write + +jobs: + snapshot: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: cms_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v4 + with: + # Token with write scope so we can push the snapshot back. + token: ${{ secrets.GITHUB_TOKEN }} + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Test with coverage + env: + DATABASE_URL: postgres://postgres:postgres@localhost:5432/cms_test + PAYLOAD_SECRET: test-secret-do-not-use-in-prod + run: pnpm test -- --coverage + - name: Aggregate + run: pnpm coverage:aggregate + - name: Commit summary.json if changed + run: | + if git diff --quiet --exit-code coverage/summary.json; then + echo "No summary.json change; skipping commit." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add coverage/summary.json + git commit -m "chore(coverage): snapshot ${GITHUB_SHA::7} + + Auto-generated by .github/workflows/coverage-snapshot.yml. + + [skip ci]" + git push origin HEAD:main