Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Fix \"rush install\" failing with ERR_PNPM_OUTDATED_LOCKFILE on PNPM 12 for subspaces with injected workspace dependencies, which now install with \"--prefer-frozen-lockfile\".",
"type": "patch"
}
],
"packageName": "@microsoft/rush",
"email": "[email protected]"
}
15 changes: 14 additions & 1 deletion libraries/rush-lib/src/logic/base/BaseInstallManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,20 @@ ${gitLfsHookHandling}
const { configuration: experiments } = this.rushConfiguration.experimentsConfiguration;

if (experiments.usePnpmFrozenLockfileForRushInstall && !allowShrinkwrapUpdates) {
args.push('--frozen-lockfile');
if (
semver.gte(this.rushConfiguration.packageManagerToolVersion, '12.0.0') &&
subspace.getPackageJsonInjectedDependenciesHash(options.variant) !== undefined
) {
// PNPM 12's frozen check rejects injected "workspace:" dependencies outside the workspace root,
// which is every Rush project. Rush has already checked the shrinkwrap against each package.json.
this._terminal.writeLine(
`PNPM ${this.rushConfiguration.packageManagerToolVersion} cannot verify injected workspace ` +
'dependencies with "--frozen-lockfile"; using "--prefer-frozen-lockfile" instead.'
);
args.push('--no-frozen-lockfile', '--prefer-frozen-lockfile');
} else {
args.push('--frozen-lockfile');
}

if (
pnpmFilterArgumentValues.length > 0 &&
Expand Down
37 changes: 33 additions & 4 deletions libraries/rush-lib/src/logic/test/BaseInstallManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,16 @@ describe('PNPM configuration arguments', () => {
jest.restoreAllMocks();
});

interface IConfigurationArgsScenario {
frozenLockfileForInstall?: boolean;
hasInjectedDependencies?: boolean;
}

function getConfigurationArgs(
pnpmVersion: string,
npmrcSettings: boolean,
pnpmOptions: Partial<Pick<PnpmOptionsConfiguration, 'autoInstallPeers' | 'resolutionMode'>> = {}
pnpmOptions: Partial<Pick<PnpmOptionsConfiguration, 'autoInstallPeers' | 'resolutionMode'>> = {},
{ frozenLockfileForInstall = false, hasInjectedDependencies = false }: IConfigurationArgsScenario = {}
): { args: string[]; warnings: string } {
jest.spyOn(npmrcUtilities, 'isVariableSetInNpmrcFile').mockReturnValue(npmrcSettings);
const terminalProvider: StringBufferTerminalProvider = new StringBufferTerminalProvider();
Expand All @@ -162,19 +168,42 @@ describe('PNPM configuration arguments', () => {
isPnpm: true,
packageManagerToolVersion: pnpmVersion,
pnpmOptions: { pnpmStore: 'global', pnpmStorePath: '/repo/common/temp/pnpm-store', ...pnpmOptions },
experimentsConfiguration: { configuration: {} }
experimentsConfiguration: {
configuration: { usePnpmFrozenLockfileForRushInstall: frozenLockfileForInstall }
}
},
_terminal: new Terminal(terminalProvider)
});
const args: string[] = [];
manager.pushConfigurationArgs(
args,
{ pnpmFilterArgumentValues: [] } as unknown as IInstallManagerOptions,
{ getSubspaceConfigFolderPath: () => '/repo/common/config/rush' } as Subspace
{ pnpmFilterArgumentValues: [], allowShrinkwrapUpdates: false } as unknown as IInstallManagerOptions,
{
getSubspaceConfigFolderPath: () => '/repo/common/config/rush',
getPackageJsonInjectedDependenciesHash: () => (hasInjectedDependencies ? 'hash' : undefined)
} as unknown as Subspace
);
return { args, warnings: terminalProvider.getWarningOutput() };
}

it.each([
['10.27.0', true, ['--frozen-lockfile']],
['12.10.1', false, ['--frozen-lockfile']],
['12.10.1', true, ['--no-frozen-lockfile', '--prefer-frozen-lockfile']]
])(
'passes lockfile flags for install with PNPM %s and injected dependencies=%s',
(pnpmVersion: string, hasInjectedDependencies: boolean, expectedFlags: string[]) => {
const { args } = getConfigurationArgs(
pnpmVersion,
false,
{},
{ frozenLockfileForInstall: true, hasInjectedDependencies }
);

expect(args.filter((arg) => arg.includes('frozen-lockfile'))).toEqual(expectedFlags);
}
);

it.each(['8.14.0', '12.11.2'])(
'uses Rush defaults when settings are absent with PNPM %s',
(pnpmVersion: string) => {
Expand Down
Loading