Skip to content

[rush] Use the common pnpm-config.json fallback for every pnpm catalog consumer - #6138

Draft
Bharat Middha (bmiddha) wants to merge 1 commit into
bmiddha-bump-rushstack-to-pnpm-12from
bmiddha-catalog-config-fallback
Draft

Bharat Middha (bmiddha) wants to merge 1 commit into
bmiddha-bump-rushstack-to-pnpm-12from
bmiddha-catalog-config-fallback

Conversation

@bmiddha

Copy link
Copy Markdown
Member

Summary

Part of the pnpm-catalog stack. This PR sits on top of #6136, which is stacked on #6133 -> #6131 (native GitHub stack #6134).

When a subspace has no pnpm-config.json of its own, rush install / rush update fall back to common/config/rush/pnpm-config.json (subspace.getPnpmOptions() || rushConfiguration.pnpmOptions) and write its globalCatalogs into the generated pnpm-workspace.yaml. Several other catalog consumers only read the subspace's own file and had no fallback, so they saw no catalogs:

  • Subspace.getPnpmCatalogsHash(), which feeds repo-state.json. The hash could disagree with what install actually wrote.
  • ProjectChangeAnalyzer catalog change detection, which diffed a file that isn't in effect, so catalog edits didn't mark catalog: consumers as changed.
  • The rush-pnpm up / rush-pnpm update catalog sync-back, which wrote to a subspace file that doesn't exist instead of the file in effect.

This PR adds one internal helper with exactly the same fallback semantics as install and routes all of these consumers through it, so install, the repo-state hash, change detection, and catalog sync can't disagree.

Details

New internal helper: libraries/rush-lib/src/logic/pnpm/EffectivePnpmOptions.ts. It is not exported from the package index.

  • getEffectivePnpmOptions(subspace, rushConfiguration): IEffectivePnpmOptions returns { pnpmOptions, pnpmConfigFilePath, isFallback }. That is the subspace's own pnpm-config.json if it exists, otherwise common/config/rush/pnpm-config.json.
  • getEffectivePnpmCatalogs(subspace, rushConfiguration) returns exactly the catalogs install writes to pnpm-workspace.yaml. It returns undefined when the package manager isn't pnpm or there are no catalogs.
  • getOtherSubspacesSharingPnpmConfig(subspace, rushConfiguration) returns the other subspaces whose effective config is the same file.
  • mergePnpmCatalogs(existing, toAdd) merges catalogs: it only adds or updates entries and never removes them.

Consumers routed through the helper:

  • Subspace.getPnpmCatalogsHash
  • InstallHelpers.resolvePnpmSettings
  • SubspacePnpmfileConfiguration
  • PnpmShrinkwrapFile, for its lockfile policy checks
  • BaseInstallManager, which now includes the effective config file in its list of potentially changed files
  • ProjectChangeAnalyzer.#detectCatalogChangesAsync, which diffs the effective file. For old catalogs at the merge base, it reads the subspace's own file first and falls back to the common file.
  • RushPnpmCommandLineParser, for the up/update catalog sync

Catalog sync when the file is shared by several subspaces. A subspace's pnpm workspace only contains that subspace's projects, so pnpm's view of the catalogs is partial.

  • When other subspaces share the effective file, the sync merges with mergePnpmCatalogs (add/update only) rather than replacing. This avoids dropping entries used by other subspaces.
  • After the sync, Rush prints one rush update --subspace <name> line for each other subspace sharing the file. Those subspaces must be updated before their lockfile catalogs and repo-state hashes match again; pnpm 12's frozen-install check compares lockfile catalogs.
  • When the file isn't shared, the existing replace behavior is kept.
  • The sync also picks up a top-level catalog: field that pnpm may write for the default catalog.
  • The sync is skipped when there is no JSON file to write to, i.e. legacy rush.json pnpmOptions.

PnpmOptionsConfiguration.globalCatalogs is now a getter. updateGlobalCatalogsAsync updates the in-memory value as well as the file, so the rush update that follows a sync uses the new catalogs. In the API report, readonly globalCatalogs becomes get globalCatalogs(). The type is unchanged.

Compatibility:

  • When subspaces are disabled, Subspace.getPnpmConfigFilePath() already pointed at common/config/rush/pnpm-config.json, so nothing changes there.
  • When subspaces are enabled and a subspace has no pnpm-config.json, the repo-state catalog hash now reflects the common catalogs. This matches what install writes, so repos in that state may see a one-time repo-state.json update.

catalogPrune/cleanupUnusedCatalogs are intentionally not enabled, since pruning from one subspace's workspace would drop entries used by other subspaces.

How it was tested

New fixture libraries/rush-lib/src/logic/test/repoWithSubspacesCommonCatalogs/ has subspaces enabled. Catalogs exist only in common/config/rush/pnpm-config.json, except for one subspace that has its own config.

New and extended tests:

  • EffectivePnpmOptions.test.ts (new)
  • Subspace.test.ts
  • InstallHelpers.test.ts: a new resolvePnpmSettings test checks that the catalogs written to the workspace file match getPnpmCatalogsHash.
  • ProjectChangeAnalyzer.test.ts: covers fallback and own-config diffs.
  • RushPnpmCommandLineParser.test.ts: covers sync into a shared file (merge, no removals, warning) and into a subspace's own file.

All commands below were run in libraries/rush-lib after bootstrapping with a locally built dev Rush (start-dev.js install and start-dev.js build --to @microsoft/rush-lib):

  • node_modules/.bin/heft build --clean: passed with no warnings. The API report was updated.
  • node_modules/.bin/heft test --test-path-pattern "EffectivePnpmOptions|InstallHelpers|RushPnpmCommandLineParser|ProjectChangeAnalyzer|Subspace|RepoState|PnpmOptions|PnpmShrinkwrap|Pnpmfile|BaseInstall": passed, 186/186.
  • node_modules/.bin/heft test (full rush-lib suite): 1938 passed, 1 failed: TarExecutable › kills tar when its signal aborts while it runs. That test also fails on the base branch without these changes (checked with git stash), so it is unrelated to this PR.

Impacted documentation

Subspaces without their own pnpm-config.json install with the catalogs
from common/config/rush/pnpm-config.json, but the repo-state.json catalog
hash, catalog change detection, and the rush-pnpm update catalog sync only
read the subspace's own file. Route all of them through a new internal
getEffectivePnpmOptions() helper so they match install.

When syncing catalogs into a pnpm-config.json that several subspaces share,
only add or update entries and tell the user which other subspaces need
"rush update".

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bmiddha
Bharat Middha (bmiddha) force-pushed the bmiddha-catalog-config-fallback branch from b6b929f to 951f191 Compare October 20, 2026 21:13

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

1 participant