Repository navigation
[rushstack] Use pnpm catalogs for external dependency versions - #6146
Draft
Bharat Middha (bmiddha) wants to merge 6 commits into
Draft
Bharat Middha (bmiddha) wants to merge 6 commits into
Bharat Middha (bmiddha) wants to merge 6 commits into
Conversation
Bharat Middha (bmiddha)
added this pull request to stack #6134
October 14, 2026 21:29
- Bump pnpmVersion from 10.27.0 to 12.13.0. pnpm 12.10.1 and older drop files re-included by .npmignore negations, which breaks injected dependencies and rush-pnpm pack (fixed upstream in pnpm 12.11.0). - Replace the globalNeverBuiltDependencies setting, which pnpm 11 removed, with globalAllowBuilds. Explicitly allow the native-binary packages that pnpm 10 previously built by default. - Regenerate both subspace lockfiles and repo-state.json with pnpm 12. The ms-feed tarball URLs that pnpm records are stripped because the feed rotates its ms-feed-N hosts and pnpm 12 rejects mismatched tarball URLs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a "default" catalog to common/config/rush/pnpm-config.json with the ECV-consistent version of each external dependency, and reference it with "catalog:" from project dependencies, devDependencies, and optionalDependencies. Specifiers that stay literal: peer dependencies, Rush project and decoupled local dependencies, allowedAlternativeVersions, npm: aliases and dist-tags, @types/vscode (validated as semver by vsce), versions that differ from the catalog in build-tests-subspace, and production dependencies of projects that build-tests-subspace installs as injected file: dependencies (PNPM cannot resolve "catalog:" in those manifests). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bharat Middha (bmiddha)
force-pushed
the
bmiddha-migrate-rushstack-to-pnpm-catalogs
branch
from
October 15, 2026 01:52
51d9b41 to
d3f36bb
Compare
…hs in snapshots The loader output snapshots embedded the pnpm virtual store folder name of css-loader. With the pnpm 12 lockfile, that name gains the resolved optional peers of webpack (@swc/core, esbuild, uglify-js) and grows to 103 characters, which pnpm shortens differently on Windows (60) than on other platforms (120). Replace the folder name with a placeholder before snapshotting so the snapshots are platform- and lockfile-independent. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
SuccessfulMutationFixture copies the repo's own PNPM installation and checks its version by running node_modules/pnpm/bin/pnpm.cjs. PNPM 11 and newer ship a native executable and replace that script with bin/pnpm.mjs, so with this repo on PNPM 12.13.0 the native install and update tests in rush-daemon and rush-cli-client failed with "Cannot find module .../bin/pnpm.cjs". Use bin/pnpm.cjs when it exists, otherwise bin/pnpm.mjs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The package feed's registry metadata only provides the sha1 `shasum` for each tarball, not the `integrity` field. PNPM 10 kept the sha512 hashes that were already in the lockfiles, but regenerating the lockfiles with PNPM 12 re-resolved every package and replaced 3526 sha512 hashes with sha1 hashes (2788 in the default subspace and 738 in build-tests-subspace). Restore the sha512 hash from the PNPM 10 lockfile for each of those packages. The package keys (name, version, and peers) are unchanged, so the tarballs are identical. Every package now has the same integrity hash as in the PNPM 10 lockfile, except 6 that PNPM 12 upgraded from sha1 to sha512. repo-state.json is regenerated. "rush update" keeps the restored hashes, and "rush install" in CI mode with an empty PNPM store downloads and verifies every tarball without changing the lockfiles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This branch has not been deployed
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.
Summary
This is layer 6 (the top) of the pnpm 12 / catalogs stack, based on #6141. It moves this repo's external dependency versions into a single pnpm catalog.
common/config/rush/pnpm-config.jsongainsglobalCatalogs.defaultwith 165 entries sorted by name, and 657 dependency specs in 171 Rush projects now use"catalog:". Neither lockfile changes any resolved version; see the churn numbers below.Merge prerequisites (do not merge before these)
rushVersioninrush.json. This PR deliberately does not bumprushVersion; the currently pinned 5.181.0 cannot install this repo with pnpm 12 or withcatalog:specs, so CI on this PR is expected to fail until then.Details
Design
default,build-tests-subspace) has its ownpnpm-config.json, so both usecommon/config/rush/pnpm-config.jsonand shareglobalCatalogs.default(effective-config behavior from [rush] Use the common pnpm-config.json fallback for every pnpm catalog consumer #6138).dependencies,devDependenciesoroptionalDependenciesof a Rush project, the dominant version (the one that is consistent underensureConsistentVersions) goes in the catalog, and those specs becomecatalog:.catalogPrune/cleanupUnusedCatalogsare not enabled: each subspace workspace only sees its own projects, so pruning a shared catalog would be unsafe.catalogModeis not set.preferredVersionsandallowedAlternativeVersionsincommon-versions.jsonare unchanged and consistent with the catalog (typescript ~5.8.2,eslint ~9.37.0,minimatch 10.2.3).nonechange files with empty comments.pnpm packreplacescatalog:with the catalog version, so published manifests are unchanged (verified below).What stays literal, and why
defaultsubspace: 650 specs cataloged (197dependencies, 453devDependencies) across 166 projects. 207 external specs stay literal:allowedAlternativeVersions(the dominant version is in the catalog)npm:aliases@types/node/@types/jestts2.9/ts3.9/ts4.9)@types/vscode:vscereadsdevDependencies['@types/vscode']with semver to check it againstengines.vscodeand rejectscatalog:@pnpm/logger: both versions in use are alternatives@pnpm/lockfile.types: its only version is an alternativebuild-tests-subspace: 7 cataloged (devDependenciesin 5 projects). 7 stay literal because they differ from the shared catalog: 5×eslint ~9.25.1(catalog has~9.37.0),webpack ~5.105.0(catalog has~5.105.2), andtypescript ~4.9.5(an alternative). Named catalogs could absorb these later.Never touched:
peerDependenciesworkspace:specs, and Rush projects consumed by version as decoupled local dependencies (@rushstack/heft, the eslint configs,decoupled-local-node-rig, and similar). These are still bumped byrepo-toolbox'sBumpDecoupledLocalDependencies, which only touches those literal specs.common/autoinstallers/**, and any package.json that is not a Rush project (test fixtures and the like)Deviation: injected cross-subspace closure stays literal for prod deps
build-tests-subspaceprojects inject default-subspace projects (workspace:*+dependenciesMeta.injected). The subspace pnpmfile shim rewrites those deps tofile:../../<folder>. pnpm then installs the injected packages from their own manifests, and it cannot resolvecatalog:there (ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER "@types/tapable@catalog:").The transitive closure of injected projects is 35 projects (
rush-lib,rush-sdk,heft,node-core-library,api-extractor, the eslint packages,local-node-rig, and others). Only 23 of them had cataloged prod deps.dependenciesandoptionalDependencieskeep literal versions (98 specs). That leaves 42 packages used only there out of the catalog.devDependenciesof closure projects are stillcatalog:, because injected packages don't install devDependencies.globalCatalogscomment inpnpm-config.jsondocuments this.Follow-up option for a lower layer: have
SubspaceGlobalPnpmfileShim.readPackageresolvecatalog:/catalog:<name>for injected workspace packages, using the source subspace's effective catalogs. After that ships, these 98 specs can be cataloged as well.Caveat: running catalog-aware
rush add(#6141) in a closure project for a production dependency writescatalog:and breaks thebuild-tests-subspaceinstall, until that shim fix lands.Lockfile churn
Measured by comparing pnpm-lock.yaml
packages,snapshotsandimportersbefore and after this change.defaultbuild-tests-subspacecatalogs.defaultblock containing only the entries that subspace uses (build-tests-subspaceuses 5:@types/node,@types/webpack-env,html-webpack-plugin,tslint,typescript). Importer specifiers change from literal ranges tocatalog:.defaultlockfile shows 646 importer specifier changes for 650 package.json specs because the lockfile lists a package that appears in both dependencies and devDependencies only once.pnpmCatalogsHash.ms-feedtarball URLs were stripped from both lockfiles; none remain.How it was tested
All commands were run with a locally built Rush containing layers 1–5 (
node apps/rush/lib-commonjs/start-dev.js …), pnpm 12.13.0 and Node 22.23.2. The validation ran before rebasing onto the new tip of #6141. The rebase was patch-identical (git range-diff), and the lower layer only changed rush-lib'sPnpmSyncUtilities.change --verifywas re-run after the rebase.rush update(both subspaces), stripms-feedtarball URLs,rush updateagainrepo-state.jsonstable, noms-feedURLsCI=true rush installdefault: frozen lockfile, "Lockfile is up to date";build-tests-subspace:--no-frozen-lockfile --prefer-frozen-lockfile(injected deps). No changes to lockfiles orrepo-state.jsonrush check,rush check --subspace default,rush check --subspace build-tests-subspacerush build --production(full repo)rush change --verify(againstorigin/mainand against the #6141 tip)rush-pnpm packfornode-core-library,heft,rush,heft-node-rig,api-documenter,heft-sass-plugin,heft-webpack5-plugin,lockfile-explorerpackage.jsonhas nocatalog:; everycatalog:ref resolves to its catalog entry; every external dependency spec is identical to the pre-migrationpackage.jsonpackage-vsixtask runsvsce package --no-dependenciesduring the build)@types/vscodeis literal, so theengines.vscodecheck passes.catalog:in the VSIX'sdevDependenciesis accepted, asworkspace:*already waspackage-extractorpackage-extractor-test-*builds passed in the full buildrush add -p @types/cors --dev -sinapps/api-documenter(smoke test of #6141, reverted)"@types/cors": "catalog:"("Assigning "catalog:" … because it is "~2.8.12" in the PNPM catalog")repo-toolboxBumpDecoupledLocalDependencies(code review)Impacted documentation
None. This changes this repo's configuration only. The rules for which specs stay literal are documented in the
globalCatalogscomment incommon/config/rush/pnpm-config.json.