From 8a2da4b5acac1620568cd625ff3fef610ee6d588 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste THERY Date: Sat, 3 Oct 2026 18:28:08 +0700 Subject: [PATCH] fix(release): publish to npm through trusted publishing The stored npm token expired, so semantic-release stopped at npm whoami with a 401 and every release from main failed. pnpm 11 exchanges the release job's GitHub OIDC identity for a short-lived npm token, so the workflow no longer passes NPM_TOKEN or writes a registry token file, and the release checks now require the job's id-token permission instead of a token. RELEASING documents the one-time trusted publisher entry on npmjs.com. --- .github/workflows/npm-publish.yml | 4 ++-- RELEASING.md | 6 ++++++ scripts/semantic-release-publish.mjs | 4 +--- scripts/semantic-release-verify.mjs | 20 +++++--------------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 577e7fa..8f8a633 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -36,7 +36,6 @@ jobs: uses: actions/setup-node@v7 with: node-version: 22 - registry-url: https://registry.npmjs.org - name: Set up pnpm run: | @@ -49,11 +48,12 @@ jobs: - name: Validate release gate run: pnpm validate + # npm trusted publishing: pnpm exchanges this job's OIDC identity for a short-lived publish + # token, so no long-lived npm token is stored in GitHub. - name: Semantic release run: pnpm exec semantic-release env: GITHUB_TOKEN: ${{ github.token }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Upload release verification artifacts uses: actions/upload-artifact@v7 diff --git a/RELEASING.md b/RELEASING.md index 4792cb8..4a316de 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -22,6 +22,12 @@ The workflow runs `pnpm validate`, then semantic-release derives the next versio Conventional Commits. It prepares and publishes `@jcode.labs/ragmir` with npm provenance. The landing is private and is not published to npm. +Publishing uses npm trusted publishing: `pnpm publish` exchanges the release job's GitHub OIDC +identity for a short-lived token, so the repository stores no npm token. The npm package settings +must list one trusted publisher with these values: organization `jcode-works`, repository +`jcode-ragmir`, workflow `npm-publish.yml`, and environment `npm-publish`. Renaming the workflow, +the environment, or the repository requires updating that entry on npmjs.com first. + The repository intentionally contains no cloud-vendor landing configuration and the npm workflow does not deploy the site. The external deployment must build with the released version in `PUBLIC_RAGMIR_VERSION`. Production must use `PUBLIC_RAGMIR_LANDING_URL=https://ragmir.com`; staging diff --git a/scripts/semantic-release-publish.mjs b/scripts/semantic-release-publish.mjs index 9f9fb93..54437ed 100644 --- a/scripts/semantic-release-publish.mjs +++ b/scripts/semantic-release-publish.mjs @@ -23,9 +23,7 @@ for (const directory of packageDirs) { if (checkOnly) { console.log(`Semantic release publish check passed for ${version}`) } else { - if (!process.env.NODE_AUTH_TOKEN) { - throw new Error("NODE_AUTH_TOKEN is required for npm publish") - } + // pnpm authenticates through npm trusted publishing with the workflow's OIDC identity. for (const directory of packageDirs) { run("pnpm", [ "--dir", diff --git a/scripts/semantic-release-verify.mjs b/scripts/semantic-release-verify.mjs index 1c06bb2..f254e00 100644 --- a/scripts/semantic-release-verify.mjs +++ b/scripts/semantic-release-verify.mjs @@ -1,4 +1,3 @@ -import { spawnSync } from "node:child_process" import { readFile } from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" @@ -36,19 +35,10 @@ for (const packageName of optionalCorePackages) { if (checkOnly) { console.log("Semantic release verify check passed.") } else if (process.env.GITHUB_ACTIONS === "true") { - if (!process.env.NODE_AUTH_TOKEN) { - throw new Error("NODE_AUTH_TOKEN is required before semantic-release can create a tag") - } - run("npm", ["whoami", "--registry=https://registry.npmjs.org"]) -} - -function run(command, args) { - const result = spawnSync(command, args, { - cwd: repoRoot, - encoding: "utf8", - stdio: "inherit", - }) - if (result.status !== 0) { - throw new Error(`Command failed: ${command} ${args.join(" ")}`) + // Trusted publishing needs the job's OIDC request credentials before a tag is created. + if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL || !process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN) { + throw new Error( + "npm trusted publishing requires the release job's id-token: write permission before semantic-release can create a tag", + ) } }