diff --git a/common/changes/@microsoft/rush/sennyeya-fix-injected-dep-hash_2026-03-17-14-17.json b/common/changes/@microsoft/rush/sennyeya-fix-injected-dep-hash_2026-03-17-14-17.json new file mode 100644 index 00000000000..494238eee0f --- /dev/null +++ b/common/changes/@microsoft/rush/sennyeya-fix-injected-dep-hash_2026-03-17-14-17.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Fixes a bug where the injected dependency state hash updated on devDependency changes that wouldn't impact the lockfile.", + "type": "none", + "packageName": "@microsoft/rush" + } + ], + "packageName": "@microsoft/rush", + "email": "aramissennyeydd@users.noreply.github.com" +} \ No newline at end of file diff --git a/libraries/rush-lib/src/api/Subspace.ts b/libraries/rush-lib/src/api/Subspace.ts index 624b1a33bd6..1058788727e 100644 --- a/libraries/rush-lib/src/api/Subspace.ts +++ b/libraries/rush-lib/src/api/Subspace.ts @@ -478,7 +478,6 @@ export class Subspace { name, bin, dependencies, - devDependencies, peerDependencies, optionalDependencies, dependenciesMeta, @@ -500,7 +499,6 @@ export class Subspace { name, bin, dependencies, - devDependencies, peerDependencies, optionalDependencies, dependenciesMeta, diff --git a/libraries/rush-lib/src/api/test/Subspace.test.ts b/libraries/rush-lib/src/api/test/Subspace.test.ts index b7e01689519..7f1844be7ce 100644 --- a/libraries/rush-lib/src/api/test/Subspace.test.ts +++ b/libraries/rush-lib/src/api/test/Subspace.test.ts @@ -74,4 +74,80 @@ describe(Subspace.name, () => { expect(hashWithCatalogs).toBeDefined(); }); }); + + describe('getPackageJsonInjectedDependenciesHash', () => { + it('returns undefined when no injected dependencies exist', () => { + const rushJsonFilename: string = path.resolve(__dirname, 'repo', 'rush-pnpm.json'); + const rushConfiguration: RushConfiguration = + RushConfiguration.loadFromConfigurationFile(rushJsonFilename); + const defaultSubspace: Subspace = rushConfiguration.defaultSubspace; + + const hash: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + expect(hash).toBeUndefined(); + }); + + it('computes a hash when injected dependencies exist', () => { + const rushJsonFilename: string = path.resolve(__dirname, 'repoInjectedDeps', 'rush.json'); + const rushConfiguration: RushConfiguration = + RushConfiguration.loadFromConfigurationFile(rushJsonFilename); + const defaultSubspace: Subspace = rushConfiguration.defaultSubspace; + + const hash: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + expect(hash).toBeDefined(); + expect(typeof hash).toBe('string'); + expect(hash).toHaveLength(40); // SHA1 hash + }); + + it('does not change when devDependencies of the injected package change', () => { + const rushJsonFilename: string = path.resolve(__dirname, 'repoInjectedDeps', 'rush.json'); + const rushConfiguration: RushConfiguration = + RushConfiguration.loadFromConfigurationFile(rushJsonFilename); + const defaultSubspace: Subspace = rushConfiguration.defaultSubspace; + + const hashBefore: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + + // Mutate devDependencies of the injected provider package + const providerProject = rushConfiguration.getProjectByName('provider')!; + const originalDevDeps = providerProject.packageJson.devDependencies; + providerProject.packageJson.devDependencies = { + ...originalDevDeps, + jest: '^29.0.0' + }; + + const hashAfter: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + + expect(hashBefore).toBeDefined(); + expect(hashAfter).toBeDefined(); + expect(hashBefore).toBe(hashAfter); + + // Restore + providerProject.packageJson.devDependencies = originalDevDeps; + }); + + it('changes when production dependencies of the injected package change', () => { + const rushJsonFilename: string = path.resolve(__dirname, 'repoInjectedDeps', 'rush.json'); + const rushConfiguration: RushConfiguration = + RushConfiguration.loadFromConfigurationFile(rushJsonFilename); + const defaultSubspace: Subspace = rushConfiguration.defaultSubspace; + + const hashBefore: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + + // Mutate dependencies of the injected provider package + const providerProject = rushConfiguration.getProjectByName('provider')!; + const originalDeps = providerProject.packageJson.dependencies; + providerProject.packageJson.dependencies = { + ...originalDeps, + axios: '^1.6.0' + }; + + const hashAfter: string | undefined = defaultSubspace.getPackageJsonInjectedDependenciesHash(); + + expect(hashBefore).toBeDefined(); + expect(hashAfter).toBeDefined(); + expect(hashBefore).not.toBe(hashAfter); + + // Restore + providerProject.packageJson.dependencies = originalDeps; + }); + }); }); diff --git a/libraries/rush-lib/src/api/test/repoInjectedDeps/consumer/package.json b/libraries/rush-lib/src/api/test/repoInjectedDeps/consumer/package.json new file mode 100644 index 00000000000..9fb49f623fd --- /dev/null +++ b/libraries/rush-lib/src/api/test/repoInjectedDeps/consumer/package.json @@ -0,0 +1,12 @@ +{ + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "provider": "workspace:*" + }, + "dependenciesMeta": { + "provider": { + "injected": true + } + } +} diff --git a/libraries/rush-lib/src/api/test/repoInjectedDeps/provider/package.json b/libraries/rush-lib/src/api/test/repoInjectedDeps/provider/package.json new file mode 100644 index 00000000000..22b969cb820 --- /dev/null +++ b/libraries/rush-lib/src/api/test/repoInjectedDeps/provider/package.json @@ -0,0 +1,10 @@ +{ + "name": "provider", + "version": "1.0.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "devDependencies": { + "typescript": "~5.3.0" + } +} diff --git a/libraries/rush-lib/src/api/test/repoInjectedDeps/rush.json b/libraries/rush-lib/src/api/test/repoInjectedDeps/rush.json new file mode 100644 index 00000000000..eebb706445e --- /dev/null +++ b/libraries/rush-lib/src/api/test/repoInjectedDeps/rush.json @@ -0,0 +1,17 @@ +{ + "pnpmVersion": "9.5.0", + "rushVersion": "5.46.1", + "projectFolderMinDepth": 1, + "projectFolderMaxDepth": 99, + + "projects": [ + { + "packageName": "consumer", + "projectFolder": "consumer" + }, + { + "packageName": "provider", + "projectFolder": "provider" + } + ] +}