From 76bdbca075ce631a5c47c3f498bac4a6d2d837dc Mon Sep 17 00:00:00 2001 From: us Date: Tue, 8 Sep 2026 19:09:41 +0300 Subject: [PATCH] ci: publish to npm through trusted publishing instead of a token The granular NPM_TOKEN expired after 90 days and the 0.34.0 release failed its npm jobs with E404 on PUT. Trusted publishing authenticates the workflow itself through OIDC, so there is no secret to rotate. The two npm jobs get id-token: write, drop NODE_AUTH_TOKEN, and update npm to a version that supports it (11.5.1 or later; the npm bundled with Node 22 is older). Each package needs its trusted publisher entry on npmjs.com once (user us, repository crw, workflow release.yml); RELEASE.md lists the packages. --- .github/workflows/release.yml | 23 ++++++++++++++++------- RELEASE.md | 22 ++++++++++++---------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f846cbc7..f59051ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -315,14 +315,15 @@ jobs: publish-npm: needs: [release-context, asset-gate] - # Temporarily disable npm publishing by setting repo variable SKIP_NPM=true - # (e.g. while the npm token can't publish — 2FA-bypass granular token not yet - # configured). The job is cleanly SKIPPED (gray), not failed/masked. Clear - # the SKIP_NPM repo variable to re-enable. See RELEASE.md. + # Temporarily disable npm publishing by setting repo variable SKIP_NPM=true. + # The job is cleanly SKIPPED (gray), not failed/masked. Clear the SKIP_NPM + # repo variable to re-enable. Publishing authenticates through npm trusted + # publishing (OIDC), configured per package on npmjs.com. See RELEASE.md. if: ${{ vars.SKIP_NPM != 'true' }} runs-on: ubuntu-latest permissions: contents: read + id-token: write # npm trusted publishing (OIDC), no token secret steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -333,6 +334,11 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org + # Trusted publishing needs npm 11.5.1 or later; the npm bundled with + # Node 22 is older. + - name: Update npm for trusted publishing + run: npm install -g npm@latest + - name: Download and verify platform release assets shell: bash env: @@ -352,7 +358,6 @@ jobs: - name: Idempotent publish (platform packages) shell: bash env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} VERSION: ${{ needs.release-context.outputs.version }} run: | set -euo pipefail @@ -405,7 +410,6 @@ jobs: - name: Idempotent publish (main package) shell: bash env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} VERSION: ${{ needs.release-context.outputs.version }} run: | set -euo pipefail @@ -576,6 +580,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + id-token: write # npm trusted publishing (OIDC), no token secret steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -586,6 +591,11 @@ jobs: node-version: 22 registry-url: https://registry.npmjs.org + # Trusted publishing needs npm 11.5.1 or later; the npm bundled with + # Node 22 is older. + - name: Update npm for trusted publishing + run: npm install -g npm@latest + - name: Build + test shell: bash run: | @@ -598,7 +608,6 @@ jobs: - name: Idempotent publish shell: bash env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} VERSION: ${{ needs.release-context.outputs.version }} run: | set -euo pipefail diff --git a/RELEASE.md b/RELEASE.md index f3fa9ecf..9f497b43 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -48,15 +48,17 @@ The pipeline is idempotent — re-run is safe. ### Temporarily disabling npm publishing -If the npm token can't publish (npm now rejects classic automation tokens with -`403 ... two-factor authentication ... required`), set the repo variable -`SKIP_NPM=true` (`gh variable set SKIP_NPM --body true`). `publish-npm` is then -cleanly **skipped** (gray, not failed) and `verify-publish` records npm as -`skipped` — the release goes green on every other registry. **Re-enable** by -clearing it (`gh variable delete SKIP_NPM`) once `NPM_TOKEN` is a *granular* -access token with publish permission (these bypass 2FA), or after configuring -npm OIDC trusted publishing for each package (the workflow already grants -`id-token: write`). +npm publishing uses **trusted publishing** (OIDC): no token secret, nothing to +rotate. Each package on npmjs.com lists this repository and `release.yml` as its +trusted publisher (package page > Settings > Trusted Publisher > GitHub Actions: +user `us`, repository `crw`, workflow filename `release.yml`, no environment). +The packages are `crw-mcp`, `crw-mcp-darwin-arm64`, `crw-mcp-darwin-x64`, +`crw-mcp-linux-arm64`, `crw-mcp-linux-x64` and `crw-sdk`. A new platform +package needs that entry once before its first release. If npm publishing has +to be paused, set the repo variable `SKIP_NPM=true` +(`gh variable set SKIP_NPM --body true`): `publish-npm` and `publish-npm-sdk` are +then cleanly **skipped** (gray, not failed) and `verify-publish` records npm as +`skipped`. Re-enable with `gh variable delete SKIP_NPM`. ### A version was published with broken metadata (e.g. wrong npm optionalDeps) @@ -77,7 +79,7 @@ These tags were cut while the release pipeline silently failed (cargo publish ou | -------------------- | -------------------------------- | -------------------------------------------- | | `CARGO_REGISTRY_TOKEN` | `publish-crates` | crates.io account → API tokens → revoke + create new with `publish-update` scope | | `PYPI_TOKEN` | `publish-pypi` | pypi.org account → API tokens → scoped to project `crw` | -| `NPM_TOKEN` | `publish-npm` | npmjs.com → access tokens → automation token | +| npm (no secret) | `publish-npm`, `publish-npm-sdk` | trusted publishing, configured per package on npmjs.com; nothing to rotate | | `GH_DISPATCH_PAT` | `dispatch-release`, `update-apt`, `update-homebrew` | GitHub fine-grained PAT with `actions:write` on this repo + `us/apt-crw` + `us/homebrew-crw` | `GITHUB_TOKEN` is auto-provisioned and does not need rotation.