[rush-lib] Add pnpm global catalog detection to rush change#5627
[rush-lib] Add pnpm global catalog detection to rush change#5627CarltonHowell wants to merge 9 commits intomicrosoft:mainfrom
Conversation
|
@microsoft-github-policy-service agree |
| } | ||
|
|
||
| // Determine which catalog names have changed | ||
| let changedCatalogNames: Set<string>; |
There was a problem hiding this comment.
We should be calculating for which packages given catalog specifiers have changed; a catalog reference is for a specific dependency, so just because one version in a catalog has changed doesn't mean that a different version in the catalog has changed.
There was a problem hiding this comment.
Thanks David, I've addressed the feedback and added more granular tests to target this intended behaviour.
common/changes/@microsoft/rush/rush-change-catalog-entries_2026-02-17-18-48.json
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Adds PNPM global catalog (globalCatalogs) change detection to rush change so that projects using the catalog: protocol are marked as changed when catalog entries are edited, including across subspaces (addresses #5624).
Changes:
- Extend
ProjectChangeAnalyzer.getChangedProjectsAsync()to detect diffs in per-subspacepnpm-config.jsoncatalog entries and mark impacted projects as changed. - Add unit tests covering catalog changes (default/named catalogs) and subspace scenarios.
- Add new test fixture repos (
repoWithCatalogs,repoWithSubspacesCatalogs) and a Rush change file.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts | Implements subspace-aware catalog diffing and project impact detection. |
| libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts | Adds unit tests for catalog change detection (including subspaces). |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/rush.json | Test fixture Rush repo definition for catalog tests. |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/a/package.json | Fixture project using default catalog dependencies. |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/b/package.json | Fixture project using tools catalog (and workspace dep). |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/c/package.json | Fixture project with no catalog dependencies. |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/d/package.json | Fixture project using default catalog (single package). |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/e/package.json | Fixture project using tools catalog in devDependencies. |
| libraries/rush-lib/src/logic/test/repoWithCatalogs/common/config/rush/pnpm-config.json | Fixture defining globalCatalogs for non-subspace repo. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/rush.json | Test fixture Rush repo definition with subspaces enabled. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/a/package.json | Fixture default-subspace project using default catalog. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/b/package.json | Fixture default-subspace project with non-catalog dependency. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/c/package.json | Fixture default-subspace project consuming a workspace dep. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/d/package.json | Fixture named-subspace project using default catalog. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/e/package.json | Fixture named-subspace project using tools catalog. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/f/package.json | Fixture named-subspace project with no catalog deps. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/g/package.json | Fixture named-subspace project using default catalog. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/h/package.json | Fixture named-subspace project using tools catalog. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/default/pnpm-config.json | Fixture default subspace globalCatalogs definition. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/default/common-versions.json | Fixture default subspace common-versions config. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/default/.pnpmfile.cjs | Fixture default subspace pnpmfile. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/project-change-analyzer-test-subspace/pnpm-config.json | Fixture named subspace globalCatalogs definition. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/project-change-analyzer-test-subspace/common-versions.json | Fixture named subspace common-versions config. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs | Fixture named subspace pnpmfile. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/rush/version-policies.json | Fixture Rush config required by test repo. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/rush/subspaces.json | Fixture enabling subspaces in test repo. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/rush/pnpm-config.json | Fixture root pnpm config enabling workspaces for subspaces. |
| libraries/rush-lib/src/logic/test/repoWithSubspacesCatalogs/common/config/rush/experiments.json | Fixture enabling cross-subspace decoupled dependency exemption for tests. |
| common/changes/@microsoft/rush/rush-change-catalog-entries_2026-02-17-18-48.json | Change file documenting the Rush CLI behavior update. |
common/changes/@microsoft/rush/rush-change-catalog-entries_2026-02-17-18-48.json
Outdated
Show resolved
Hide resolved
…6-02-17-18-48.json Co-authored-by: Ian Clanton-Thuon <iclanton@users.noreply.github.com>
dmichon-msft
left a comment
There was a problem hiding this comment.
Few readability suggestions.
| } | ||
| } | ||
|
|
||
| const changedCatalogPackages: Map<string, Set<string>> = new Map<string, Set<string>>(); |
There was a problem hiding this comment.
| const changedCatalogPackages: Map<string, Set<string>> = new Map<string, Set<string>>(); | |
| const changedCatalogPackages: Map<string, Set<string>> = new Map(); |
unnecessary verbosity
| changedCatalogPackages.set(catalogName, new Set(Object.keys(packages))); | ||
| continue; | ||
| } | ||
| const changedPkgs: Set<string> = new Set<string>(); |
There was a problem hiding this comment.
| const changedPkgs: Set<string> = new Set<string>(); | |
| const changedPkgs: Set<string> = new Set(); |
| } | ||
| // Check for packages that were removed from this catalog | ||
| for (const pkgName of Object.keys(oldPackages)) { | ||
| if (!(pkgName in packages)) { |
There was a problem hiding this comment.
packages inherits from Object.prototype, so, e.g. 'toString' in packages returns true. Need to use Object.hasOwn or Object.prototype.hasOwnProperty.call(packages, pkgName)
... or create a new Set with the keys during the iteration over Object.entries(packages) above.
|
|
||
| // Check for catalogs that were entirely removed | ||
| for (const [catalogName, oldPackages] of Object.entries(oldCatalogs)) { | ||
| if (!(catalogName in currentCatalogs)) { |
There was a problem hiding this comment.
Same issue, though since Object.entries(currentCatalogs) is already invoked, we should use the array returned by that invocation to create a Map or Set for use here.
| for (const project of subspaceProjects) { | ||
| const { dependencies, devDependencies, optionalDependencies, peerDependencies } = project.packageJson; | ||
| const allDeps: Record<string, string>[] = [ | ||
| dependencies ?? {}, | ||
| devDependencies ?? {}, | ||
| optionalDependencies ?? {}, | ||
| peerDependencies ?? {} | ||
| ]; | ||
|
|
||
| let isAffected: boolean = false; | ||
| for (const deps of allDeps) { | ||
| if (isAffected) { | ||
| break; | ||
| } | ||
| for (const [depName, depVersion] of Object.entries(deps)) { | ||
| const specifier: DependencySpecifier = DependencySpecifier.parseWithCache(depName, depVersion); | ||
| if (specifier.specifierType === DependencySpecifierType.Catalog) { | ||
| // versionSpecifier holds the catalog name (empty string for "catalog:") | ||
| const catalogName: string = specifier.versionSpecifier || 'default'; | ||
| const changedPkgs: Set<string> | undefined = changedCatalogPackages.get(catalogName); | ||
| if (changedPkgs?.has(depName)) { | ||
| isAffected = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (isAffected) { | ||
| changedProjects.add(project); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Refactor to use subspaceProjects.forEach, since having the loop iteration be a function invocation allows changedProjects.add(project); return; for more ergonomic early return.
Alternatively, or in addition, flatten allDeps first and then use a singly nested loop instead of double nested, so that you only have to break one layer up.
Summary
rush changedoes not detect change to catalog version of dependency #5624rush changedoes not consider changes to pnpm global catalog changes. Runningrush changenow considers any chances to catalog entries, be it additions of version changes.Details
getChangedProjectsAsync()between the per-project file change detection and the includeExternalDependencies block. The new logic:a. Guards on
rushConfiguration.isPnpmandglobalCatalogsexistingb. Computes the relative path to
pnpm-config.jsonand checks if it's in changedFilesc. Loads the old
pnpm-config.jsonto find which catalog names changedd. If the old file doesn't exist (new creation), treats all current catalogs as changed
e. Builds a map of catalogName → Set by scanning each project's dependencies, devDependencies, and optionalDependencies for
catalog: protocolentriesf. Marks affected projects and their consumingProjects as changed
How it was tested
Impacted documentation