diff --git a/apps/lockfile-explorer/package.json b/apps/lockfile-explorer/package.json index f71f9dd739c..00274dbf88b 100644 --- a/apps/lockfile-explorer/package.json +++ b/apps/lockfile-explorer/package.json @@ -66,7 +66,7 @@ "js-yaml": "~3.13.1", "open": "~8.4.0", "update-notifier": "~5.1.0", - "@pnpm/dependency-path": "~2.1.2", + "@pnpm/dependency-path-lockfile-pre-v9": "npm:@pnpm/dependency-path@~2.1.2", "semver": "~7.5.4", "@rushstack/rush-sdk": "workspace:*", "@rushstack/ts-command-line": "workspace:*" diff --git a/apps/lockfile-explorer/src/utils/shrinkwrap.ts b/apps/lockfile-explorer/src/utils/shrinkwrap.ts index 3d96e44b4e2..8cb289fc1e6 100644 --- a/apps/lockfile-explorer/src/utils/shrinkwrap.ts +++ b/apps/lockfile-explorer/src/utils/shrinkwrap.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import * as dp from '@pnpm/dependency-path'; +import * as dependencyPathLockfilePreV9 from '@pnpm/dependency-path-lockfile-pre-v9'; interface IPackageInfo { name: string; @@ -12,7 +12,7 @@ interface IPackageInfo { export function convertLockfileV6DepPathToV5DepPath(newDepPath: string): string { if (!newDepPath.includes('@', 2) || newDepPath.startsWith('file:')) return newDepPath; const index = newDepPath.indexOf('@', newDepPath.indexOf('/@') + 2); - if (newDepPath.includes('(') && index > dp.indexOfPeersSuffix(newDepPath)) return newDepPath; + if (newDepPath.includes('(') && index > dependencyPathLockfilePreV9.indexOfPeersSuffix(newDepPath)) return newDepPath; return `${newDepPath.substring(0, index)}/${newDepPath.substring(index + 1)}`; } @@ -21,7 +21,7 @@ export function parseDependencyPath(shrinkwrapFileMajorVersion: number, newDepPa if (shrinkwrapFileMajorVersion === 6) { dependencyPath = convertLockfileV6DepPathToV5DepPath(newDepPath); } - const packageInfo = dp.parse(dependencyPath); + const packageInfo = dependencyPathLockfilePreV9.parse(dependencyPath); return { name: packageInfo.name as string, peersSuffix: packageInfo.peersSuffix, diff --git a/common/changes/@microsoft/rush/feature-support_pnpmv9_2024-11-19-08-24.json b/common/changes/@microsoft/rush/feature-support_pnpmv9_2024-11-19-08-24.json new file mode 100644 index 00000000000..d60cf92c6c7 --- /dev/null +++ b/common/changes/@microsoft/rush/feature-support_pnpmv9_2024-11-19-08-24.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Support pnpm lockfile v9, which is used by default starting in pnpm v9.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/lockfile-explorer/feature-support_pnpmv9_2024-11-25-14-05.json b/common/changes/@rushstack/lockfile-explorer/feature-support_pnpmv9_2024-11-25-14-05.json new file mode 100644 index 00000000000..22d17643a68 --- /dev/null +++ b/common/changes/@rushstack/lockfile-explorer/feature-support_pnpmv9_2024-11-25-14-05.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lockfile-explorer", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/lockfile-explorer" +} \ No newline at end of file diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index 8935ea64348..e76901fd1f9 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -102,6 +102,10 @@ "name": "@pnpm/logger", "allowedCategories": [ "libraries" ] }, + { + "name": "@pnpm/lockfile.types", + "allowedCategories": [ "libraries" ] + }, { "name": "@redis/client", "allowedCategories": [ "libraries" ] diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index a59dd52673c..f6148aaaf44 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -882,6 +882,17 @@ packages: dependencies: rfc4648: 1.5.3 + /@pnpm/crypto.base32-hash@3.0.1: + resolution: {integrity: sha512-DM4RR/tvB7tMb2FekL0Q97A5PCXNyEC+6ht8SaufAUFSJNxeozqHw9PHTZR03mzjziPzNQLOld0pNINBX3srtw==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.polyfill': 1.0.0 + rfc4648: 1.5.3 + + /@pnpm/crypto.polyfill@1.0.0: + resolution: {integrity: sha512-WbmsqqcUXKKaAF77ox1TQbpZiaQcr26myuMUu+WjUtoWYgD3VP6iKYEvSx35SZ6G2L316lu+pv+40A2GbWJc1w==} + engines: {node: '>=18.12'} + /@pnpm/dependency-path@2.1.8: resolution: {integrity: sha512-ywBaTjy0iSEF7lH3DlF8UXrdL2bw4AQFV2tTOeNeY7wc1W5CE+RHSJhf9MXBYcZPesqGRrPiU7Pimj3l05L9VA==} engines: {node: '>=16.14'} @@ -889,7 +900,15 @@ packages: '@pnpm/crypto.base32-hash': 2.0.0 '@pnpm/types': 9.4.2 encode-registry: 3.0.1 - semver: 7.5.4 + semver: 7.6.3 + + /@pnpm/dependency-path@5.1.7: + resolution: {integrity: sha512-MKCyaTy1r9fhBXAnhDZNBVgo6ThPnicwJEG203FDp7pGhD7NruS/FhBI+uMd7GNsK3D7aIFCDAgbWpNTXn/eWw==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.base32-hash': 3.0.1 + '@pnpm/types': 12.2.0 + semver: 7.6.3 /@pnpm/error@1.4.0: resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==} @@ -913,6 +932,14 @@ packages: p-settle: 4.1.1 ramda: 0.27.2 + /@pnpm/lockfile.types@1.0.3: + resolution: {integrity: sha512-A7vUWktnhDkrIs+WmXm7AdffJVyVYJpQUEouya/DYhB+Y+tQ3BXjZ6CV0KybqLgI/8AZErgCJqFxA0GJH6QDjA==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/patching.types': 1.0.0 + '@pnpm/types': 12.2.0 + dev: false + /@pnpm/package-bins@4.1.0: resolution: {integrity: sha512-57/ioGYLBbVRR80Ux9/q2i3y8Q+uQADc3c+Yse8jr/60YLOi3jcWz13e2Jy+ANYtZI258Qc5wk2X077rp0Ly/Q==} engines: {node: '>=10.16'} @@ -921,6 +948,11 @@ packages: fast-glob: 3.3.2 is-subdir: 1.2.0 + /@pnpm/patching.types@1.0.0: + resolution: {integrity: sha512-juCdQCC1USqLcOhVPl1tYReoTO9YH4fTullMnFXXcmpsDM7Dkn3tzuOQKC3oPoJ2ozv+0EeWWMtMGqn2+IM3pQ==} + engines: {node: '>=18.12'} + dev: false + /@pnpm/read-modules-dir@2.0.3: resolution: {integrity: sha512-i9OgRvSlxrTS9a2oXokhDxvQzDtfqtsooJ9jaGoHkznue5aFCTSrNZFQ6M18o8hC03QWfnxaKi0BtOvNkKu2+A==} engines: {node: '>=10.13'} @@ -953,6 +985,10 @@ packages: sort-keys: 4.2.0 strip-bom: 4.0.0 + /@pnpm/types@12.2.0: + resolution: {integrity: sha512-5RtwWhX39j89/Tmyv2QSlpiNjErA357T/8r1Dkg+2lD3P7RuS7Xi2tChvmOC3VlezEFNcWnEGCOeKoGRkDuqFA==} + engines: {node: '>=18.12'} + /@pnpm/types@6.4.0: resolution: {integrity: sha512-nco4+4sZqNHn60Y4VE/fbtlShCBqipyUO+nKRPvDHqLrecMW9pzHWMVRxk4nrMRoeowj3q0rX3GYRBa8lsHTAg==} engines: {node: '>=10.16'} @@ -2306,7 +2342,7 @@ packages: require-package-name: 2.0.1 resolve: 1.22.8 resolve-from: 5.0.0 - semver: 7.5.4 + semver: 7.6.3 yargs: 16.2.0 transitivePeerDependencies: - supports-color @@ -4812,7 +4848,7 @@ packages: got: 11.8.6 registry-auth-token: 4.2.2 registry-url: 5.1.0 - semver: 7.5.4 + semver: 7.6.3 /pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -5285,7 +5321,6 @@ packages: resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - dev: true /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} @@ -5835,7 +5870,7 @@ packages: is-yarn-global: 0.3.0 latest-version: 5.1.0 pupa: 2.1.1 - semver: 7.5.4 + semver: 7.6.3 semver-diff: 3.1.1 xdg-basedir: 4.0.0 @@ -6469,7 +6504,8 @@ packages: name: '@microsoft/rush-lib' engines: {node: '>=5.6.0'} dependencies: - '@pnpm/dependency-path': 2.1.8 + '@pnpm/dependency-path': 5.1.7 + '@pnpm/dependency-path-lockfile-pre-v9': /@pnpm/dependency-path@2.1.8 '@pnpm/link-bins': 5.3.25 '@rushstack/heft-config-file': file:../../../libraries/heft-config-file(@types/node@18.17.15) '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@18.17.15) @@ -6515,6 +6551,7 @@ packages: id: file:../../../libraries/rush-sdk name: '@rushstack/rush-sdk' dependencies: + '@pnpm/lockfile.types': 1.0.3 '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@18.17.15) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@18.17.15) '@rushstack/package-deps-hash': file:../../../libraries/package-deps-hash(@types/node@18.17.15) diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index eb79004ae00..df164086b0a 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "a3198248beff4dfe8ef9d9c798b873ead75ef5cd", + "pnpmShrinkwrapHash": "bd75fc59a5df40deec1cf3db51e99ab1a7eb35f6", "preferredVersionsHash": "ce857ea0536b894ec8f346aaea08cfd85a5af648", - "packageJsonInjectedDependenciesHash": "35920c267d4a49b8de4b78d88cf91c7f46b83ebb" + "packageJsonInjectedDependenciesHash": "3f1f7f2e64fc15d64eef6c0311adc38dff344509" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 76ff151fd2d..6ae5a6b130c 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -189,9 +189,9 @@ importers: '@microsoft/rush-lib': specifier: workspace:* version: link:../../libraries/rush-lib - '@pnpm/dependency-path': - specifier: ~2.1.2 - version: 2.1.8 + '@pnpm/dependency-path-lockfile-pre-v9': + specifier: npm:@pnpm/dependency-path@~2.1.2 + version: /@pnpm/dependency-path@2.1.8 '@rushstack/node-core-library': specifier: workspace:* version: link:../../libraries/node-core-library @@ -3333,8 +3333,11 @@ importers: ../../../libraries/rush-lib: dependencies: '@pnpm/dependency-path': - specifier: ~2.1.2 - version: 2.1.8 + specifier: ~5.1.7 + version: 5.1.7 + '@pnpm/dependency-path-lockfile-pre-v9': + specifier: npm:@pnpm/dependency-path@~2.1.2 + version: /@pnpm/dependency-path@2.1.8 '@pnpm/link-bins': specifier: ~5.3.7 version: 5.3.25 @@ -3438,6 +3441,9 @@ importers: specifier: ~8.3.2 version: 8.3.2 devDependencies: + '@pnpm/lockfile.types': + specifier: ~1.0.3 + version: 1.0.3 '@pnpm/logger': specifier: 4.0.0 version: 4.0.0 @@ -3498,6 +3504,9 @@ importers: ../../../libraries/rush-sdk: dependencies: + '@pnpm/lockfile.types': + specifier: ~1.0.3 + version: 1.0.3 '@rushstack/lookup-by-path': specifier: workspace:* version: link:../lookup-by-path @@ -9760,6 +9769,19 @@ packages: rfc4648: 1.5.3 dev: false + /@pnpm/crypto.base32-hash@3.0.1: + resolution: {integrity: sha512-DM4RR/tvB7tMb2FekL0Q97A5PCXNyEC+6ht8SaufAUFSJNxeozqHw9PHTZR03mzjziPzNQLOld0pNINBX3srtw==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.polyfill': 1.0.0 + rfc4648: 1.5.3 + dev: false + + /@pnpm/crypto.polyfill@1.0.0: + resolution: {integrity: sha512-WbmsqqcUXKKaAF77ox1TQbpZiaQcr26myuMUu+WjUtoWYgD3VP6iKYEvSx35SZ6G2L316lu+pv+40A2GbWJc1w==} + engines: {node: '>=18.12'} + dev: false + /@pnpm/dependency-path@2.1.8: resolution: {integrity: sha512-ywBaTjy0iSEF7lH3DlF8UXrdL2bw4AQFV2tTOeNeY7wc1W5CE+RHSJhf9MXBYcZPesqGRrPiU7Pimj3l05L9VA==} engines: {node: '>=16.14'} @@ -9770,6 +9792,15 @@ packages: semver: 7.5.4 dev: false + /@pnpm/dependency-path@5.1.7: + resolution: {integrity: sha512-MKCyaTy1r9fhBXAnhDZNBVgo6ThPnicwJEG203FDp7pGhD7NruS/FhBI+uMd7GNsK3D7aIFCDAgbWpNTXn/eWw==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/crypto.base32-hash': 3.0.1 + '@pnpm/types': 12.2.0 + semver: 7.6.3 + dev: false + /@pnpm/error@1.4.0: resolution: {integrity: sha512-vxkRrkneBPVmP23kyjnYwVOtipwlSl6UfL+h+Xa3TrABJTz5rYBXemlTsU5BzST8U4pD7YDkTb3SQu+MMuIDKA==} engines: {node: '>=10.16'} @@ -9801,6 +9832,13 @@ packages: '@pnpm/types': 9.4.2 dev: true + /@pnpm/lockfile.types@1.0.3: + resolution: {integrity: sha512-A7vUWktnhDkrIs+WmXm7AdffJVyVYJpQUEouya/DYhB+Y+tQ3BXjZ6CV0KybqLgI/8AZErgCJqFxA0GJH6QDjA==} + engines: {node: '>=18.12'} + dependencies: + '@pnpm/patching.types': 1.0.0 + '@pnpm/types': 12.2.0 + /@pnpm/logger@4.0.0: resolution: {integrity: sha512-SIShw+k556e7S7tLZFVSIHjCdiVog1qWzcKW2RbLEHPItdisAFVNIe34kYd9fMSswTlSRLS/qRjw3ZblzWmJ9Q==} engines: {node: '>=12.17'} @@ -9818,6 +9856,10 @@ packages: is-subdir: 1.2.0 dev: false + /@pnpm/patching.types@1.0.0: + resolution: {integrity: sha512-juCdQCC1USqLcOhVPl1tYReoTO9YH4fTullMnFXXcmpsDM7Dkn3tzuOQKC3oPoJ2ozv+0EeWWMtMGqn2+IM3pQ==} + engines: {node: '>=18.12'} + /@pnpm/read-modules-dir@2.0.3: resolution: {integrity: sha512-i9OgRvSlxrTS9a2oXokhDxvQzDtfqtsooJ9jaGoHkznue5aFCTSrNZFQ6M18o8hC03QWfnxaKi0BtOvNkKu2+A==} engines: {node: '>=10.13'} @@ -9853,6 +9895,10 @@ packages: strip-bom: 4.0.0 dev: false + /@pnpm/types@12.2.0: + resolution: {integrity: sha512-5RtwWhX39j89/Tmyv2QSlpiNjErA357T/8r1Dkg+2lD3P7RuS7Xi2tChvmOC3VlezEFNcWnEGCOeKoGRkDuqFA==} + engines: {node: '>=18.12'} + /@pnpm/types@6.4.0: resolution: {integrity: sha512-nco4+4sZqNHn60Y4VE/fbtlShCBqipyUO+nKRPvDHqLrecMW9pzHWMVRxk4nrMRoeowj3q0rX3GYRBa8lsHTAg==} engines: {node: '>=10.16'} diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index 9c7351c3f3b..edf4f924d81 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "387d2507a3b0dee1dd0ca694ced243f0edaa0dc1", + "pnpmShrinkwrapHash": "7b913e5ca364b30654436bba1a36ea570496f25c", "preferredVersionsHash": "ce857ea0536b894ec8f346aaea08cfd85a5af648" } diff --git a/libraries/rush-lib/package.json b/libraries/rush-lib/package.json index 909e90bc62d..b5219932f21 100644 --- a/libraries/rush-lib/package.json +++ b/libraries/rush-lib/package.json @@ -29,7 +29,8 @@ }, "license": "MIT", "dependencies": { - "@pnpm/dependency-path": "~2.1.2", + "@pnpm/dependency-path-lockfile-pre-v9": "npm:@pnpm/dependency-path@~2.1.2", + "@pnpm/dependency-path": "~5.1.7", "@pnpm/link-bins": "~5.3.7", "@rushstack/heft-config-file": "workspace:*", "@rushstack/lookup-by-path": "workspace:*", @@ -66,6 +67,7 @@ "pnpm-sync-lib": "0.2.9" }, "devDependencies": { + "@pnpm/lockfile.types": "~1.0.3", "@pnpm/logger": "4.0.0", "local-node-rig": "workspace:*", "@rushstack/heft-webpack5-plugin": "workspace:*", diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 3b627490037..1afc6c0f2e6 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -229,7 +229,8 @@ export class PnpmLinkManager extends BaseLinkManager { const pathToLocalInstallation: string = await this._getPathToLocalInstallationAsync( tarballEntry, absolutePathToTgzFile, - folderNameSuffix + folderNameSuffix, + tempProjectDependencyKey ); const parentShrinkwrapEntry: IPnpmShrinkwrapDependencyYaml | undefined = @@ -289,7 +290,8 @@ export class PnpmLinkManager extends BaseLinkManager { private async _getPathToLocalInstallationAsync( tarballEntry: string, absolutePathToTgzFile: string, - folderSuffix: string + folderSuffix: string, + tempProjectDependencyKey: string ): Promise { if (this._pnpmVersion.major === 6) { // PNPM 6 changed formatting to replace all ':' and '/' chars with '+'. Additionally, folder names > 120 @@ -319,8 +321,22 @@ export class PnpmLinkManager extends BaseLinkManager { folderName, RushConstants.nodeModulesFolderName ); - } else if (this._pnpmVersion.major >= 8) { + } else if (this._pnpmVersion.major >= 9) { const { depPathToFilename } = await import('@pnpm/dependency-path'); + + // project@file+projects+presentation-integration-tests.tgz_jsdom@11.12.0 + // The second parameter is max length of virtual store dir, default is 120 https://pnpm.io/next/npmrc#virtual-store-dir-max-length + // TODO Read virtual-store-dir-max-length from .npmrc + const folderName: string = depPathToFilename(tempProjectDependencyKey, 120); + return path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.nodeModulesFolderName, + '.pnpm', + folderName, + RushConstants.nodeModulesFolderName + ); + } else if (this._pnpmVersion.major >= 8) { + const { depPathToFilename } = await import('@pnpm/dependency-path-lockfile-pre-v9'); // PNPM 8 changed the local path format again and the hashing algorithm, and // is now using the scoped '@pnpm/dependency-path' package // See https://github.com/pnpm/pnpm/releases/tag/v8.0.0 diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts new file mode 100644 index 00000000000..236afa90610 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkWrapFileConverters.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Fork https://github.com/pnpm/pnpm/blob/main/lockfile/fs/src/lockfileFormatConverters.ts + * + * Pnpm lockfile v9 have some breaking changes on the lockfile format. For Example, the "packages" field has been split into "packages" and "snapshots" two parts. + * Rush should not parse the lockfile by itself, but should rely on pnpm to parse the lockfile. + * To ensure consistency with pnpm's parsing logic, I copied the relevant logic from @pnpm/lockfile.fs to this file. + * + * There are some reasons for copying the relevant logic instead of depending on @pnpm/lockfile.fs directly: + * 1. @pnpm/lockfile.fs has a exports filed in package.json, which will cause convertLockfileV9ToLockfileObject cannot be imported directly. + * 2. @pnpm/lockfile.fs only provides asynchronous read methods, while rush requires synchronous reading of the lockfile file. + * Perhaps this file will be deleted in the future and instead depend on @pnpm/lockfile.fs directly. + */ +import { removeSuffix } from '@pnpm/dependency-path'; +import type { + InlineSpecifiersProjectSnapshot, + InlineSpecifiersResolvedDependencies, + Lockfile, + LockfileFile, + LockfileFileV9, + PackageSnapshots, + ProjectSnapshot, + ResolvedDependencies +} from '@pnpm/lockfile.types'; + +type DepPath = string & { __brand: 'DepPath' }; +// eslint-disable-next-line @typescript-eslint/typedef +const DEPENDENCIES_FIELDS = ['optionalDependencies', 'dependencies', 'devDependencies'] as const; + +function revertProjectSnapshot(from: InlineSpecifiersProjectSnapshot): ProjectSnapshot { + const specifiers: ResolvedDependencies = {}; + + function moveSpecifiers(fromDep: InlineSpecifiersResolvedDependencies): ResolvedDependencies { + const resolvedDependencies: ResolvedDependencies = {}; + for (const [depName, { specifier, version }] of Object.entries(fromDep)) { + const existingValue: string = specifiers[depName]; + if (existingValue != null && existingValue !== specifier) { + throw new Error( + `Project snapshot lists the same dependency more than once with conflicting versions: ${depName}` + ); + } + + specifiers[depName] = specifier; + resolvedDependencies[depName] = version; + } + return resolvedDependencies; + } + + const dependencies: ResolvedDependencies | undefined = + from.dependencies == null ? from.dependencies : moveSpecifiers(from.dependencies); + const devDependencies: ResolvedDependencies | undefined = + from.devDependencies == null ? from.devDependencies : moveSpecifiers(from.devDependencies); + const optionalDependencies: ResolvedDependencies | undefined = + from.optionalDependencies == null ? from.optionalDependencies : moveSpecifiers(from.optionalDependencies); + + return { + ...from, + specifiers, + dependencies, + devDependencies, + optionalDependencies + }; +} + +function convertFromLockfileFileMutable(lockfileFile: LockfileFile): LockfileFileV9 { + if (typeof lockfileFile?.importers === 'undefined') { + lockfileFile.importers = { + '.': { + dependenciesMeta: lockfileFile.dependenciesMeta, + publishDirectory: lockfileFile.publishDirectory + } + }; + for (const depType of DEPENDENCIES_FIELDS) { + if (lockfileFile[depType] != null) { + lockfileFile.importers['.'][depType] = lockfileFile[depType]; + delete lockfileFile[depType]; + } + } + } + return lockfileFile as LockfileFileV9; +} + +function mapValues(obj: Record, mapper: (val: T, key: string) => U): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = mapper(value, key); + } + return result; +} + +/** + * Convert lockfile v9 object to standard lockfile object. + * + * This function will mutate the lockfile object. It will: + * 1. Ensure importers['.'] exists. + * 2. Merge snapshots and packages into packages. + * 3. Extract specifier from importers['xxx'] into the specifiers field. + */ +export function convertLockfileV9ToLockfileObject(lockfile: LockfileFileV9): Lockfile { + const { importers, ...rest } = convertFromLockfileFileMutable(lockfile); + + const packages: PackageSnapshots = {}; + for (const [depPath, pkg] of Object.entries(lockfile.snapshots ?? {})) { + const pkgId: string = removeSuffix(depPath); + packages[depPath as DepPath] = Object.assign(pkg, lockfile.packages?.[pkgId]); + } + return { + ...rest, + packages, + importers: mapValues(importers ?? {}, revertProjectSnapshot) + }; +} diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts index 5801d1dc1cc..7812c840621 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts @@ -14,6 +14,7 @@ import { InternalError } from '@rushstack/node-core-library'; import { Colorize, type ITerminal } from '@rushstack/terminal'; +import * as dependencyPathLockfilePreV9 from '@pnpm/dependency-path-lockfile-pre-v9'; import * as dependencyPath from '@pnpm/dependency-path'; import { BaseShrinkwrapFile } from '../base/BaseShrinkwrapFile'; @@ -32,9 +33,23 @@ import { PnpmOptionsConfiguration } from './PnpmOptionsConfiguration'; import type { IPnpmfile, IPnpmfileContext } from './IPnpmfile'; import type { Subspace } from '../../api/Subspace'; import { CustomTipId, type CustomTipsConfiguration } from '../../api/CustomTipsConfiguration'; +import type { + ProjectId, + Lockfile, + PackageSnapshot, + ProjectSnapshot, + LockfileFileV9, + ResolvedDependencies +} from '@pnpm/lockfile.types'; +import { convertLockfileV9ToLockfileObject } from './PnpmShrinkWrapFileConverters'; const yamlModule: typeof import('js-yaml') = Import.lazy('js-yaml', require); +export enum ShrinkwrapFileMajorVersion { + V6 = 6, + V9 = 9 +} + export interface IPeerDependenciesMetaYaml { optional?: boolean; } @@ -47,11 +62,14 @@ export interface IPnpmV8VersionSpecifier { version: string; specifier: string; } -export type IPnpmVersionSpecifier = IPnpmV7VersionSpecifier | IPnpmV8VersionSpecifier; - -export interface IPnpmShrinkwrapDependencyYaml { - /** Information about the resolved package */ - resolution?: { +export type IPnpmV9VersionSpecifier = string; +export type IPnpmVersionSpecifier = + | IPnpmV7VersionSpecifier + | IPnpmV8VersionSpecifier + | IPnpmV9VersionSpecifier; + +export interface IPnpmShrinkwrapDependencyYaml extends Omit { + resolution: { /** The directory this package should clone, for injected dependencies */ directory?: string; /** The hash of the tarball, to ensure archive integrity */ @@ -59,103 +77,110 @@ export interface IPnpmShrinkwrapDependencyYaml { /** The name of the tarball, if this was from a TGZ file */ tarball?: string; }; - /** The list of bundled dependencies in this package */ - bundledDependencies?: ReadonlyArray; - /** The list of dependencies and the resolved version */ - dependencies?: Record; - /** The list of optional dependencies and the resolved version */ - optionalDependencies?: Record; - /** The list of peer dependencies and the resolved version */ - peerDependencies?: Record; - /** - * Used to indicate optional peer dependencies, as described in this RFC: - * https://github.com/yarnpkg/rfcs/blob/master/accepted/0000-optional-peer-dependencies.md - */ - peerDependenciesMeta?: Record; - /** The name of the package, if the package is a local tarball */ - name?: string; - /** If this is an optional dependency */ - optional?: boolean; - /** The values of process.platform supported by this package */ - os?: readonly string[]; - /** The values of process.arch supported by this package */ - cpu?: readonly string[]; - /** The libc runtimes supported by this package */ - libc?: readonly string[]; } -export interface IPnpmShrinkwrapImporterYaml { - /** The list of resolved version numbers for direct dependencies */ - dependencies?: Record; - /** The list of resolved version numbers for dev dependencies */ - devDependencies?: Record; - /** The list of resolved version numbers for optional dependencies */ - optionalDependencies?: Record; - /** The list of metadata for dependencies declared inside dependencies, optionalDependencies, and devDependencies. */ - dependenciesMeta?: Record; +export type IPnpmShrinkwrapImporterYaml = ProjectSnapshot; + +export interface IPnpmShrinkwrapYaml extends Lockfile { /** - * The list of specifiers used to resolve dependency versions - * - * @remarks - * This has been removed in PNPM v8 + * This interface represents the raw pnpm-lock.YAML file + * Example: + * { + * "dependencies": { + * "@rush-temp/project1": "file:./projects/project1.tgz" + * }, + * "packages": { + * "file:projects/library1.tgz": { + * "dependencies: { + * "markdown": "0.5.0" + * }, + * "name": "@rush-temp/library1", + * "resolution": { + * "tarball": "file:projects/library1.tgz" + * }, + * "version": "0.0.0" + * }, + * "markdown/0.5.0": { + * "resolution": { + * "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=" + * } + * } + * }, + * "registry": "http://localhost:4873/", + * "shrinkwrapVersion": 3, + * "specifiers": { + * "@rush-temp/project1": "file:./projects/project1.tgz" + * } + * } */ - specifiers?: Record; -} - -/** - * This interface represents the raw pnpm-lock.YAML file - * Example: - * { - * "dependencies": { - * "@rush-temp/project1": "file:./projects/project1.tgz" - * }, - * "packages": { - * "file:projects/library1.tgz": { - * "dependencies: { - * "markdown": "0.5.0" - * }, - * "name": "@rush-temp/library1", - * "resolution": { - * "tarball": "file:projects/library1.tgz" - * }, - * "version": "0.0.0" - * }, - * "markdown/0.5.0": { - * "resolution": { - * "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=" - * } - * } - * }, - * "registry": "http://localhost:4873/", - * "shrinkwrapVersion": 3, - * "specifiers": { - * "@rush-temp/project1": "file:./projects/project1.tgz" - * } - * } - */ -export interface IPnpmShrinkwrapYaml { - /** The version of the lockfile format */ - lockfileVersion?: string | number; /** The list of resolved version numbers for direct dependencies */ - dependencies: Record; - /** The list of importers for local workspace projects */ - importers: Record; - /** The description of the solved graph */ - packages: Record; - /** URL of the registry which was used */ - registry: string; + dependencies?: Record; /** The list of specifiers used to resolve direct dependency versions */ - specifiers: Record; - /** The list of override version number for dependencies */ - overrides?: { [dependency: string]: string }; - /** The checksum of package extensions fields for extending dependencies */ - packageExtensionsChecksum?: string; + specifiers?: Record; + /** URL of the registry which was used */ + registry?: string; } export interface ILoadFromFileOptions { withCaching?: boolean; } +export function parsePnpm9DependencyKey( + dependencyName: string, + versionSpecifier: IPnpmVersionSpecifier +): DependencySpecifier | undefined { + if (!versionSpecifier) { + return undefined; + } + + const dependencyKey: string = normalizePnpmVersionSpecifier(versionSpecifier); + + // Example: file:projects/project2 + // Example: project-2@file:projects/project2 + // Example: link:../projects/project1 + if (/(file|link):/.test(dependencyKey)) { + // If it starts with an NPM scheme such as "file:projects/my-app.tgz", we don't support that + return undefined; + } + + const { peersIndex } = dependencyPath.indexOfPeersSuffix(dependencyKey); + if (peersIndex !== -1) { + // Remove peer suffix + const key: string = dependencyKey.slice(0, peersIndex); + + // Example: 7.26.0 + if (semver.valid(key)) { + return new DependencySpecifier(dependencyName, key); + } + } + + // Example: @babel/preset-env@7.26.0 -> name=@babel/preset-env version=7.26.0 + // Example: @babel/preset-env@7.26.0(peer@1.2.3) -> name=@babel/preset-env version=7.26.0 + // Example: https://github.com/jonschlinkert/pad-left/tarball/2.1.0 -> name=undefined version=undefined + // Example: pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0 -> name=pad-left nonSemverVersion=https://xxxx + // Example: pad-left@https://codeload.github.com/jonschlinkert/pad-left/tar.gz/7798d648225aa5 -> name=pad-left nonSemverVersion=https://xxxx + const dependency: dependencyPath.DependencyPath = dependencyPath.parse(dependencyKey); + + const name: string = dependency.name ?? dependencyName; + const version: string = dependency.version ?? dependency.nonSemverVersion ?? dependencyKey; + + // Example: https://xxxx/pad-left/tarball/2.1.0 + // Example: https://github.com/jonschlinkert/pad-left/tarball/2.1.0 + // Example: https://codeload.github.com/jonschlinkert/pad-left/tar.gz/7798d648225aa5d879660a37c408ab4675b65ac7 + if (/^https?:/.test(version)) { + return new DependencySpecifier(name, version); + } + + // Is it an alias for a different package? + if (name === dependencyName) { + // No, it's a regular dependency + return new DependencySpecifier(name, version); + } else { + // If the parsed package name is different from the dependencyName, then this is an NPM package alias + return new DependencySpecifier(dependencyName, `npm:${name}@${version}`); + } +} + /** * Given an encoded "dependency key" from the PNPM shrinkwrap file, this parses it into an equivalent * DependencySpecifier. @@ -309,12 +334,33 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { this.overrides = new Map(Object.entries(shrinkwrapJson.overrides || {})); this.packageExtensionsChecksum = shrinkwrapJson.packageExtensionsChecksum; - // Importers only exist in workspaces - this.isWorkspaceCompatible = this.importers.size > 0; + // Lockfile v9 always has "." in importers filed. + this.isWorkspaceCompatible = + this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9 + ? this.importers.size > 1 + : this.importers.size > 0; this._integrities = new Map(); } + public static getLockfileV9PackageId(name: string, version: string): string { + /** + * name@1.2.3 -> name@1.2.3 + * name@1.2.3(peer) -> name@1.2.3(peer) + * https://xxx/@a/b -> name@https://xxx/@a/b + * file://xxx -> name@file://xxx + * 1.2.3 -> name@1.2.3 + */ + + if (/https?:/.test(version)) { + return /@https?:/.test(version) ? version : `${name}@${version}`; + } else if (/file:/.test(version)) { + return /@file:/.test(version)? version : `${name}@${version}`; + } + + return dependencyPath.removeSuffix(version).includes('@', 1) ? version : `${name}@${version}`; + } + public static loadFromFile( shrinkwrapYamlFilePath: string, { withCaching }: ILoadFromFileOptions = {} @@ -342,8 +388,39 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { } public static loadFromString(shrinkwrapContent: string): PnpmShrinkwrapFile { - const parsedData: IPnpmShrinkwrapYaml = yamlModule.safeLoad(shrinkwrapContent); - return new PnpmShrinkwrapFile(parsedData); + const shrinkwrapJson: IPnpmShrinkwrapYaml = yamlModule.safeLoad(shrinkwrapContent); + if ((shrinkwrapJson as LockfileFileV9).snapshots) { + const lockfile: IPnpmShrinkwrapYaml | null = convertLockfileV9ToLockfileObject( + shrinkwrapJson as LockfileFileV9 + ); + /** + * In Lockfile V9, + * 1. There is no top-level dependencies field, but it is a property of the importers field. + * 2. The version may is not equal to the key in the package field. Thus, it needs to be standardized in the form of `:`. + * + * importers: + * .: + * dependencies: + * 'project1': + * specifier: file:./projects/project1 + * version: file:projects/project1 + * + * packages: + * project1@file:projects/project1: + * resolution: {directory: projects/project1, type: directory} + */ + const dependencies: ResolvedDependencies | undefined = + lockfile.importers['.' as ProjectId]?.dependencies; + if (dependencies) { + lockfile.dependencies = {}; + for (const [name, versionSpecifier] of Object.entries(dependencies)) { + lockfile.dependencies[name] = PnpmShrinkwrapFile.getLockfileV9PackageId(name, versionSpecifier); + } + } + return new PnpmShrinkwrapFile(lockfile); + } + + return new PnpmShrinkwrapFile(shrinkwrapJson); } public getShrinkwrapHash(experimentsConfig?: IExperimentsJson): string { @@ -479,7 +556,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { private _convertLockfileV6DepPathToV5DepPath(newDepPath: string): string { if (!newDepPath.includes('@', 2) || newDepPath.startsWith('file:')) return newDepPath; const index: number = newDepPath.indexOf('@', newDepPath.indexOf('/@') + 2); - if (newDepPath.includes('(') && index > dependencyPath.indexOfPeersSuffix(newDepPath)) return newDepPath; + if (newDepPath.includes('(') && index > dependencyPathLockfilePreV9.indexOfPeersSuffix(newDepPath)) return newDepPath; return `${newDepPath.substring(0, index)}/${newDepPath.substring(index + 1)}`; } @@ -493,7 +570,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { if (this.shrinkwrapFileMajorVersion >= 6) { depPath = this._convertLockfileV6DepPathToV5DepPath(packagePath); } - const pkgInfo: ReturnType = dependencyPath.parse(depPath); + const pkgInfo: ReturnType = dependencyPathLockfilePreV9.parse(depPath); return this._getPackageId(pkgInfo.name as string, pkgInfo.version as string); } @@ -594,6 +671,11 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { const dependency: IPnpmShrinkwrapDependencyYaml | undefined = this.packages.get(value); if (dependency?.resolution?.tarball && value.startsWith(dependency.resolution.tarball)) { return new DependencySpecifier(dependencyName, dependency.resolution.tarball); + } + + if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) { + const { version, nonSemverVersion } = dependencyPath.parse(value); + value = version ?? nonSemverVersion ?? value; } else { let underscoreOrParenthesisIndex: number = value.indexOf('_'); if (underscoreOrParenthesisIndex < 0) { @@ -974,7 +1056,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { } } } else { - // PNPM v8 + // >= PNPM v8 const importerOptionalDependencies: Set = new Set( Object.keys(importer.optionalDependencies ?? {}) ); @@ -1022,20 +1104,32 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { return true; } } else { - if (typeof specifierFromLockfile === 'string') { - throw new Error( - `The PNPM lockfile is in an unexpected format. The "${name}" package is specified as ` + - `"${specifierFromLockfile}" instead of an object.` - ); - } else { + if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) { // TODO: Emit an error message when someone tries to override a version of something in one of their // local repo packages. let resolvedVersion: string = this.overrides.get(name) ?? version; // convert path in posix style, otherwise pnpm install will fail in subspace case resolvedVersion = Path.convertToSlashes(resolvedVersion); - if (specifierFromLockfile.specifier !== resolvedVersion && !isDevDepFallThrough && !isOptional) { + const specifier: string = importer.specifiers[name]; + if (specifier !== resolvedVersion && !isDevDepFallThrough && !isOptional) { return true; } + } else { + if (typeof specifierFromLockfile === 'string') { + throw new Error( + `The PNPM lockfile is in an unexpected format. The "${name}" package is specified as ` + + `"${specifierFromLockfile}" instead of an object.` + ); + } else { + // TODO: Emit an error message when someone tries to override a version of something in one of their + // local repo packages. + let resolvedVersion: string = this.overrides.get(name) ?? version; + // convert path in posix style, otherwise pnpm install will fail in subspace case + resolvedVersion = Path.convertToSlashes(resolvedVersion); + if (specifierFromLockfile.specifier !== resolvedVersion && !isDevDepFallThrough && !isOptional) { + return true; + } + } } } } @@ -1151,7 +1245,9 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { private _getPackageId(name: string, versionSpecifier: IPnpmVersionSpecifier): string { const version: string = normalizePnpmVersionSpecifier(versionSpecifier); - if (this.shrinkwrapFileMajorVersion >= 6) { + if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) { + return PnpmShrinkwrapFile.getLockfileV9PackageId(name, version); + } else if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) { if (version.startsWith('@github')) { // This is a github repo reference return version; @@ -1169,10 +1265,10 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { pnpmDependencyKey: IPnpmVersionSpecifier ): DependencySpecifier | undefined { if (pnpmDependencyKey) { - const result: DependencySpecifier | undefined = parsePnpmDependencyKey( - dependencyName, - pnpmDependencyKey - ); + const result: DependencySpecifier | undefined = + this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9 + ? parsePnpm9DependencyKey(dependencyName, pnpmDependencyKey) + : parsePnpmDependencyKey(dependencyName, pnpmDependencyKey); if (!result) { throw new Error( diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts new file mode 100644 index 00000000000..7234f7721fc --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapConverters.test.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import type { LockfileFileV9, PackageSnapshot, ProjectSnapshot } from '@pnpm/lockfile.types'; +import { convertLockfileV9ToLockfileObject } from '../PnpmShrinkWrapFileConverters'; +import { FileSystem } from '@rushstack/node-core-library'; +import yamlModule from 'js-yaml'; + +describe(convertLockfileV9ToLockfileObject.name, () => { + const lockfileContent: string = FileSystem.readFile( + `${__dirname}/yamlFiles/pnpm-lock-v9/pnpm-lock-v9.yaml` + ); + const lockfileJson: LockfileFileV9 = yamlModule.safeLoad(lockfileContent); + const lockfile = convertLockfileV9ToLockfileObject(lockfileJson); + + it('merge packages and snapshots', () => { + const packages = new Map(Object.entries(lockfile.packages || {})); + const padLeftPackage = packages.get('pad-left@2.1.0'); + expect(padLeftPackage).toBeDefined(); + expect(padLeftPackage?.dependencies).toEqual({ + 'repeat-string': '1.6.1' + }); + }); + + it("importers['.']", () => { + const importers = new Map(Object.entries(lockfile.importers || {})); + + const currentPackage = importers.get('.'); + expect(currentPackage).toBeDefined(); + + expect(currentPackage?.dependencies).toEqual({ + jquery: '3.7.1', + 'pad-left': '2.1.0' + }); + + expect(currentPackage?.specifiers).toEqual({ + jquery: '^3.7.1', + 'pad-left': '^2.1.0' + }); + }); +}); diff --git a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts index 91f787e57b5..aa1170e999b 100644 --- a/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts +++ b/libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import { type DependencySpecifier, DependencySpecifierType } from '../../DependencySpecifier'; -import { PnpmShrinkwrapFile, parsePnpmDependencyKey } from '../PnpmShrinkwrapFile'; +import { PnpmShrinkwrapFile, parsePnpm9DependencyKey, parsePnpmDependencyKey } from '../PnpmShrinkwrapFile'; import { RushConfiguration } from '../../../api/RushConfiguration'; import type { RushConfigurationProject } from '../../../api/RushConfigurationProject'; @@ -126,6 +126,99 @@ describe(PnpmShrinkwrapFile.name, () => { }); }); + describe(parsePnpm9DependencyKey.name, () => { + it('Does not support file:// specifiers', () => { + expect(parsePnpm9DependencyKey(DEPENDENCY_NAME, 'file:///path/to/file')).toBeUndefined(); + expect(parsePnpm9DependencyKey(DEPENDENCY_NAME, 'pad-left@file:///path/to/file')).toBeUndefined(); + expect(parsePnpm9DependencyKey(DEPENDENCY_NAME, 'link:///path/to/file')).toBeUndefined(); + }); + + it('Supports a variety of non-aliased package specifiers', () => { + function testSpecifiers(specifiers: string[], expectedName: string, expectedVersion: string): void { + for (const specifier of specifiers) { + const parsedSpecifier: DependencySpecifier | undefined = parsePnpm9DependencyKey( + expectedName, + specifier + ); + expect(parsedSpecifier).toBeDefined(); + expect(parsedSpecifier!.specifierType).toBe(DependencySpecifierType.Version); + expect(parsedSpecifier!.packageName).toBe(expectedName); + expect(parsedSpecifier!.versionSpecifier).toBe(expectedVersion); + } + } + + // non-scoped, non-prerelease + testSpecifiers( + [`${DEPENDENCY_NAME}@${VERSION}`, `${DEPENDENCY_NAME}@${VERSION}(peer@3.5.0+peer2@1.17.7)`], + DEPENDENCY_NAME, + VERSION + ); + + // scoped, non-prerelease + testSpecifiers( + [ + `${SCOPED_DEPENDENCY_NAME}@${VERSION}`, + `${SCOPED_DEPENDENCY_NAME}@${VERSION}(peer@3.5.0+peer2@1.17.7)` + ], + SCOPED_DEPENDENCY_NAME, + VERSION + ); + + // non-scoped, prerelease + testSpecifiers( + [ + `${DEPENDENCY_NAME}@${PRERELEASE_VERSION}`, + `${DEPENDENCY_NAME}@${PRERELEASE_VERSION}(peer@3.5.0+peer2@1.17.7)` + ], + DEPENDENCY_NAME, + PRERELEASE_VERSION + ); + + // scoped, prerelease + testSpecifiers( + [ + `${SCOPED_DEPENDENCY_NAME}@${PRERELEASE_VERSION}`, + `${SCOPED_DEPENDENCY_NAME}@${PRERELEASE_VERSION}(peer@3.5.0+peer2@1.17.7)` + ], + SCOPED_DEPENDENCY_NAME, + PRERELEASE_VERSION + ); + }); + + it('Supports aliased package specifiers (v9)', () => { + const parsedSpecifier: DependencySpecifier | undefined = parsePnpm9DependencyKey( + SCOPED_DEPENDENCY_NAME, + `${DEPENDENCY_NAME}@${VERSION}` + ); + expect(parsedSpecifier).toBeDefined(); + expect(parsedSpecifier!.specifierType).toBe(DependencySpecifierType.Alias); + expect(parsedSpecifier!.packageName).toBe(SCOPED_DEPENDENCY_NAME); + expect(parsedSpecifier!.versionSpecifier).toMatchInlineSnapshot(`"npm:${DEPENDENCY_NAME}@${VERSION}"`); + }); + + it('Supports URL package specifiers', () => { + const specifiers: string[] = [ + 'https://github.com/jonschlinkert/pad-left/tarball/2.1.0', + 'https://xxx.xxxx.org/pad-left/-/pad-left-2.1.0.tgz', + 'https://codeload.github.com/jonschlinkert/pad-left/tar.gz/7798d648225aa5d879660a37c408ab4675b65ac7', + `${SCOPED_DEPENDENCY_NAME}@http://abc.com/jonschlinkert/pad-left/tarball/2.1.0`, + `${SCOPED_DEPENDENCY_NAME}@https://xxx.xxxx.org/pad-left/-/pad-left-2.1.0.tgz`, + `${SCOPED_DEPENDENCY_NAME}@https://codeload.github.com/jonschlinkert/pad-left/tar.gz/7798d648225aa5d879660a37c408ab4675b65ac7` + ]; + + for (const specifier of specifiers) { + const parsedSpecifier: DependencySpecifier | undefined = parsePnpm9DependencyKey( + SCOPED_DEPENDENCY_NAME, + specifier + ); + expect(parsedSpecifier).toBeDefined(); + expect(parsedSpecifier!.specifierType).toBe(DependencySpecifierType.Remote); + expect(parsedSpecifier!.packageName).toBe(SCOPED_DEPENDENCY_NAME); + expect(parsedSpecifier!.versionSpecifier).toBe(specifier.replace(`${SCOPED_DEPENDENCY_NAME}@`, '')); + } + }); + }); + describe('Check is workspace project modified', () => { describe('pnpm lockfile major version 5', () => { it('can detect not modified', async () => { @@ -228,6 +321,64 @@ describe(PnpmShrinkwrapFile.name, () => { ).resolves.toBe(false); }); }); + + describe('pnpm lockfile major version 9', () => { + it('can detect not modified', async () => { + const project = getMockRushProject(); + const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( + `${__dirname}/yamlFiles/pnpm-lock-v9/not-modified.yaml` + ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace, + undefined + ) + ).resolves.toBe(false); + }); + + it('can detect modified', async () => { + const project = getMockRushProject(); + const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( + `${__dirname}/yamlFiles/pnpm-lock-v9/modified.yaml` + ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace, + undefined + ) + ).resolves.toBe(true); + }); + + it('can detect overrides', async () => { + const project = getMockRushProject(); + const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( + `${__dirname}/yamlFiles/pnpm-lock-v9/overrides-not-modified.yaml` + ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace, + undefined + ) + ).resolves.toBe(false); + }); + + it('can handle the inconsistent version of a package declared in dependencies and devDependencies', async () => { + const project = getMockRushProject2(); + const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile( + `${__dirname}/yamlFiles/pnpm-lock-v9/inconsistent-dep-devDep.yaml` + ); + await expect( + pnpmShrinkwrapFile.isWorkspaceProjectModifiedAsync( + project, + project.rushConfiguration.defaultSubspace, + undefined + ) + ).resolves.toBe(false); + }); + }); }); }); diff --git a/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/inconsistent-dep-devDep.yaml b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/inconsistent-dep-devDep.yaml new file mode 100644 index 00000000000..7f26989631c --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/inconsistent-dep-devDep.yaml @@ -0,0 +1,26 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../apps/bar: + dependencies: + prettier: + specifier: ~2.3.0 + version: 2.3.2 + +packages: + + prettier@2.3.2: + resolution: {integrity: sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==} + engines: {node: '>=10.13.0'} + hasBin: true + +snapshots: + + prettier@2.3.2: {} diff --git a/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/modified.yaml b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/modified.yaml new file mode 100644 index 00000000000..71509e89efe --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/modified.yaml @@ -0,0 +1,35 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../apps/foo: + dependencies: + tslib: + specifier: ~2.3.0 + version: 2.3.1 + devDependencies: + typescript: + specifier: ~5.0.4 + version: 5.0.4 + +packages: + + tslib@2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + + typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + +snapshots: + + tslib@2.3.1: {} + + typescript@5.0.4: {} diff --git a/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/not-modified.yaml b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/not-modified.yaml new file mode 100644 index 00000000000..47ef29262c3 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/not-modified.yaml @@ -0,0 +1,35 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../apps/foo: + dependencies: + tslib: + specifier: ~2.3.1 + version: 2.3.1 + devDependencies: + typescript: + specifier: ~5.0.4 + version: 5.0.4 + +packages: + + tslib@2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + + typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + +snapshots: + + tslib@2.3.1: {} + + typescript@5.0.4: {} diff --git a/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/overrides-not-modified.yaml b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/overrides-not-modified.yaml new file mode 100644 index 00000000000..d21630b1d81 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/overrides-not-modified.yaml @@ -0,0 +1,38 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + typescript: 5.0.4 + +importers: + + .: {} + + ../../apps/foo: + dependencies: + tslib: + specifier: ~2.3.1 + version: 2.3.1 + devDependencies: + typescript: + specifier: 5.0.4 + version: 5.0.4 + +packages: + + tslib@2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + + typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + +snapshots: + + tslib@2.3.1: {} + + typescript@5.0.4: {} diff --git a/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/pnpm-lock-v9.yaml b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/pnpm-lock-v9.yaml new file mode 100644 index 00000000000..9fe144e1b91 --- /dev/null +++ b/libraries/rush-lib/src/logic/pnpm/test/yamlFiles/pnpm-lock-v9/pnpm-lock-v9.yaml @@ -0,0 +1,39 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + jquery: + specifier: ^3.7.1 + version: 3.7.1 + pad-left: + specifier: ^2.1.0 + version: 2.1.0 + +packages: + + jquery@3.7.1: + resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==} + + pad-left@2.1.0: + resolution: {integrity: sha512-HJxs9K9AztdIQIAIa/OIazRAUW/L6B9hbQDxO4X07roW3eo9XqZc2ur9bn1StH9CnbbI9EgvejHQX7CBpCF1QA==} + engines: {node: '>=0.10.0'} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + +snapshots: + + jquery@3.7.1: {} + + pad-left@2.1.0: + dependencies: + repeat-string: 1.6.1 + + repeat-string@1.6.1: {} diff --git a/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts b/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts index 513b5a54e9c..2104bb07b10 100644 --- a/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts +++ b/libraries/rush-lib/src/logic/test/ShrinkwrapFile.test.ts @@ -6,7 +6,11 @@ import { JsonFile } from '@rushstack/node-core-library'; import type { BaseShrinkwrapFile } from '../base/BaseShrinkwrapFile'; import { ShrinkwrapFileFactory } from '../ShrinkwrapFileFactory'; -import { parsePnpmDependencyKey, PnpmShrinkwrapFile } from '../pnpm/PnpmShrinkwrapFile'; +import { + parsePnpmDependencyKey, + PnpmShrinkwrapFile, + ShrinkwrapFileMajorVersion +} from '../pnpm/PnpmShrinkwrapFile'; import { DependencySpecifier } from '../DependencySpecifier'; import { NpmShrinkwrapFile } from '../npm/NpmShrinkwrapFile'; import type { RushConfigurationProject } from '../../api/RushConfigurationProject'; @@ -73,12 +77,28 @@ describe(PnpmShrinkwrapFile.name, () => { '@rush-temp/project1' ) ).toEqual(false); - expect( - shrinkwrapFile.tryEnsureCompatibleDependency( - new DependencySpecifier('@scope/testDep', '>=2.0.0 <3.0.0'), - '@rush-temp/project3' - ) - ).toEqual(true); + + if ( + shrinkwrapFile instanceof PnpmShrinkwrapFile && + shrinkwrapFile.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9 + ) { + expect( + shrinkwrapFile.tryEnsureCompatibleDependency( + new DependencySpecifier( + '@scope/testDep', + 'https://github.com/jonschlinkert/pad-left/tarball/2.1.0' + ), + '@rush-temp/project3' + ) + ).toEqual(true); + } else { + expect( + shrinkwrapFile.tryEnsureCompatibleDependency( + new DependencySpecifier('@scope/testDep', '>=2.0.0 <3.0.0'), + '@rush-temp/project3' + ) + ).toEqual(true); + } }); it('extracts temp projects successfully', () => { @@ -121,6 +141,15 @@ describe(PnpmShrinkwrapFile.name, () => { validateNonWorkspaceLockfile(shrinkwrapFile); }); + + describe('V9 lockfile', () => { + const filename: string = path.resolve( + __dirname, + '../../../src/logic/test/shrinkwrapFile/non-workspace-pnpm-lock-v9.yaml' + ); + const shrinkwrapFile: BaseShrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile('pnpm', filename)!; + validateNonWorkspaceLockfile(shrinkwrapFile); + }); }); describe('workspace', () => { @@ -181,6 +210,17 @@ describe(PnpmShrinkwrapFile.name, () => { validateWorkspaceLockfile(shrinkwrapFile); }); + + describe('V9 lockfile', () => { + const filename: string = path.resolve( + __dirname, + '../../../src/logic/test/shrinkwrapFile/workspace-pnpm-lock-v9.yaml' + ); + + const shrinkwrapFile: BaseShrinkwrapFile = ShrinkwrapFileFactory.getShrinkwrapFile('pnpm', filename)!; + + validateWorkspaceLockfile(shrinkwrapFile); + }); }); }); diff --git a/libraries/rush-lib/src/logic/test/__snapshots__/ShrinkwrapFile.test.ts.snap b/libraries/rush-lib/src/logic/test/__snapshots__/ShrinkwrapFile.test.ts.snap index 5192b36bf2f..c4f9d4453dd 100644 --- a/libraries/rush-lib/src/logic/test/__snapshots__/ShrinkwrapFile.test.ts.snap +++ b/libraries/rush-lib/src/logic/test/__snapshots__/ShrinkwrapFile.test.ts.snap @@ -99,3 +99,53 @@ Array [ ], ] `; + +exports[`PnpmShrinkwrapFile workspace V9 lockfile verifies project dependencies: project1 1`] = ` +Array [ + Array [ + Object { + "../../project1": "../../project1:6yFTI2g+Ny0Au80xpo6zIY61TCNDUuLUd6EgLlbOBtc=:", + "jquery@1.12.3": "sha512-FzM42/Ew+Hb8ha2OlhHRBLgWIZS32gZ0+NvWTf+ZvVvGaIlJkOiXQyb7VBjv4L6fJfmTrRf3EsAmbfsHDhfemw==", + "pad-left@1.0.2": "sha512-saxSV1EYAytuZDtQYEwi0DPzooG6aN18xyHrnJtzwjVwmMauzkEecd7hynVJGolNGk1Pl9tltmZqfze4TZTCxg==", + "repeat-string@1.6.1": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + }, + "project1/.rush/temp/shrinkwrap-deps.json", + Object { + "ensureFolderExists": true, + }, + ], +] +`; + +exports[`PnpmShrinkwrapFile workspace V9 lockfile verifies project dependencies: project2 1`] = ` +Array [ + Array [ + Object { + "../../project2": "../../project2:l6v/HWUhScMI0m4k6D5qHiCOFj3Z0GoIFJEcp4I63w0=:", + "jquery@2.2.4": "sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==", + "q@1.5.0": "sha512-VVMcd+HnuWZalHPycK7CsbVJ+sSrrrnCvHcW38YJVK9Tywnb5DUWJjONi81bLUj7aqDjIXnePxBl5t1r/F/ncg==", + }, + "project2/.rush/temp/shrinkwrap-deps.json", + Object { + "ensureFolderExists": true, + }, + ], +] +`; + +exports[`PnpmShrinkwrapFile workspace V9 lockfile verifies project dependencies: project3 1`] = ` +Array [ + Array [ + Object { + "../../project3": "../../project3:vMoje8cXfsHYOc6EXbxEw/qyBGXGUL1RApmNfwl7oA8=:", + "pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0": "pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0:bKrL+SvVYubL0HwTq/GOOXq1d05LTQ+HGqlXabzGEAU=:", + "q@1.5.0": "sha512-VVMcd+HnuWZalHPycK7CsbVJ+sSrrrnCvHcW38YJVK9Tywnb5DUWJjONi81bLUj7aqDjIXnePxBl5t1r/F/ncg==", + "repeat-string@1.6.1": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + }, + "project3/.rush/temp/shrinkwrap-deps.json", + Object { + "ensureFolderExists": true, + }, + ], +] +`; diff --git a/libraries/rush-lib/src/logic/test/shrinkwrapFile/non-workspace-pnpm-lock-v9.yaml b/libraries/rush-lib/src/logic/test/shrinkwrapFile/non-workspace-pnpm-lock-v9.yaml new file mode 100644 index 00000000000..b74c0511f12 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/shrinkwrapFile/non-workspace-pnpm-lock-v9.yaml @@ -0,0 +1,196 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@pnpm/dependency-path': + specifier: ^5.1.7 + version: 5.1.7 + '@pnpm/lockfile.utils': + specifier: ^1.0.4 + version: 1.0.4 + '@rush-temp/project1': + specifier: file:./projects/project1 + version: project1@file:projects/project1 + '@rush-temp/project2': + specifier: file:./projects/project2 + version: project2@file:projects/project2 + '@rush-temp/project3': + specifier: file:./projects/project3 + version: project3@file:projects/project3 + pad-left: + specifier: 1.0.0 + version: 1.0.0 + +packages: + + '@pnpm/crypto.base32-hash@3.0.1': + resolution: {integrity: sha512-DM4RR/tvB7tMb2FekL0Q97A5PCXNyEC+6ht8SaufAUFSJNxeozqHw9PHTZR03mzjziPzNQLOld0pNINBX3srtw==} + engines: {node: '>=18.12'} + + '@pnpm/crypto.polyfill@1.0.0': + resolution: {integrity: sha512-WbmsqqcUXKKaAF77ox1TQbpZiaQcr26myuMUu+WjUtoWYgD3VP6iKYEvSx35SZ6G2L316lu+pv+40A2GbWJc1w==} + engines: {node: '>=18.12'} + + '@pnpm/dependency-path@5.1.7': + resolution: {integrity: sha512-MKCyaTy1r9fhBXAnhDZNBVgo6ThPnicwJEG203FDp7pGhD7NruS/FhBI+uMd7GNsK3D7aIFCDAgbWpNTXn/eWw==} + engines: {node: '>=18.12'} + + '@pnpm/lockfile.types@1.0.3': + resolution: {integrity: sha512-A7vUWktnhDkrIs+WmXm7AdffJVyVYJpQUEouya/DYhB+Y+tQ3BXjZ6CV0KybqLgI/8AZErgCJqFxA0GJH6QDjA==} + engines: {node: '>=18.12'} + + '@pnpm/lockfile.utils@1.0.4': + resolution: {integrity: sha512-ptHO2muziYyNCwpsuaPtaRgKiHMrE/lkGI4nqbHnRWWgfdJbTeL1tq+b/EUsxjlKlJ/a9Q4z2C+t38g+9bhTJg==} + engines: {node: '>=18.12'} + + '@pnpm/patching.types@1.0.0': + resolution: {integrity: sha512-juCdQCC1USqLcOhVPl1tYReoTO9YH4fTullMnFXXcmpsDM7Dkn3tzuOQKC3oPoJ2ozv+0EeWWMtMGqn2+IM3pQ==} + engines: {node: '>=18.12'} + + '@pnpm/pick-fetcher@3.0.0': + resolution: {integrity: sha512-2eisylRAU/jeuxFEPnS1gjLZKJGbYc4QEtEW6MVUYjO4Xi+2ttkSm7825S0J5IPpUIvln8HYPCUS0eQWSfpOaQ==} + engines: {node: '>=18.12'} + + '@pnpm/ramda@0.28.1': + resolution: {integrity: sha512-zcAG+lvU0fMziNeGXpPyCyCJYp5ZVrPElEE4t14jAmViaihohocZ+dDkcRIyAomox8pQsuZnv1EyHR+pOhmUWw==} + + '@pnpm/resolver-base@13.0.4': + resolution: {integrity: sha512-d6GtsaXDN1VmVdeB6ohrhwGwQfvYpEX/XkBZyRT0Hp772WabWVfaulvicwdh/8o7Rpzy7IV/2hKnDpodUY00lw==} + engines: {node: '>=18.12'} + + '@pnpm/types@12.2.0': + resolution: {integrity: sha512-5RtwWhX39j89/Tmyv2QSlpiNjErA357T/8r1Dkg+2lD3P7RuS7Xi2tChvmOC3VlezEFNcWnEGCOeKoGRkDuqFA==} + engines: {node: '>=18.12'} + + get-npm-tarball-url@2.1.0: + resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==} + engines: {node: '>=12.17'} + + jquery@1.12.3: + resolution: {integrity: sha512-FzM42/Ew+Hb8ha2OlhHRBLgWIZS32gZ0+NvWTf+ZvVvGaIlJkOiXQyb7VBjv4L6fJfmTrRf3EsAmbfsHDhfemw==} + + jquery@2.2.4: + resolution: {integrity: sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==} + + pad-left@1.0.0: + resolution: {integrity: sha512-VIgD7DviaDL6QCj+jEU1jpjXlu0z/sl4yzAmFLmM7YvM3ZRKLaxZAe+sZ1hKHeYUeI4zoZHfMetDpazu/uAwsw==} + engines: {node: '>=0.10.0'} + + pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0: + resolution: {tarball: https://github.com/jonschlinkert/pad-left/tarball/2.1.0} + version: 2.1.0 + engines: {node: '>=0.10.0'} + + project1@file:projects/project1: + resolution: {directory: projects/project1, type: directory} + + project2@file:projects/project2: + resolution: {directory: projects/project2, type: directory} + + project3@file:projects/project3: + resolution: {directory: projects/project3, type: directory} + + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + rfc4648@1.5.3: + resolution: {integrity: sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + +snapshots: + + '@pnpm/crypto.base32-hash@3.0.1': + dependencies: + '@pnpm/crypto.polyfill': 1.0.0 + rfc4648: 1.5.3 + + '@pnpm/crypto.polyfill@1.0.0': {} + + '@pnpm/dependency-path@5.1.7': + dependencies: + '@pnpm/crypto.base32-hash': 3.0.1 + '@pnpm/types': 12.2.0 + semver: 7.6.3 + + '@pnpm/lockfile.types@1.0.3': + dependencies: + '@pnpm/patching.types': 1.0.0 + '@pnpm/types': 12.2.0 + + '@pnpm/lockfile.utils@1.0.4': + dependencies: + '@pnpm/dependency-path': 5.1.7 + '@pnpm/lockfile.types': 1.0.3 + '@pnpm/pick-fetcher': 3.0.0 + '@pnpm/resolver-base': 13.0.4 + '@pnpm/types': 12.2.0 + get-npm-tarball-url: 2.1.0 + ramda: '@pnpm/ramda@0.28.1' + + '@pnpm/patching.types@1.0.0': {} + + '@pnpm/pick-fetcher@3.0.0': {} + + '@pnpm/ramda@0.28.1': {} + + '@pnpm/resolver-base@13.0.4': + dependencies: + '@pnpm/types': 12.2.0 + + '@pnpm/types@12.2.0': {} + + get-npm-tarball-url@2.1.0: {} + + jquery@1.12.3: {} + + jquery@2.2.4: {} + + pad-left@1.0.0: + dependencies: + repeat-string: 1.6.1 + + pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0: + dependencies: + repeat-string: 1.6.1 + + project1@file:projects/project1: + dependencies: + jquery: 1.12.3 + pad-left: 1.0.0 + + project2@file:projects/project2: + dependencies: + jquery: 2.2.4 + q: 1.5.1 + + project3@file:projects/project3: + dependencies: + '@scope/testDep': pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0 + q: 1.5.1 + + q@1.5.1: {} + + repeat-string@1.6.1: {} + + rfc4648@1.5.3: {} + + semver@7.6.3: {} diff --git a/libraries/rush-lib/src/logic/test/shrinkwrapFile/workspace-pnpm-lock-v9.yaml b/libraries/rush-lib/src/logic/test/shrinkwrapFile/workspace-pnpm-lock-v9.yaml new file mode 100644 index 00000000000..ad160af09b8 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/shrinkwrapFile/workspace-pnpm-lock-v9.yaml @@ -0,0 +1,83 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + ../../project1: + dependencies: + jquery: + specifier: 1.12.3 + version: 1.12.3 + pad-left: + specifier: ^1.0.0 + version: 1.0.2 + + ../../project2: + dependencies: + jquery: + specifier: 2.2.4 + version: 2.2.4 + q: + specifier: ~1.5.0 + version: 1.5.0 + + ../../project3: + dependencies: + '@scope/testDep': + specifier: https://github.com/jonschlinkert/pad-left/tarball/2.1.0 + version: pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0 + q: + specifier: 1.5.0 + version: 1.5.0 + +packages: + + jquery@1.12.3: + resolution: {integrity: sha512-FzM42/Ew+Hb8ha2OlhHRBLgWIZS32gZ0+NvWTf+ZvVvGaIlJkOiXQyb7VBjv4L6fJfmTrRf3EsAmbfsHDhfemw==} + + jquery@2.2.4: + resolution: {integrity: sha512-lBHj60ezci2u1v2FqnZIraShGgEXq35qCzMv4lITyHGppTnA13rwR0MgwyNJh9TnDs3aXUvd1xjAotfraMHX/Q==} + + pad-left@1.0.2: + resolution: {integrity: sha512-saxSV1EYAytuZDtQYEwi0DPzooG6aN18xyHrnJtzwjVwmMauzkEecd7hynVJGolNGk1Pl9tltmZqfze4TZTCxg==} + engines: {node: '>=0.10.0'} + + pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0: + resolution: {tarball: https://github.com/jonschlinkert/pad-left/tarball/2.1.0} + version: 2.1.0 + engines: {node: '>=0.10.0'} + + q@1.5.0: + resolution: {integrity: sha512-VVMcd+HnuWZalHPycK7CsbVJ+sSrrrnCvHcW38YJVK9Tywnb5DUWJjONi81bLUj7aqDjIXnePxBl5t1r/F/ncg==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + +snapshots: + + jquery@1.12.3: {} + + jquery@2.2.4: {} + + pad-left@1.0.2: + dependencies: + repeat-string: 1.6.1 + + pad-left@https://github.com/jonschlinkert/pad-left/tarball/2.1.0: + dependencies: + repeat-string: 1.6.1 + + q@1.5.0: {} + + repeat-string@1.6.1: {} diff --git a/libraries/rush-sdk/package.json b/libraries/rush-sdk/package.json index 4e3456cf129..ba251eb5a66 100644 --- a/libraries/rush-sdk/package.json +++ b/libraries/rush-sdk/package.json @@ -38,6 +38,7 @@ }, "license": "MIT", "dependencies": { + "@pnpm/lockfile.types": "~1.0.3", "@rushstack/lookup-by-path": "workspace:*", "@rushstack/node-core-library": "workspace:*", "@rushstack/package-deps-hash": "workspace:*",