From 8e2f3c7ff59440253a844397e1e1abc57ee33442 Mon Sep 17 00:00:00 2001 From: Sabyasachi Date: Fri, 14 Aug 2026 12:11:59 +0000 Subject: [PATCH] ci(release): publish on release published, and verify tag matches version The trigger was release:created. GitHub fires created when a draft release is saved, which would publish from the default branch before the tag exists; it then fires published (not created) when that draft is released, so the real release would ship nothing. Every release so far was created and published in one action, which is why this never bit. The job also published whatever version happened to be in package.json at the release commit, so a tag and a version could disagree silently. A guard now compares them and fails the job. publishConfig.registry pins GitHub Packages. CI worked only because setup-node writes the runner .npmrc; a local npm publish would target npmjs.org and fail against a scope we do not own. Lint and build join the pre-publish test gate. Co-Authored-By: Claude Opus 5 (1M context) --- .../workflows/npm-publish-github-packages.yml | 28 ++++++++++++++++--- package.json | 5 +++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/npm-publish-github-packages.yml b/.github/workflows/npm-publish-github-packages.yml index e8d96a3..5faba15 100644 --- a/.github/workflows/npm-publish-github-packages.yml +++ b/.github/workflows/npm-publish-github-packages.yml @@ -1,11 +1,15 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages +# Publishes to GitHub Packages when a release is published. +# See .claude/skills/release/SKILL.md for the release procedure. name: Node.js Package on: release: - types: [created] + # published, not created: GitHub fires `created` when a *draft* is saved, which + # would publish from the default branch before the tag exists — and then fires + # `published` (not `created`) when that draft is released, so the real release + # would ship nothing. + types: [published] env: NODE_VERSION: 24 @@ -21,6 +25,8 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: pnpm - run: pnpm install --frozen-lockfile + - run: pnpm run lint + - run: pnpm run build - run: pnpm test publish-gpr: @@ -38,9 +44,23 @@ jobs: cache: pnpm registry-url: https://npm.pkg.github.com/ - run: pnpm install --frozen-lockfile + + # The release tag and package.json must agree, or the published version silently + # differs from the tag people will go looking for. TAG comes through env rather + # than being interpolated into the script. + - name: Verify tag matches package.json version + env: + TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="v$(node -p 'require("./package.json").version')" + if [ "$VERSION" != "$TAG" ]; then + echo "Release tag $TAG does not match package.json $VERSION" >&2 + exit 1 + fi + # npm publish, not pnpm publish: pnpm adds git-state checks the release flow # does not need. prepublishOnly runs the build either way. # No --provenance: GitHub Packages does not accept provenance attestations. - run: npm publish env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index b2d3cf6..74e5350 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,8 @@ "typescript": "^6.0.3", "typescript-eslint": "^8.67.0" }, - "packageManager": "pnpm@10.13.1" + "packageManager": "pnpm@10.13.1", + "publishConfig": { + "registry": "https://npm.pkg.github.com/" + } }