User story
As a contributor, I want the project to use pnpm instead of npm, so that package installation is protected against supply-chain attacks that exploit npm's resolution and download model.
Background
npm's package resolution model has known supply-chain attack vectors. The mini sha hulud attack (June 2026) is a recent example targeting npm's package publishing and resolution flow, demonstrating how malicious packages can be injected and resolved transparently under npm.
Key risks with npm:
- Hoisted
node_modules layout enables phantom-dependency exploitation
- No content-addressed store — a tampered package isn't detected at install time
npm install does not enforce lock-file integrity by default
pnpm addresses these directly:
- Content-addressed store: each package version is stored once with an integrity hash; a tampered package fails on hash mismatch
- Strict
node_modules layout: no phantom dependencies by default
pnpm-lock.yaml includes per-package integrity hashes and is deterministic
Related: npm supply-chain attack surface documented at https://pnpm.io/motivation
Acceptance criteria
Happy path
Given the repo is cloned fresh
When pnpm install is run
Then all dependencies install successfully using pnpm-lock.yaml and pnpm audit reports no critical/high vulnerabilities
Given any npm run <script> command from package.json
When run as pnpm run <script> (or pnpm <script>)
Then it produces identical output to the previous npm invocation
Given the CI workflow runs
When pnpm is installed and pnpm install --frozen-lockfile is executed
Then the build and test suite pass without modification
Error path / edge case
Given pnpm-lock.yaml is present but package-lock.json is accidentally re-generated
When CI runs
Then CI fails loudly (frozen lockfile check catches the mismatch)
Out of scope
- Upgrading any dependency versions as part of this migration
- Changing workspace configuration (no monorepo restructure)
- Migrating
.npmrc auth tokens (out of scope; handled separately if needed)
Technical context
- Current lock file:
package-lock.json (npm v10)
package.json has no packageManager field yet
- CI: check
.github/workflows/ for any npm ci or npm install calls that need updating
- No private registry or scoped packages that would require special
.npmrc config
Implementation details
- Install pnpm globally (
npm install -g pnpm or via corepack enable && corepack prepare pnpm@latest --activate)
- Delete
package-lock.json
- Run
pnpm import to generate pnpm-lock.yaml from the existing lock file, then verify with pnpm install
- Add
"packageManager": "[email protected]" to package.json (use the installed version)
- Run the full test suite:
pnpm test, pnpm run test:react, pnpm run lint, pnpm run build
- Update
.github/workflows/ — replace npm ci with pnpm install --frozen-lockfile; add a pnpm setup step (e.g. actions/setup-node + pnpm/action-setup)
- Add
package-lock.json to .gitignore to prevent accidental regeneration
- Update install instructions in
README.md and CLAUDE.md
- Run
pnpm audit and resolve any critical/high findings before merging
Additional test scenarios
pnpm run shorts:wizard launches without error
pnpm run dev starts the Next.js dev server
pnpm run build produces a successful production build
pnpm audit exits 0 (or documents any accepted exceptions)
Hard constraints
pnpm-lock.yaml must be committed; package-lock.json must be deleted and gitignored
- No dependency version bumps in this PR — pure tooling migration only
- CI must use
--frozen-lockfile to prevent silent lock-file drift
Dependency issues
- Independent of all current refactor phases (0–9); can be done on a standalone
chore/migrate-npm-to-pnpm branch off main
User story
As a contributor, I want the project to use pnpm instead of npm, so that package installation is protected against supply-chain attacks that exploit npm's resolution and download model.
Background
npm's package resolution model has known supply-chain attack vectors. The mini sha hulud attack (June 2026) is a recent example targeting npm's package publishing and resolution flow, demonstrating how malicious packages can be injected and resolved transparently under npm.
Key risks with npm:
node_moduleslayout enables phantom-dependency exploitationnpm installdoes not enforce lock-file integrity by defaultpnpm addresses these directly:
node_moduleslayout: no phantom dependencies by defaultpnpm-lock.yamlincludes per-package integrity hashes and is deterministicRelated: npm supply-chain attack surface documented at https://pnpm.io/motivation
Acceptance criteria
Happy path
Given the repo is cloned fresh
When
pnpm installis runThen all dependencies install successfully using
pnpm-lock.yamlandpnpm auditreports no critical/high vulnerabilitiesGiven any
npm run <script>command frompackage.jsonWhen run as
pnpm run <script>(orpnpm <script>)Then it produces identical output to the previous npm invocation
Given the CI workflow runs
When pnpm is installed and
pnpm install --frozen-lockfileis executedThen the build and test suite pass without modification
Error path / edge case
Given
pnpm-lock.yamlis present butpackage-lock.jsonis accidentally re-generatedWhen CI runs
Then CI fails loudly (frozen lockfile check catches the mismatch)
Out of scope
.npmrcauth tokens (out of scope; handled separately if needed)Technical context
package-lock.json(npm v10)package.jsonhas nopackageManagerfield yet.github/workflows/for anynpm ciornpm installcalls that need updating.npmrcconfigImplementation details
npm install -g pnpmor viacorepack enable && corepack prepare pnpm@latest --activate)package-lock.jsonpnpm importto generatepnpm-lock.yamlfrom the existing lock file, then verify withpnpm install"packageManager": "[email protected]"topackage.json(use the installed version)pnpm test,pnpm run test:react,pnpm run lint,pnpm run build.github/workflows/— replacenpm ciwithpnpm install --frozen-lockfile; add a pnpm setup step (e.g.actions/setup-node+pnpm/action-setup)package-lock.jsonto.gitignoreto prevent accidental regenerationREADME.mdandCLAUDE.mdpnpm auditand resolve any critical/high findings before mergingAdditional test scenarios
pnpm run shorts:wizardlaunches without errorpnpm run devstarts the Next.js dev serverpnpm run buildproduces a successful production buildpnpm auditexits 0 (or documents any accepted exceptions)Hard constraints
pnpm-lock.yamlmust be committed;package-lock.jsonmust be deleted and gitignored--frozen-lockfileto prevent silent lock-file driftDependency issues
chore/migrate-npm-to-pnpmbranch offmain