From ccbcd3d71929489c618ac4d213a48de3fb57fb50 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 11 Jul 2026 00:13:51 -0400 Subject: [PATCH] ci(publish): harden the release job per review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR #1647 review: - Assert the release tag matches the root package.json version before publishing (fail fast, before install/pack:verify) — publishing is irreversible, so a release drafted without `npm version` or cut from the wrong commit shouldn't silently ship a mismatched version. Tolerates a leading `v`. - Add a `concurrency: { group: publish-npm, cancel-in-progress: false }` guard so two releases in quick succession can't run overlapping publishes. - Note in-comment that the triple build (build job → pack:verify → prepack) is intentional, so a future reader doesn't "optimize" prepack away and break the tarball. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw --- .github/workflows/main.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f64fe027..6fb078f9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,6 +89,11 @@ jobs: if: github.event_name == 'release' environment: release needs: build + # Serialize publishes so two releases cut in quick succession can't run + # overlapping `npm publish`es. Never cancel an in-flight publish. + concurrency: + group: publish-npm + cancel-in-progress: false permissions: contents: read # Required for npm provenance (`--provenance` mints a signed attestation @@ -105,6 +110,21 @@ jobs: cache: 'npm' registry-url: 'https://registry.npmjs.org' + - name: Assert release tag matches package version + # `npm publish` ships whatever `version` is in the root package.json, + # regardless of the release's tag. Fail fast (before the heavy install / + # pack:verify) if they disagree — e.g. a release drafted without running + # `npm version`, or cut from the wrong commit — since publishing is + # irreversible. Tolerates the conventional leading `v` (npm version tags + # as `vX.Y.Z`). + run: | + PKG="$(node -p "require('./package.json').version")" + TAG="${{ github.event.release.tag_name }}" + if [ "${TAG#v}" != "$PKG" ]; then + echo "Release tag '$TAG' does not match package.json version '$PKG'" + exit 1 + fi + - name: Install dependencies (root + all clients) run: npm install @@ -116,6 +136,10 @@ jobs: - name: Publish to npm (single package, with provenance) # `prepack` (`npm run build`) rebuilds the client bundles into the tarball. + # The build runs three times on this path (build job → pack:verify → + # prepack); the redundancy is intentional — each is a clean-tree rebuild + # and the `prepack` one is what actually populates the published tarball, + # so don't "optimize" it away. run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}