From f20e3f8ff4d6a5e32dacccf77ba081181f0277bc Mon Sep 17 00:00:00 2001 From: dipto0321 Date: Thu, 2 Jul 2026 02:25:13 +0600 Subject: [PATCH] chore(ci): publish nodeup-cli to npm via OIDC Trusted Publishing Wires the npm wrapper into the release workflow. After GoReleaser finishes, a second job runs `npm publish --access public` from nodeup-npm/ using OIDC Trusted Publishing - no NPM_TOKEN secret, no rotation, no leak surface. The trust is registered on the npmjs.com side under "Trusted Publisher", pointing at this workflow by filename. Three things had to be right for the OIDC exchange to fire: 1. `permissions: id-token: write` on the publish job. Without it, the runner won't mint an OIDC token. 2. No `registry-url` on setup-node. With it, setup-node writes `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` to .npmrc, and an empty-expanded value still counts as "auth configured" - npm then tries classic auth with empty creds and never initiates the OIDC exchange (npm/documentation#1960). 3. A clean .npmrc with only the registry URL. Without it, npm doesn't see the ACTIONS_ID_TOKEN_REQUEST_* env vars and skips the exchange. Node 24 is pinned because OIDC trusted publishing needs npm >= 11.5.1 / Node >= 22.14.0. Also updates the file's header comment to reflect the new step and document the npmjs.com trust entry as the first thing to check if the publish job ever fails with ENEEDAUTH. Refs #35. Co-Authored-By: Sonnet 4.6 --- .github/workflows/release.yml | 69 ++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 860860a..0c4d89b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,10 @@ # - Creates a GitHub Release with an auto-generated changelog # - Pushes to the homebrew-tap repo (if HOMEBREW_TAP_TOKEN is set) # - Pushes to the scoop-bucket repo (if SCOOP_BUCKET_TOKEN is set) +# 4. Publish the nodeup-cli npm wrapper via OIDC Trusted Publishing. +# GitHub Actions exchanges its built-in OIDC token for a one-hour +# publish token scoped to the `nodeup-cli` package. No NPM_TOKEN +# secret, no rotation, no leak surface. # # Required secrets (configured per-repo): # - HOMEBREW_TAP_TOKEN: fine-grained PAT with `contents:write` on @@ -18,11 +22,13 @@ # - GITHUB_TOKEN: provided automatically by GitHub Actions, used by # GoReleaser to create the Release. # -# Optional secrets: -# - NPM_TOKEN: if set, release.yml will additionally publish the -# `nodeup` npm wrapper. Configure the publish step after the GoReleaser -# step (currently a placeholder; full npm wrapper lives in -# nodeup-npm/ in Phase 7). +# npm publish uses OIDC Trusted Publishing — no secret required. The +# trust is registered on the npmjs.com side at +# https://www.npmjs.com/package/nodeup-cli/access +# under "Trusted Publisher", pointing at this workflow by filename +# (release.yml). If the publish job ever fails with ENEEDAUTH, the +# first thing to check is that the trust entry on npmjs.com still +# matches this workflow's filename, repo name, and GitHub user/org. name: Release @@ -64,4 +70,55 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} - SCOOP_BUCKET_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }} \ No newline at end of file + SCOOP_BUCKET_TOKEN: ${{ secrets.SCOOP_BUCKET_TOKEN }} + + publish-npm: + name: Publish nodeup-cli to npm (OIDC) + runs-on: ubuntu-latest + needs: goreleaser + # OIDC Trusted Publishing — the trust is registered on the npmjs.com + # side at https://www.npmjs.com/package/nodeup-cli/access under + # "Trusted Publisher", pointing at this workflow by filename. No + # NPM_TOKEN secret is needed; the runner mints an OIDC token, + # npmjs.com exchanges it for a one-hour publish token scoped to + # the nodeup-cli package. + # + # IMPORTANT: OIDC only kicks in when npm sees NO `_authToken` line + # in .npmrc for the target registry. The actions/setup-node action + # writes `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` to + # .npmrc when given `registry-url`, and an empty-expanded value + # still counts as "auth configured" — npm then tries classic auth + # with empty creds and never initiates the OIDC exchange (issue + # npm/documentation#1960). So we deliberately: + # 1. Don't set `registry-url` on setup-node. + # 2. Don't set NODE_AUTH_TOKEN in the env. + # 3. Write a clean .npmrc with only the registry URL. + if: startsWith(github.ref, 'refs/tags/v') + permissions: + id-token: write # required for OIDC trusted publishing + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + # Node 24 ships npm 11.x, which is the minimum for OIDC + # trusted publishing (npm >= 11.5.1, Node >= 22.14.0). + # Deliberately no `registry-url` here — see the comment on + # the publish-npm job above. + node-version: '24' + + # Write a clean .npmrc that contains only the registry URL, no + # _authToken line. Without this, npm won't see the + # ACTIONS_ID_TOKEN_REQUEST_* env vars and won't initiate the + # OIDC token exchange. + - name: Configure registry (clean .npmrc for OIDC) + run: | + mkdir -p ~/.npm + printf 'registry=https://registry.npmjs.org/\n' > ~/.npmrc + + - name: Publish nodeup-cli + working-directory: ./nodeup-npm + run: npm publish --access public \ No newline at end of file