diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3adc4b68..d127b34b 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -98,11 +98,17 @@ jobs: fetch-depth: 0 - uses: './.github/actions/setup' - run: | - npm config set workspaces-update false npx multi-semantic-release \ --deps.release=inherit \ --ignore-private-packages env: + # multi-semantic-release rewrites each package's "*" dependency + # ranges to the versions this run is about to publish, so reifying + # the workspace mid-run resolves versions the registry does not have + # yet. This has to travel as an env var: @semantic-release/npm runs + # `npm version` with --userconfig pointed at a temp file, which + # displaces ~/.npmrc, and env beats every config file. + npm_config_workspaces_update: 'false' GIT_AUTHOR_EMAIL: ${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com GIT_COMMITTER_EMAIL: diff --git a/scripts/npm-trust b/scripts/npm-trust new file mode 100755 index 00000000..537377a7 --- /dev/null +++ b/scripts/npm-trust @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Registers GitHub Actions as the npm trusted publisher for every public +# workspace package. npm stores that relationship per package, and without it +# the OIDC token exchange in .github/workflows/push.yml gets rejected and +# @semantic-release/npm falls back to looking for an NPM_TOKEN. A newly added +# package needs a run of this script before its first release. +# +# Requires npm >= 11.15.0, write access to every package, and account-level +# 2FA. npm asks for an OTP on the first package; the npmjs.com prompt offers to +# skip 2FA for the next five minutes, which covers the rest of the run. +# +# Usage: scripts/npm-trust [package...] + +repo=code-like-a-carpenter/workbench +workflow=push.yml +required_npm=11.15.0 + +npm_version="$(npm --version)" +if [ "$(printf '%s\n%s\n' "$required_npm" "$npm_version" | sort -V | head -n1)" != "$required_npm" ]; then + echo "npm trust requires npm >= $required_npm, but npm is $npm_version" >&2 + exit 1 +fi + +if [ $# -gt 0 ]; then + packages=("$@") +else + mapfile -t packages < <( + npm pkg get name private --ws --json | node -e ' + const chunks = []; + process.stdin.on("data", (chunk) => chunks.push(chunk)); + process.stdin.on("end", () => { + for (const pkg of Object.values(JSON.parse(chunks.join("")))) { + if (pkg.private !== true) { + console.log(pkg.name); + } + } + }); + ' + ) +fi + +failed=() +for package in "${packages[@]}"; do + echo "==> $package" + if ! npm trust github "$package" \ + --file "$workflow" \ + --repo "$repo" \ + --allow-publish \ + --yes; then + failed+=("$package") + fi + # The registry rate limits the trust endpoint; npm's docs suggest a 2s gap. + sleep 2 +done + +if [ "${#failed[@]}" -gt 0 ]; then + echo "Failed to configure:" >&2 + printf ' %s\n' "${failed[@]}" >&2 + exit 1 +fi