Skip to content

build(release): publish to npm via Trusted Publishing (OIDC) with semantic-release 25 - #2

Open
SahinurDEV wants to merge 1 commit into
mainfrom
feat/npm-trusted-publishing
Open

SahinurDEV wants to merge 1 commit into
mainfrom
feat/npm-trusted-publishing

Conversation

@SahinurDEV

Copy link
Copy Markdown
Owner

What this does

This PR switches the Release workflow's npm publishing to npm Trusted Publishing (OIDC), so the expired/invalid NPM_TOKEN secret that broke run 37893945394 is no longer needed.

  • semantic-release ^24.1.0 → ^25.0.9. The lockfile now resolves:
    • @semantic-release/npm 13.2.0. It first tries a GitHub Actions OIDC → npm token exchange and only falls back to NPM_TOKEN if that fails. It ships npm 11.21.0; Trusted Publishing needs ≥ 11.5.1.
    • @semantic-release/github 12.0.10. This fixes the fail-step crash Variable $owner of type String! was provided invalid value (a bug in 11.0.6).
    • @semantic-release/changelog 6.0.3 and @semantic-release/git 10.0.1 stay as they are; their peer dependency semantic-release >=20.1.0 is satisfied.
  • README: the release section now describes the setup without a token.
  • Provenance: npm creates provenance attestations automatically for trusted publishes from a public repo. I did not add publishConfig.provenance: true, because that would break manual npm publish from a laptop.

The only lockfile changes are in the semantic-release dependency tree. No runtime or build dependency changed version.

Relation to #1: this PR is independent of #1 and replaces it. Both edit package.json/package-lock.json, so merge this one and close #1.

Workflow file: works as is, optional hardening patch below

My token can't push changes to .github/workflows/* (it lacks the workflow OAuth scope), so this PR does not change release.yml. None of the patch is required:

  • the current workflow already has permissions: id-token: write;
  • node-version: 22.x resolves to Node ≥ 22.14, which semantic-release 25 needs;
  • the plugin tries OIDC before it reads NPM_TOKEN, so a leftover secret does no harm.

The recommended follow-up below (apply it from the web editor or a token that has the workflow scope):

  • moves the job to Node 24.x;
  • removes NPM_TOKEN from the job;
  • turns off the dependency cache in the publishing job, as npm recommends;
  • documents the permissions.
release.yml patch
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7b879eb..7481c19 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,10 +3,15 @@ name: Release
 # Runs semantic-release on every push to main. It only actually cuts a
 # release (version bump, CHANGELOG.md, git tag, GitHub release, npm publish)
 # when there are unreleased Conventional Commits (feat:/fix:/etc.) since the
-# last release, AND the NPM_TOKEN secret below is configured. Until you add
-# that secret, this workflow safely fails at the publish step and nothing is
-# published — publishing manually via `npm login && npm publish --access
-# public` (see RELEASE_NOTES.md) is unaffected either way.
+# last release.
+#
+# npm publishing uses npm Trusted Publishing (OIDC): no NPM_TOKEN secret is
+# needed. The job's `id-token: write` permission lets npm exchange a
+# short-lived GitHub OIDC token for a one-off publish credential, and npm
+# attaches a provenance attestation automatically. This requires a trusted
+# publisher for @sahinur/forgedata on npmjs.com pointing at this repository
+# and this workflow file (`release.yml`), plus Node >= 22.14 and npm >= 11.5.1
+# (semantic-release 25 / @semantic-release/npm 13 bundle a recent npm).
 on:
   push:
     branches: [main]
@@ -18,10 +23,10 @@ jobs:
     name: Release
     runs-on: ubuntu-latest
     permissions:
-      contents: write
-      issues: write
-      pull-requests: write
-      id-token: write
+      contents: write # push the release commit/tag, create the GitHub release
+      issues: write # comment on released issues / open a failure issue
+      pull-requests: write # comment on released pull requests
+      id-token: write # npm Trusted Publishing (OIDC) + provenance
     steps:
       - uses: actions/checkout@v7
         with:
@@ -30,9 +35,11 @@ jobs:
       - name: Setup Node
         uses: actions/setup-node@v6
         with:
-          node-version: 22.x
-          cache: "npm"
+          node-version: 24.x
           registry-url: "https://registry.npmjs.org"
+          # No dependency cache in the release job (avoids cache poisoning of
+          # the publishing build), as recommended by npm for trusted publishing.
+          package-manager-cache: false
 
       - name: Install dependencies
         run: npm ci
@@ -50,4 +57,3 @@ jobs:
         run: npx semantic-release
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Verification (local, not on CI)

  • Node 24.21.0 / npm 11.19.0: clean npm ci works (no lockfile drift). npm run lint, typecheck, test:coverage (26 files, 250 tests, 100% coverage) and build all pass.
  • Node 20.19.2 / npm 9.2.0: the same steps pass. npm ci prints only EBADENGINE warnings, for release tooling that needs Node ≥ 22.14. Install still succeeds, so the CI matrix's 18/20 jobs should keep passing (this PR's CI run will confirm).
  • Simulated GitHub Actions run of @semantic-release/npm verifyConditions with no NPM_TOKEN:
    • It logs Verifying OIDC context for publishing from GitHub Actions and attempts the OIDC exchange first.
    • It fails locally only because there is no Actions ID-token endpoint, then falls back to looking for NPM_TOKEN.
  • I did not tag, publish or re-run anything.

One-time owner steps on npmjs.com

  1. Sign in to https://www.npmjs.com as the package owner. Open @sahinur/forgedata → Settings → Trusted publishing (https://www.npmjs.com/package/@sahinur/forgedata/access).
  2. Under Select your publisher, click GitHub Actions and fill in exactly (case-sensitive):
    • Organization or user: SahinurDEV
    • Repository: ForgeData
    • Workflow filename: release.yml (filename only, no path)
    • Environment name: (leave empty)
    • Allowed actions: tick "npm publish". Configurations created after 2026-09-03 default to npm stage publish only, which semantic-release's plain npm publish can't use.
  3. Timing: a new trusted publisher configuration expires if it doesn't complete a successful publish within 2 days. Merge this PR first. Then create the configuration only when a release-worthy commit (feat: / fix: / perf:) is about to land on main. This PR's own build: commit does not trigger a release. If the configuration expires, delete it and create it again.
  4. After the first successful publish (the npm page should show a provenance badge):
    • set Settings → Publishing access to "Require two-factor authentication and disallow tokens";
    • revoke the old npm automation/granular token;
    • delete the NPM_TOKEN repo secret (Settings → Secrets and variables → Actions).

…ease 25

- semantic-release ^24.1.0 -> ^25.0.9 (brings @semantic-release/npm 13.2.0
  with OIDC trusted publishing and bundled npm 11.21.0, and
  @semantic-release/github 12.0.10 which fixes the fail-step
  'Variable $owner of type String! was provided invalid value' crash)
- README: document the token-less release setup

This branch has not been deployed

No deployments
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