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