Skip to content

[WOODPECKER-4406] Migrate package manager from npm to pnpm#4738

Merged
kerrie-wu merged 38 commits into
mainfrom
WOODPECKER-4406
Jul 2, 2026
Merged

[WOODPECKER-4406] Migrate package manager from npm to pnpm#4738
kerrie-wu merged 38 commits into
mainfrom
WOODPECKER-4406

Conversation

@viktor-yang

@viktor-yang Viktor Yang (viktor-yang) commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

https://skyscanner.atlassian.net/browse/WOODPECKER-4406

What changed

This PR migrates the Backpack monorepo package manager from npm to pnpm.

It was generated by the skill, then reviewed and refined by a person.

Why

pnpm provides faster installs, stricter dependency isolation (phantom dependency prevention), and better monorepo workspace support — aligning Backpack's toolchain with the direction set in monorepo

Checklist

  • Build/CI infrastructure change only — no component, test, or Storybook changes
  • No README.md changes needed
  • No accessibility checks needed

- Replace npm with pnpm across all GitHub Actions workflows
- Add pnpm/action-setup step to all CI jobs
- Switch cache key from package-lock.json to pnpm-lock.yaml
- Add pnpm-workspace.yaml and pnpm-lock.yaml
- Remove package-lock.json
- Update package.json: packageManager field, engines, overrides → pnpm.overrides
- Update npm run → pnpm run in scripts
- Fix Jest transformIgnorePatterns to handle .pnpm virtual store
- Remove workspaces field (moved to pnpm-workspace.yaml)
Copilot AI review requested due to automatic review settings June 25, 2026 03:16
@viktor-yang Viktor Yang (viktor-yang) added dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code skip-changelog labels Jun 25, 2026
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

Copilot AI left a comment

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.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Migrates the monorepo’s package manager from npm to pnpm, updating workspace config, scripts, and CI workflows to use pnpm and the new lockfile.

Changes:

  • Introduces pnpm workspace + lockfile configuration and updates root package.json to require pnpm.
  • Replaces npm ci / npm run … usage across GitHub Actions with pnpm equivalents and updates cache keys.
  • Updates repo docs/templates and tooling configs (.npmrc, .gitignore) for pnpm.

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Adds pnpm workspace globs replacing package.json workspaces.
package.json Declares pnpm as package manager, updates scripts, Jest config, and moves overrides to pnpm.overrides.
AGENTS.md Updates agent/developer instructions to use pnpm commands.
.specify/templates/tasks-template.md Updates validation steps from npm to pnpm.
.specify/templates/plan-template.md Updates documented package manager requirement to pnpm.
.specify/memory/constitution.md Updates supported tooling list from npm to pnpm.
.npmrc Adds pnpm-related install behavior settings.
.gitignore Ignores pnpm debug logs and package-lock.json.
.github/workflows/sync-figma-variables.yml Switches install + token sync steps to pnpm.
.github/workflows/release.yml Switches caching/install/build steps to pnpm and pnpm lockfile hashing.
.github/workflows/pr.yml Switches caching/install/build steps to pnpm and pnpm lockfile hashing.
.github/workflows/main.yml Switches caching/install/build steps to pnpm and pnpm lockfile hashing.
.github/workflows/dev-release.yml Switches install/build and cache naming/keys to pnpm.
.github/workflows/backpack-adoption-guard-release.yml Switches install step to pnpm.
.github/workflows/_build.yml Updates build/test pipelines to pnpm, including React 19 override step.
.github/actions/figma-token-sync-pr/action.yml Switches token build step to pnpm.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/_build.yml Outdated
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

Each job now uses setup-node cache: 'pnpm' + pnpm install --frozen-lockfile
instead of manually caching node_modules/. This fixes esbuild-not-found
failures caused by pnpm's isolated linker creating per-package node_modules
that weren't included in the cached path.

Also removes the stale CACHE_NAME env var and fixes the React19 job which
was missing pnpm setup and still referenced package-lock.json in its cache key.
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

pnpm's isolated linker does not hoist transitive dependencies, so
phantom deps that worked under npm are no longer accessible. Explicitly
declare stylelint and @types/lodash to restore the binaries and type
declarations that were previously available via hoisting.
…host

- Add bpk-storybook-utils (workspace:*) and @jest/globals as devDependencies
  in backpack-web so pnpm creates proper symlinks; fixes import/no-unresolved
  ESLint errors caused by pnpm isolation removing npm hoisting
- Fix backpack-storybook-host deps from bare '*' to 'workspace:*' for
  explicit workspace protocol
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

pnpm isolation does not hoist @types/node from other workspace packages,
causing TS2688 when tsconfig.lib.json references the 'node' type library.
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

pnpm stores packages in node_modules/.pnpm/ virtual store. Jest resolves
modules to the physical path (.pnpm/...) rather than the symlink path.
The old pattern only matched node_modules/@Skyscanner, but Jest saw
node_modules/.pnpm first and skipped transformation, causing ESM files
(base.es6.js) to fail with 'SyntaxError: Unexpected token export'.

Adding \.pnpm to the negative lookahead lets Jest pass through to the
inner node_modules/@Skyscanner match.
pnpm isolation does not hoist @types/node from transitive deps.
tsconfig.lib.json explicitly references 'node' in compilerOptions.types,
so the consuming package must declare it directly.
pnpm isolation does not hoist transitive deps; scripts/jest/normalizeUseIdSerializer.js
requires pretty-format directly, and scripts/react-19/transforms/strip-proptypes.test.js
requires jscodeshift directly. Both must be declared explicitly.
@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

skyscanner-backpack-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

Package source files (e.g. packages/package-name/src/Component.js) were updated, but snapshots weren't. Have you checked that the tests still pass?

Browser support

If this is a visual change, make sure you've tested it in multiple browsers.

Generated by 🚫 dangerJS against cc13b54

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

Comment thread package.json
"@skyscanner/bpk-svgs/dist/svgs/^.+\\.svg$": "<rootDir>/scripts/stubs/fileStub.js",
"^react($|/.+)": "<rootDir>/node_modules/react$1"
"^react($|/.+)": "<rootDir>/node_modules/react$1",
"^react-dom($|/.+)": "<rootDir>/node_modules/react-dom$1"

@viktor-yang Viktor Yang (viktor-yang) Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Two react-dom instances exist at runtime: the root is upgraded to 19.2.5 by pnpm add -w, but @ark-ui/react and @zag-js/react still load their lockfile-locked peer instance react-dom@18.3.1. When react-dom@18 initialises, moduleNameMapper redirects its require('react') to react@19, whose restructured internals no longer expose ReactCurrentDispatcher — crash.

Fixed by adding "^react-dom($|/.+)": "<rootDir>/node_modules/react-dom$1" to moduleNameMapper, so all require('react-dom') calls — including those from the @18 peer instances — are redirected to the root react-dom@19.2.5, eliminating the version mismatch.
image

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

shell: bash -l {0}

env:
CACHE_NAME: node-modules-cache-v2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Replace the mannual cache to the PNPM cache in actions/setup-node


- name: Override React to 19.2.5
run: npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19.0.14 @types/react-dom@19.0.6 @types/prop-types
run: pnpm_config_lockfile=false pnpm add -w react@19.2.5 react-dom@19.2.5 @types/react@19.0.14 @types/react-dom@19.0.6 @types/prop-types @types/react-transition-group@4.4.12

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Declare the @types/react-transition-group@4.4.12 to fix below type error:

image

Reason: Because npm uses flat hoisting, npm install @types/react@19 overwrites the single shared node_modules/@types/react/ that every package resolves to, so @types/react-transition-group automatically picks up the new version. With pnpm, @types/react-transition-group@4.4.11 declares @types/react as a dependency, so pnpm installs a private isolated copy (18.3.1) inside .pnpm/ that the root override can never touch — causing a type mismatch. 4.4.12 fixed this by moving @types/react to peerDependencies, so pnpm no longer installs a private copy and resolves directly to the root 19.0.14.

npm (flat hoisting)
─────────────────────────────────────────────────────
node_modules/
└── @types/react/  ← 19.0.14 (overwritten, single copy)
                        ▲
                        │ both resolve to the same copy ✅
                        │
@types/react-transition-group ──────────────────────┘


pnpm + 4.4.11  (@types/react as "dependencies")
─────────────────────────────────────────────────────
node_modules/
├── @types/react/                          ← 19.0.14 (root, overridden)
└── .pnpm/
    └── @types+react-transition-group@4.4.11/
        └── node_modules/
            └── @types/react/              ← 18.3.1 (private copy, untouched)
                    ▲
                    │ resolves to OLD copy → type mismatch ❌
                    │
@types/react-transition-group ──────────────────────┘


pnpm + 4.4.12  (@types/react as "peerDependencies")
─────────────────────────────────────────────────────
node_modules/
├── @types/react/                          ← 19.0.14 (root)
└── .pnpm/
    └── @types+react-transition-group@4.4.12/
        └── node_modules/
            └── @types/react/              ← (no private copy, peer resolved from root)
                    ▲
                    │ resolves to root copy → types aligned ✅
                    │
@types/react-transition-group ──────────────────────┘

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

**Solution:** For new components, this is expected on first run:
```bash
npm run jest -- packages/backpack-web/src/bpk-component-[name] -u
ppnpm run jest -- packages/backpack-web/src/bpk-component-[name] -u

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.

There's a typo with a double "p".


- name: Override React to 19.2.5
run: npm install --no-save react@19.2.5 react-dom@19.2.5 @types/react@19.0.14 @types/react-dom@19.0.6 @types/prop-types
run: pnpm_config_lockfile=false pnpm add -w react@19.2.5 react-dom@19.2.5 @types/react@19.0.14 @types/react-dom@19.0.6 @types/prop-types @types/react-transition-group@4.4.12

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.

pnpm_config_lockfile=false is not a format pnpm recognises — only npm_config_lockfile=false (npm-compat) or PNPM_CONFIG_LOCKFILE=false (pnpm uppercase prefix) work. This prefix will be silently ignored, so pnpm add will still write the lockfile.

Suggest:

run: PNPM_CONFIG_LOCKFILE=false pnpm add -w react@19.2.5 ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, Kerrie. I checked the official document:

Environment variables whose names start with pnpm_config_ (or PNPM_CONFIG_) are loaded into configuration. These override settings from pnpm-workspace.yaml but not CLI arguments.

Comment thread package.json
Comment on lines +204 to +208
"pnpm": {
"overrides": {
"braces": "3.0.3"
},
"neverBuiltDependencies": ["node-sass"]

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.

engines.pnpm allows >=9.15.9, but package.json#pnpm.overrides and neverBuiltDependencies are only read by pnpm 9 — pnpm 10+ moved these settings to pnpm-workspace.yaml and ignores the package.json#pnpm field with a warning.

This means on pnpm 10/11:

  • overrides.braces: 3.0.3 (security patch) is silently dropped
  • neverBuiltDependencies: ["node-sass"] is ignored, triggering native compilation and likely breaking install

Suggest moving these to pnpm-workspace.yaml:

overrides:
  braces: "3.0.3"
neverBuiltDependencies:
  - node-sass

Or constrain engines to pnpm 9.x if pnpm 10+ is not intended to be supported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, Kerrie. The packageManager is set to pnpm@9.15.9 now:
https://github.com/Skyscanner/backpack/pull/4738/changes#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519.

And pnpm-workspace.yaml is supported by pnpm 11: https://pnpm.io/package_json.
So I think we can move the configs to the pnpm-workspace.yaml when we migrate to the pnpm 11

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@skyscanner-backpack-bot

Copy link
Copy Markdown
Contributor

Visit https://backpack.github.io/storybook-prs/4738 to see this build running in a browser.

@kerrie-wu kerrie-wu left a comment

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.

LGTM

@kerrie-wu kerrie-wu merged commit f03a266 into main Jul 2, 2026
15 checks passed
@kerrie-wu kerrie-wu deleted the WOODPECKER-4406 branch July 2, 2026 05:58
kerrie-wu added a commit that referenced this pull request Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code skip-changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants