Skip to content

Commit c7563bd

Browse files
authored
rush-resolver-cache-plugin: add pnpm 10 / lockfile v9 compatibility
1 parent f7eebd6 commit c7563bd

File tree

5 files changed

+147
-22
lines changed

5 files changed

+147
-22
lines changed

rush-plugins/rush-resolver-cache-plugin/src/afterInstallAsync.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
import { existsSync, readdirSync } from 'node:fs';
5+
46
import type {
57
RushSession,
68
RushConfiguration,
@@ -79,7 +81,12 @@ export async function afterInstallAsync(
7981

8082
const lockFilePath: string = subspace.getCommittedShrinkwrapFilePath(variant);
8183

82-
const pnpmStoreDir: string = `${rushConfiguration.pnpmOptions.pnpmStorePath}/v3/files/`;
84+
const pnpmStorePath: string = rushConfiguration.pnpmOptions.pnpmStorePath;
85+
// pnpm 10 uses v10/index/ for index files; pnpm 8 uses v3/files/
86+
const pnpmStoreV10IndexDir: string = `${pnpmStorePath}/v10/index/`;
87+
const pnpmStoreV3FilesDir: string = `${pnpmStorePath}/v3/files/`;
88+
const useV10Store: boolean = existsSync(pnpmStoreV10IndexDir);
89+
const pnpmStoreDir: string = useV10Store ? pnpmStoreV10IndexDir : pnpmStoreV3FilesDir;
8390

8491
terminal.writeLine(`Using pnpm-lock from: ${lockFilePath}`);
8592
terminal.writeLine(`Using pnpm store folder: ${pnpmStoreDir}`);
@@ -167,9 +174,49 @@ export async function afterInstallAsync(
167174
const hash: string = Buffer.from(descriptionFileHash.slice(prefixIndex + 1), 'base64').toString('hex');
168175

169176
// The pnpm store directory has index files of package contents at paths:
170-
// <store>/v3/files/<hash (0-2)>/<hash (2-)>-index.json
177+
// pnpm 8: <store>/v3/files/<hash (0-2)>/<hash (2-)>-index.json
178+
// pnpm 10: <store>/v10/index/<hash (0-2)>/<hash (2-64)>-<name>@<version>.json
171179
// See https://github.com/pnpm/pnpm/blob/f394cfccda7bc519ceee8c33fc9b68a0f4235532/store/cafs/src/getFilePathInCafs.ts#L33
172-
const indexPath: string = `${pnpmStoreDir}${hash.slice(0, 2)}/${hash.slice(2)}-index.json`;
180+
let indexPath: string;
181+
if (useV10Store) {
182+
// pnpm 10 truncates integrity hashes to 32 bytes (64 hex chars) for index paths.
183+
const truncHash: string = hash.length > 64 ? hash.slice(0, 64) : hash;
184+
const hashDir: string = truncHash.slice(0, 2);
185+
const hashRest: string = truncHash.slice(2);
186+
// Build the bare name@version using context.name and version from the .pnpm folder path.
187+
// The .pnpm folder name format is: <name+ver>_<peer1>_<peer2>/node_modules/<name>
188+
const pkgName: string = (context.name || '').replace(/\//g, '+');
189+
const pnpmSegment: string | undefined = context.descriptionFileRoot.split('/node_modules/.pnpm/')[1];
190+
const folderName: string = pnpmSegment ? pnpmSegment.split('/node_modules/')[0] : '';
191+
const namePrefix: string = `${pkgName}@`;
192+
const nameStart: number = folderName.indexOf(namePrefix);
193+
let nameVer: string = folderName;
194+
if (nameStart !== -1) {
195+
const afterName: string = folderName.slice(nameStart + namePrefix.length);
196+
// Version ends at first _ followed by a letter/@ (peer dep separator)
197+
const peerSep: number = afterName.search(/_[a-zA-Z@]/);
198+
const version: string = peerSep !== -1 ? afterName.slice(0, peerSep) : afterName;
199+
nameVer = `${pkgName}@${version}`;
200+
}
201+
indexPath = `${pnpmStoreDir}${hashDir}/${hashRest}-${nameVer}.json`;
202+
// For truncated/hashed folder names, nameVer from the folder path may be wrong.
203+
// Fallback: scan the directory for a file matching the hash prefix.
204+
if (!existsSync(indexPath)) {
205+
const dir: string = `${pnpmStoreDir}${hashDir}/`;
206+
const filePrefix: string = `${hashRest}-`;
207+
try {
208+
const files: string[] = readdirSync(dir);
209+
const match: string | undefined = files.find((f) => f.startsWith(filePrefix));
210+
if (match) {
211+
indexPath = dir + match;
212+
}
213+
} catch {
214+
// ignore
215+
}
216+
}
217+
} else {
218+
indexPath = `${pnpmStoreDir}${hash.slice(0, 2)}/${hash.slice(2)}-index.json`;
219+
}
173220

174221
try {
175222
const indexContent: string = await FileSystem.readFileAsync(indexPath);

rush-plugins/rush-resolver-cache-plugin/src/computeResolverCacheFromLockfileAsync.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ export async function computeResolverCacheFromLockfileAsync(
187187
name = key.slice(1, versionIndex);
188188
}
189189

190+
if (!name) {
191+
// Handle v9 lockfile keys: @scope/name@version or name@version
192+
const searchStart: number = key.startsWith('@') ? key.indexOf('/') + 1 : 0;
193+
const versionIndex: number = key.indexOf('@', searchStart);
194+
if (versionIndex !== -1) {
195+
name = key.slice(0, versionIndex);
196+
}
197+
}
198+
190199
if (!name) {
191200
throw new Error(`Missing name for ${key}`);
192201
}

rush-plugins/rush-resolver-cache-plugin/src/helpers.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ export function resolveDependencyKey(
7979
} else if (specifier.startsWith('file:')) {
8080
return getDescriptionFileRootFromKey(lockfileFolder, specifier, key);
8181
} else {
82+
// In v9 lockfiles, aliased dependency values use the full package key format
83+
// (e.g., 'string-width@4.2.3' or '@types/events@3.0.0') instead of bare versions.
84+
// A bare version starts with a digit; a full key starts with a letter or @.
85+
if (/^[a-zA-Z@]/.test(specifier)) {
86+
return getDescriptionFileRootFromKey(lockfileFolder, specifier);
87+
}
8288
return getDescriptionFileRootFromKey(lockfileFolder, `/${key}@${specifier}`);
8389
}
8490
}
@@ -92,7 +98,17 @@ export function resolveDependencyKey(
9298
*/
9399
export function getDescriptionFileRootFromKey(lockfileFolder: string, key: string, name?: string): string {
94100
if (!key.startsWith('file:')) {
95-
name = key.slice(1, key.indexOf('@', 2));
101+
if (key.startsWith('/')) {
102+
// v6 format: /name@version or /@scope/name@version
103+
name = key.slice(1, key.indexOf('@', 2));
104+
} else if (!name) {
105+
// v9 format: name@version or @scope/name@version
106+
const searchStart: number = key.startsWith('@') ? key.indexOf('/') + 1 : 0;
107+
const versionIndex: number = key.indexOf('@', searchStart);
108+
if (versionIndex !== -1) {
109+
name = key.slice(0, versionIndex);
110+
}
111+
}
96112
}
97113
if (!name) {
98114
throw new Error(`Missing package name for ${key}`);

rush-plugins/rush-resolver-cache-plugin/src/test/__snapshots__/helpers.test.ts.snap

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,56 @@ exports[`createBase32Hash hashes: a 1`] = `"btaxlooa6g3kqmodthrgs5zgme"`;
66

77
exports[`createBase32Hash hashes: abracadabra 1`] = `"5rjiprc7bzyoyiwvf2f4x3vwia"`;
88

9-
exports[`depPathToFilename formats: /@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)(@fluentui/merge-styles@8.6.2)(@fluentui/react@8.117.5)(@fluentui/theme@2.6.45)(@fluentui/utilities@8.15.2)(chart.js@2.9.4)(lodash@4.17.21)(moment@2.29.4)(prop-types@15.8.1)(react-dnd-html5-backend@14.1.0)(react-dnd@14.0.5)(react-dom@17.0.1)(react-intersection-observer@8.34.0)(react@17.0.1) 1`] = `"@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0_@fluentui+merge-style_yt7yh6tpppbzu7nx3lzx3f3ife"`;
9+
exports[`depPathToFilename formats v6 keys (leading /): /@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)(@fluentui/merge-styles@8.6.2)(@fluentui/react@8.117.5)(@fluentui/theme@2.6.45)(@fluentui/utilities@8.15.2)(chart.js@2.9.4)(lodash@4.17.21)(moment@2.29.4)(prop-types@15.8.1)(react-dnd-html5-backend@14.1.0)(react-dnd@14.0.5)(react-dom@17.0.1)(react-intersection-observer@8.34.0)(react@17.0.1) 1`] = `"@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0_@fluentui+merge-style_yt7yh6tpppbzu7nx3lzx3f3ife"`;
1010

11-
exports[`depPathToFilename formats: /@storybook/core@6.5.15(@storybook/builder-webpack5@6.5.15)(@storybook/manager-webpack5@6.5.15)(eslint@8.57.0)(react-dom@17.0.1)(react@17.0.1)(typescript@5.3.3)(webpack@5.88.1) 1`] = `"@storybook+core@6.5.15_@storybook+builder-webpack5@6.5.15_@storybook+manager-webpack5@6.5.15__wnqw6tyxzeiksxiymflshxscdq"`;
11+
exports[`depPathToFilename formats v6 keys (leading /): /@storybook/core@6.5.15(@storybook/builder-webpack5@6.5.15)(@storybook/manager-webpack5@6.5.15)(eslint@8.57.0)(react-dom@17.0.1)(react@17.0.1)(typescript@5.3.3)(webpack@5.88.1) 1`] = `"@storybook+core@6.5.15_@storybook+builder-webpack5@6.5.15_@storybook+manager-webpack5@6.5.15__wnqw6tyxzeiksxiymflshxscdq"`;
1212

13-
exports[`depPathToFilename formats: /@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2) 1`] = `"@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2"`;
13+
exports[`depPathToFilename formats v6 keys (leading /): /@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2) 1`] = `"@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2"`;
1414

15-
exports[`depPathToFilename formats: /autoprefixer@9.8.8 1`] = `"autoprefixer@9.8.8"`;
15+
exports[`depPathToFilename formats v6 keys (leading /): /autoprefixer@9.8.8 1`] = `"autoprefixer@9.8.8"`;
1616

17-
exports[`depPathToFilename formats: /autoprefixer@10.4.18(postcss@8.4.36) 1`] = `"autoprefixer@10.4.18_postcss@8.4.36"`;
17+
exports[`depPathToFilename formats v6 keys (leading /): /autoprefixer@10.4.18(postcss@8.4.36) 1`] = `"autoprefixer@10.4.18_postcss@8.4.36"`;
1818

19-
exports[`depPathToFilename formats: /react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2) 1`] = `"react-transition-group@4.4.5_react-dom@17.0.2_react@17.0.2"`;
19+
exports[`depPathToFilename formats v6 keys (leading /): /react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2) 1`] = `"react-transition-group@4.4.5_react-dom@17.0.2_react@17.0.2"`;
2020

21-
exports[`depPathToFilename formats: file:../../../libraries/ts-command-line(@types/node@18.17.15) 1`] = `"file+..+..+..+libraries+ts-command-line_@types+node@18.17.15"`;
21+
exports[`depPathToFilename formats v6 keys (leading /): file:../../../libraries/ts-command-line(@types/node@18.17.15) 1`] = `"file+..+..+..+libraries+ts-command-line_@types+node@18.17.15"`;
2222

23-
exports[`depPathToFilename formats: file:../../../rigs/local-node-rig 1`] = `"file+..+..+..+rigs+local-node-rig"`;
23+
exports[`depPathToFilename formats v6 keys (leading /): file:../../../rigs/local-node-rig 1`] = `"file+..+..+..+rigs+local-node-rig"`;
2424

25-
exports[`getDescriptionFileRootFromKey parses: "/@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)(@fluentui/merge-styles@8.6.2)(@fluentui/react@8.117.5)(@fluentui/theme@2.6.45)(@fluentui/utilities@8.15.2)(chart.js@2.9.4)(lodash@4.17.21)(moment@2.29.4)(prop-types@15.8.1)(react-dnd-html5-backend@14.1.0)(react-dnd@14.0.5)(react-dom@17.0.1)(react-intersection-observer@8.34.0)(react@17.0.1)",undefined 1`] = `"/$/node_modules/.pnpm/@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0_@fluentui+merge-style_yt7yh6tpppbzu7nx3lzx3f3ife/node_modules/@some/package"`;
25+
exports[`depPathToFilename formats v9 keys (no leading /): @some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0) 1`] = `"@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0"`;
2626

27-
exports[`getDescriptionFileRootFromKey parses: "/@storybook/core@6.5.15(@storybook/builder-webpack5@6.5.15)(@storybook/manager-webpack5@6.5.15)(eslint@8.57.0)(react-dom@17.0.1)(react@17.0.1)(typescript@5.3.3)(webpack@5.88.1)",undefined 1`] = `"/$/node_modules/.pnpm/@storybook+core@6.5.15_@storybook+builder-webpack5@6.5.15_@storybook+manager-webpack5@6.5.15__wnqw6tyxzeiksxiymflshxscdq/node_modules/@storybook/core"`;
27+
exports[`depPathToFilename formats v9 keys (no leading /): @typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2) 1`] = `"@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2"`;
2828

29-
exports[`getDescriptionFileRootFromKey parses: "/@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2)",undefined 1`] = `"/$/node_modules/.pnpm/@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2/node_modules/@typescript-eslint/utils"`;
29+
exports[`depPathToFilename formats v9 keys (no leading /): autoprefixer@9.8.8 1`] = `"autoprefixer@9.8.8"`;
3030

31-
exports[`getDescriptionFileRootFromKey parses: "/autoprefixer@9.8.8",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@9.8.8/node_modules/autoprefixer"`;
31+
exports[`depPathToFilename formats v9 keys (no leading /): autoprefixer@10.4.18(postcss@8.4.36) 1`] = `"autoprefixer@10.4.18_postcss@8.4.36"`;
3232

33-
exports[`getDescriptionFileRootFromKey parses: "/autoprefixer@10.4.18(postcss@8.4.36)",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@10.4.18_postcss@8.4.36/node_modules/autoprefixer"`;
33+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)(@fluentui/merge-styles@8.6.2)(@fluentui/react@8.117.5)(@fluentui/theme@2.6.45)(@fluentui/utilities@8.15.2)(chart.js@2.9.4)(lodash@4.17.21)(moment@2.29.4)(prop-types@15.8.1)(react-dnd-html5-backend@14.1.0)(react-dnd@14.0.5)(react-dom@17.0.1)(react-intersection-observer@8.34.0)(react@17.0.1)",undefined 1`] = `"/$/node_modules/.pnpm/@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0_@fluentui+merge-style_yt7yh6tpppbzu7nx3lzx3f3ife/node_modules/@some/package"`;
3434

35-
exports[`getDescriptionFileRootFromKey parses: "/react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2)",undefined 1`] = `"/$/node_modules/.pnpm/react-transition-group@4.4.5_react-dom@17.0.2_react@17.0.2/node_modules/react-transition-group"`;
35+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/@storybook/core@6.5.15(@storybook/builder-webpack5@6.5.15)(@storybook/manager-webpack5@6.5.15)(eslint@8.57.0)(react-dom@17.0.1)(react@17.0.1)(typescript@5.3.3)(webpack@5.88.1)",undefined 1`] = `"/$/node_modules/.pnpm/@storybook+core@6.5.15_@storybook+builder-webpack5@6.5.15_@storybook+manager-webpack5@6.5.15__wnqw6tyxzeiksxiymflshxscdq/node_modules/@storybook/core"`;
3636

37-
exports[`getDescriptionFileRootFromKey parses: "file:../../../libraries/ts-command-line(@types/node@18.17.15)",@rushstack/ts-command-line 1`] = `"/$/node_modules/.pnpm/file+..+..+..+libraries+ts-command-line_@types+node@18.17.15/node_modules/@rushstack/ts-command-line"`;
37+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2)",undefined 1`] = `"/$/node_modules/.pnpm/@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2/node_modules/@typescript-eslint/utils"`;
3838

39-
exports[`getDescriptionFileRootFromKey parses: "file:../../../rigs/local-node-rig",local-node-rig 1`] = `"/$/node_modules/.pnpm/file+..+..+..+rigs+local-node-rig/node_modules/local-node-rig"`;
39+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/autoprefixer@9.8.8",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@9.8.8/node_modules/autoprefixer"`;
40+
41+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/autoprefixer@10.4.18(postcss@8.4.36)",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@10.4.18_postcss@8.4.36/node_modules/autoprefixer"`;
42+
43+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "/react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2)",undefined 1`] = `"/$/node_modules/.pnpm/react-transition-group@4.4.5_react-dom@17.0.2_react@17.0.2/node_modules/react-transition-group"`;
44+
45+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "file:../../../libraries/ts-command-line(@types/node@18.17.15)",@rushstack/ts-command-line 1`] = `"/$/node_modules/.pnpm/file+..+..+..+libraries+ts-command-line_@types+node@18.17.15/node_modules/@rushstack/ts-command-line"`;
46+
47+
exports[`getDescriptionFileRootFromKey parses v6 keys (leading /): "file:../../../rigs/local-node-rig",local-node-rig 1`] = `"/$/node_modules/.pnpm/file+..+..+..+rigs+local-node-rig/node_modules/local-node-rig"`;
48+
49+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)",undefined 1`] = `"/$/node_modules/.pnpm/@some+package@1.2.3_@azure+msal-browser@2.28.1_@azure+msal-common@6.4.0/node_modules/@some/package"`;
50+
51+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2)",undefined 1`] = `"/$/node_modules/.pnpm/@typescript-eslint+utils@6.19.1_eslint@7.7.0_typescript@5.4.2/node_modules/@typescript-eslint/utils"`;
52+
53+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "autoprefixer@9.8.8",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@9.8.8/node_modules/autoprefixer"`;
54+
55+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "autoprefixer@10.4.18(postcss@8.4.36)",undefined 1`] = `"/$/node_modules/.pnpm/autoprefixer@10.4.18_postcss@8.4.36/node_modules/autoprefixer"`;
56+
57+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "file:../../../libraries/ts-command-line(@types/node@18.17.15)",@rushstack/ts-command-line 1`] = `"/$/node_modules/.pnpm/file+..+..+..+libraries+ts-command-line_@types+node@18.17.15/node_modules/@rushstack/ts-command-line"`;
58+
59+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "file:../../../rigs/local-node-rig",local-node-rig 1`] = `"/$/node_modules/.pnpm/file+..+..+..+rigs+local-node-rig/node_modules/local-node-rig"`;
60+
61+
exports[`getDescriptionFileRootFromKey parses v9 keys (no leading /): "react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2)",undefined 1`] = `"/$/node_modules/.pnpm/react-transition-group@4.4.5_react-dom@17.0.2_react@17.0.2/node_modules/react-transition-group"`;

rush-plugins/rush-resolver-cache-plugin/src/test/helpers.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe(createBase32Hash.name, () => {
1212
});
1313

1414
describe(depPathToFilename.name, () => {
15-
it('formats', () => {
15+
it('formats v6 keys (leading /)', () => {
1616
for (const input of [
1717
'/autoprefixer@9.8.8',
1818
'/autoprefixer@10.4.18(postcss@8.4.36)',
@@ -26,10 +26,21 @@ describe(depPathToFilename.name, () => {
2626
expect(depPathToFilename(input)).toMatchSnapshot(input);
2727
}
2828
});
29+
30+
it('formats v9 keys (no leading /)', () => {
31+
for (const input of [
32+
'autoprefixer@9.8.8',
33+
'autoprefixer@10.4.18(postcss@8.4.36)',
34+
'@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)',
35+
'@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2)'
36+
]) {
37+
expect(depPathToFilename(input)).toMatchSnapshot(input);
38+
}
39+
});
2940
});
3041

3142
describe(getDescriptionFileRootFromKey.name, () => {
32-
it('parses', () => {
43+
it('parses v6 keys (leading /)', () => {
3344
const lockfileRoot: string = '/$';
3445
for (const { key, name } of [
3546
{ key: '/autoprefixer@9.8.8' },
@@ -51,4 +62,24 @@ describe(getDescriptionFileRootFromKey.name, () => {
5162
expect(getDescriptionFileRootFromKey(lockfileRoot, key, name)).toMatchSnapshot(`"${key}",${name}`);
5263
}
5364
});
65+
66+
it('parses v9 keys (no leading /)', () => {
67+
const lockfileRoot: string = '/$';
68+
for (const { key, name } of [
69+
{ key: 'autoprefixer@9.8.8' },
70+
{ key: 'autoprefixer@10.4.18(postcss@8.4.36)' },
71+
{ key: 'react-transition-group@4.4.5(react-dom@17.0.2)(react@17.0.2)' },
72+
{
73+
key: '@some/package@1.2.3(@azure/msal-browser@2.28.1)(@azure/msal-common@6.4.0)'
74+
},
75+
{ key: '@typescript-eslint/utils@6.19.1(eslint@7.7.0)(typescript@5.4.2)' },
76+
{ key: 'file:../../../rigs/local-node-rig', name: 'local-node-rig' },
77+
{
78+
key: 'file:../../../libraries/ts-command-line(@types/node@18.17.15)',
79+
name: '@rushstack/ts-command-line'
80+
}
81+
]) {
82+
expect(getDescriptionFileRootFromKey(lockfileRoot, key, name)).toMatchSnapshot(`"${key}",${name}`);
83+
}
84+
});
5485
});

0 commit comments

Comments
 (0)