From b97ed5b60a8afba15bf6b18b4f5f1d5ef615dfd4 Mon Sep 17 00:00:00 2001 From: jamlen Date: Wed, 12 Aug 2026 22:22:15 +0100 Subject: [PATCH] ci: prepare release workflow for npm trusted publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #134. This is the repo-side half; the trusted publisher still has to be configured at npmjs.com before it takes effect. Adds an explicit npm CLI upgrade to the release job. Trusted publishing needs npm >= 11.5.1 and Node 22 bundles npm 10.9.8, so without this the publish fails even once OIDC is configured — and it fails in a non-obvious way. @semantic-release/npm 13.1.5 treats a successful OIDC exchange as proof that authentication is handled and returns early from verifyAuth: if (await oidcContextEstablished(registry, pkg, context)) { return; // no token written } await setNpmrcAuth(npmrc, registry, context); await verifyTokenAuth(registry, npmrc, context, pkgRoot); The exchanged token is only used as a boolean probe — it is never forwarded. publish.js then runs `npm publish --userconfig ` against an npmrc with no credentials in it, so the npm CLI has to perform its own OIDC exchange. An npm that cannot do that arrives with nothing. NPM_TOKEN is deliberately left in place. The plugin falls back to it when the exchange fails, so it stays as the rollback until a real publish has succeeded over OIDC. Also corrects CLAUDE.md, which described NPM_TOKEN as the required secret "with publish + provenance rights". Provenance was never actually enabled — nothing passes --provenance, there is no publishConfig, and deride@2.2.0 has no attestations. Trusted publishing generates it automatically, so this delivers what that line claimed. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 14 +++++++++++++- CLAUDE.md | 11 ++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ecdf41..c6b0252 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: contents: write # creating git tags and GitHub releases issues: write # semantic-release opens issues on failure pull-requests: write - id-token: write # npm provenance + id-token: write # OIDC — npm trusted publishing + provenance steps: - uses: actions/checkout@v6 with: @@ -31,6 +31,18 @@ jobs: node-version: 22 cache: pnpm registry-url: 'https://registry.npmjs.org' + + # Trusted publishing (OIDC) needs npm CLI >= 11.5.1. Node 22 bundles npm + # 10.9.x, which cannot perform the exchange. This matters because + # @semantic-release/npm treats a successful exchange as proof that auth is + # handled and returns *without* writing any token into .npmrc — so it then + # runs `npm publish` with no credentials, and an npm that can't do OIDC + # fails there. Installs use pnpm; publishing still goes through npm. + - name: Ensure npm supports trusted publishing + run: | + npm install -g npm@^11.5.1 + npm --version + - run: pnpm install --frozen-lockfile # Re-run the gate before publishing so a red build never reaches npm, diff --git a/CLAUDE.md b/CLAUDE.md index 01e88a5..c265567 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,16 @@ If a squash is unavoidable, rename the squash commit message to `feat: …` or ` **`@semantic-release/git` and `@semantic-release/changelog` are intentionally not used, and are no longer installed.** Both would try to push a `chore(release):` commit back to the release branch, which branch protection rejects. Don't re-add them to `devDependencies` — they sat there unused (and generating dependabot majors) until they were removed. `package.json` and `CHANGELOG.md` in the repo are not auto-maintained — the authoritative record is the [GitHub Releases page](https://github.com/guzzlerio/deride/releases). -**Secrets required by the release workflow:** `NPM_TOKEN` (automation token with publish + provenance rights). `GITHUB_TOKEN` is provided automatically. +**npm authentication uses trusted publishing (OIDC) — not a long-lived token.** The publisher is configured registry-side at npmjs.com → deride → Settings → Trusted Publisher, pinned to this repo and `release.yml`. The workflow grants `id-token: write`, and `@semantic-release/npm` exchanges the GitHub OIDC token for a short-lived registry credential. + +Two consequences worth knowing before editing `release.yml`: + +- **Renaming the workflow file breaks publishing.** The trusted publisher config pins the filename. Rename it and the OIDC exchange returns 404 and the release fails. +- **npm CLI must be >= 11.5.1.** Node 22 bundles npm 10.9.x, so the workflow upgrades npm explicitly. On a successful exchange the plugin writes *no* token to `.npmrc`, so `npm publish` has to authenticate via OIDC itself — an older npm reaches that point with no credentials. Don't remove that step. + +Provenance is generated automatically under trusted publishing. It was never actually enabled before (nothing passed `--provenance`, and `deride@2.2.0` has no attestations) despite the workflow comment claiming otherwise. + +**Secrets:** `GITHUB_TOKEN` is provided automatically. `NPM_TOKEN` is retained only as a fallback — `@semantic-release/npm` uses it when the OIDC exchange fails. It is not the primary path. ## Conventional Commits — enforced on every PR