diff --git a/.github/workflows/reusable-ci.yml b/.github/workflows/reusable-ci.yml index 6c86906..c09d23d 100644 --- a/.github/workflows/reusable-ci.yml +++ b/.github/workflows/reusable-ci.yml @@ -1,6 +1,14 @@ # Reusable implementation — repos call this via the synced ci.yml stub. # Edit here; never in a consumer repo. # +# Caching policy: every setup-node step caches ~/.npm keyed on the lockfile +# hash. This only changes where npm ci fetches tarballs from — node_modules +# is still wiped and rebuilt, and npm ci still verifies every package +# against the lockfile's integrity hashes, so the lockfile-integrity +# guarantee this workflow exists for is untouched. Lockfile-bump PRs are +# cold by definition. reusable-release deliberately stays UNCACHED +# (package-manager-cache: false) — keep the publish path pristine. +# # This is the merge gate rulesets require (context: "install / install"). # It ports nswds-design's local CI workflow: the pull_request event checks # out the test merge of PR + base, so a bad conflict resolution or a @@ -61,11 +69,15 @@ jobs: uses: actions/setup-node@v7 with: node-version-file: '.nvmrc' + cache: 'npm' + cache-dependency-path: package-lock.json - name: Setup Node.js (fallback) if: ${{ hashFiles('.nvmrc') == '' }} uses: actions/setup-node@v7 with: node-version: ${{ inputs.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json # Fails on any manifest/lockfile mismatch — the check Snyk-corrupted # lockfiles used to sail past when nothing enforced it. @@ -99,11 +111,15 @@ jobs: uses: actions/setup-node@v7 with: node-version-file: '.nvmrc' + cache: 'npm' + cache-dependency-path: package-lock.json - name: Setup Node.js (fallback) if: ${{ hashFiles('.nvmrc') == '' }} uses: actions/setup-node@v7 with: node-version: ${{ inputs.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json - name: Install dependencies run: npm clean-install @@ -145,11 +161,15 @@ jobs: uses: actions/setup-node@v7 with: node-version-file: '.nvmrc' + cache: 'npm' + cache-dependency-path: package-lock.json - name: Setup Node.js (fallback) if: ${{ steps.prettier.outputs.present == 'true' && hashFiles('.nvmrc') == '' }} uses: actions/setup-node@v7 with: node-version: ${{ inputs.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json - name: Install dependencies if: ${{ steps.prettier.outputs.present == 'true' }} @@ -190,11 +210,15 @@ jobs: uses: actions/setup-node@v7 with: node-version-file: '.nvmrc' + cache: 'npm' + cache-dependency-path: package-lock.json - name: Setup Node.js (fallback) if: ${{ hashFiles('.nvmrc') == '' }} uses: actions/setup-node@v7 with: node-version: ${{ inputs.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json - name: Install dependencies if: ${{ hashFiles('vitest.config.*') != '' }} diff --git a/workflow-stubs/ci.yml b/workflow-stubs/ci.yml index ec26634..4e0bf71 100644 --- a/workflow-stubs/ci.yml +++ b/workflow-stubs/ci.yml @@ -7,6 +7,15 @@ name: CI on: pull_request: +# Rapid successive pushes to a PR previously ran full duplicate 4-job CI to +# completion; only the latest push's run matters for the merge gate, so +# supersede in-flight runs. Lives HERE (not in the reusable) because +# workflow-level concurrency in a called workflow is not applied — the run +# belongs to this caller. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read