diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3154af4..bc40580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,28 @@ on: branches: [main] jobs: + # Stale-green guard. A PR can show a green CI run that was executed BEFORE a + # breaking commit landed on the base branch — merging it would ship a broken + # main. This job FAILS if the PR branch does not contain origin/ as an + # ancestor, forcing an "Update branch" before the PR can merge. + up-to-date-with-base: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: Fail if PR branch is behind its base branch + run: | + BASE="${{ github.event.pull_request.base.ref }}" + git fetch origin "${BASE}" --depth=1 + if git merge-base --is-ancestor "origin/${BASE}" HEAD; then + echo "PR branch contains origin/${BASE} — up to date." + else + echo "::error::PR branch is behind origin/${BASE}. Update the branch (merge/rebase ${BASE}) and re-run CI so it validates against current base." + exit 1 + fi + build-and-test: runs-on: ubuntu-latest steps: @@ -18,6 +40,9 @@ jobs: cache: 'npm' - run: npm ci + # `npm run build` runs `tsc && vite build && node scripts/prerender.mjs`. + # The prerender step is part of CI — a local `vite build` alone is NOT a + # valid gate. `npm run gate` (package.json) runs this exact sequence. - run: npm run build - run: npm test diff --git a/package.json b/package.json index b31343e..858951b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build": "tsc && vite build && node scripts/prerender.mjs", "preview": "vite preview", "test": "vitest run", + "gate": "tsc --noEmit && npm run build && vitest run", "test:watch": "vitest", "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui",