From 75a69d93d067f2f14fa76a1d290af3b92d616d96 Mon Sep 17 00:00:00 2001 From: Denis Akiyakov Date: Mon, 24 Feb 2025 19:13:14 +0400 Subject: [PATCH] fix(core): improve packages recognition when the package version is an external package (#29529) ## Current Behavior After updating from nx@20.0.12 to nx@20.3.0 our docusaurus dep starts blocking us from running any nx command because of the error described in the linked issue ```bash The "nx/js/dependencies-and-lockfile" plugin threw an error while creating dependencies: Target project does not exist: npm:react-helmet-async@npm:@slorber/react-helmet-async@* ``` ## Expected Behavior Packages with such deps, like: `"react-helmet-async": "npm:@slorber/react-helmet-async@*"` shouldn't break any nx command ## Related Issue(s) Fixes #27285 --- .../plugins/js/lock-file/yarn-parser.spec.ts | 119 +++++++++++++++++- .../src/plugins/js/lock-file/yarn-parser.ts | 30 +++-- 2 files changed, 135 insertions(+), 14 deletions(-) diff --git a/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts b/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts index 7f0da52f40ebe7..700bb84ab3c750 100644 --- a/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts +++ b/packages/nx/src/plugins/js/lock-file/yarn-parser.spec.ts @@ -937,6 +937,123 @@ __metadata: } `); }); + + it('should parse external module by yarn classic', () => { + const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@7.26.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.6", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.8", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.25.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + +"@docusaurus/core@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.7.0.tgz#e871586d099093723dfe6de81c1ce610aeb20292" + integrity sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ== + dependencies: + "@docusaurus/module-type-aliases" "3.7.0" + react-helmet-async "npm:@slorber/react-helmet-async@1.3.0" + +"@docusaurus/module-type-aliases@3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz#15c0745b829c6966c5b3b2c2527c72b54830b0e5" + integrity sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg== + dependencies: + react-helmet-async "npm:@slorber/react-helmet-async@*" + +"react-helmet-async@npm:@slorber/react-helmet-async@*", "react-helmet-async@npm:@slorber/react-helmet-async@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz#11fbc6094605cf60aa04a28c17e0aab894b4ecff" + integrity sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A== + dependencies: + "@babel/runtime" "^7.12.5" +`; + + const packageJson: PackageJson = { + name: '@my-ns/example', + version: '0.0.1', + type: 'commonjs', + dependencies: { + '@docusaurus/core': '3.7.0', + }, + }; + + const hash = uniq('mock-hash'); + const externalNodes = getYarnLockfileNodes(lockFile, hash, packageJson); + const pg = { + nodes: {}, + dependencies: {}, + externalNodes, + }; + const ctx: CreateDependenciesContext = { + projects: {}, + externalNodes, + fileMap: { + nonProjectFiles: [], + projectFileMap: {}, + }, + filesToProcess: { + nonProjectFiles: [], + projectFileMap: {}, + }, + nxJsonConfiguration: null, + workspaceRoot: '/virtual', + }; + const dependencies = getYarnLockfileDependencies(lockFile, hash, ctx); + const builder = new ProjectGraphBuilder(pg); + for (const dep of dependencies) { + builder.addDependency( + dep.source, + dep.target, + dep.type, + 'sourceFile' in dep ? dep.sourceFile : null + ); + } + const graph = builder.getUpdatedProjectGraph(); + + expect(graph.externalNodes).toMatchInlineSnapshot(` + { + "npm:@babel/runtime@7.26.0": { + "data": { + "hash": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "packageName": "@babel/runtime", + "version": "7.26.0", + }, + "name": "npm:@babel/runtime@7.26.0", + "type": "npm", + }, + "npm:@docusaurus/core@3.7.0": { + "data": { + "hash": "sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==", + "packageName": "@docusaurus/core", + "version": "3.7.0", + }, + "name": "npm:@docusaurus/core@3.7.0", + "type": "npm", + }, + "npm:@docusaurus/module-type-aliases@3.7.0": { + "data": { + "hash": "sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==", + "packageName": "@docusaurus/module-type-aliases", + "version": "3.7.0", + }, + "name": "npm:@docusaurus/module-type-aliases@3.7.0", + "type": "npm", + }, + "npm:react-helmet-async": { + "data": { + "hash": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", + "packageName": "react-helmet-async", + "version": "npm:@slorber/react-helmet-async@*", + }, + "name": "npm:react-helmet-async", + "type": "npm", + }, + } + `); + }); }); }); @@ -2809,5 +2926,5 @@ __metadata: }); function uniq(str: string) { - return `str-${(Math.random() * 10000).toFixed(0)}`; + return `${str}-${(Math.random() * 10000).toFixed(0)}`; } diff --git a/packages/nx/src/plugins/js/lock-file/yarn-parser.ts b/packages/nx/src/plugins/js/lock-file/yarn-parser.ts index 12c8480e5e8609..6b28f8a3b8352b 100644 --- a/packages/nx/src/plugins/js/lock-file/yarn-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/yarn-parser.ts @@ -74,7 +74,7 @@ export function getYarnLockfileNodes( // yarn classic splits keys when parsing so we need to stich them back together const groupedDependencies = groupDependencies(dependencies, isBerry); - return getNodes(groupedDependencies, packageJson, keyMap, isBerry); + return getNodes(groupedDependencies, packageJson, isBerry); } export function getYarnLockfileDependencies( @@ -92,7 +92,7 @@ export function getYarnLockfileDependencies( // yarn classic splits keys when parsing so we need to stich them back together const groupedDependencies = groupDependencies(dependencies, isBerry); - return getDependencies(groupedDependencies, keyMap, ctx); + return getDependencies(groupedDependencies, ctx); } function getPackageNameKeyPairs(keys: string): Map> { @@ -111,7 +111,6 @@ function getPackageNameKeyPairs(keys: string): Map> { function getNodes( dependencies: Record, packageJson: NormalizedPackageJson, - keyMap: Map, isBerry: boolean ) { const nodes: Map> = new Map(); @@ -131,7 +130,12 @@ function getNodes( nameKeyPairs.forEach((keySet, packageName) => { const keysArray = Array.from(keySet); // use key relevant to the package name - const version = findVersion(packageName, keysArray[0], snapshot, isBerry); + const [version, isAlias] = findVersion( + packageName, + keysArray[0], + snapshot, + isBerry + ); // use keys linked to the extracted package name keysArray.forEach((key) => { @@ -144,9 +148,10 @@ function getNodes( const node: ProjectGraphExternalNode = { type: 'npm', - name: version - ? `npm:${packageName}@${version}` - : `npm:${packageName}`, + name: + version && !isAlias + ? `npm:${packageName}@${version}` + : `npm:${packageName}`, data: { version, packageName, @@ -229,7 +234,7 @@ function findVersion( key: string, snapshot: YarnDependency, isBerry: boolean -): string { +): [string, boolean] | [string] { const versionRange = key.slice(key.indexOf('@', 1) + 1); // check for alias packages const isAlias = isBerry @@ -237,7 +242,7 @@ function findVersion( : versionRange.startsWith('npm:'); if (isAlias) { - return versionRange; + return [versionRange, true]; } // check for berry tarball packages if ( @@ -247,14 +252,14 @@ function findVersion( snapshot.resolution.split('::')[0] !== `${packageName}@npm:${snapshot.version}` ) { - return snapshot.resolution.slice(packageName.length + 1); + return [snapshot.resolution.slice(packageName.length + 1)]; } if (!isBerry && isTarballPackage(versionRange, snapshot)) { - return snapshot.resolved; + return [snapshot.resolved]; } // otherwise it's a standard version - return snapshot.version; + return [snapshot.version]; } // check if snapshot represents tarball package @@ -289,7 +294,6 @@ function getHoistedVersion(packageName: string): string { function getDependencies( dependencies: Record, - keyMap: Map, ctx: CreateDependenciesContext ) { const projectGraphDependencies: RawProjectGraphDependency[] = [];