From a983747852d1a9769a39f17fc9dc4e1ca85c9851 Mon Sep 17 00:00:00 2001 From: lizhixuan Date: Mon, 26 Oct 2026 21:57:15 +0800 Subject: [PATCH] ci: publish through npm trusted publishing (OIDC) instead of NPM_TOKEN npm is removing direct publish from tokens that bypass 2FA (target January 2027). The release job now gets id-token: write and npm 11, drops the npm token and registry-url, and lets changesets/action v1.9's OIDC path publish; every version gains a provenance statement. A patch changeset republishes all 21 packages so each new trusted-publisher configuration is used within npm's 2-day window, and scripts/trust-publishers.sh registers release.yml as the trusted publisher of every public package. --- .changeset/trusted-publishing.md | 25 ++++++++++++++++ .github/workflows/release.yml | 15 ++++++---- scripts/trust-publishers.sh | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 .changeset/trusted-publishing.md create mode 100755 scripts/trust-publishers.sh diff --git a/.changeset/trusted-publishing.md b/.changeset/trusted-publishing.md new file mode 100644 index 0000000..e9f4943 --- /dev/null +++ b/.changeset/trusted-publishing.md @@ -0,0 +1,25 @@ +--- +'@refkit/core': patch +'@refkit/mcp': patch +'@refkit/provider-artic': patch +'@refkit/provider-brave': patch +'@refkit/provider-europeana': patch +'@refkit/provider-flickr': patch +'@refkit/provider-freesound': patch +'@refkit/provider-gutendex': patch +'@refkit/provider-internet-archive': patch +'@refkit/provider-jamendo': patch +'@refkit/provider-met': patch +'@refkit/provider-nailbook': patch +'@refkit/provider-openverse': patch +'@refkit/provider-pexels': patch +'@refkit/provider-pixabay': patch +'@refkit/provider-poetrydb': patch +'@refkit/provider-polyhaven': patch +'@refkit/provider-rijksmuseum': patch +'@refkit/provider-smithsonian': patch +'@refkit/provider-unsplash': patch +'@refkit/provider-wikimedia-commons': patch +--- + +Published from GitHub Actions through npm trusted publishing (OIDC) instead of a long-lived npm token, so every version now carries a signed provenance statement that links it to the exact commit and workflow run that built it. No code changes. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9651f0..072dd16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: permissions: contents: write # changesets commits the version PR + tags pull-requests: write # changesets opens the "Version Packages" PR + id-token: write # npm trusted publishing (OIDC): no npm token anywhere steps: - uses: actions/checkout@v7 - uses: pnpm/action-setup@v6 @@ -19,13 +20,17 @@ jobs: with: node-version: 22 cache: pnpm - registry-url: 'https://registry.npmjs.org' + # Trusted publishing needs npm >= 11.5.1. Stay on 11: `changeset publish` reads + # `npm info --json` as an object, and npm 12 returns an array. + - run: npm install -g npm@11 - run: pnpm install --frozen-lockfile - run: pnpm typecheck - run: pnpm test:run - # Publishes with NPM_TOKEN. To switch to token-free OIDC later: add - # `id-token: write` above, `- run: npm i -g npm@latest`, drop the NPM_TOKEN - # env, and register this repo + release.yml as each package's trusted publisher. + # Publishes through npm trusted publishing: every public package lists this repo + # and `release.yml` as its trusted publisher on npmjs.com. With no NPM_TOKEN set, + # changesets/action (v1.9+) leaves ~/.npmrc alone and `changeset publish` -> + # `pnpm publish` -> `npm publish` exchanges the job's OIDC token, which also signs + # a provenance statement. An `_authToken` in any .npmrc would bypass OIDC. - name: Create release PR or publish uses: changesets/action@v1 with: @@ -33,5 +38,3 @@ jobs: publish: pnpm run release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/scripts/trust-publishers.sh b/scripts/trust-publishers.sh new file mode 100755 index 0000000..d7fd715 --- /dev/null +++ b/scripts/trust-publishers.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Register this repo's release workflow (.github/workflows/release.yml) as the npm +# trusted publisher of every public @refkit package, so CI publishes through OIDC +# with no npm token. +# +# Run it yourself, once per package, from the repo root: +# npm login # an interactive session with 2FA +# bash scripts/trust-publishers.sh # or pass package names to limit the run +# +# Requirements and rules (npm docs, 2026-10): +# - npm >= 11.15 locally (`npm trust`), 2FA enabled on the account. Tokens that +# bypass 2FA cannot change trusted publishing settings. +# - Each package must already exist on npm. A brand-new package is published once by +# hand first, then added here. +# - The first request asks for 2FA. Tick "skip 2FA for the next 5 minutes" so the +# loop finishes unattended. +# - npm allows one trusted publisher per package here; an existing one is reported +# and skipped. `npm trust list ` / `npm trust revoke --id ` manage it. +# - A new configuration must be used for a successful publish within 2 days or npm +# expires it, so run this right before merging the Version Packages PR. +set -uo pipefail +cd "$(dirname "$0")/.." + +if [ "$#" -gt 0 ]; then + names=("$@") +else + names=() + for manifest in packages/*/package.json; do + name=$(node -p "const j = require('./$manifest'); j.private ? '' : j.name") + [ -n "$name" ] && names+=("$name") + done +fi + +failed=() +for name in "${names[@]}"; do + echo "== $name" + if ! npm trust github "$name" --repo refkitjs/refkit --file release.yml --allow-publish --yes; then + failed+=("$name") + fi + sleep 2 +done + +echo +if [ "${#failed[@]}" -eq 0 ]; then + echo "Configured ${#names[@]} package(s)." +else + echo "Failed for ${#failed[@]} of ${#names[@]}: ${failed[*]}" + echo "Check with: npm trust list " + exit 1 +fi