get-pnpm moves from pnpm/get.pnpm.io into pnpm/npm/get-pnpm, at 0.0.9 (get.pnpm.io be4e983, where its history stays). - The pnpm v12 npm package depends on it with `workspace:*`, so a pnpm release bundles get-pnpm's source as of that release. A wrapper fix no longer needs a get-pnpm release, a catalog bump, and a minimumReleaseAge exclusion first. bundle-node-gyp builds it before the deploy injects its build output. - It is released on its own: a `get-pnpm` product in bump.ts and create-release-pr.yml (off by default), `get-pnpm@<version>` tags, and build-get-pnpm/publish-get-pnpm jobs in release.yml. It is private in the workspace so the TypeScript release's recursive publish skips it; the packed manifest drops the flag. - Its runtime dependencies stay exact, through a `get-pnpm` named catalog. Its lint is the root ESLint config. Its TLS tests use the fixture the Rust network tests use. - The npm-wrapper CI job runs its tests on Node.js 24 and 22, beside the wrapper's. The forwarding proxy in its download tests forwards only to the servers the tests start. npm's registry signing keys were pinned in five places: v11, v12, get-pnpm, and get.pnpm.io's install.sh and install.ps1. Only v11's copy was refreshed, by the release PR, and the two repositories handled a withdrawn key in opposite ways. They now come from .config/npm-signing-keys.json: - .github/scripts/update-npm-signing-keys.mjs refreshes it from npm on every release PR, not only v11 ones. A key npm stops listing stays, marked withdrawn. - The meta-updater renders it into the v11, v12, and get-pnpm copies, so `pnpm lint:meta` fails on a copy edited by hand. pnpm v11 and v12 keep trusting withdrawn keys, so packages signed before a rotation verify. get-pnpm drops them, as get.pnpm.io's installers do. - get.pnpm.io's update script is to read this file.
248 lines
11 KiB
YAML
248 lines
11 KiB
YAML
name: Create Release PR
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
target:
|
|
description: Branch to release (the PR base; e.g. main or release/11.1).
|
|
default: main
|
|
required: true
|
|
# Each product is a separate checkbox, so a release can cover any
|
|
# combination. Only the checked products' pending changesets are consumed;
|
|
# the rest stay in the ledger for a later release. This lets a rapid v12
|
|
# (Rust) cadence release without dragging the TypeScript CLI (v11) along —
|
|
# and with it the near-daily "Update available!" notification. Checking
|
|
# every box releases everything pending.
|
|
pnpm11:
|
|
description: Release pnpm v11 (the TypeScript CLI) and the rest of the default lane.
|
|
type: boolean
|
|
default: true
|
|
pnpm:
|
|
description: Release pnpm (the Rust CLI, v12 alpha prerelease) and its @pnpm/napi addon.
|
|
type: boolean
|
|
default: true
|
|
pnpr:
|
|
description: Release pnpr (the registry server).
|
|
type: boolean
|
|
default: true
|
|
# Off by default: get-pnpm releases on its own cadence, and the pnpm
|
|
# releases bundle its workspace source whether or not it is published.
|
|
get_pnpm:
|
|
description: Release get-pnpm (the installer package).
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
|
|
# Serialize per target so two dispatches for the same branch can't race on the
|
|
# force-pushed release-pr/<target> branch.
|
|
concurrency:
|
|
group: create-release-pr-${{ github.event.inputs.target }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
create-release-pr:
|
|
if: github.repository == 'pnpm/pnpm' # Only run on the main repository, not forks
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
env:
|
|
# The husky hooks are a developer safety net; CI has its own gates. Without
|
|
# this, `pnpm install` wires the hooks and the pre-push hook runs the full
|
|
# TS compile/lint plus the Rust clippy/doc sweep during "Commit and push"
|
|
# (the shallow clone and URL remote make its change detection always
|
|
# conclude that Rust sources changed).
|
|
HUSKY: 0
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
# Don't persist the write-scoped token in .git/config; the install below
|
|
# runs third-party lifecycle scripts. Pushing is done with an explicit,
|
|
# single-use remote URL in the "Commit and push" step.
|
|
persist-credentials: false
|
|
|
|
- name: Initialize Rust before dependency installation
|
|
uses: $/.github/actions/rustup
|
|
with:
|
|
restore-cache: false
|
|
|
|
- name: Install pnpm and Node
|
|
uses: pnpm/setup@fa36a5ef7dcdefc3a7cb25655c122e00cf6e636e # v3.1.2
|
|
with:
|
|
runtime: node@26.3.0
|
|
|
|
# Base the release on the tip of the target branch, fetched explicitly so the
|
|
# run is correct even when dispatched from another ref. A force-push to
|
|
# release-pr/<target> reuses an already-open release PR for the same target
|
|
# rather than opening a second.
|
|
- name: Prepare release branch
|
|
env:
|
|
TARGET: ${{ github.event.inputs.target }}
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git fetch origin "$TARGET"
|
|
git checkout -B "release-pr/$TARGET" FETCH_HEAD
|
|
|
|
# Refresh .config/npm-signing-keys.json from what npm advertises. pnpm v11,
|
|
# pnpm v12, and get-pnpm verify package-manager binaries against these keys,
|
|
# so a stale set could break verification after a key rotation. The bump
|
|
# step's update-manifests renders the file into each product's copy, and any
|
|
# drift is committed below along with the version bumps, so the new trust
|
|
# roots are reviewed as part of the release PR diff.
|
|
- name: Update embedded npm signing keys
|
|
run: node .github/scripts/update-npm-signing-keys.mjs --update
|
|
|
|
# Refresh the embedded Node.js release keys (used to verify the signature of
|
|
# a downloaded runtime's SHASUMS256.txt) from the canonical
|
|
# nodejs/release-keys list, so a new release signer cannot break Node.js
|
|
# runtime verification. Reviewed in the release PR diff like the npm keys.
|
|
- name: Update embedded Node.js release keys
|
|
if: github.event.inputs.pnpm11 == 'true'
|
|
run: node pnpm11/crypto/shasums-file/scripts/update-node-release-keys.mjs --update
|
|
|
|
# A refreshed trust root must show up in the changelogs, so synthesize a
|
|
# changeset for whichever key sets drifted before `pnpm bump` consumes the
|
|
# pending changesets. The `drifted` output drives an explicit review signal
|
|
# on the PR, so a trust-root change cannot hide in a large version-bump diff.
|
|
# The npm keys changeset names every product that embeds them; the intents
|
|
# of products this run does not release stay pending for their next one.
|
|
- name: Add changesets for refreshed keys
|
|
id: keys
|
|
run: |
|
|
drifted=""
|
|
if ! git diff --quiet -- .config/npm-signing-keys.json; then
|
|
drifted="npm registry signing keys"
|
|
cat > .changeset/release-refresh-npm-signing-keys.md <<'EOF'
|
|
---
|
|
"@pnpm/deps.security.signatures": patch
|
|
"pnpm": patch
|
|
"pacquet": patch
|
|
"get-pnpm": patch
|
|
---
|
|
|
|
Updated the embedded npm registry signing keys to the set currently advertised by npm.
|
|
EOF
|
|
fi
|
|
if ! git diff --quiet -- pnpm11/crypto/shasums-file/src/nodeReleaseKeys.ts; then
|
|
drifted="${drifted:+$drifted and }Node.js release keys"
|
|
cat > .changeset/release-refresh-node-release-keys.md <<'EOF'
|
|
---
|
|
"@pnpm/crypto.shasums-file": patch
|
|
"pnpm": patch
|
|
---
|
|
|
|
Updated the embedded Node.js release keys to the current canonical `nodejs/release-keys` list.
|
|
EOF
|
|
fi
|
|
echo "drifted=$drifted" >> "$GITHUB_OUTPUT"
|
|
|
|
# Consumes the pending changesets: bumps versions, writes changelogs, updates
|
|
# the ledger, and syncs manifests. Only the checked products' intents are
|
|
# consumed; the rest stay in the ledger for a later release. A no-op (nothing
|
|
# pending in scope) leaves the tree clean and the steps below skip.
|
|
- name: Bump versions
|
|
env:
|
|
RELEASE_PNPM11: ${{ github.event.inputs.pnpm11 }}
|
|
RELEASE_PNPM: ${{ github.event.inputs.pnpm }}
|
|
RELEASE_PNPR: ${{ github.event.inputs.pnpr }}
|
|
RELEASE_GET_PNPM: ${{ github.event.inputs.get_pnpm }}
|
|
run: |
|
|
args=()
|
|
[ "$RELEASE_PNPM11" = "true" ] && args+=(--release pnpm11)
|
|
[ "$RELEASE_PNPM" = "true" ] && args+=(--release pnpm)
|
|
[ "$RELEASE_PNPR" = "true" ] && args+=(--release pnpr)
|
|
[ "$RELEASE_GET_PNPM" = "true" ] && args+=(--release get-pnpm)
|
|
if [ ${#args[@]} -eq 0 ]; then
|
|
echo "Select at least one product to release." >&2
|
|
exit 1
|
|
fi
|
|
pnpm run bump -- "${args[@]}"
|
|
|
|
- name: Check for changes
|
|
id: changes
|
|
run: |
|
|
if [ -z "$(git status --porcelain)" ]; then
|
|
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# One release PR covers every product; the commit subject lists the ones
|
|
# whose version actually changed against the target branch, e.g.
|
|
# "chore(release): 11.13.0, pacquet 12.0.0-alpha.9, pnpr 0.2.0". When only
|
|
# the TypeScript CLI bumped, the subject stays the historical bare version.
|
|
# `@pnpm/napi` is not listed separately: it is a versioning.fixed group with
|
|
# pacquet, so it always carries pacquet's version.
|
|
- name: Compose release summary
|
|
id: version
|
|
if: steps.changes.outputs.changed == 'true'
|
|
run: |
|
|
summary=""
|
|
add() {
|
|
local label="$1" manifest="$2" new old
|
|
new=$(jq -r .version "$manifest")
|
|
old=$(git show "FETCH_HEAD:$manifest" 2>/dev/null | jq -r .version || echo "")
|
|
if [ "$new" != "$old" ]; then
|
|
summary="${summary:+$summary, }${label:+$label }$new"
|
|
fi
|
|
}
|
|
add "" pnpm11/pnpm/package.json
|
|
add "pacquet" pnpm/npm/pnpm/package.json
|
|
add "pnpr" pnpr/npm/pnpr/package.json
|
|
add "get-pnpm" pnpm/npm/get-pnpm/package.json
|
|
# Changes without a version bump (e.g. refreshed trust-root changesets
|
|
# consumed into changelogs only) still need a commit subject.
|
|
if [ -z "$summary" ]; then
|
|
summary=$(jq -r .version pnpm11/pnpm/package.json)
|
|
fi
|
|
echo "version=$summary" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Commit and push
|
|
if: steps.changes.outputs.changed == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.UPDATE_LOCKFILE_TOKEN }}
|
|
TARGET: ${{ github.event.inputs.target }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
git add -A
|
|
git commit -m "chore(release): ${VERSION}"
|
|
git push -f "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "release-pr/$TARGET"
|
|
|
|
- name: Create PR if needed
|
|
if: steps.changes.outputs.changed == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.UPDATE_LOCKFILE_TOKEN }}
|
|
TARGET: ${{ github.event.inputs.target }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
BRANCH="release-pr/$TARGET"
|
|
|
|
# An already-open PR now points at the freshly force-pushed branch, so
|
|
# there is nothing more to do.
|
|
if [ -n "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then
|
|
echo "PR already exists; the new versions were force-pushed to it"
|
|
else
|
|
gh pr create \
|
|
--title "chore(release): ${VERSION}" \
|
|
--body "Automated release PR created by the create-release-pr workflow.
|
|
|
|
Releasing \`${TARGET}\`: ${VERSION}. Merging this PR consumes the pending changesets and records them in the committed \`.changeset/ledger.yaml\`. Afterwards a maintainer pushes a signed tag per released product to run the release workflow — see RELEASING.md." \
|
|
--base "$TARGET" \
|
|
--head "$BRANCH"
|
|
fi
|
|
|
|
# Embedded trust roots changed in this release, so leave a hard-to-miss
|
|
# comment: the refreshed keys need deliberate review, not a scroll-past
|
|
# among the version bumps and changelogs.
|
|
- name: Flag refreshed trust roots on the PR
|
|
if: steps.changes.outputs.changed == 'true' && steps.keys.outputs.drifted != ''
|
|
env:
|
|
GH_TOKEN: ${{ secrets.UPDATE_LOCKFILE_TOKEN }}
|
|
TARGET: ${{ github.event.inputs.target }}
|
|
DRIFTED: ${{ steps.keys.outputs.drifted }}
|
|
run: |
|
|
PR=$(gh pr list --head "release-pr/$TARGET" --state open --json number --jq '.[0].number')
|
|
gh pr comment "$PR" --body "⚠️ This release refreshes embedded trust roots: **${DRIFTED}**. Review those key diffs deliberately before merging — they gate signature verification of Node.js runtimes and package-manager binaries."
|