diff --git a/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/.modules.yaml.ts b/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/.modules.yaml.ts new file mode 100644 index 0000000000000..6ea2b02292e41 --- /dev/null +++ b/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/.modules.yaml.ts @@ -0,0 +1,47 @@ +export default `hoistPattern: + - '*' +hoistedDependencies: + /ansi-regex/5.0.1: + ansi-regex: private + /ansi-styles/4.3.0: + ansi-styles: private + /color-convert/2.0.1: + color-convert: private + /color-name/1.1.4: + color-name: private + /eastasianwidth/0.2.0: + eastasianwidth: private + /emoji-regex/8.0.0: + emoji-regex: private + /is-fullwidth-code-point/3.0.0: + is-fullwidth-code-point: private + /string-width/4.2.3: + string-width-cjs: private + /string-width/5.1.2: + string-width: private + /strip-ansi/6.0.1: + strip-ansi-cjs: private + /strip-ansi/7.1.0: + strip-ansi: private + /wrap-ansi/7.0.0: + wrap-ansi-cjs: private + /wrap-ansi/8.1.0: + wrap-ansi: private +included: + dependencies: true + devDependencies: true + optionalDependencies: true +injectedDeps: {} +layoutVersion: 5 +nodeLinker: isolated +packageManager: pnpm@8.15.7 +pendingBuilds: [] +prunedAt: Fri, 26 Apr 2024 15:10:54 GMT +publicHoistPattern: + - '*eslint*' + - '*prettier*' +registries: + default: https://registry.npmjs.org/ +skipped: [] +storeDir: /Users/jdoe/Library/pnpm/store/v3 +virtualStoreDir: .pnpm`; diff --git a/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/pnpm-lock.yaml.ts b/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/pnpm-lock.yaml.ts new file mode 100644 index 0000000000000..7eea6ca2e29a0 --- /dev/null +++ b/packages/nx/src/plugins/js/lock-file/__fixtures__/mixed-keys/pnpm-lock.yaml.ts @@ -0,0 +1,133 @@ +export default `lockfileVersion: '6.0' + +dependencies: + '@isaacs/cliui': + specifier: ^8.0.2 + version: 8.0.2 + cliui: + specifier: ^8.0.1 + version: 8.0.1 + +packages: + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: false + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: false + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: false + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: false + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: false + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: false +`; diff --git a/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts b/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts index 52ee056631895..49bcbf0dea77c 100644 --- a/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts +++ b/packages/nx/src/plugins/js/lock-file/pnpm-parser.spec.ts @@ -860,4 +860,282 @@ describe('pnpm LockFile utility', () => { expect(Object.keys(externalNodes).length).toEqual(5); }); }); + + describe('mixed keys', () => { + let lockFile, lockFileHash; + + beforeEach(() => { + const fileSys = { + 'node_modules/@isaacs/cliui/package.json': '{"version": "8.0.2"}', + 'node_modules/ansi-regex/package.json': '{"version": "5.0.1"}', + 'node_modules/ansi-styles/package.json': '{"version": "4.3.0"}', + 'node_modules/cliui/package.json': '{"version": "8.0.1"}', + 'node_modules/color-convert/package.json': '{"version": "2.0.1"}', + 'node_modules/color-name/package.json': '{"version": "1.1.4"}', + 'node_modules/eastasianwidth/package.json': '{"version": "0.2.0"}', + 'node_modules/emoji-regex/package.json': '{"version": "8.0.0"}', + 'node_modules/is-fullwidth-code-point/package.json': + '{"version": "3.0.0"}', + 'node_modules/string-width/package.json': '{"version": "5.1.2"}', + 'node_modules/string-width-cjs/package.json': '{"version": "4.2.3"}', + 'node_modules/strip-ansi/package.json': '{"version": "7.0.1"}', + 'node_modules/strip-ansi-cjs/package.json': '{"version": "6.0.1"}', + 'node_modules/wrap-ansi/package.json': '{"version": "8.1.0"}', + 'node_modules/wrap-ansi-cjs/package.json': '{"version": "7.0.0"}', + 'node_modules/.modules.yaml': require(joinPathFragments( + __dirname, + '__fixtures__/mixed-keys/.modules.yaml' + )).default, + }; + vol.fromJSON(fileSys, '/root'); + + lockFile = require(joinPathFragments( + __dirname, + '__fixtures__/mixed-keys/pnpm-lock.yaml' + )).default; + lockFileHash = '__fixtures__/mixed-keys/pnpm-lock.yaml'; + }); + + it('should parse classic and prune packages with mixed keys', () => { + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/mixed-keys/package.json' + )); + + const externalNodes = getPnpmLockfileNodes(lockFile, lockFileHash); + let graph: ProjectGraph = { + nodes: {}, + dependencies: {}, + externalNodes, + }; + const ctx: CreateDependenciesContext = { + projects: {}, + externalNodes, + fileMap: { + nonProjectFiles: [], + projectFileMap: {}, + }, + filesToProcess: { + nonProjectFiles: [], + projectFileMap: {}, + }, + nxJsonConfiguration: null, + workspaceRoot: '/virtual', + }; + const dependencies = getPnpmLockfileDependencies( + lockFile, + lockFileHash, + ctx + ); + + const builder = new ProjectGraphBuilder(graph); + for (const dep of dependencies) { + builder.addDependency( + dep.source, + dep.target, + dep.type, + 'sourceFile' in dep ? dep.sourceFile : null + ); + } + graph = builder.getUpdatedProjectGraph(); + + expect(graph.externalNodes).toMatchInlineSnapshot(` + { + "npm:@isaacs/cliui": { + "data": { + "hash": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "packageName": "@isaacs/cliui", + "version": "8.0.2", + }, + "name": "npm:@isaacs/cliui", + "type": "npm", + }, + "npm:ansi-regex": { + "data": { + "hash": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "packageName": "ansi-regex", + "version": "5.0.1", + }, + "name": "npm:ansi-regex", + "type": "npm", + }, + "npm:ansi-regex@6.0.1": { + "data": { + "hash": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "packageName": "ansi-regex", + "version": "6.0.1", + }, + "name": "npm:ansi-regex@6.0.1", + "type": "npm", + }, + "npm:ansi-styles": { + "data": { + "hash": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "packageName": "ansi-styles", + "version": "4.3.0", + }, + "name": "npm:ansi-styles", + "type": "npm", + }, + "npm:ansi-styles@6.2.1": { + "data": { + "hash": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "packageName": "ansi-styles", + "version": "6.2.1", + }, + "name": "npm:ansi-styles@6.2.1", + "type": "npm", + }, + "npm:cliui": { + "data": { + "hash": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "packageName": "cliui", + "version": "8.0.1", + }, + "name": "npm:cliui", + "type": "npm", + }, + "npm:color-convert": { + "data": { + "hash": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "packageName": "color-convert", + "version": "2.0.1", + }, + "name": "npm:color-convert", + "type": "npm", + }, + "npm:color-name": { + "data": { + "hash": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "packageName": "color-name", + "version": "1.1.4", + }, + "name": "npm:color-name", + "type": "npm", + }, + "npm:eastasianwidth": { + "data": { + "hash": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "packageName": "eastasianwidth", + "version": "0.2.0", + }, + "name": "npm:eastasianwidth", + "type": "npm", + }, + "npm:emoji-regex": { + "data": { + "hash": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "packageName": "emoji-regex", + "version": "8.0.0", + }, + "name": "npm:emoji-regex", + "type": "npm", + }, + "npm:emoji-regex@9.2.2": { + "data": { + "hash": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "packageName": "emoji-regex", + "version": "9.2.2", + }, + "name": "npm:emoji-regex@9.2.2", + "type": "npm", + }, + "npm:is-fullwidth-code-point": { + "data": { + "hash": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "packageName": "is-fullwidth-code-point", + "version": "3.0.0", + }, + "name": "npm:is-fullwidth-code-point", + "type": "npm", + }, + "npm:string-width": { + "data": { + "hash": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "packageName": "string-width", + "version": "5.1.2", + }, + "name": "npm:string-width", + "type": "npm", + }, + "npm:string-width-cjs": { + "data": { + "hash": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "packageName": "string-width-cjs", + "version": "npm:string-width@4.2.3", + }, + "name": "npm:string-width-cjs", + "type": "npm", + }, + "npm:string-width@4.2.3": { + "data": { + "hash": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "packageName": "string-width", + "version": "4.2.3", + }, + "name": "npm:string-width@4.2.3", + "type": "npm", + }, + "npm:strip-ansi-cjs": { + "data": { + "hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "packageName": "strip-ansi-cjs", + "version": "npm:strip-ansi@6.0.1", + }, + "name": "npm:strip-ansi-cjs", + "type": "npm", + }, + "npm:strip-ansi@6.0.1": { + "data": { + "hash": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "packageName": "strip-ansi", + "version": "6.0.1", + }, + "name": "npm:strip-ansi@6.0.1", + "type": "npm", + }, + "npm:strip-ansi@7.1.0": { + "data": { + "hash": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "packageName": "strip-ansi", + "version": "7.1.0", + }, + "name": "npm:strip-ansi@7.1.0", + "type": "npm", + }, + "npm:wrap-ansi": { + "data": { + "hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "packageName": "wrap-ansi", + "version": "8.1.0", + }, + "name": "npm:wrap-ansi", + "type": "npm", + }, + "npm:wrap-ansi-cjs": { + "data": { + "hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "packageName": "wrap-ansi-cjs", + "version": "npm:wrap-ansi@7.0.0", + }, + "name": "npm:wrap-ansi-cjs", + "type": "npm", + }, + "npm:wrap-ansi@7.0.0": { + "data": { + "hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "packageName": "wrap-ansi", + "version": "7.0.0", + }, + "name": "npm:wrap-ansi@7.0.0", + "type": "npm", + }, + } + `); + + const prunedGraph = pruneProjectGraph(graph, packageJson); + const result = stringifyPnpmLockfile(prunedGraph, lockFile, packageJson); + expect(result).toEqual(lockFile); + }); + }); }); diff --git a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts index 068fddaae4e5e..8026a8e1327bf 100644 --- a/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/pnpm-parser.ts @@ -65,6 +65,52 @@ export function getPnpmLockfileDependencies( return getDependencies(data, keyMap, isV6, ctx); } +function matchPropValue( + record: Record, + key: string, + originalPackageName: string +): string | undefined { + if (!record) { + return undefined; + } + const index = Object.values(record).findIndex((version) => version === key); + if (index > -1) { + return Object.keys(record)[index]; + } + // check if non-aliased name is found + if ( + record[originalPackageName] && + key.startsWith(`/${originalPackageName}/${record[originalPackageName]}`) + ) { + return originalPackageName; + } +} + +function matchedDependencyName( + importer: Partial, + key: string, + originalPackageName: string +): string | undefined { + return ( + matchPropValue(importer.dependencies, key, originalPackageName) || + matchPropValue(importer.optionalDependencies, key, originalPackageName) || + matchPropValue(importer.peerDependencies, key, originalPackageName) + ); +} + +function createHashFromSnapshot(snapshot: PackageSnapshot) { + return ( + snapshot.resolution?.['integrity'] || + (snapshot.resolution?.['tarball'] + ? hashArray([snapshot.resolution['tarball']]) + : undefined) + ); +} + +function isLockFileKey(depVersion: string) { + return depVersion.startsWith('/'); +} + function getNodes( data: Lockfile, keyMap: Map, @@ -72,42 +118,118 @@ function getNodes( ): Record { const nodes: Map> = new Map(); - Object.entries(data.packages).forEach(([key, snapshot]) => { - findPackageNames(key, snapshot, data).forEach((packageName) => { - const rawVersion = findVersion(key, packageName); - const version = parseBaseVersion(rawVersion, isV6); - - // we don't need to keep duplicates, we can just track the keys - const existingNode = nodes.get(packageName)?.get(version); - if (existingNode) { - keyMap.set(key, existingNode); - return; + const maybeAliasedPackageVersions = new Map(); // + + const packageNames = new Set<{ + key: string; + packageName: string; + hash?: string; + }>(); + for (const [key, snapshot] of Object.entries(data.packages)) { + const originalPackageName = extractNameFromKey(key); + if (!originalPackageName) { + continue; + } + // snapshot already has a name + if (snapshot.name) { + packageNames.add({ + key, + packageName: snapshot.name, + hash: createHashFromSnapshot(snapshot), + }); + } + const rootDependencyName = + matchedDependencyName(data.importers['.'], key, originalPackageName) || + // only root importers have devDependencies + matchPropValue( + data.importers['.'].devDependencies, + key, + originalPackageName + ); + if (rootDependencyName) { + packageNames.add({ + key, + packageName: rootDependencyName, + hash: createHashFromSnapshot(snapshot), + }); + } + + if (!snapshot.name && !rootDependencyName) { + packageNames.add({ + key, + packageName: originalPackageName, + hash: createHashFromSnapshot(snapshot), + }); + } + + if (snapshot.peerDependencies) { + for (const [depName, depVersion] of Object.entries( + snapshot.peerDependencies + )) { + if (isLockFileKey(depVersion)) { + maybeAliasedPackageVersions.set(depVersion, depName); + } } + } + if (snapshot.optionalDependencies) { + for (const [depName, depVersion] of Object.entries( + snapshot.optionalDependencies + )) { + if (isLockFileKey(depVersion)) { + maybeAliasedPackageVersions.set(depVersion, depName); + } + } + } + if (snapshot.dependencies) { + for (const [depName, depVersion] of Object.entries( + snapshot.dependencies + )) { + if (isLockFileKey(depVersion)) { + maybeAliasedPackageVersions.set(depVersion, depName); + } + } + } + const aliasedDep = maybeAliasedPackageVersions.get(key); + if (aliasedDep) { + packageNames.add({ + key, + packageName: aliasedDep, + hash: createHashFromSnapshot(snapshot), + }); + } + } + + for (const { key, packageName, hash } of packageNames) { + const rawVersion = findVersion(key, packageName); + if (!rawVersion) { + continue; + } + const version = parseBaseVersion(rawVersion, isV6); + if (!version) { + continue; + } + + if (!nodes.has(packageName)) { + nodes.set(packageName, new Map()); + } + + if (!nodes.get(packageName).has(version)) { const node: ProjectGraphExternalNode = { type: 'npm', name: version ? `npm:${packageName}@${version}` : `npm:${packageName}`, data: { version, packageName, - hash: - snapshot.resolution?.['integrity'] || - hashArray( - snapshot.resolution?.['tarball'] - ? [snapshot.resolution['tarball']] - : [packageName, version] - ), + hash: hash ?? hashArray([packageName, version]), }, }; - + nodes.get(packageName).set(version, node); keyMap.set(key, node); - if (!nodes.has(packageName)) { - nodes.set(packageName, new Map([[version, node]])); - } else { - nodes.get(packageName).set(version, node); - } - }); - }); + } else { + keyMap.set(key, nodes.get(packageName).get(version)); + } + } const hoistedDeps = loadPnpmHoistedDepsDefinition(); const results: Record = {}; @@ -323,67 +445,6 @@ function findVersion(key: string, packageName: string): string { return key; } -function findPackageNames( - key: string, - snapshot: PackageSnapshot, - data: Lockfile -): string[] { - const packageNames = new Set(); - const originalPackageName = extractNameFromKey(key); - - const matchPropValue = (record: Record): string => { - if (!record) { - return undefined; - } - const index = Object.values(record).findIndex((version) => version === key); - if (index > -1) { - return Object.keys(record)[index]; - } - // check if non aliased name is found - if ( - record[originalPackageName] && - key.startsWith(`/${originalPackageName}/${record[originalPackageName]}`) - ) { - return originalPackageName; - } - }; - - const matchedDependencyName = ( - importer: Partial - ): string => { - return ( - matchPropValue(importer.dependencies) || - matchPropValue(importer.optionalDependencies) || - matchPropValue(importer.peerDependencies) - ); - }; - - // snapshot already has a name - if (snapshot.name) { - packageNames.add(snapshot.name); - } - // it'a a root dependency - const rootDependencyName = - matchedDependencyName(data.importers['.']) || - // only root importers have devDependencies - matchPropValue(data.importers['.'].devDependencies); - if (rootDependencyName) { - packageNames.add(rootDependencyName); - } - // find a snapshot that has a dependency that points to this snapshot - const snapshots = Object.values(data.packages); - for (let i = 0; i < snapshots.length; i++) { - const dependencyName = matchedDependencyName(snapshots[i]); - if (dependencyName) { - packageNames.add(dependencyName); - } - } - if (packageNames.size === 0) { - packageNames.add(originalPackageName); - } - return Array.from(packageNames); -} - function getVersion(key: string, packageName: string): string { const KEY_NAME_SEPARATOR_LENGTH = 2; // leading and trailing slash