Migrate from Yarn 1 to pnpm - #35
Open
Devon-White wants to merge 5 commits into
Open
Conversation
Yarn 1.22 is EOL and was the root cause of several problems this branch
worked around rather than fixed:
- Scoped `resolutions` are silently ignored, so three dev-only advisories
(js-yaml x2, brace-expansion) had to be allow-listed in Dependency Review.
The unscoped form is unusable: it rewrites every range for a name, and
forcing js-yaml to 4.x breaks read-yaml-file's `safeLoad`, which
@changesets/cli -- our release tooling -- depends on.
- `yarn workspace <name> add` drops hoisted root devDependencies from
node_modules, which is why the React 18 CI leg needed a trailing
`yarn install`.
- Undeclared dependencies resolved by hoisting, hiding real packaging bugs.
pnpm fixes all three. Overrides are path-scoped, so js-yaml resolves to
4.3.1 where lerna needs it while read-yaml-file keeps 3.15.1; the allow-list
is deleted and the blunt `tar` resolution with it. Verified in the lockfile:
zero occurrences of js-yaml@4.1, brace-expansion@5.0.8 or tar@6.
Also fixes the React matrix, which was only half honest. The old step ran
`yarn add -W --dev @types/react@^18`, which rewrites the root while both
packages pin ^19, so Yarn nested v19 and the React 18 leg type-checked
against React 19 types. pnpm overrides apply tree-wide: the leg now resolves
@types/react 18.3.31, and scripts/checkReactVersions.js asserts it from the
lockfile.
Making that honest immediately caught a real bug the leg was supposed to
catch: useDropdownState annotated its ref as RefObject<HTMLDivElement | null>,
the React 19 shape, which React 18 rejects for a `ref` prop. Both packages
advertise `react: ^18 || ^19`, so this was a broken support claim. The return
type is now inferred, which is correct under either major. ts-jest is
transpile-only, so the existing runtime test could never have caught it --
its comment claimed otherwise and has been corrected.
Strictness surfaced three undeclared dependencies, all real:
- chokidar, imported by scripts/copyUntypedFiles.js
- @docusaurus/module-type-aliases, in tsconfig.test.json's `types`
- tslib, needed by root test files under `importHelpers`
Notes:
- pnpm is pinned to 10.34.5: pnpm 11 requires Node >=22.13 and both
packages declare Node >=20.
- .npmrc sets enable-pre-post-scripts, which pnpm disabled in v7, so
prebuild:website stays wired to build:packages.
- lerna stays as the task runner (npmClient: pnpm). It is no longer
load-bearing for security now that overrides can be scoped.
- Consumer-facing `yarn add` lines in the package READMEs are untouched.
Verified on Node 20.20.2 with pnpm 10.34.5, on both React majors:
install --frozen-lockfile, build:packages, format:check, lint, type-check,
test:ci (27 tests), tsc -p tsconfig.test.json and build:website all exit 0.
Devon-White
requested review from
Manny-r31,
briankwest,
hey-august and
niravcodes
as code owners
August 17, 2026 11:06
Dependency Review SummaryThe full dependency review summary was too large to display here (3247KB, limit is 1024KB). Please download the artifact named "dependency-review-summary" to view the complete report. |
Swapping yarn.lock for pnpm-lock.yaml makes the whole dependency graph look new to Dependency Review, so pre-existing advisories that were previously in the base lockfile now read as introduced. Two surfaced: image-size 2.0.2, reached through @docusaurus/core. Both have patched=NONE via the GitHub advisory API -- no release fixes them at any version -- and both are parser DoS in a build-time image reader that never runs in a deployed site. That is exactly what allow-ghsas is for; the dev-only advisories lerna and nx pin are handled by pnpm.overrides instead. Also drops the pull_request branch filter from ci.yml. It only fired for PRs targeting main or develop, so this PR -- stacked on the branch it builds on -- got no CI at all, which is the one thing that cannot be verified locally. Push events still only build main and develop.
Dependency Review flagged serialize-javascript@6.0.2 (GHSA-5c6j-r48x-rmvq, high, RCE via RegExp.flags). It was previously written off as unfixable at Docusaurus 3.x, but that was a Yarn limitation, not a real one: the advisory is patched in 7.0.3 and only two build-time webpack plugins pull it, so a scoped override moves them without touching anything else. copy-webpack-plugin>serialize-javascript: ^7.0.5 css-minimizer-webpack-plugin>serialize-javascript: ^7.0.5 Resolves to 7.1.0. Verified the cross-major bump is safe by building the website, which is what exercises both plugins: exit 0, 55 documents. Adds a changeset for the React 18 ref fix, which is consumer-facing -- a site on React 18 that type-checked a swizzled CopyPageContent hit TS2322.
facebook/docusaurus keeps all pnpm configuration in pnpm-workspace.yaml and has no `pnpm` section in package.json at all. That is also where pnpm's own docs now point. Moved overrides and onlyBuiltDependencies across and annotated each override with the GHSA it addresses, as they do. Verified byte-identical lockfile before and after, plus the full gate on Node 20.20.2: build:packages, format:check, lint, type-check, test:ci (27 tests) and build:website all exit 0. Two of their settings are deliberately NOT adopted: nodeLinker: 'hoisted' -- they opt out of pnpm's strict layout, likely for create-docusaurus templates and test-website scaffolding. We want the strict default: it is what surfaced chokidar, @docusaurus/module-type-aliases and tslib as undeclared. Keep this in mind as the escape hatch if a Docusaurus plugin ever fails to resolve. linkWorkspacePackages: true instead of workspace:* -- their comment says the protocol would require adopting pnpm for publishing. That does not apply here: only website uses workspace:*, and it is private, so the protocol never reaches a published manifest.
Moving the pnpm settings out of package.json broke both matrix legs: scripts/pinReact.js still wrote pkg.pnpm.overrides, which no longer exists, so it died with "Cannot read properties of undefined (reading 'overrides')" before either leg installed. It now edits pnpm-workspace.yaml with yaml's parseDocument, which keeps the comments explaining why each existing override is there -- a plain parse/stringify round-trip would drop them. Verified the full React 18 leg end to end: react 18.3.1, @types/react 18.3.31, then build:packages, type-check, test:ci and build:website all exit 0; restoring the default resolves 19.2.8 / 19.2.18.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces Yarn 1.22 (EOL) with pnpm. Stacked on #34 — review that first; the base retargets to
mainwhen it merges.Why
Yarn 1 was the root cause of three things #34 had to work around:
resolutionsare silently ignored, so three dev-only advisories had to be allow-listed. The unscoped form is unusable — it rewrites every range for a name, and forcingjs-yamlto 4.x breaksread-yaml-file'ssafeLoad, which@changesets/clidepends on.yarn workspace <name> adddrops hoisted root devDependencies, which is why the React 18 CI leg needed a trailingyarn install.What this buys
js-yamlresolves to 4.3.1 where lerna needs it whileread-yaml-filekeeps 3.15.1. Lockfile has zero occurrences ofjs-yaml@4.1,brace-expansion@5.0.8ortar@6. The blunttarresolution is gone too.@types/react@^19, so Yarn nested v19 and the React 18 leg type-checked against React 19 types. Overrides apply tree-wide — the leg now resolves@types/react18.3.31, asserted from the lockfile byscripts/checkReactVersions.js.useDropdownStateannotated its ref asRefObject<HTMLDivElement | null>— the React 19 shape, which React 18 rejects for arefprop. Both packages advertisereact: ^18 || ^19, so that support claim was broken. The return type is now inferred, correct under either major. ts-jest is transpile-only, so the existing runtime test could never have caught it.serialize-javascript@6.0.2(GHSA-5c6j-r48x-rmvq, high, RCE) was called unfixable at Docusaurus 3.x — but that was a Yarn limitation. It's patched in 7.0.3 and only two build-time webpack plugins pull it, so a scoped override moves them to 7.1.0 and nothing else. Verified safe by building the website, which exercises both plugins.chokidar(imported byscripts/copyUntypedFiles.js),@docusaurus/module-type-aliases(intsconfig.test.json'stypes), andtslib(root test files underimportHelpers).Two things this surfaced that needed handling
Swapping lockfile formats makes the entire dependency graph look new to a delta gate, so pre-existing advisories that sat in the base lockfile now read as introduced. Two
image-sizeadvisories are allow-listed — both havepatched=NONEvia the advisory API, and they're parser DoS in a build-time image reader that never runs in a deployed site.ci.ymlalso only fired for PRs targetingmain/develop, so this PR got no CI at all — the one thing that can't be verified locally. Thepull_requestbranch filter is dropped; push events still only buildmainanddevelop.Notes
.npmrcsetsenable-pre-post-scripts, which pnpm disabled in v7, soprebuild:websitestays wired tobuild:packages.npmClient: pnpm) for its topological ordering andwebsitefiltering. It's no longer load-bearing for security now that overrides can be scoped.yarn addlines in the package READMEs are untouched — this changes how the repo is developed, not how the packages are installed.Verification
Node 20.20.2, pnpm 10.34.5, both React majors:
install --frozen-lockfile,build:packages,format:check,lint,type-check,test:ci(27 tests),tsc -p tsconfig.test.jsonandbuild:websiteall exit 0.