Skip to content

Commit 745aec6

Browse files
authored
Fix an erroneous 'The shrinkwrap file has not been updated to support workspaces...' error. (#5515)
1 parent 178acfd commit 745aec6

15 files changed

Lines changed: 240 additions & 91 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Fix an issue where `rush update` will error complaining that the shrinkwrap file hasn't been updated to support workspaces in a subspace with no projects.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ export class ProjectChangeAnalyzer {
167167
}
168168

169169
if (rushConfiguration.isPnpm) {
170-
const currentShrinkwrap: PnpmShrinkwrapFile | undefined =
171-
PnpmShrinkwrapFile.loadFromFile(fullShrinkwrapPath);
170+
const subspaceHasNoProjects: boolean = subspace.getProjects().length === 0;
171+
const currentShrinkwrap: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile(
172+
fullShrinkwrapPath,
173+
{ subspaceHasNoProjects }
174+
);
172175

173176
if (!currentShrinkwrap) {
174177
throw new Error(`Unable to obtain current shrinkwrap file.`);
@@ -179,7 +182,9 @@ export class ProjectChangeAnalyzer {
179182
blobSpec: `${mergeCommit}:${relativeShrinkwrapFilePath}`,
180183
repositoryRoot: repoRoot
181184
});
182-
const oldShrinkWrap: PnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromString(oldShrinkwrapText);
185+
const oldShrinkWrap: PnpmShrinkwrapFile = PnpmShrinkwrapFile.loadFromString(oldShrinkwrapText, {
186+
subspaceHasNoProjects
187+
});
183188

184189
for (const project of subspaceProjects) {
185190
if (

libraries/rush-lib/src/logic/RepoStateFile.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ export class RepoStateFile {
181181
rushConfiguration.pnpmOptions.preventManualShrinkwrapChanges;
182182
if (preventShrinkwrapChanges) {
183183
const pnpmShrinkwrapFile: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile(
184-
subspace.getCommittedShrinkwrapFilePath(variant)
184+
subspace.getCommittedShrinkwrapFilePath(variant),
185+
{ subspaceHasNoProjects: subspace.getProjects().length === 0 }
185186
);
186187

187188
if (pnpmShrinkwrapFile) {

libraries/rush-lib/src/logic/ShrinkwrapFileFactory.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,41 @@ import { NpmShrinkwrapFile } from './npm/NpmShrinkwrapFile';
77
import { PnpmShrinkwrapFile } from './pnpm/PnpmShrinkwrapFile';
88
import { YarnShrinkwrapFile } from './yarn/YarnShrinkwrapFile';
99

10+
export interface IShrinkwrapFileFactoryOptions {
11+
packageManager: PackageManagerName;
12+
subspaceHasNoProjects: boolean;
13+
}
14+
15+
export interface IGetShrinkwrapFileOptions extends IShrinkwrapFileFactoryOptions {
16+
shrinkwrapFilePath: string;
17+
}
18+
19+
export interface IParseShrinkwrapFileOptions extends IShrinkwrapFileFactoryOptions {
20+
shrinkwrapContent: string;
21+
}
22+
1023
export class ShrinkwrapFileFactory {
11-
public static getShrinkwrapFile(
12-
packageManager: PackageManagerName,
13-
shrinkwrapFilename: string
14-
): BaseShrinkwrapFile | undefined {
24+
public static getShrinkwrapFile(options: IGetShrinkwrapFileOptions): BaseShrinkwrapFile | undefined {
25+
const { packageManager, shrinkwrapFilePath, subspaceHasNoProjects } = options;
1526
switch (packageManager) {
1627
case 'npm':
17-
return NpmShrinkwrapFile.loadFromFile(shrinkwrapFilename);
28+
return NpmShrinkwrapFile.loadFromFile(shrinkwrapFilePath);
1829
case 'pnpm':
19-
return PnpmShrinkwrapFile.loadFromFile(shrinkwrapFilename);
30+
return PnpmShrinkwrapFile.loadFromFile(shrinkwrapFilePath, { subspaceHasNoProjects });
2031
case 'yarn':
21-
return YarnShrinkwrapFile.loadFromFile(shrinkwrapFilename);
32+
return YarnShrinkwrapFile.loadFromFile(shrinkwrapFilePath);
2233
default:
2334
throw new Error(`Invalid package manager: ${packageManager}`);
2435
}
2536
}
2637

27-
public static parseShrinkwrapFile(
28-
packageManager: PackageManagerName,
29-
shrinkwrapContent: string
30-
): BaseShrinkwrapFile | undefined {
38+
public static parseShrinkwrapFile(options: IParseShrinkwrapFileOptions): BaseShrinkwrapFile | undefined {
39+
const { packageManager, shrinkwrapContent, subspaceHasNoProjects } = options;
3140
switch (packageManager) {
3241
case 'npm':
3342
return NpmShrinkwrapFile.loadFromString(shrinkwrapContent);
3443
case 'pnpm':
35-
return PnpmShrinkwrapFile.loadFromString(shrinkwrapContent);
44+
return PnpmShrinkwrapFile.loadFromString(shrinkwrapContent, { subspaceHasNoProjects });
3645
case 'yarn':
3746
return YarnShrinkwrapFile.loadFromString(shrinkwrapContent);
3847
default:

libraries/rush-lib/src/logic/base/BaseInstallManager.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,15 @@ export abstract class BaseInstallManager {
259259
]);
260260

261261
if (this.options.allowShrinkwrapUpdates && !shrinkwrapIsUpToDate) {
262-
const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilePath(variant);
263-
const shrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile(
264-
this.rushConfiguration.packageManager,
265-
committedShrinkwrapFileName
266-
);
262+
const shrinkwrapFilePath: string = subspace.getCommittedShrinkwrapFilePath(variant);
263+
const shrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile({
264+
packageManager: this.rushConfiguration.packageManager,
265+
shrinkwrapFilePath,
266+
subspaceHasNoProjects: subspace.getProjects().length === 0
267+
});
267268
shrinkwrapFile?.validateShrinkwrapAfterUpdate(this.rushConfiguration, subspace, this._terminal);
268269
// Copy (or delete) common\temp\pnpm-lock.yaml --> common\config\rush\pnpm-lock.yaml
269-
Utilities.syncFile(subspace.getTempShrinkwrapFilename(), committedShrinkwrapFileName);
270+
Utilities.syncFile(subspace.getTempShrinkwrapFilename(), shrinkwrapFilePath);
270271
} else {
271272
// TODO: Validate whether the package manager updated it in a nontrivial way
272273
}
@@ -470,12 +471,13 @@ export abstract class BaseInstallManager {
470471

471472
// (If it's a full update, then we ignore the shrinkwrap from Git since it will be overwritten)
472473
if (!this.options.fullUpgrade) {
473-
const committedShrinkwrapFileName: string = subspace.getCommittedShrinkwrapFilePath(variant);
474+
const shrinkwrapFilePath: string = subspace.getCommittedShrinkwrapFilePath(variant);
474475
try {
475-
shrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile(
476-
this.rushConfiguration.packageManager,
477-
committedShrinkwrapFileName
478-
);
476+
shrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile({
477+
packageManager: this.rushConfiguration.packageManager,
478+
shrinkwrapFilePath,
479+
subspaceHasNoProjects: subspace.getProjects().length === 0
480+
});
479481
} catch (ex) {
480482
terminal.writeLine();
481483
terminal.writeLine(

libraries/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -690,10 +690,11 @@ export class WorkspaceInstallManager extends BaseInstallManager {
690690
// more up-to-date than the checked-in shrinkwrap since filtered installs are not written back.
691691
// Note that if there are no projects, or if we're in PNPM workspace mode and there are no
692692
// projects with dependencies, a lockfile won't be generated.
693-
const tempShrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile(
694-
this.rushConfiguration.packageManager,
695-
subspace.getTempShrinkwrapFilename()
696-
);
693+
const tempShrinkwrapFile: BaseShrinkwrapFile | undefined = ShrinkwrapFileFactory.getShrinkwrapFile({
694+
packageManager: this.rushConfiguration.packageManager,
695+
shrinkwrapFilePath: subspace.getTempShrinkwrapFilename(),
696+
subspaceHasNoProjects: subspace.getProjects().length === 0
697+
});
697698

698699
if (tempShrinkwrapFile) {
699700
// Write or delete all project shrinkwraps related to the install

libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
type IPnpmVersionSpecifier,
2828
normalizePnpmVersionSpecifier
2929
} from './PnpmShrinkwrapFile';
30+
import type { Subspace } from '../../api/Subspace';
3031

3132
// special flag for debugging, will print extra diagnostic information,
3233
// but comes with performance cost
@@ -59,10 +60,12 @@ export class PnpmLinkManager extends BaseLinkManager {
5960

6061
protected async _linkProjectsAsync(): Promise<void> {
6162
if (this._rushConfiguration.projects.length > 0) {
63+
const subspace: Subspace = this._rushConfiguration.defaultSubspace;
6264
// Use shrinkwrap from temp as the committed shrinkwrap may not always be up to date
6365
// See https://github.com/microsoft/rushstack/issues/1273#issuecomment-492779995
6466
const pnpmShrinkwrapFile: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile(
65-
this._rushConfiguration.defaultSubspace.getTempShrinkwrapFilename()
67+
subspace.getTempShrinkwrapFilename(),
68+
{ subspaceHasNoProjects: subspace.getProjects().length === 0 }
6669
);
6770

6871
if (!pnpmShrinkwrapFile) {
@@ -323,7 +326,9 @@ export class PnpmLinkManager extends BaseLinkManager {
323326
RushConstants.nodeModulesFolderName
324327
);
325328
} else if (this._pnpmVersion.major >= 10) {
326-
const pnpmKitV10: typeof import('@rushstack/rush-pnpm-kit-v10') = await import('@rushstack/rush-pnpm-kit-v10');
329+
const pnpmKitV10: typeof import('@rushstack/rush-pnpm-kit-v10') = await import(
330+
'@rushstack/rush-pnpm-kit-v10'
331+
);
327332

328333
// project@file+projects+presentation-integration-tests.tgz_jsdom@11.12.0
329334
// The second parameter is max length of virtual store dir,
@@ -341,7 +346,9 @@ export class PnpmLinkManager extends BaseLinkManager {
341346
RushConstants.nodeModulesFolderName
342347
);
343348
} else if (this._pnpmVersion.major >= 9) {
344-
const pnpmKitV9: typeof import('@rushstack/rush-pnpm-kit-v9') = await import('@rushstack/rush-pnpm-kit-v9');
349+
const pnpmKitV9: typeof import('@rushstack/rush-pnpm-kit-v9') = await import(
350+
'@rushstack/rush-pnpm-kit-v9'
351+
);
345352

346353
// project@file+projects+presentation-integration-tests.tgz_jsdom@11.12.0
347354
// The second parameter is max length of virtual store dir, for v9 default is 120 https://pnpm.io/9.x/npmrc#virtual-store-dir-max-length
@@ -355,7 +362,9 @@ export class PnpmLinkManager extends BaseLinkManager {
355362
RushConstants.nodeModulesFolderName
356363
);
357364
} else if (this._pnpmVersion.major >= 8) {
358-
const pnpmKitV8: typeof import('@rushstack/rush-pnpm-kit-v8') = await import('@rushstack/rush-pnpm-kit-v8');
365+
const pnpmKitV8: typeof import('@rushstack/rush-pnpm-kit-v8') = await import(
366+
'@rushstack/rush-pnpm-kit-v8'
367+
);
359368
// PNPM 8 changed the local path format again and the hashing algorithm, and
360369
// is now using the scoped '@pnpm/dependency-path' package
361370
// See https://github.com/pnpm/pnpm/releases/tag/v8.0.0

libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ export interface IPnpmShrinkwrapYaml extends Lockfile {
129129
registry?: string;
130130
}
131131

132-
export interface ILoadFromFileOptions {
132+
export interface ILoadFromStringOptions {
133+
subspaceHasNoProjects: boolean;
134+
}
135+
136+
export interface ILoadFromFileOptions extends ILoadFromStringOptions {
133137
withCaching?: boolean;
134138
}
135139

@@ -323,7 +327,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
323327
private readonly _integrities: Map<string, Map<string, string>>;
324328
private _pnpmfileConfiguration: PnpmfileConfiguration | undefined;
325329

326-
private constructor(shrinkwrapJson: IPnpmShrinkwrapYaml, hash: string) {
330+
private constructor(shrinkwrapJson: IPnpmShrinkwrapYaml, hash: string, subspaceHasNoProjects: boolean) {
327331
super();
328332
this.hash = hash;
329333
this._shrinkwrapJson = shrinkwrapJson;
@@ -351,11 +355,21 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
351355
this.overrides = new Map(Object.entries(shrinkwrapJson.overrides || {}));
352356
this.packageExtensionsChecksum = shrinkwrapJson.packageExtensionsChecksum;
353357

354-
// Lockfile v9 always has "." in importers filed.
355-
this.isWorkspaceCompatible =
356-
this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9
357-
? this.importers.size > 1
358-
: this.importers.size > 0;
358+
let isWorkspaceCompatible: boolean;
359+
const importerCount: number = this.importers.size;
360+
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) {
361+
// Lockfile v9 always has "." in importers filed.
362+
if (subspaceHasNoProjects) {
363+
// If there are no projects in this subspace, the "." importer will be the only importer
364+
isWorkspaceCompatible = importerCount === 1;
365+
} else {
366+
isWorkspaceCompatible = importerCount > 1;
367+
}
368+
} else {
369+
isWorkspaceCompatible = importerCount > 0;
370+
}
371+
372+
this.isWorkspaceCompatible = isWorkspaceCompatible;
359373

360374
this._integrities = new Map();
361375
}
@@ -387,11 +401,11 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
387401

388402
public static loadFromFile(
389403
shrinkwrapYamlFilePath: string,
390-
options: ILoadFromFileOptions = {}
404+
options: ILoadFromFileOptions
391405
): PnpmShrinkwrapFile | undefined {
392406
try {
393407
const shrinkwrapContent: string = FileSystem.readFile(shrinkwrapYamlFilePath);
394-
return PnpmShrinkwrapFile.loadFromString(shrinkwrapContent);
408+
return PnpmShrinkwrapFile.loadFromString(shrinkwrapContent, options);
395409
} catch (error) {
396410
if (FileSystem.isNotExistError(error as Error)) {
397411
return undefined; // file does not exist
@@ -400,13 +414,17 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
400414
}
401415
}
402416

403-
public static loadFromString(shrinkwrapContent: string): PnpmShrinkwrapFile {
417+
public static loadFromString(
418+
shrinkwrapContent: string,
419+
options: ILoadFromStringOptions
420+
): PnpmShrinkwrapFile {
404421
const hash: string = crypto.createHash('sha-256').update(shrinkwrapContent, 'utf8').digest('hex');
405422
const cached: PnpmShrinkwrapFile | undefined = cacheByLockfileHash.get(hash);
406423
if (cached) {
407424
return cached;
408425
}
409426

427+
const { subspaceHasNoProjects } = options;
410428
const shrinkwrapJson: IPnpmShrinkwrapYaml = yamlModule.load(shrinkwrapContent) as IPnpmShrinkwrapYaml;
411429
if ((shrinkwrapJson as LockfileFileV9).snapshots) {
412430
const lockfile: IPnpmShrinkwrapYaml | null = convertLockfileV9ToLockfileObject(
@@ -436,10 +454,11 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
436454
lockfile.dependencies[name] = PnpmShrinkwrapFile.getLockfileV9PackageId(name, versionSpecifier);
437455
}
438456
}
439-
return new PnpmShrinkwrapFile(lockfile, hash);
457+
458+
return new PnpmShrinkwrapFile(lockfile, hash, subspaceHasNoProjects);
440459
}
441460

442-
return new PnpmShrinkwrapFile(shrinkwrapJson, hash);
461+
return new PnpmShrinkwrapFile(shrinkwrapJson, hash, subspaceHasNoProjects);
443462
}
444463

445464
public getShrinkwrapHash(experimentsConfig?: IExperimentsJson): string {

0 commit comments

Comments
 (0)