From d378c1fc2484c18562b8444f49bacae3264e60c8 Mon Sep 17 00:00:00 2001 From: LPegasus Date: Wed, 3 Dec 2025 20:59:00 +0800 Subject: [PATCH 1/5] [rush-lib] ProjectChangeAnalyzer should check non-default subspace pnpm-lock.yaml changes --- ...space-pnpmlock-check_2025-12-03-13-27.json | 10 ++ .../src/logic/ProjectChangeAnalyzer.ts | 98 ++++++++----- .../logic/test/ProjectChangeAnalyzer.test.ts | 135 +++++++++++++++++- .../test/repoWithSubspaces/a/package.json | 5 + .../test/repoWithSubspaces/b/package.json | 8 ++ .../test/repoWithSubspaces/c/package.json | 8 ++ .../common/config/rush/experiments.json | 3 + .../common/config/rush/pnpm-config.json | 4 + .../common/config/rush/subspaces.json | 5 + .../common/config/rush/version-policies.json | 1 + .../config/subspaces/default/.pnpmfile.cjs | 7 + .../subspaces/default/common-versions.json | 8 ++ .../.pnpmfile.cjs | 7 + .../common-versions.json | 8 ++ .../test/repoWithSubspaces/d/package.json | 8 ++ .../test/repoWithSubspaces/e/package.json | 8 ++ .../test/repoWithSubspaces/f/package.json | 5 + .../logic/test/repoWithSubspaces/rush.json | 34 +++++ 18 files changed, 324 insertions(+), 38 deletions(-) create mode 100644 common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/a/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/b/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/c/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/experiments.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/pnpm-config.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/subspaces.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/version-policies.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/common-versions.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/common-versions.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/d/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/e/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/f/package.json create mode 100644 libraries/rush-lib/src/logic/test/repoWithSubspaces/rush.json diff --git a/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json b/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json new file mode 100644 index 00000000000..2e1fdb356e9 --- /dev/null +++ b/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Fix: ProjectChangeAnalyzer should check pnpm-lock.yaml changes for all subspaces.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index ecf09749f5a..6856ce7f9be 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -17,6 +17,7 @@ import { import type { ITerminal } from '@rushstack/terminal'; import type { RushConfiguration } from '../api/RushConfiguration'; +import type { Subspace } from '../api/Subspace'; import { RushProjectConfiguration } from '../api/RushProjectConfiguration'; import type { RushConfigurationProject } from '../api/RushConfigurationProject'; import { BaseProjectShrinkwrapFile } from './base/BaseProjectShrinkwrapFile'; @@ -134,53 +135,76 @@ export class ProjectChangeAnalyzer { // Even though changing the installed version of a nested dependency merits a change file, // ignore lockfile changes for `rush change` for the moment + const subspaces: ReadonlySet = rushConfiguration.subspacesFeatureEnabled + ? new Set(rushConfiguration.subspaces) + : new Set([rushConfiguration.defaultSubspace]); + const variantToUse: string | undefined = variant ?? (await this._rushConfiguration.getCurrentlyInstalledVariantAsync()); - const fullShrinkwrapPath: string = - rushConfiguration.defaultSubspace.getCommittedShrinkwrapFilePath(variantToUse); - const relativeShrinkwrapFilePath: string = Path.convertToSlashes( - path.relative(repoRoot, fullShrinkwrapPath) - ); - const shrinkwrapStatus: IFileDiffStatus | undefined = changedFiles.get(relativeShrinkwrapFilePath); + await Async.forEachAsync(subspaces, async (subspace: Subspace) => { + const fullShrinkwrapPath: string = subspace.getCommittedShrinkwrapFilePath(variantToUse); - if (shrinkwrapStatus) { - if (shrinkwrapStatus.status !== 'M') { - terminal.writeLine(`Lockfile was created or deleted. Assuming all projects are affected.`); - return new Set(rushConfiguration.projects); - } - - if (rushConfiguration.isPnpm) { - const currentShrinkwrap: PnpmShrinkwrapFile | undefined = - PnpmShrinkwrapFile.loadFromFile(fullShrinkwrapPath); + const relativeShrinkwrapFilePath: string = Path.convertToSlashes( + path.relative(repoRoot, fullShrinkwrapPath) + ); + const shrinkwrapStatus: IFileDiffStatus | undefined = changedFiles.get(relativeShrinkwrapFilePath); + const subspaceProjects: RushConfigurationProject[] = subspace.getProjects(); - if (!currentShrinkwrap) { - throw new Error(`Unable to obtain current shrinkwrap file.`); + if (shrinkwrapStatus) { + if (shrinkwrapStatus.status !== 'M') { + if (rushConfiguration.subspacesFeatureEnabled) { + terminal.writeLine( + `"${subspace.subspaceName}" subspace lockfile was created or deleted. Assuming all projects are affected.` + ); + } else { + terminal.writeLine(`Lockfile was created or deleted. Assuming all projects are affected.`); + } + for (const project of subspaceProjects) { + changedProjects.add(project); + } + return; } - const oldShrinkwrapText: string = await this._git.getBlobContentAsync({ - // : syntax: https://git-scm.com/docs/gitrevisions - blobSpec: `${mergeCommit}:${relativeShrinkwrapFilePath}`, - repositoryRoot: repoRoot - }); - const oldShrinkWrap: PnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromString(oldShrinkwrapText); - - for (const project of rushConfiguration.projects) { - if ( - currentShrinkwrap - .getProjectShrinkwrap(project) - .hasChanges(oldShrinkWrap.getProjectShrinkwrap(project)) - ) { - changedProjects.add(project); + if (rushConfiguration.isPnpm) { + const currentShrinkwrap: PnpmShrinkwrapFile | undefined = + PnpmShrinkwrapFile.loadFromFile(fullShrinkwrapPath); + + if (!currentShrinkwrap) { + throw new Error(`Unable to obtain current shrinkwrap file.`); + } + + const oldShrinkwrapText: string = await this._git.getBlobContentAsync({ + // : syntax: https://git-scm.com/docs/gitrevisions + blobSpec: `${mergeCommit}:${relativeShrinkwrapFilePath}`, + repositoryRoot: repoRoot + }); + const oldShrinkWrap: PnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromString(oldShrinkwrapText); + + for (const project of subspaceProjects) { + if ( + currentShrinkwrap + .getProjectShrinkwrap(project) + .hasChanges(oldShrinkWrap.getProjectShrinkwrap(project)) + ) { + changedProjects.add(project); + } } + } else { + if (rushConfiguration.subspacesFeatureEnabled) { + terminal.writeLine( + `"${subspace.subspaceName}" subspace lockfile has changed and lockfile content comparison is only supported for pnpm. Assuming all projects are affected.` + ); + } else { + terminal.writeLine( + `Lockfile has changed and lockfile content comparison is only supported for pnpm. Assuming all projects are affected.` + ); + } + subspace.getProjects().forEach((project) => changedProjects.add(project)); + return; } - } else { - terminal.writeLine( - `Lockfile has changed and lockfile content comparison is only supported for pnpm. Assuming all projects are affected.` - ); - return new Set(rushConfiguration.projects); } - } + }); } return changedProjects; diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 9ab196ea8a8..950b5edb920 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -41,6 +41,67 @@ jest.mock(`@rushstack/package-deps-hash`, () => { }, hashFilesAsync(rootDirectory: string, filePaths: Iterable): ReadonlyMap { return new Map(Array.from(filePaths, (filePath: string) => [filePath, filePath])); + }, + getRepoChanges(): Map { + return new Map([ + [ + // Test subspace lockfile change detection + 'common/config/subspaces/project-change-analyzer-test-subspace/pnpm-lock.yaml', + { + mode: 'modified', + newhash: 'newhash', + oldhash: 'oldhash', + status: 'M' + } + ], + [ + // Test lockfile deletion detection + 'common/config/subspaces/default/pnpm-lock.yaml', + { + mode: 'deleted', + newhash: '', + oldhash: 'oldhash', + status: 'D' + } + ] + ]); + } + }; +}); + +const { Git: OriginGit } = jest.requireActual('../Git'); +/** Mock Git to test `getChangedProjectsAsync` */ +jest.mock('../Git', () => { + return { + Git: class MockGit extends OriginGit { + public async determineIfRefIsACommitAsync(ref: string): Promise { + return true; + } + public async getMergeBaseAsync(ref1: string, ref2: string): Promise { + return 'merge-base-sha'; + } + public async getBlobContentAsync(opts: { blobSpec: string; repositoryRoot: string }): Promise { + return ''; + } + } + }; +}); + +const OriginalPnpmShrinkwrapFile = jest.requireActual('../pnpm/PnpmShrinkwrapFile').PnpmShrinkwrapFile; +jest.mock('../pnpm/PnpmShrinkwrapFile', () => { + return { + PnpmShrinkwrapFile: { + loadFromFile: (fullShrinkwrapPath: string): PnpmShrinkwrapFile => { + return OriginalPnpmShrinkwrapFile.loadFromString(_getMockedPnpmShrinkwrapFile()); + }, + loadFromString: (text: string): PnpmShrinkwrapFile => { + return OriginalPnpmShrinkwrapFile.loadFromString( + _getMockedPnpmShrinkwrapFile() + // Change dependencies version + .replace(/1\.0\.1/g, '1.0.0') + .replace(/foo_1_0_1/g, 'foo_1_0_0') + ); + } } }; }); @@ -55,7 +116,7 @@ jest.mock('../incremental/InputsSnapshot', () => { import { resolve } from 'node:path'; -import type { IDetailedRepoState } from '@rushstack/package-deps-hash'; +import type { IDetailedRepoState, IFileDiffStatus } from '@rushstack/package-deps-hash'; import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal'; import { ProjectChangeAnalyzer } from '../ProjectChangeAnalyzer'; @@ -65,6 +126,7 @@ import type { GetInputsSnapshotAsyncFn, IInputsSnapshotParameters } from '../incremental/InputsSnapshot'; +import type { PnpmShrinkwrapFile } from '../pnpm/PnpmShrinkwrapFile'; describe(ProjectChangeAnalyzer.name, () => { beforeEach(() => { @@ -101,4 +163,75 @@ describe(ProjectChangeAnalyzer.name, () => { expect(mockInput.additionalHashes).toEqual(new Map()); }); }); + + describe.skip(ProjectChangeAnalyzer.prototype.getChangedProjectsAsync.name, () => { + it('Subspaces detects external changes', async () => { + const rootDir: string = resolve(__dirname, 'repoWithSubspaces'); + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile( + resolve(rootDir, 'rush.json') + ); + const projectChangeAnalyzer: ProjectChangeAnalyzer = new ProjectChangeAnalyzer(rushConfiguration); + + const terminalProvider: StringBufferTerminalProvider = new StringBufferTerminalProvider(true); + const terminal: Terminal = new Terminal(terminalProvider); + + const changedProjects = await projectChangeAnalyzer.getChangedProjectsAsync({ + enableFiltering: false, + includeExternalDependencies: true, + targetBranchName: 'main', + terminal + }); + + // a,b,c is included because of change modifier is not modified + // d is included because its dependency foo version changed in the subspace lockfile + ['a', 'b', 'c', 'd'].forEach((projectName) => { + expect(changedProjects.has(rushConfiguration.getProjectByName(projectName)!)).toBe(true); + }); + + // e depends on d via workspace:*, but its own package.json didn't change, so it's not included. e will be included by expandConsumers if needed. + ['e', 'f'].forEach((projectName) => { + expect(changedProjects.has(rushConfiguration.getProjectByName(projectName)!)).toBe(false); + }); + }); + }); }); + +/** + * Create a fake pnpm-lock.yaml content matches "libraries/rush-lib/src/logic/test/repoWithSubspaces" test repo + */ +function _getMockedPnpmShrinkwrapFile(): string { + return `lockfileVersion: '9.0' + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../../d: + dependencies: + foo: + specifier: ~1.0.0 + version: 1.0.1 + + ../../../e: + dependencies: + d: + specifier: workspace:* + version: link:../../../d + + ../../../f: + dependencies: + +packages: + + foo@1.0.1: + resolution: {integrity: 'foo_1_0_1'} + +snapshots: + + foo@1.0.1: {} +`; +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/a/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/a/package.json new file mode 100644 index 00000000000..e57f46f8473 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/a/package.json @@ -0,0 +1,5 @@ +{ + "name": "a", + "version": "1.0.0", + "description": "Test package a" +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/b/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/b/package.json new file mode 100644 index 00000000000..3a7fdf92a46 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/b/package.json @@ -0,0 +1,8 @@ +{ + "name": "b", + "version": "2.0.0", + "description": "Test package b", + "dependencies": { + "foo": "~1.0.0" + } +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/c/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/c/package.json new file mode 100644 index 00000000000..84d308bd6c0 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/c/package.json @@ -0,0 +1,8 @@ +{ + "name": "c", + "version": "3.1.1", + "description": "Test package c", + "dependencies": { + "b": "workspace:*" + } +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/experiments.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/experiments.json new file mode 100644 index 00000000000..a20c6d24388 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/experiments.json @@ -0,0 +1,3 @@ +{ + "exemptDecoupledDependenciesBetweenSubspaces": true +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/pnpm-config.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/pnpm-config.json new file mode 100644 index 00000000000..fdb1cb3ac59 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/pnpm-config.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/pnpm-config.schema.json", + "useWorkspaces": true +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/subspaces.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/subspaces.json new file mode 100644 index 00000000000..ab4e6b3a3c4 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/subspaces.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/subspaces.schema.json", + "subspacesEnabled": true, + "subspaceNames": ["project-change-analyzer-test-subspace"] +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/version-policies.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/version-policies.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/rush/version-policies.json @@ -0,0 +1 @@ +[] diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs new file mode 100644 index 00000000000..6e08f3ba765 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = { + hooks: { + readPackage + } +}; diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/common-versions.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/common-versions.json new file mode 100644 index 00000000000..9280fe7b96d --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/common-versions.json @@ -0,0 +1,8 @@ +/** + * This configuration file specifies NPM dependency version selections that affect all projects + * in a Rush repo. More documentation is available on the Rush website: https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", + "ensureConsistentVersions": true +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs new file mode 100644 index 00000000000..6e08f3ba765 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = { + hooks: { + readPackage + } +}; diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/common-versions.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/common-versions.json new file mode 100644 index 00000000000..9280fe7b96d --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/common-versions.json @@ -0,0 +1,8 @@ +/** + * This configuration file specifies NPM dependency version selections that affect all projects + * in a Rush repo. More documentation is available on the Rush website: https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", + "ensureConsistentVersions": true +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/d/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/d/package.json new file mode 100644 index 00000000000..fc4d11d2037 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/d/package.json @@ -0,0 +1,8 @@ +{ + "name": "d", + "version": "4.1.1", + "description": "Test package d", + "dependencies": { + "foo": "~1.0.0" + } +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/e/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/e/package.json new file mode 100644 index 00000000000..c7210f01031 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/e/package.json @@ -0,0 +1,8 @@ +{ + "name": "e", + "version": "10.10.0", + "description": "Test package e", + "dependencies": { + "d": "workspace:*" + } +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/f/package.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/f/package.json new file mode 100644 index 00000000000..5d6ea8a762e --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/f/package.json @@ -0,0 +1,5 @@ +{ + "name": "f", + "version": "10.10.0", + "description": "Test package f" +} diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/rush.json b/libraries/rush-lib/src/logic/test/repoWithSubspaces/rush.json new file mode 100644 index 00000000000..895d07437cd --- /dev/null +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/rush.json @@ -0,0 +1,34 @@ +{ + "rushVersion": "1.0.5", + "pnpmVersion": "9.15.0", + + "projects": [ + { + "packageName": "a", + "projectFolder": "a" + }, + { + "packageName": "b", + "projectFolder": "b" + }, + { + "packageName": "c", + "projectFolder": "c" + }, + { + "packageName": "d", + "projectFolder": "d", + "subspaceName": "project-change-analyzer-test-subspace" + }, + { + "packageName": "e", + "projectFolder": "e", + "subspaceName": "project-change-analyzer-test-subspace" + }, + { + "packageName": "f", + "projectFolder": "f", + "subspaceName": "project-change-analyzer-test-subspace" + } + ] +} From d47f112d1a9f8ac563baa3e3591b8bbe28662d5c Mon Sep 17 00:00:00 2001 From: Pegasusknight Date: Thu, 4 Dec 2025 09:48:13 +0800 Subject: [PATCH 2/5] Update common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json Co-authored-by: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> --- ...zer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json b/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json index 2e1fdb356e9..56156231f79 100644 --- a/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json +++ b/common/changes/@microsoft/rush/fix-project-change-analyzer-nondefault-subspace-pnpmlock-check_2025-12-03-13-27.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/rush", - "comment": "Fix: ProjectChangeAnalyzer should check pnpm-lock.yaml changes for all subspaces.", + "comment": "Fix an issue where ProjectChangeAnalyzer checked the pnpm-lock.yaml file in the default subspace only, when it should consider all subspaces.", "type": "none" } ], From 42f9c4f8a74d21d1003e995be97858783baa0812 Mon Sep 17 00:00:00 2001 From: Pegasusknight Date: Thu, 4 Dec 2025 09:48:27 +0800 Subject: [PATCH 3/5] Update libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts Co-authored-by: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> --- libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 950b5edb920..a9c5901932e 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -69,7 +69,7 @@ jest.mock(`@rushstack/package-deps-hash`, () => { }; }); -const { Git: OriginGit } = jest.requireActual('../Git'); +const { Git: OriginalGit } = jest.requireActual('../Git'); /** Mock Git to test `getChangedProjectsAsync` */ jest.mock('../Git', () => { return { From 208eb5be7b1d27397460aaf9768f88b803a3ebcc Mon Sep 17 00:00:00 2001 From: LPegasus Date: Thu, 4 Dec 2025 10:13:40 +0800 Subject: [PATCH 4/5] [rush-lib] ProjectChangeAnalyzer L138-L140, use array instead of Set for subspaces iteration --- libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts | 6 +++--- .../rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index 6856ce7f9be..42b3566a497 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -135,9 +135,9 @@ export class ProjectChangeAnalyzer { // Even though changing the installed version of a nested dependency merits a change file, // ignore lockfile changes for `rush change` for the moment - const subspaces: ReadonlySet = rushConfiguration.subspacesFeatureEnabled - ? new Set(rushConfiguration.subspaces) - : new Set([rushConfiguration.defaultSubspace]); + const subspaces: Iterable = rushConfiguration.subspacesFeatureEnabled + ? rushConfiguration.subspaces + : [rushConfiguration.defaultSubspace]; const variantToUse: string | undefined = variant ?? (await this._rushConfiguration.getCurrentlyInstalledVariantAsync()); diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index a9c5901932e..4474cc4e62b 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -73,7 +73,7 @@ const { Git: OriginalGit } = jest.requireActual('../Git'); /** Mock Git to test `getChangedProjectsAsync` */ jest.mock('../Git', () => { return { - Git: class MockGit extends OriginGit { + Git: class MockGit extends OriginalGit { public async determineIfRefIsACommitAsync(ref: string): Promise { return true; } @@ -164,7 +164,7 @@ describe(ProjectChangeAnalyzer.name, () => { }); }); - describe.skip(ProjectChangeAnalyzer.prototype.getChangedProjectsAsync.name, () => { + describe(ProjectChangeAnalyzer.prototype.getChangedProjectsAsync.name, () => { it('Subspaces detects external changes', async () => { const rootDir: string = resolve(__dirname, 'repoWithSubspaces'); const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile( From 3b24a1477e0815d2060db5f38fa5b4c075c12c7d Mon Sep 17 00:00:00 2001 From: LPegasus Date: Thu, 4 Dec 2025 11:09:17 +0800 Subject: [PATCH 5/5] [rush-lib] Fix test for ProjectChangeAnalyzer 1. Fix `.pnpmfile.cjs` `readPackage` function is not defined. 2. Fix the comment of why 'e' is not in changedProjects. --- .../rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts | 3 ++- .../common/config/subspaces/default/.pnpmfile.cjs | 4 +++- .../project-change-analyzer-test-subspace/.pnpmfile.cjs | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts index 4474cc4e62b..2667f16e360 100644 --- a/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts +++ b/libraries/rush-lib/src/logic/test/ProjectChangeAnalyzer.test.ts @@ -188,7 +188,8 @@ describe(ProjectChangeAnalyzer.name, () => { expect(changedProjects.has(rushConfiguration.getProjectByName(projectName)!)).toBe(true); }); - // e depends on d via workspace:*, but its own package.json didn't change, so it's not included. e will be included by expandConsumers if needed. + // e depends on d via workspace:*, but its calculated lockfile (e.g. "e/.rush/temp/shrinkwrap-deps.json") didn't change. + // So it's not included. e will be included by `expandConsumers` if needed. ['e', 'f'].forEach((projectName) => { expect(changedProjects.has(rushConfiguration.getProjectByName(projectName)!)).toBe(false); }); diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs index 6e08f3ba765..ee041f83a4e 100644 --- a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/default/.pnpmfile.cjs @@ -2,6 +2,8 @@ module.exports = { hooks: { - readPackage + readPackage(pkgJson) { + return pkgJson; + } } }; diff --git a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs index 6e08f3ba765..ee041f83a4e 100644 --- a/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs +++ b/libraries/rush-lib/src/logic/test/repoWithSubspaces/common/config/subspaces/project-change-analyzer-test-subspace/.pnpmfile.cjs @@ -2,6 +2,8 @@ module.exports = { hooks: { - readPackage + readPackage(pkgJson) { + return pkgJson; + } } };