From 2d71e5637a1ef5beb4f80c64a05ad1bb65162cd8 Mon Sep 17 00:00:00 2001 From: Logan Lindquist Land Date: Sun, 2 Aug 2026 13:42:48 -0500 Subject: [PATCH] chore: migrate to pnpm 11 with supply-chain defaults Bumps packageManager from pnpm@10.34.5 to pnpm@11.18.0. pnpm 11 is pure ESM and requires Node >=22, so the Node 20 CI leg could no longer install. Node 20 reached end-of-life in April 2026, so it is dropped from the matrix and engines.node is raised from ">=20" to ">=22" to match what is actually tested. Adds pnpm-workspace.yaml declaring pnpm 11's supply-chain defaults explicitly: - minimumReleaseAge: 1440 (block packages younger than 24h) - blockExoticSubdeps: true (no git/tarball transitive deps) - strictDepBuilds: true (fail install on unapproved postinstalls) - verifyDepsBeforeRun: install (re-check lockfile before pnpm run) allowBuilds is empty: no dependency in this tree requests a build script, and strictDepBuilds makes that an enforced property rather than an assumption. The lockfile is unchanged: lockfileVersion 9.0 is identical between pnpm 10 and 11. Closes #27 --- .github/workflows/ci.yml | 4 +++- package.json | 4 ++-- pnpm-workspace.yaml | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 pnpm-workspace.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6650eea..ba49389 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,10 @@ jobs: strategy: fail-fast: false matrix: + # Node 20 is not tested: pnpm 11 is pure ESM and requires Node >=22, so + # `pnpm install` cannot run on it. Node 20 reached end-of-life in April + # 2026, and engines.node is set to >=22 to match what is actually tested. node-version: - - 20.x - 22.x - 24.x steps: diff --git a/package.json b/package.json index 6d9c948..12ecfbd 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,9 @@ "type": "module", "license": "MIT", "author": "Logan Lindquist Land", - "packageManager": "pnpm@10.34.5", + "packageManager": "pnpm@11.18.0", "engines": { - "node": ">=20" + "node": ">=22" }, "repository": { "type": "git", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..0b1f57e --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,16 @@ +# pnpm 11 supply-chain hardening. +# These are pnpm 11's defaults, stated explicitly so the security posture is +# visible in review and survives future changes to pnpm's own defaults. + +minimumReleaseAge: 1440 # block packages younger than 24h +blockExoticSubdeps: true # no git/tarball transitive deps +strictDepBuilds: true # fail install on unapproved postinstalls +verifyDepsBeforeRun: install # re-check lockfile before `pnpm run` + +# Postinstall allowlist. Empty map = block all build scripts. +# +# Deliberately empty: no dependency in this tree requests a build script. With +# strictDepBuilds enabled, `pnpm install` would fail with ERR_PNPM_IGNORED_BUILDS +# if one appeared, so this staying empty is an enforced property, not an +# oversight. Add an entry only after auditing that package's postinstall. +allowBuilds: {}