Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ name: Publish
# Publishes to npm with provenance attestations. Trigger by publishing a
# GitHub Release (tag `vX.Y.Z`), or manually via workflow_dispatch.
#
# One-time setup: add an npm Automation token as the `NPM_TOKEN` repository
# secret (Settings → Secrets and variables → Actions), OR configure a trusted
# publisher on npmjs.com (OIDC, no token needed). Provenance requires a public
# repo and the `id-token: write` permission below.
# Auth is a trusted publisher (OIDC) — npmjs.com → simple-builder → Settings →
# Trusted Publisher, pointing at this repo and this workflow filename. There is
# no token anywhere: npm exchanges the Actions OIDC token for a short-lived
# credential, which is why `id-token: write` is required below. The package is
# set to "disallow tokens", so this is the only publish route. Renaming this
# file, or moving the publish step to another workflow, breaks publishing until
# the trusted publisher is updated to match.

on:
release:
Expand Down Expand Up @@ -35,7 +38,8 @@ jobs:
# The tag is passed through `env:`, never interpolated into the script
# body: `${{ }}` expands before bash runs, so a tag name containing shell
# metacharacters (git permits $ ` " ; |) would otherwise execute here —
# in a job holding id-token: write and NPM_TOKEN.
# in a job holding id-token: write, i.e. able to mint a publish
# credential for the package.
env:
TAG: ${{ github.event.release.tag_name }}
run: |
Expand Down Expand Up @@ -68,9 +72,13 @@ jobs:
node --unhandled-rejections=strict smoke.mjs

- name: Publish with provenance
# No NODE_AUTH_TOKEN on purpose. setup-node's registry-url writes an
# .npmrc containing `_authToken=${NODE_AUTH_TOKEN}`; with no token
# secret that expands to an EMPTY credential, which npm can read as
# "auth already configured" and skip the OIDC exchange — a 401 after
# every gate above has run. Trusted publishing wants the token absent.
# npm >= 11.5.1 is what implements the exchange; 22.x ships older.
run: |
npm install -g npm@latest
npm --version
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,19 @@ Kept OUT of the differential fuzzer's domain (covered by unit tests instead):
Bump `version` in `package.json`, then publish a GitHub Release tagged `vX.Y.Z`.
The publish workflow re-runs the full gate and publishes to npm with provenance.
See `.github/workflows/publish.yml`.

Auth is an npm **trusted publisher** (OIDC): npmjs.com has the repo and the
workflow *filename* on record, and npm exchanges the Actions OIDC token for a
short-lived credential at publish time. Consequences worth knowing before you
touch the release path:

- There is no npm token, in repo secrets or anywhere else, and the package is
set to *disallow* tokens — this workflow is the only way to publish. Do not
add `NODE_AUTH_TOKEN` back: `setup-node`'s `registry-url` writes
`_authToken=${NODE_AUTH_TOKEN}` into `.npmrc`, and an empty value there can
read as configured-auth and suppress the OIDC exchange.
- Renaming `publish.yml`, or moving the publish step into a different workflow
file, breaks publishing until the trusted publisher is updated to match.
- The tag must agree with `package.json` — a guard step fails the run otherwise.
Workflows for a `release` event run from the *tagged* commit, so land any
workflow change on `master` before you cut the tag.
Loading