Skip to content

Commit 3a6cedb

Browse files
authored
[rush] Fix an issue with validation of packageExtensionsChecksum. (microsoft#5395)
* Fix an issue with validation of packageExtensionsChecksum. * fixup! Fix an issue with validation of packageExtensionsChecksum.
1 parent 31e7377 commit 3a6cedb

9 files changed

Lines changed: 106 additions & 26 deletions

File tree

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 with validation of the `pnpm-lock.yaml` `packageExtensionsChecksum` field in pnpm v10.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

common/config/rush/nonbrowser-approved-packages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,10 @@
846846
"name": "npm-packlist",
847847
"allowedCategories": [ "libraries" ]
848848
},
849+
{
850+
"name": "object-hash",
851+
"allowedCategories": [ "libraries" ]
852+
},
849853
{
850854
"name": "open",
851855
"allowedCategories": [ "libraries" ]

common/config/subspaces/build-tests-subspace/pnpm-lock.yaml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
22
{
3-
"pnpmShrinkwrapHash": "9eaf55ba29de8d94e17af33463f3b6ce0480400b",
3+
"pnpmShrinkwrapHash": "f2c06df75a96061e31624e1a556e34660a569623",
44
"preferredVersionsHash": "550b4cee0bef4e97db6c6aad726df5149d20e7d9",
5-
"packageJsonInjectedDependenciesHash": "5f2a311100f4ad0bc6735377670f5d7cfd664127"
5+
"packageJsonInjectedDependenciesHash": "14f4881943e5d03a361f079944eb76e1501b3e18"
66
}

common/config/subspaces/default/pnpm-lock.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
22
{
3-
"pnpmShrinkwrapHash": "b14e05fb6e67142fd6a574fe462bee93cb828511",
3+
"pnpmShrinkwrapHash": "c643d2cc7e2c60ef034a6557fc89760140d42f66",
44
"preferredVersionsHash": "61cd419c533464b580f653eb5f5a7e27fe7055ca"
55
}

libraries/rush-lib/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"js-yaml": "~4.1.0",
5858
"npm-check": "~6.0.1",
5959
"npm-package-arg": "~6.1.0",
60+
"object-hash": "3.0.0",
6061
"pnpm-sync-lib": "0.3.2",
6162
"read-package-tree": "~5.1.5",
6263
"rxjs": "~6.6.7",
@@ -70,8 +71,6 @@
7071
"devDependencies": {
7172
"@pnpm/lockfile.types": "~1.0.3",
7273
"@pnpm/logger": "4.0.0",
73-
"eslint": "~9.25.1",
74-
"local-node-rig": "workspace:*",
7574
"@rushstack/heft-webpack5-plugin": "workspace:*",
7675
"@rushstack/heft": "workspace:*",
7776
"@rushstack/operation-graph": "workspace:*",
@@ -81,12 +80,15 @@
8180
"@types/inquirer": "7.3.1",
8281
"@types/js-yaml": "4.0.9",
8382
"@types/npm-package-arg": "6.1.0",
83+
"@types/object-hash": "~3.0.6",
8484
"@types/read-package-tree": "5.1.0",
8585
"@types/semver": "7.5.0",
8686
"@types/ssri": "~7.1.0",
8787
"@types/strict-uri-encode": "2.0.0",
8888
"@types/tar": "6.1.6",
8989
"@types/webpack-env": "1.18.8",
90+
"eslint": "~9.25.1",
91+
"local-node-rig": "workspace:*",
9092
"webpack": "~5.98.0"
9193
},
9294
"publishOnlyDependencies": {

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

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,41 @@ export class WorkspaceInstallManager extends BaseInstallManager {
399399
}
400400

401401
// Check if packageExtensionsChecksum matches globalPackageExtension's hash
402-
const packageExtensionsChecksum: string | undefined = this._getPackageExtensionChecksum(
403-
pnpmOptions.globalPackageExtensions
404-
);
402+
let packageExtensionsChecksum: string | undefined;
403+
let existingPackageExtensionsChecksum: string | undefined;
404+
if (shrinkwrapFile) {
405+
existingPackageExtensionsChecksum = shrinkwrapFile.packageExtensionsChecksum;
406+
let packageExtensionsChecksumAlgorithm: string | undefined;
407+
if (existingPackageExtensionsChecksum) {
408+
const dashIndex: number = existingPackageExtensionsChecksum.indexOf('-');
409+
if (dashIndex === -1) {
410+
packageExtensionsChecksumAlgorithm = existingPackageExtensionsChecksum.substring(0, dashIndex);
411+
}
412+
413+
if (packageExtensionsChecksumAlgorithm && packageExtensionsChecksumAlgorithm !== 'sha256') {
414+
this._terminal.writeErrorLine(
415+
`The existing packageExtensionsChecksum algorithm "${packageExtensionsChecksumAlgorithm}" is not supported. ` +
416+
`This may indicate that the shrinkwrap was created with a newer version of PNPM than Rush supports.`
417+
);
418+
throw new AlreadyReportedError();
419+
}
420+
}
421+
422+
const globalPackageExtensions: Record<string, unknown> | undefined =
423+
pnpmOptions.globalPackageExtensions;
424+
// https://github.com/pnpm/pnpm/blob/ba9409ffcef0c36dc1b167d770a023c87444822d/pkg-manager/core/src/install/index.ts#L331
425+
if (globalPackageExtensions && Object.keys(globalPackageExtensions).length !== 0) {
426+
if (packageExtensionsChecksumAlgorithm) {
427+
// In PNPM v10, the algorithm changed to SHA256 and the digest changed from hex to base64
428+
packageExtensionsChecksum = await createObjectChecksumAsync(globalPackageExtensions);
429+
} else {
430+
packageExtensionsChecksum = createObjectChecksumLegacy(globalPackageExtensions);
431+
}
432+
}
433+
}
434+
405435
const packageExtensionsChecksumAreEqual: boolean =
406-
packageExtensionsChecksum === shrinkwrapFile?.packageExtensionsChecksum;
436+
packageExtensionsChecksum === existingPackageExtensionsChecksum;
407437

408438
if (!packageExtensionsChecksumAreEqual) {
409439
shrinkwrapWarnings.push("The package extension hash doesn't match the current shrinkwrap.");
@@ -420,18 +450,6 @@ export class WorkspaceInstallManager extends BaseInstallManager {
420450
return { shrinkwrapIsUpToDate, shrinkwrapWarnings };
421451
}
422452

423-
private _getPackageExtensionChecksum(
424-
packageExtensions: Record<string, unknown> | undefined
425-
): string | undefined {
426-
// https://github.com/pnpm/pnpm/blob/ba9409ffcef0c36dc1b167d770a023c87444822d/pkg-manager/core/src/install/index.ts#L331
427-
const packageExtensionsChecksum: string | undefined =
428-
Object.keys(packageExtensions ?? {}).length === 0
429-
? undefined
430-
: createObjectChecksum(packageExtensions!);
431-
432-
return packageExtensionsChecksum;
433-
}
434-
435453
protected async canSkipInstallAsync(
436454
lastModifiedDate: Date,
437455
subspace: Subspace,
@@ -793,10 +811,36 @@ export class WorkspaceInstallManager extends BaseInstallManager {
793811

794812
/**
795813
* Source: https://github.com/pnpm/pnpm/blob/ba9409ffcef0c36dc1b167d770a023c87444822d/pkg-manager/core/src/install/index.ts#L821-L824
796-
* @param obj
797-
* @returns
798814
*/
799-
function createObjectChecksum(obj: Record<string, unknown>): string {
815+
function createObjectChecksumLegacy(obj: Record<string, unknown>): string {
800816
const s: string = JSON.stringify(Sort.sortKeys(obj));
801817
return createHash('md5').update(s).digest('hex');
802818
}
819+
820+
/**
821+
* Source: https://github.com/pnpm/pnpm/blob/bdbd31aa4fa6546d65b6eee50a79b51879340d40/crypto/object-hasher/src/index.ts#L8-L12
822+
*/
823+
const defaultOptions: import('object-hash').NormalOption = {
824+
respectType: false,
825+
algorithm: 'sha256',
826+
encoding: 'base64'
827+
};
828+
829+
/**
830+
* https://github.com/pnpm/pnpm/blob/bdbd31aa4fa6546d65b6eee50a79b51879340d40/crypto/object-hasher/src/index.ts#L21-L26
831+
*/
832+
const withSortingOptions: import('object-hash').NormalOption = {
833+
...defaultOptions,
834+
unorderedArrays: true,
835+
unorderedObjects: true,
836+
unorderedSets: true
837+
};
838+
839+
/**
840+
* Source: https://github.com/pnpm/pnpm/blob/bdbd31aa4fa6546d65b6eee50a79b51879340d40/crypto/object-hasher/src/index.ts#L45-L49
841+
*/
842+
async function createObjectChecksumAsync(obj: Record<string, unknown>): Promise<string> {
843+
const { default: hash } = await import('object-hash');
844+
const packageExtensionsChecksum: string = hash(obj, withSortingOptions);
845+
return `${defaultOptions.algorithm}-${packageExtensionsChecksum}`;
846+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
580580
*/
581581
private _parseDependencyPath(packagePath: string): string {
582582
let depPath: string = packagePath;
583-
if (this.shrinkwrapFileMajorVersion >= 6) {
583+
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) {
584584
depPath = this._convertLockfileV6DepPathToV5DepPath(packagePath);
585585
}
586586
const pkgInfo: ReturnType<typeof dependencyPathLockfilePreV9.parse> =
@@ -998,7 +998,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
998998

999999
const allDependencies: PackageJsonDependency[] = [...dependencyList, ...devDependencyList];
10001000

1001-
if (this.shrinkwrapFileMajorVersion < 6) {
1001+
if (this.shrinkwrapFileMajorVersion < ShrinkwrapFileMajorVersion.V6) {
10021002
// PNPM <= v7
10031003

10041004
// Then get the unique package names and map them to package versions.

0 commit comments

Comments
 (0)