Skip to content

chore(ci): publish nodeup-cli to npm via OIDC Trusted Publishing#39

Merged
dipto0321 merged 1 commit into
mainfrom
chore/release/oidc-publish-step
Jul 1, 2026
Merged

chore(ci): publish nodeup-cli to npm via OIDC Trusted Publishing#39
dipto0321 merged 1 commit into
mainfrom
chore/release/oidc-publish-step

Conversation

@dipto0321

Copy link
Copy Markdown
Owner

Summary

Wires the npm wrapper into the release workflow via OIDC Trusted
Publishing
. After GoReleaser finishes, a second job runs
npm publish --access public from nodeup-npm/ using the OIDC
token that the GitHub Actions runner mints automatically. No
NPM_TOKEN secret, no rotation, no leak surface.

The trust is already registered on the npmjs.com side under
"Trusted Publisher" — the screenshot you sent shows
dipto0321/nodeup + release.yml + npm publish permission.
This PR wires up the workflow half of that trust.

Why this needed three subtle things to be right

OIDC trusted publishing only fires when npm CLI sees no
_authToken line in .npmrc for the target registry
. The
actions/setup-node@v4 action writes
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} to .npmrc
when given registry-url: 'https://registry.npmjs.org', and an
empty-expanded value still counts as "auth configured" — npm then
tries classic auth with empty creds and never initiates the OIDC
exchange. (Tracked upstream at
npm/documentation#1960.)

So the publish job has to:

  1. Declare permissions: id-token: write so the runner mints an
    OIDC token.
  2. Not pass registry-url to setup-node — otherwise it
    writes the _authToken line.
  3. Write a clean .npmrc with only the registry URL — guarantees
    no _authToken line is present even if some future change to
    the setup-node action introduces one.

Node 24 is pinned because OIDC trusted publishing requires
npm ≥ 11.5.1 and Node ≥ 22.14.0.

Linked issues

Refs #35 (tracking issue for the npm-publish step)

Type of change

  • feat — new feature (MINOR bump)
  • fix — bug fix (PATCH bump)
  • refactor — no behavior change
  • docs — documentation only
  • chore — CI/CD
  • ci — CI/CD only
  • build — build system only
  • Breaking change (MAJOR bump) — explain below

Checklist

  • Title follows Conventional Commits (chore(ci): subject)
  • YAML parses (python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))")
  • I ran make ci locally — workflow-only change, Go code untouched
  • I added or updated tests — N/A, workflow change
  • I updated relevant docs (header comment in release.yml
    explains the trust entry and the troubleshooting pointer)
  • No new linter warnings

Scope notes / things reviewers may want to look at

  • Verification path. This PR can be verified end-to-end once
    you push a v0.0.0-test tag (not v1.0.0). The trust entry on
    npmjs.com already matches this workflow's filename, so the OIDC
    exchange should work. If it doesn't, the first thing to check
    is that:
    1. The trust entry on npmjs.com still points at
      dipto0321/nodeup + release.yml.
    2. The job has id-token: write.
    3. No NODE_AUTH_TOKEN or NPM_TOKEN env var is set in the
      workflow that would land in .npmrc.
  • No NPM_TOKEN secret is required. This is the whole point
    of the OIDC change — once the workflow is merged, you can
    delete any NPM_TOKEN repo/org secret you might have set up
    during the planning phase. None was ever created in this repo,
    so nothing to clean up.
  • Provenance is automatic. GitHub Actions OIDC trusted
    publishing generates provenance automatically for public
    packages from public repos. The npm publish command does
    not include --provenance for that reason — adding it
    manually would try to mint a second OIDC token and fail.
  • Concurrency. The existing
    concurrency: { group: release-${{ github.ref }}, cancel-in-progress: false }
    applies to the whole workflow, so the publish job inherits the
    no-cancel guarantee. A partially-published release is the bad
    case we're protecting against.

How to verify

# YAML parses
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"

# Inspect the trust entry
gh pr checks 39 --watch --fail-fast

After merge, push a test tag and watch the publish-npm job in
the action run. On success the new version appears at
https://www.npmjs.com/package/nodeup-cli?activeTab=versions.

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 <noreply@puku.sh>
@cocogitto-bot

cocogitto-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

✔️ f20e3f8 - Conventional commits check succeeded.

@dipto0321 dipto0321 merged commit fb691f7 into main Jul 1, 2026
10 checks passed
@dipto0321 dipto0321 deleted the chore/release/oidc-publish-step branch July 1, 2026 20:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant