From 4fb3b8b05bce7043f4c25dd22e1aac97431c5e11 Mon Sep 17 00:00:00 2001 From: Agustin Delgado Date: Thu, 15 Oct 2026 15:25:54 -0300 Subject: [PATCH] ci: publish through npm trusted publishing --- .github/workflows/release.yml | 24 ++++++------------------ scripts/publish.mjs | 5 +++++ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 507112ba..115d34ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,10 @@ concurrency: # The whole permission surface: push the release branch, publish, tag. permissions: contents: write + # npm trusted publishing: the job asks GitHub for an OIDC token, and pnpm + # exchanges it for a short npm token that can publish `@human-kit/ui` only. + # There is no npm secret. + id-token: write jobs: release: @@ -96,31 +100,15 @@ jobs: echo "Merging it publishes to npm." } >> "$GITHUB_STEP_SUMMARY" - # `changeset publish` authenticates through an `.npmrc`, and nothing else - # here writes one: the repo ships none, and `actions/setup-node` only - # generates one when given `registry-url`. `changesets/action` used to do - # this itself, which is why it was invisible until that action was dropped — - # an unauthenticated publish comes back as `E404 undefined`, because npm - # masks "you may not publish here" as "no such package". - # - # Written to $HOME rather than the workspace so it cannot be committed by a - # later step, and only on the branch that actually publishes. - - name: Authenticate with npm - if: steps.version.outputs.versioned == 'false' - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$HOME/.npmrc" - # Only when there was nothing left to version — that is, the release branch # has been merged. `changeset publish` skips versions already on npm, so a # rerun is a no-op, and it recovers a release whose version commit landed - # but whose publish did not. + # but whose publish did not. No `.npmrc` carries a token, so a failed OIDC + # exchange stops the publish instead of falling back to a stale credential. - name: Publish if: steps.version.outputs.versioned == 'false' run: pnpm run release env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # `changeset publish` creates the `@human-kit/ui@` tags locally and diff --git a/scripts/publish.mjs b/scripts/publish.mjs index 1cbdc7b6..7e145ac6 100644 --- a/scripts/publish.mjs +++ b/scripts/publish.mjs @@ -34,6 +34,11 @@ execSync('pnpm exec changeset publish', { stdio: 'inherit' }); // // `changeset publish` REJECTS an explicit `--tag` in pre mode, so the tag is moved // afterwards. It is idempotent, and a no-op once the version already carries it. +// +// The Release workflow has no npm token: it publishes through trusted publishing, +// which authorizes the publish and nothing else by default. Before the next +// prerelease, give this step a credential (for example, allow `npm dist-tag` on the +// trusted publisher at npmjs.com), or it fails. if (pre) { const pkg = JSON.parse(readFileSync(packageUrl, 'utf8')); console.log(`Pointing the "${pre.tag}" dist-tag at ${pkg.version}.`);