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
17 changes: 17 additions & 0 deletions .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,25 @@ jobs:
- name: Verify the release tarball
run: pnpm --filter @workspacejson/cli exec node ../../scripts/verify-package-tarball.mjs

# Both variables carry the same secret, because two different config
# layers each want their own name and the wrong one alone is silently
# useless:
#
# NODE_AUTH_TOKEN — the userconfig `setup-node` writes to $RUNNER_TEMP.
# NPM_TOKEN — the repository's own `.npmrc`, which reads
# `//registry.npmjs.org/:_authToken=${NPM_TOKEN}`.
#
# npm's project config outranks the userconfig, so the repo `.npmrc` wins
# for any command run from the repository root. With NPM_TOKEN unset it
# does not fall back to the userconfig token — it sends the *literal*
# string `Bearer ${NPM_TOKEN}`, which the registry rejects as 401. That is
# exactly how the 0.5.0 release failed, and it reads as a bad credential
# rather than a misconfigured workflow, which is what made it worth a
# comment this long.
- name: Confirm the publish credential reached the runner
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: node scripts/verify-npm-publish-access.mjs

# npm rather than pnpm, for two reasons: pnpm 9 exposes no --provenance
Expand All @@ -119,6 +135,7 @@ jobs:
working-directory: packages/cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance --access public

# A publish that npm accepts can still be unusable — wrong files, broken
Expand Down
16 changes: 16 additions & 0 deletions scripts/verify-npm-publish-access.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

import { spawnSync } from "node:child_process";

// The repository `.npmrc` reads `//registry.npmjs.org/:_authToken=${NPM_TOKEN}`,
// and npm's project config outranks the userconfig `setup-node` writes. If
// NPM_TOKEN is missing npm does not fall back — it sends the literal string
// `Bearer ${NPM_TOKEN}` and the registry answers 401, which looks identical to a
// revoked or mistyped token. Checking the variable first turns half an hour of
// suspecting the credential into one accurate line.
const token = process.env.NPM_TOKEN;
if (!token || token.startsWith("${")) {
console.error(
token
? `NPM_TOKEN is present but unexpanded (${JSON.stringify(token)}). The repository .npmrc interpolates it, so npm would send that literal text as the bearer token and the registry would reject it as 401.`
: "NPM_TOKEN is not set. The repository .npmrc interpolates it, so npm would send the literal text \"${NPM_TOKEN}\" as the bearer token and the registry would reject it as 401 — which reads as a bad credential rather than a missing one.",
);
process.exit(1);
}

const username = JSON.parse(run("npm", ["whoami", "--json"]));

// `npm access` reports package visibility or package enumeration; it does not
Expand Down
Loading