Skip to content

chore: upgrade pnpm 9 → 11 with supply-chain protection#235

Open
cameronapak wants to merge 8 commits into
mainfrom
chore/pnpm-11-upgrade-supply-chain-protection
Open

chore: upgrade pnpm 9 → 11 with supply-chain protection#235
cameronapak wants to merge 8 commits into
mainfrom
chore/pnpm-11-upgrade-supply-chain-protection

Conversation

@cameronapak

@cameronapak cameronapak commented May 12, 2026

Copy link
Copy Markdown
Collaborator

RELATES TO https://lifechurch.atlassian.net/browse/YPE-2688

The recent news of seeing more and more supply-chain attacks via npm has caused me to want to make sure we're as secure as can be. That's what this PR does.

Summary

  • Upgrade pnpm 9.0.0 → 11.10.0 with minimumReleaseAge: 4320 (3-day cooldown) to mitigate supply-chain attacks on new package versions
  • Move overrides from package.jsonpnpm-workspace.yaml (pnpm 11 breaking change — overrides in package.json no longer enforce for auto-installed peers)
  • Pin every override to an exact version — vite was the last loose range (>=5.4.217.3.3), which could otherwise resolve a compromised newer release silently
  • Remove version pins from all CI workflows — pnpm/action-setup@v4 now reads from packageManager field (single source of truth)

Changes

File Change
package.json packageManager: "pnpm@11.10.0", engines.pnpm: ">=11.0.0", removed pnpm.overrides, added @internal/eslint-config + eslint-plugin-storybook as root devDeps
pnpm-workspace.yaml Added minimumReleaseAge, exact-pinned overrides (incl. vite: "7.3.3"), allowBuilds (@swc/core: false — tsup uses esbuild, swc native build unneeded)
CONTRIBUTING.md Prerequisites bumped to Node >=22, pnpm >=11 to match engines
.github/workflows/ci.yml Removed 4x version: 9.0.0 pins
.github/workflows/release.yml Removed version: 9.0.0 pin
.github/workflows/storybook.yml Removed version: 9.0.0 pin
AGENTS.md Updated pnpm version refs, added supply-chain protection docs

pnpm 11 breaking changes handled

  • Overrides must live in pnpm-workspace.yaml (not package.json) to enforce for auto-installed peers
  • Build scripts require allowBuilds approval (esbuild, @parcel/watcher, msw)
  • Workspace packages not hoisted to root — @internal/eslint-config and eslint-plugin-storybook must be root devDependencies
  • minimumReleaseAge blocks packages published < 3 days ago; override with --force if needed urgently

Verification

  • pnpm lint — all 7 packages pass
  • pnpm typecheck — all 6 packages pass
  • pnpm test — 751 tests pass (core: 298, hooks: 275, ui: 178)

Greptile Summary

This PR upgrades pnpm from 9.0.0 to 11.10.0 and adds supply-chain protections including a 3-day minimumReleaseAge cooldown, exact-pinned overrides, and a packageManager hash for corepack integrity. It also migrates pnpm settings from .npmrc and package.json into pnpm-workspace.yaml as required by pnpm 11's breaking changes.

  • pnpm 11 migration: Overrides moved from package.json → pnpm-workspace.yaml, autoInstallPeers/strictPeerDependencies/publicHoistPattern migrated from .npmrc, and allowBuilds added for native build control.
  • CI hardening: pnpm/action-setup@v4 version pins removed in favor of the packageManager field as the single source of truth; Node bumped from 20 → 22 across all workflows.
  • Supply-chain additions: minimumReleaseAge: 4320 (3-day cooldown), exact vite: "7.3.3" override replacing the former loose >=5.4.21 range, and a SHA-512 hash appended to the packageManager field.

Confidence Score: 4/5

Safe to merge after tightening the Node.js minimum version floor — the rest of the pnpm 11 migration is mechanically correct and well-documented.

The engines.node field in package.json, CONTRIBUTING.md, and AGENTS.md all state >=22.0.0, but pnpm 11 depends on the node:sqlite built-in introduced in Node 22.13. A developer on any 22.0–22.12 patch release would pass the engines gate but crash immediately when pnpm runs. AGENTS.md within this same PR already documents the correct floor, making this an internal contradiction.

package.json (engines.node floor), CONTRIBUTING.md and AGENTS.md (matching version documentation)

Important Files Changed

Filename Overview
package.json pnpm upgraded to 11.10.0 with SHA-512 hash; overrides section removed (moved to pnpm-workspace.yaml); engines.node set to >=22.0.0, which is under-specified — pnpm 11 requires Node 22.13+ (node:sqlite built-in)
pnpm-workspace.yaml Correctly receives overrides, autoInstallPeers, strictPeerDependencies, publicHoistPattern, minimumReleaseAge, and allowBuilds migrated from .npmrc and package.json; all changes align with pnpm 11 config requirements
.github/workflows/ci.yml Version pins removed from all four pnpm/action-setup steps (now reads from packageManager field); Node bumped from 20 to 22 across all jobs
.github/workflows/release.yml pnpm version pin removed; no other changes needed
.github/workflows/storybook.yml pnpm version pin removed; Node bumped from 20 to 22
.npmrc Correctly stripped of all pnpm config (now lives in pnpm-workspace.yaml per pnpm 11); replaced with an explanatory comment and link
AGENTS.md Node/pnpm version references updated; supply-chain protection section added; correctly notes pnpm 11 requires Node >= 22.13
CONTRIBUTING.md Prerequisites updated from Node >=20 / pnpm >=9 to Node >=22 / pnpm >=11

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Developer runs pnpm install] --> B{Node.js >= 22.13?}
    B -- No --> C[ERR_UNKNOWN_BUILTIN_MODULE node:sqlite crash]
    B -- Yes --> D{Package published < 3 days ago?}
    D -- Yes --> E[Blocked by minimumReleaseAge 4320 min cooldown]
    E --> F[Override with --force if urgent]
    D -- No --> G{workspace:* package?}
    G -- Yes --> H[Resolved locally no registry fetch]
    G -- No --> I{In allowBuilds?}
    I -- false --> J[Build scripts skipped e.g. @swc/core]
    I -- true --> K[Build scripts run e.g. esbuild, msw]
    K --> L[Install complete]
    H --> L
    J --> L
    F --> D
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Developer runs pnpm install] --> B{Node.js >= 22.13?}
    B -- No --> C[ERR_UNKNOWN_BUILTIN_MODULE node:sqlite crash]
    B -- Yes --> D{Package published < 3 days ago?}
    D -- Yes --> E[Blocked by minimumReleaseAge 4320 min cooldown]
    E --> F[Override with --force if urgent]
    D -- No --> G{workspace:* package?}
    G -- Yes --> H[Resolved locally no registry fetch]
    G -- No --> I{In allowBuilds?}
    I -- false --> J[Build scripts skipped e.g. @swc/core]
    I -- true --> K[Build scripts run e.g. esbuild, msw]
    K --> L[Install complete]
    H --> L
    J --> L
    F --> D
Loading

Comments Outside Diff (1)

  1. pnpm-lock.yaml, line 194-195 (link)

    P2 @swc/core no longer resolved as a tsup peer

    Across every package (root, packages/core, packages/hooks, packages/ui), tsup@8.5.0 previously resolved with @swc/core@1.13.5 as a satisfied peer; after this upgrade it resolves without it. This means tsup will now use esbuild as its transformer instead of SWC. If any package's tsup.config.ts explicitly sets esbuildOptions or SWC-specific options, behaviour changes silently. The test suite passing is reassuring, but it's worth confirming no tsup.config references experimentalDts or similar options that behaved differently under SWC.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: pnpm-lock.yaml
    Line: 194-195
    
    Comment:
    **`@swc/core` no longer resolved as a `tsup` peer**
    
    Across every package (`root`, `packages/core`, `packages/hooks`, `packages/ui`), `tsup@8.5.0` previously resolved with `@swc/core@1.13.5` as a satisfied peer; after this upgrade it resolves without it. This means tsup will now use esbuild as its transformer instead of SWC. If any package's `tsup.config.ts` explicitly sets esbuildOptions or SWC-specific options, behaviour changes silently. The test suite passing is reassuring, but it's worth confirming no `tsup.config` references `experimentalDts` or similar options that behaved differently under SWC.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Cursor

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
package.json:7-8
The `engines.node` floor of `22.0.0` is too low for pnpm 11. pnpm 11 relies on the `node:sqlite` built-in module, which was only added in Node 22.13. A developer on Node 22.0–22.12 would pass the `engines` gate and then hit a cryptic `ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite` crash from pnpm itself. AGENTS.md in this same PR already documents the correct minimum: "pnpm 11 requires Node >= 22.13".

```suggestion
    "node": ">=22.13.0",
    "pnpm": ">=11.0.0"
```

Reviews (8): Last reviewed commit: "chore: migrate dead .npmrc settings to p..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

- Upgrade pnpm 9.0.0 → 11.1.1 (packageManager, engines, corepack)
- Add minimumReleaseAge: 4320 (3-day cooldown) to pnpm-workspace.yaml
- Move overrides from package.json → pnpm-workspace.yaml (pnpm 11 requirement)
- Add @internal/eslint-config and eslint-plugin-storybook as root devDeps
- Add allowBuilds for esbuild, @parcel/watcher, msw
- Remove version pins from CI workflows (reads from packageManager field)
- Update AGENTS.md with pnpm 11 refs and supply-chain docs
@changeset-bot

changeset-bot Bot commented May 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 62b088e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Comment thread pnpm-workspace.yaml Outdated
@cameronapak cameronapak marked this pull request as draft May 12, 2026 15:02
- Bump node-version from 20 → 22 in ci.yml and storybook.yml (pnpm 11 requires Node >= 22.13)
- Bump engines.node from >=20 → >=22 in package.json
- Remove minimumReleaseAgeExclude — workspace packages bypass the gate inherently
- Update AGENTS.md Node requirement references
@cameronapak cameronapak marked this pull request as ready for review May 12, 2026 15:26
@cameronapak

Copy link
Copy Markdown
Collaborator Author

Hey @jhampton, Can I get your feedback on this PR? Mainly around the idea of it. I just want to make sure that with the rise of supply chain attacks, that we are protected on our repos. So I just want to share this with you and let me know so that I can hear your feedback (any feedback)

cameronapak and others added 4 commits June 3, 2026 12:40
Resolve pnpm-lock.yaml conflict by regenerating against main (YPE-642
verse action popover) with pnpm 11.10.0.

Supply-chain hardening follow-ups:
- pnpm 11.1.1 -> 11.10.0 (latest stable 11.x; corepack-written hash)
- vite override ">=5.4.21" -> exact "7.3.3" (closes the last loose
  version range; a >= let a bad newer release resolve silently)
- allowBuilds: '@swc/core': false (tsup uses esbuild by default; no
  tsup.config requests swc, so its native postinstall is unneeded)
- CONTRIBUTING.md prerequisites: Node >=22, pnpm >=11 (match engines)

Verified: pnpm lint (7/7), typecheck (6/6), test (751 pass: core 298,
hooks 275, ui 178), build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cameronapak

cameronapak commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

CLAUDITHAN SAYS

Re: the Greptile note that @swc/core is "no longer resolved as a tsup peer" (pnpm-lock.yaml 194-195) — Cam asked me to verify it. It doesn't hold, on two counts:

1. Stale premise. After the lockfile regen on the current head, @swc/core@1.13.5 is resolved as a tsup peer across all four packages:

pnpm-lock.yaml:59,175,221,366
  tsup@8.5.0(...)(@swc/core@1.13.5(@swc/helpers@0.5.17))(...)

Greptile reviewed an earlier commit where swc had transiently dropped out; the regen restored it, so "resolves without it" no longer describes this code.

2. Wrong mechanism regardless. tsup always transforms/bundles with esbuild. @swc/core is an optional peer used for exactly one thing — emitting legacy decorator metadata (emitDecoratorMetadata). This repo uses no decorators (grep emitDecoratorMetadata / experimentalDecorators → 0 hits), so swc is never on the code path whether installed or not. The experimentalDts concern also doesn't apply: the tsup config sets dts: false (types come from tsc + API Extractor).

Net: nothing to fix. The green build is expected, not just reassuring — esbuild was always the transformer. (Also FYI: allowBuilds: @swc/core: false only skips the native binary's postinstall; harmless here since nothing invokes swc.)

Comment thread .npmrc Outdated
Comment on lines 1 to 5

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Medium — .npmrc settings are now silently dead, and the hoisting behavior changed
pnpm 11 only reads auth/registry settings from .npmrc; everything else must live in pnpm-workspace.yaml (pnpm 11 release notes). This PR only removed a comment from .npmrc and left the functional settings behind:

All five lines are now ignored. Two are harmless because they match pnpm 11 defaults (autoInstallPeers: true, strictPeerDependencies: false). But publicHoistPattern defaults to [], so eslint, prettier, and @types/* packages are no longer hoisted to root node_modules. That's a silent resolution change — it's likely why the PR had to add @internal/eslint-config and eslint-plugin-storybook as root devDependencies to keep lint working. CI passes today, but any editor tooling or script relying on hoisted @types/* / prettier resolution may behave differently locally. Recommendation: either intentionally delete these lines (and note the hoisting change in the PR description) or migrate them to pnpm-workspace.yaml as publicHoistPattern. Leaving dead config that looks functional is the worst option."

Cursor brought this up during my review for this PR. Probably not a big deal but just verify if it's a real problem or not before merging.

@cameronapak cameronapak Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI AI AI A I

Verified against pnpm's own docs — the Cursor note is correct on the substance:

  • pnpm 11 reads only auth/registry from .npmrc; all other settings must live in pnpm-workspace.yaml (11.0 release notes).
  • publicHoistPattern default is [], so *eslint* / *prettier* / @types/* were no longer hoisted to root. autoInstallPeers/strictPeerDependencies matched defaults, so harmless-but-dead.

Fixed in 62b088e: migrated publicHoistPattern (+ the two peer settings) into pnpm-workspace.yaml and reduced .npmrc to a comment. Chose migrate-over-delete to preserve the repo's intended hoisting rather than silently drop it.

Proof it took effect: pnpm install --force now hoists the full @types/* set to root node_modules (was just @types/node before). Lint + typecheck pass; lockfile unchanged.

One correction to the note: the guess that this is "why the PR added @internal/eslint-config / eslint-plugin-storybook as root devDeps" doesn't hold — those are workspace packages hoisted by a different mechanism, unrelated to publicHoistPattern.

Dustin-Kelley
Dustin-Kelley previously approved these changes Jul 7, 2026
pnpm 11 reads only auth/registry settings from .npmrc; the
publicHoistPattern / autoInstallPeers / strictPeerDependencies lines
were silently ignored. Verified publicHoistPattern default is [] under
pnpm 11, so eslint/prettier/@types/* were no longer hoisted to root.

Migrate them to pnpm-workspace.yaml to preserve the repo's intended
hoisting. Confirmed via `pnpm install --force`: full @types/* set is
hoisted to root node_modules again (was just @types/node before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread package.json
Comment on lines +7 to +8
"node": ">=22.0.0",
"pnpm": ">=11.0.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The engines.node floor of 22.0.0 is too low for pnpm 11. pnpm 11 relies on the node:sqlite built-in module, which was only added in Node 22.13. A developer on Node 22.0–22.12 would pass the engines gate and then hit a cryptic ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite crash from pnpm itself. AGENTS.md in this same PR already documents the correct minimum: "pnpm 11 requires Node >= 22.13".

Suggested change
"node": ">=22.0.0",
"pnpm": ">=11.0.0"
"node": ">=22.13.0",
"pnpm": ">=11.0.0"
Prompt To Fix With AI
This is a comment left during a code review.
Path: package.json
Line: 7-8

Comment:
The `engines.node` floor of `22.0.0` is too low for pnpm 11. pnpm 11 relies on the `node:sqlite` built-in module, which was only added in Node 22.13. A developer on Node 22.0–22.12 would pass the `engines` gate and then hit a cryptic `ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:sqlite` crash from pnpm itself. AGENTS.md in this same PR already documents the correct minimum: "pnpm 11 requires Node >= 22.13".

```suggestion
    "node": ">=22.13.0",
    "pnpm": ">=11.0.0"
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not sure about this lol

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original comment was incorrect. I verified against the pnpm 11 release notes and official docs — the documented minimum is Node.js >= 22.0.0, not 22.13. While pnpm 11 does use node:sqlite internally, it does not require 22.13 specifically.

Your "node": ">=22.0.0" is correct. Disregard this comment.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dustin-Kelley its funny becuase this is the exact same thing Cluade called out with a little more info
package.json:7 — engines.node is set to ">=22.0.0", but pnpm 11.1.1 (pinned via packageManager) actually requires Node >=22.13(verified against npm registry metadata). The PR's own AGENTS.md:122 edit even says so in a parenthetical added in the same commit:"Minimum version 22.0.0 required (pnpm 11 requires Node >= 22.13)" — the doc and the enforced engines field disagree with each other.A contributor on Node 22.0–22.12 passes the engines check but then hits a confusing pnpm failure. Fix: bump engines.node to">=22.13.0".

@cameronapak may be worth verifying

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants