Adopt pnpm catalogs for devDependencies (#93071)

### What?

Adopt pnpm [catalogs](https://pnpm.io/catalogs) for shared hoisted devDependencies: `typescript`, `jest`, `eslint`, and `prettier`. Each is pinned once in `pnpm-workspace.yaml` and referenced as `"<name>": "catalog:"` by every consumer.

Also adds the missing `dom.iterable` lib to `@next/routing`'s tsconfig, required by `URLSearchParams.entries()` in TypeScript 6.

### Why?

Several packages invoke these binaries in their build, test, or lint scripts but do not declare them in their own `devDependencies` — they rely on the hoisted root install.

This breaks Turborepo's cache invalidation: without a declared dep, a package has no lockfile edge to the tool, so bumping its version (e.g. #91257's TypeScript 5 → 6 upgrade) does not invalidate that package's task hash. `@next/routing`'s `types` task has been a remote-cache hit on canary since the TS 6 bump, even though it now fails locally on a cold cache — CI is replaying a stale pre-bump success.

The same latent hazard exists for `jest` (`@next/codemod`), `eslint` (`@next/bundle-analyzer-ui`), and `prettier` (`create-next-app`).

### How?

- `pnpm-workspace.yaml` defines `catalog:` entries for each tool.
- Consumer packages declare `"<name>": "catalog:"`. pnpm still hoists one copy, but each consumer now has a real lockfile edge to the resolved version.
- Root `package.json` migrates each pin to `"catalog:"` so the workspace file is the single source of truth.

Turborepo needs no changes — its existing per-package hashing picks up lockfile edge changes automatically. Future catalog bumps will invalidate every consumer's cache.

`@types/node` is a deliberate omission from this round: several packages intentionally pin different majors (`^22`, `^20`, `20.14.2`), so migrating it needs per-package intent review rather than bulk conversion.

<!-- NEXT_JS_LLM_PR -->
This commit is contained in:
Luke Sandberg
2026-04-21 11:53:42 -07:00
committed by GitHub
parent 2b846de4c5
commit c52634747c
11 changed files with 150 additions and 53 deletions
+2 -1
View File
@@ -35,10 +35,11 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"cross-env": "10.1.0",
"eslint": "catalog:",
"inliner": "1.13.1",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
"typescript": "6.0.2"
"typescript": "catalog:"
}
}
+4 -4
View File
@@ -198,7 +198,7 @@
"dd-trace": "4.12.0",
"es5-ext": "0.10.53",
"escape-string-regexp": "2.0.0",
"eslint": "9.37.0",
"eslint": "catalog:",
"eslint-config-next": "workspace:*",
"eslint-formatter-codeframe": "7.32.1",
"eslint-plugin-eslint-plugin": "5.2.1",
@@ -230,7 +230,7 @@
"image-size": "1.2.1",
"is-animated": "2.0.2",
"isomorphic-unfetch": "3.0.0",
"jest": "29.7.0",
"jest": "catalog:",
"jest-diff": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-extended": "4.0.2",
@@ -262,7 +262,7 @@
"postcss-pseudoelements": "5.0.0",
"postcss-short-size": "4.0.0",
"postcss-trolling": "0.1.7",
"prettier": "3.6.2",
"prettier": "catalog:",
"pretty-bytes": "5.3.0",
"pretty-ms": "7.0.0",
"random-seed": "0.3.0",
@@ -302,7 +302,7 @@
"tsec": "0.2.1",
"tsx": "4.19.2",
"turbo": "2.9.4",
"typescript": "6.0.2",
"typescript": "catalog:",
"typescript-eslint": "8.46.0",
"unfetch": "4.2.0",
"wait-port": "0.2.2",
+1
View File
@@ -41,6 +41,7 @@
"cross-spawn": "7.0.3",
"fast-glob": "3.3.1",
"picocolors": "1.1.1",
"prettier": "catalog:",
"prettier-plugin-tailwindcss": "0.6.2",
"prompts": "2.4.2",
"tar": "7.5.7",
+2 -1
View File
@@ -17,7 +17,8 @@
},
"devDependencies": {
"@types/eslint": "9.6.1",
"eslint": "9.37.0"
"eslint": "9.37.0",
"typescript": "catalog:"
},
"scripts": {
"dev": "pnpm build",
+2 -1
View File
@@ -40,6 +40,7 @@
"@types/jscodeshift": "0.11.0",
"@types/prompts": "2.4.2",
"@types/semver": "7.3.1",
"typescript": "6.0.2"
"jest": "catalog:",
"typescript": "catalog:"
}
}
+2 -1
View File
@@ -32,6 +32,7 @@
"devDependencies": {
"@vercel/ncc": "0.34.0",
"dotenv": "16.3.1",
"dotenv-expand": "10.0.0"
"dotenv-expand": "10.0.0",
"typescript": "catalog:"
}
}
+2 -1
View File
@@ -34,6 +34,7 @@
"@types/jest": "^29.5.0",
"@vercel/ncc": "0.34.0",
"jest": "^29.5.0",
"ts-jest": "^29.1.0"
"ts-jest": "^29.1.0",
"typescript": "catalog:"
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": false,
"lib": ["es2022", "dom"],
"lib": ["es2022", "dom", "dom.iterable"],
"rootDir": "src",
"types": ["jest"]
},
@@ -27,6 +27,7 @@
},
"devDependencies": {
"react-refresh": "0.12.0",
"typescript": "catalog:",
"webpack": "^4 || ^5"
}
}
+128 -43
View File
@@ -4,6 +4,21 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
catalogs:
default:
eslint:
specifier: 9.37.0
version: 9.37.0
jest:
specifier: 29.7.0
version: 29.7.0
prettier:
specifier: 3.6.2
version: 3.6.2
typescript:
specifier: 6.0.2
version: 6.0.2
overrides:
webpack: 5.98.0
browserslist: 4.28.1
@@ -304,7 +319,7 @@ importers:
specifier: 2.0.0
version: 2.0.0
eslint:
specifier: 9.37.0
specifier: 'catalog:'
version: 9.37.0([email protected])
eslint-config-next:
specifier: workspace:*
@@ -400,7 +415,7 @@ importers:
specifier: 3.0.0
version: 3.0.0([email protected])
jest:
specifier: 29.7.0
specifier: 'catalog:'
version: 29.7.0(@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected])
jest-diff:
specifier: 29.7.0
@@ -496,7 +511,7 @@ importers:
specifier: 0.1.7
version: 0.1.7
prettier:
specifier: 3.6.2
specifier: 'catalog:'
version: 3.6.2
pretty-bytes:
specifier: 5.3.0
@@ -616,7 +631,7 @@ importers:
specifier: 2.9.4
version: 2.9.4
typescript:
specifier: 6.0.2
specifier: 'catalog:'
version: 6.0.2
typescript-eslint:
specifier: 8.46.0
@@ -721,6 +736,9 @@ importers:
cross-env:
specifier: 10.1.0
version: 10.1.0
eslint:
specifier: 'catalog:'
version: 9.37.0([email protected])
inliner:
specifier: 1.13.1
version: 1.13.1
@@ -734,7 +752,7 @@ importers:
specifier: 1.3.3
version: 1.3.3
typescript:
specifier: 6.0.2
specifier: 'catalog:'
version: 6.0.2
bench/app-router-server:
@@ -961,6 +979,9 @@ importers:
picocolors:
specifier: 1.1.1
version: 1.1.1
prettier:
specifier: 'catalog:'
version: 3.6.2
prettier-plugin-tailwindcss:
specifier: 0.6.2
version: 0.6.2([email protected])
@@ -1038,6 +1059,9 @@ importers:
eslint:
specifier: 9.37.0
version: 9.37.0([email protected])
typescript:
specifier: 'catalog:'
version: 6.0.2
packages/font:
devDependencies:
@@ -1812,8 +1836,11 @@ importers:
'@types/semver':
specifier: 7.3.1
version: 7.3.1
jest:
specifier: 'catalog:'
version: 29.7.0(@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected])
typescript:
specifier: 6.0.2
specifier: 'catalog:'
version: 6.0.2
packages/next-env:
@@ -1827,6 +1854,9 @@ importers:
dotenv-expand:
specifier: 10.0.0
version: 10.0.0
typescript:
specifier: 'catalog:'
version: 6.0.2
packages/next-mdx:
dependencies:
@@ -1886,6 +1916,9 @@ importers:
ts-jest:
specifier: ^29.1.0
version: 29.4.5(@babel/[email protected])(@jest/[email protected])(@jest/[email protected])([email protected](@babel/[email protected]))([email protected])([email protected](@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected]))([email protected])
typescript:
specifier: 'catalog:'
version: 6.0.2
packages/next-rspack:
dependencies:
@@ -1910,6 +1943,9 @@ importers:
react-refresh:
specifier: 0.12.0
version: 0.12.0
typescript:
specifier: 'catalog:'
version: 6.0.2
webpack:
specifier: 5.98.0
version: 5.98.0(@swc/[email protected](@swc/[email protected]))
@@ -11303,11 +11339,6 @@ packages:
resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==}
engines: {node: '>=4'}
[email protected]:
resolution: {integrity: sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==}
engines: {node: '>=8'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==}
engines: {node: '>=8'}
@@ -11906,10 +11937,6 @@ packages:
resolution: {integrity: sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==}
engines: {node: '>=8'}
[email protected]:
resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==}
engines: {node: '>=10'}
[email protected]:
resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==}
engines: {node: '>=10'}
@@ -20012,7 +20039,7 @@ snapshots:
'@eslint/[email protected]':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.0
debug: 4.4.3
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -20749,7 +20776,7 @@ snapshots:
glob: 7.1.7
graceful-fs: 4.2.11
istanbul-lib-coverage: 3.2.0
istanbul-lib-instrument: 6.0.1
istanbul-lib-instrument: 6.0.3
istanbul-lib-report: 3.0.0
istanbul-lib-source-maps: 4.0.0
istanbul-reports: 3.1.6
@@ -23777,7 +23804,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.46.0([email protected])
'@typescript-eslint/types': 8.46.0
debug: 4.4.0
debug: 4.4.3
typescript: 6.0.2
transitivePeerDependencies:
- supports-color
@@ -23801,7 +23828,7 @@ snapshots:
'@typescript-eslint/types': 8.46.0
'@typescript-eslint/typescript-estree': 8.46.0([email protected])
'@typescript-eslint/utils': 8.46.0([email protected]([email protected]))([email protected])
debug: 4.4.0
debug: 4.4.3
eslint: 9.37.0([email protected])
ts-api-utils: 2.1.0([email protected])
typescript: 6.0.2
@@ -23816,7 +23843,7 @@ snapshots:
dependencies:
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/visitor-keys': 5.62.0
debug: 4.4.0
debug: 4.4.3
globby: 11.1.0
is-glob: 4.0.3
semver: 7.6.3
@@ -24970,7 +24997,7 @@ snapshots:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.0
debug: 4.4.3
http-errors: 2.0.0
iconv-lite: 0.6.3
on-finished: 2.4.1
@@ -27342,7 +27369,7 @@ snapshots:
[email protected]([email protected]):
dependencies:
debug: 4.4.0
debug: 4.4.3
esbuild: 0.25.9
transitivePeerDependencies:
- supports-color
@@ -27428,6 +27455,26 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]))([email protected]):
dependencies:
'@next/eslint-plugin-next': 16.2.0-canary.91
eslint: 9.37.0([email protected])
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected]))
eslint-plugin-import: 2.32.0([email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
eslint-plugin-jsx-a11y: 6.10.2([email protected]([email protected]))
eslint-plugin-react: 7.37.0([email protected]([email protected]))
eslint-plugin-react-hooks: 7.0.0([email protected]([email protected]))
globals: 16.4.0
typescript-eslint: 8.46.0([email protected]([email protected]))([email protected])
optionalDependencies:
typescript: 6.0.2
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-webpack
- eslint-plugin-import-x
- supports-color
[email protected]:
dependencies:
'@babel/code-frame': 7.12.11
@@ -27441,6 +27488,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0
eslint: 9.37.0([email protected])
get-tsconfig: 4.12.0
is-bun-module: 2.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0([email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
eslint-plugin-import-x: 4.3.1([email protected]([email protected]))([email protected])
transitivePeerDependencies:
- supports-color
[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected]([email protected])):
dependencies:
'@nolyfill/is-core-module': 1.0.39
@@ -27498,6 +27561,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]([email protected])([email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected])):
dependencies:
debug: 3.2.7
optionalDependencies:
eslint: 9.37.0([email protected])
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected]))
transitivePeerDependencies:
- supports-color
[email protected]([email protected]([email protected])):
dependencies:
eslint: 9.37.0([email protected])
@@ -27578,6 +27651,33 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
[email protected]([email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected])):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
array.prototype.findlastindex: 1.2.6
array.prototype.flat: 1.3.3
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
eslint: 9.37.0([email protected])
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1([email protected])([email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected])))([email protected]([email protected])))([email protected]([email protected]))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
minimatch: 3.1.2
object.fromentries: 2.0.8
object.groupby: 1.0.3
object.values: 1.2.1
semver: 6.3.1
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
[email protected]([email protected]([email protected]))([email protected](@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected]))([email protected]):
dependencies:
'@typescript-eslint/utils': 5.62.0([email protected]([email protected]))([email protected])
@@ -27800,7 +27900,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.0
debug: 4.4.3
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -28325,7 +28425,7 @@ snapshots:
[email protected]:
dependencies:
debug: 4.4.0
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
@@ -29497,11 +29597,6 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
pkg-dir: 4.2.0
resolve-cwd: 3.0.0
[email protected]:
dependencies:
pkg-dir: 4.2.0
@@ -30082,16 +30177,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]:
dependencies:
'@babel/core': 7.26.10
'@babel/parser': 7.27.0
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.0
semver: 7.6.3
transitivePeerDependencies:
- supports-color
[email protected]:
dependencies:
'@babel/core': 7.26.10
@@ -30226,7 +30311,7 @@ snapshots:
chalk: 4.1.2
create-jest: 29.7.0(@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected])
exit: 0.1.2
import-local: 3.0.2
import-local: 3.1.0
jest-config: 29.7.0(@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected])
jest-util: 29.7.0
jest-validate: 29.7.0
@@ -30683,7 +30768,7 @@ snapshots:
dependencies:
'@jest/core': 29.7.0([email protected])
'@jest/types': 29.6.3
import-local: 3.0.2
import-local: 3.1.0
jest-cli: 29.7.0(@types/[email protected](patch_hash=9c31d25336aea4076b3cb942c35e59fd160c540947f01ea455a060367153f9fd))([email protected])
transitivePeerDependencies:
- '@types/node'
@@ -35618,7 +35703,7 @@ snapshots:
[email protected]:
dependencies:
debug: 4.4.0
debug: 4.4.3
depd: 2.0.0
is-promise: 4.0.0
parseurl: 1.3.3
@@ -35861,7 +35946,7 @@ snapshots:
[email protected]:
dependencies:
debug: 4.4.0
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
+5
View File
@@ -6,6 +6,11 @@ packages:
- 'turbopack/crates/*/js'
- 'turbopack/crates/turbopack-tests/tests/execution'
- 'turbopack/packages/*'
catalog:
eslint: 9.37.0
jest: 29.7.0
prettier: 3.6.2
typescript: 6.0.2
updateNotifier: false
publicHoistPattern:
- '*eslint*'