diff --git a/packages/nx/src/plugins/js/lock-file/lock-file.ts b/packages/nx/src/plugins/js/lock-file/lock-file.ts index bada01072549e..8dafa3d49f2f1 100644 --- a/packages/nx/src/plugins/js/lock-file/lock-file.ts +++ b/packages/nx/src/plugins/js/lock-file/lock-file.ts @@ -14,7 +14,7 @@ import { workspaceRoot } from '../../../utils/workspace-root'; import { ProjectGraph } from '../../../config/project-graph'; import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder'; import { PackageJson } from '../../../utils/package-json'; -import { fileHasher, hashArray } from '../../../hasher/file-hasher'; +import { hashArray } from '../../../hasher/file-hasher'; import { output } from '../../../utils/output'; import { parseNpmLockfile, stringifyNpmLockfile } from './npm-parser'; @@ -22,6 +22,7 @@ import { parsePnpmLockfile, stringifyPnpmLockfile } from './pnpm-parser'; import { parseYarnLockfile, stringifyYarnLockfile } from './yarn-parser'; import { pruneProjectGraph } from './project-graph-pruning'; import { normalizePackageJson } from './utils/package-json'; +import { readJsonFile } from '../../../utils/fileutils'; const YARN_LOCK_FILE = 'yarn.lock'; const NPM_LOCK_FILE = 'package-lock.json'; @@ -86,7 +87,8 @@ export function parseLockFile( try { if (packageManager === 'yarn') { const content = readFileSync(YARN_LOCK_PATH, 'utf8'); - parseYarnLockfile(content, builder); + const packageJson = readJsonFile('package.json'); + parseYarnLockfile(content, packageJson, builder); return builder.getUpdatedProjectGraph(); } if (packageManager === 'pnpm') { @@ -145,19 +147,19 @@ export function createLockFile( ): string { const normalizedPackageJson = normalizePackageJson(packageJson); const content = readFileSync(getLockFileName(packageManager), 'utf8'); + const rootPackageJson = readJsonFile('package.json'); const builder = new ProjectGraphBuilder(); try { if (packageManager === 'yarn') { - parseYarnLockfile(content, builder); + parseYarnLockfile(content, rootPackageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, packageJson); return stringifyYarnLockfile(prunedGraph, content, normalizedPackageJson); } if (packageManager === 'pnpm') { parsePnpmLockfile(content, builder); - const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, packageJson); return stringifyPnpmLockfile(prunedGraph, content, normalizedPackageJson); 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 567c1e3a9d8a9..9192ad4de64f9 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 @@ -157,6 +157,7 @@ describe('yarn LockFile utility', () => { }); let lockFile; + let packageJson; let graph: ProjectGraph; @@ -166,7 +167,11 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/nextjs/yarn.lock' )).default; - parseYarnLockfile(lockFile, builder); + packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/nextjs/package.json' + )); + parseYarnLockfile(lockFile, packageJson, builder); graph = builder.getUpdatedProjectGraph(); }); @@ -219,10 +224,141 @@ describe('yarn LockFile utility', () => { describe('auxiliary packages', () => { beforeEach(() => { const fileSys = { + 'node_modules/@eslint/eslintrc/package.json': '{"version": "1.3.3"}', + 'node_modules/@humanwhocodes/config-array/package.json': + '{"version": "0.11.7"}', + 'node_modules/@humanwhocodes/module-importer/package.json': + '{"version": "1.0.1"}', + 'node_modules/@humanwhocodes/object-schema/package.json': + '{"version": "1.2.1"}', + 'node_modules/@nodelib/fs.scandir/package.json': '{"version": "2.1.5"}', + 'node_modules/@nodelib/fs.stat/package.json': '{"version": "2.0.5"}', + 'node_modules/@nodelib/fs.walk/package.json': '{"version": "1.2.8"}', + 'node_modules/@nrwl/devkit/package.json': '{"version": "15.0.13"}', + 'node_modules/@phenomnomnominal/tsquery/package.json': + '{"version": "4.1.1"}', + 'node_modules/acorn/package.json': '{"version": "8.8.1"}', + 'node_modules/acorn-jsx/package.json': '{"version": "5.3.2"}', + 'node_modules/ajv/package.json': '{"version": "6.12.6"}', + 'node_modules/ansi-regex/package.json': '{"version": "5.0.1"}', + 'node_modules/ansi-styles/package.json': '{"version": "4.3.0"}', + 'node_modules/app-root-path/package.json': '{"version": "3.1.0"}', + 'node_modules/argparse/package.json': '{"version": "2.0.1"}', + 'node_modules/async/package.json': '{"version": "3.2.4"}', + 'node_modules/balanced-match/package.json': '{"version": "1.0.2"}', 'node_modules/brace-expansion/package.json': '{"version": "1.1.11"}', + 'node_modules/callsites/package.json': '{"version": "3.1.0"}', + 'node_modules/chalk/package.json': '{"version": "4.1.2"}', + '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/concat-map/package.json': '{"version": "0.0.1"}', + 'node_modules/cross-spawn/package.json': '{"version": "7.0.3"}', + 'node_modules/debug/package.json': '{"version": "4.3.4"}', + 'node_modules/deep-is/package.json': '{"version": "0.1.4"}', + 'node_modules/doctrine/package.json': '{"version": "3.0.0"}', + 'node_modules/ejs/package.json': '{"version": "3.1.8"}', + 'node_modules/emoji-regex/package.json': '{"version": "8.0.0"}', + 'node_modules/escalade/package.json': '{"version": "3.1.1"}', + 'node_modules/escape-string-regexp/package.json': + '{"version": "4.0.0"}', + 'node_modules/eslint/package.json': '{"version": "8.29.0"}', + 'node_modules/eslint-plugin-disable-autofix/package.json': + '{"version": "3.0.0"}', + 'node_modules/eslint-rule-composer/package.json': + '{"version": "0.3.0"}', + 'node_modules/eslint-scope/package.json': '{"version": "7.1.1"}', + 'node_modules/eslint-utils/package.json': '{"version": "3.0.0"}', 'node_modules/eslint-visitor-keys/package.json': '{"version": "3.3.0"}', - 'node_modules/ignore/package.json': '{"version": "5.2.4"}', + 'node_modules/espree/package.json': '{"version": "9.4.1"}', + 'node_modules/esquery/package.json': '{"version": "1.4.0"}', + 'node_modules/esrecurse/package.json': '{"version": "4.3.0"}', + 'node_modules/estraverse/package.json': '{"version": "5.3.0"}', + 'node_modules/esutils/package.json': '{"version": "2.0.3"}', + 'node_modules/fast-deep-equal/package.json': '{"version": "3.1.3"}', + 'node_modules/fast-json-stable-stringify/package.json': + '{"version": "2.1.0"}', + 'node_modules/fast-levenshtein/package.json': '{"version": "2.0.6"}', + 'node_modules/fastq/package.json': '{"version": "1.14.0"}', + 'node_modules/file-entry-cache/package.json': '{"version": "6.0.1"}', + 'node_modules/filelist/package.json': '{"version": "1.0.4"}', + 'node_modules/find-up/package.json': '{"version": "5.0.0"}', + 'node_modules/flat-cache/package.json': '{"version": "3.0.4"}', + 'node_modules/flatted/package.json': '{"version": "3.2.7"}', + 'node_modules/fs.realpath/package.json': '{"version": "1.0.0"}', + 'node_modules/get-caller-file/package.json': '{"version": "2.0.5"}', + 'node_modules/glob/package.json': '{"version": "7.2.3"}', + 'node_modules/glob-parent/package.json': '{"version": "6.0.2"}', + 'node_modules/globals/package.json': '{"version": "13.18.0"}', + 'node_modules/grapheme-splitter/package.json': '{"version": "1.0.4"}', + 'node_modules/has-flag/package.json': '{"version": "4.0.0"}', + 'node_modules/ignore/package.json': '{"version": "5.2.1"}', + 'node_modules/import-fresh/package.json': '{"version": "3.3.0"}', + 'node_modules/imurmurhash/package.json': '{"version": "0.1.4"}', + 'node_modules/inflight/package.json': '{"version": "1.0.6"}', + 'node_modules/inherits/package.json': '{"version": "2.0.4"}', + 'node_modules/is-extglob/package.json': '{"version": "2.1.1"}', + 'node_modules/is-fullwidth-code-point/package.json': + '{"version": "3.0.0"}', + 'node_modules/is-glob/package.json': '{"version": "4.0.3"}', + 'node_modules/is-path-inside/package.json': '{"version": "3.0.3"}', + 'node_modules/isexe/package.json': '{"version": "2.0.0"}', + 'node_modules/jake/package.json': '{"version": "10.8.5"}', + 'node_modules/js-sdsl/package.json': '{"version": "4.2.0"}', + 'node_modules/js-tokens/package.json': '{"version": "4.0.0"}', + 'node_modules/js-yaml/package.json': '{"version": "4.1.0"}', + 'node_modules/json-schema-traverse/package.json': + '{"version": "0.4.1"}', + 'node_modules/json-stable-stringify-without-jsonify/package.json': + '{"version": "1.0.1"}', + 'node_modules/levn/package.json': '{"version": "0.4.1"}', + 'node_modules/locate-path/package.json': '{"version": "6.0.0"}', + 'node_modules/lodash.merge/package.json': '{"version": "4.6.2"}', + 'node_modules/loose-envify/package.json': '{"version": "1.4.0"}', + 'node_modules/lru-cache/package.json': '{"version": "6.0.0"}', 'node_modules/minimatch/package.json': '{"version": "3.1.2"}', + 'node_modules/ms/package.json': '{"version": "2.1.2"}', + 'node_modules/natural-compare/package.json': '{"version": "1.4.0"}', + 'node_modules/once/package.json': '{"version": "1.4.0"}', + 'node_modules/optionator/package.json': '{"version": "0.9.1"}', + 'node_modules/p-limit/package.json': '{"version": "3.1.0"}', + 'node_modules/p-locate/package.json': '{"version": "5.0.0"}', + 'node_modules/parent-module/package.json': '{"version": "1.0.1"}', + 'node_modules/path-exists/package.json': '{"version": "4.0.0"}', + 'node_modules/path-is-absolute/package.json': '{"version": "1.0.1"}', + 'node_modules/path-key/package.json': '{"version": "3.1.1"}', + 'node_modules/postgres/package.json': '{"version": "3.2.4"}', + 'node_modules/prelude-ls/package.json': '{"version": "1.2.1"}', + 'node_modules/punycode/package.json': '{"version": "2.1.1"}', + 'node_modules/queue-microtask/package.json': '{"version": "1.2.3"}', + 'node_modules/react/package.json': '{"version": "18.2.0"}', + 'node_modules/regexpp/package.json': '{"version": "3.2.0"}', + 'node_modules/require-directory/package.json': '{"version": "2.1.1"}', + 'node_modules/resolve-from/package.json': '{"version": "4.0.0"}', + 'node_modules/reusify/package.json': '{"version": "1.0.4"}', + 'node_modules/rimraf/package.json': '{"version": "3.0.2"}', + 'node_modules/run-parallel/package.json': '{"version": "1.2.0"}', + 'node_modules/semver/package.json': '{"version": "7.3.4"}', + 'node_modules/shebang-command/package.json': '{"version": "2.0.0"}', + 'node_modules/shebang-regex/package.json': '{"version": "3.0.0"}', + 'node_modules/string-width/package.json': '{"version": "4.2.3"}', + 'node_modules/strip-ansi/package.json': '{"version": "6.0.1"}', + 'node_modules/strip-json-comments/package.json': '{"version": "3.1.1"}', + 'node_modules/supports-color/package.json': '{"version": "7.2.0"}', + 'node_modules/text-table/package.json': '{"version": "0.2.0"}', + 'node_modules/tslib/package.json': '{"version": "2.4.1"}', + 'node_modules/type-check/package.json': '{"version": "0.4.0"}', + 'node_modules/type-fest/package.json': '{"version": "0.20.2"}', + 'node_modules/uri-js/package.json': '{"version": "4.4.1"}', + 'node_modules/which/package.json': '{"version": "2.0.2"}', + 'node_modules/word-wrap/package.json': '{"version": "1.2.3"}', + 'node_modules/wrap-ansi/package.json': '{"version": "7.0.0"}', + 'node_modules/wrappy/package.json': '{"version": "1.0.2"}', + 'node_modules/y18n/package.json': '{"version": "5.0.8"}', + 'node_modules/yallist/package.json': '{"version": "4.0.0"}', + 'node_modules/yargs/package.json': '{"version": "17.6.2"}', + 'node_modules/yargs-parser/package.json': '{"version": "21.1.1"}', + 'node_modules/yocto-queue/package.json': '{"version": "0.1.0"}', }; vol.fromJSON(fileSys, '/root'); }); @@ -232,8 +368,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/auxiliary-packages/yarn.lock' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(classicLockFile, builder); + parseYarnLockfile(classicLockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(127); @@ -289,6 +429,10 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/auxiliary-packages/yarn.lock' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/package.json' + )); const normalizedPackageJson = { name: 'test', version: '0.0.0', @@ -311,7 +455,7 @@ describe('yarn LockFile utility', () => { )).default; const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson); const result = stringifyYarnLockfile( @@ -349,7 +493,7 @@ describe('yarn LockFile utility', () => { )).default; const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, normalizedPackageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson); const result = stringifyYarnLockfile( @@ -370,8 +514,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/auxiliary-packages/yarn-berry.lock' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(berryLockFile, builder); + parseYarnLockfile(berryLockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(129); @@ -448,9 +596,13 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/auxiliary-packages/yarn-berry.lock.pruned' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson); const result = stringifyYarnLockfile( @@ -464,6 +616,81 @@ describe('yarn LockFile utility', () => { }); }); + describe('auxiliary packages PnP', () => { + it('should parse yarn berry pnp', () => { + const berryLockFile = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/yarn-berry.lock' + )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/auxiliary-packages/package.json' + )); + const builder = new ProjectGraphBuilder(); + parseYarnLockfile(berryLockFile, packageJson, builder); + const graph = builder.getUpdatedProjectGraph(); + expect(Object.keys(graph.externalNodes).length).toEqual(129); + + expect(graph.externalNodes['npm:react']).toMatchInlineSnapshot(` + { + "data": { + "hash": "88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b", + "packageName": "react", + "version": "18.2.0", + }, + "name": "npm:react", + "type": "npm", + } + `); + + expect(graph.externalNodes['npm:typescript']).toMatchInlineSnapshot(` + { + "data": { + "hash": "ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db", + "packageName": "typescript", + "version": "4.8.4", + }, + "name": "npm:typescript", + "type": "npm", + } + `); + expect(graph.externalNodes['npm:@nrwl/devkit']).toMatchInlineSnapshot(` + { + "data": { + "hash": "7dcc3600998448c496228e062d7edd8ecf959fa1ddb9721e91bb1f60f1a2284fd0e12e09edc022170988e2fb54acf101c79dc09fe9c54a21c9941e682eb73b92", + "packageName": "@nrwl/devkit", + "version": "15.0.13", + }, + "name": "npm:@nrwl/devkit", + "type": "npm", + } + `); + expect(graph.externalNodes['npm:postgres']).toMatchInlineSnapshot(` + { + "data": { + "hash": "521660853e0c9f1c604cf43d32c75e2b4675e2d912eaec7bb6749716539dd53f1dfaf575a422087f6a53362f5162f9a4b8a88cc1dadf9d7580423fc05137767a", + "packageName": "postgres", + "version": "https://github.com/charsleysa/postgres.git#commit=3b1a01b2da3e2fafb1a79006f838eff11a8de3cb", + }, + "name": "npm:postgres", + "type": "npm", + } + `); + expect(graph.externalNodes['npm:eslint-plugin-disable-autofix']) + .toMatchInlineSnapshot(` + { + "data": { + "hash": "fb7272c37e5701df14a79d0f8a9d6a0cb521972011ba91d70290eefc33fca589307908a6fb63e2985257b1c7cc3839c076d1c8def0caabddf21a91f13d7c8fc1", + "packageName": "eslint-plugin-disable-autofix", + "version": "npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0", + }, + "name": "npm:eslint-plugin-disable-autofix", + "type": "npm", + } + `); + }); + }); + describe('duplicate packages', () => { beforeEach(() => { const fileSys = { @@ -507,8 +734,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/duplicate-package/yarn.lock' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/duplicate-package/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(classicLockFile, builder); + parseYarnLockfile(classicLockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(371); }); @@ -538,7 +769,7 @@ describe('yarn LockFile utility', () => { '__fixtures__/optional/package.json' )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(103); @@ -716,8 +947,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/pruning/typescript/package.json' )); + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/pruning/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, typescriptPackageJson); const result = stringifyYarnLockfile( @@ -743,8 +978,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/pruning/devkit-yargs/package.json' )); + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/pruning/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); const prunedGraph = pruneProjectGraph(graph, multiPackageJson); const result = stringifyYarnLockfile( @@ -776,8 +1015,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/workspaces/yarn.lock' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/workspaces/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(5); }); @@ -787,8 +1030,12 @@ describe('yarn LockFile utility', () => { __dirname, '__fixtures__/workspaces/yarn.lock.berry' )).default; + const packageJson = require(joinPathFragments( + __dirname, + '__fixtures__/workspaces/package.json' + )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(Object.keys(graph.externalNodes).length).toEqual(5); }); @@ -809,6 +1056,17 @@ describe('yarn LockFile utility', () => { types: './src/index.d.ts', }; + beforeEach(() => { + const fileSys = { + 'node_modules/@gitlab-examples/semantic-release-npm/package.json': + '{"version": "2.0.1"}', + 'node_modules/tslib/package.json': '{"version": "2.5.0"}', + 'node_modules/type-fest/package.json': '{"version": "0.20.2"}', + 'node_modules/@gar/promisify/package.json': '{"version": "1.1.3"}', + }; + vol.fromJSON(fileSys, '/root'); + }); + it('should parse and prune classic', () => { const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 @@ -830,8 +1088,21 @@ type-fest@^0.20.2: integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== `; + const packageJson: PackageJson = { + name: '@my-ns/example', + version: '0.0.1', + type: 'commonjs', + dependencies: { + '@gitlab-examples/semantic-release-npm': '^2.0.1', + 'type-fest': '^0.20.2', + }, + peerDependencies: { + tslib: '^2.4.0', + }, + }; + const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(graph.externalNodes['npm:tslib']).toMatchInlineSnapshot(` { @@ -905,8 +1176,22 @@ __metadata: languageName: node linkType: hard `; + + const packageJson: PackageJson = { + name: '@my-ns/example', + version: '0.0.1', + type: 'commonjs', + dependencies: { + '@gitlab-examples/semantic-release-npm': '^2.0.1', + '@gar/promisify': '^1.1.3', + }, + peerDependencies: { + tslib: '^2.4.0', + }, + }; + const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(graph.externalNodes['npm:tslib']).toMatchInlineSnapshot(` { @@ -973,7 +1258,21 @@ __metadata: describe('mixed keys', () => { beforeEach(() => { const fileSys = { - 'node_modules/wrap-ansi/package.json': '{"version": "7.0.0"}', + '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"}', }; vol.fromJSON(fileSys, '/root'); @@ -990,7 +1289,7 @@ __metadata: )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(graph.externalNodes).toMatchInlineSnapshot(` { @@ -1003,13 +1302,13 @@ __metadata: "name": "npm:@isaacs/cliui", "type": "npm", }, - "npm:ansi-regex@5.0.1": { + "npm:ansi-regex": { "data": { "hash": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "packageName": "ansi-regex", "version": "5.0.1", }, - "name": "npm:ansi-regex@5.0.1", + "name": "npm:ansi-regex", "type": "npm", }, "npm:ansi-regex@6.0.1": { @@ -1021,13 +1320,13 @@ __metadata: "name": "npm:ansi-regex@6.0.1", "type": "npm", }, - "npm:ansi-styles@4.3.0": { + "npm:ansi-styles": { "data": { "hash": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "packageName": "ansi-styles", "version": "4.3.0", }, - "name": "npm:ansi-styles@4.3.0", + "name": "npm:ansi-styles", "type": "npm", }, "npm:ansi-styles@6.2.1": { @@ -1075,13 +1374,13 @@ __metadata: "name": "npm:eastasianwidth", "type": "npm", }, - "npm:emoji-regex@8.0.0": { + "npm:emoji-regex": { "data": { "hash": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "packageName": "emoji-regex", "version": "8.0.0", }, - "name": "npm:emoji-regex@8.0.0", + "name": "npm:emoji-regex", "type": "npm", }, "npm:emoji-regex@9.2.2": { @@ -1102,6 +1401,15 @@ __metadata: "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==", @@ -1120,13 +1428,13 @@ __metadata: "name": "npm:string-width@4.2.3", "type": "npm", }, - "npm:string-width@5.1.2": { + "npm:strip-ansi": { "data": { - "hash": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "packageName": "string-width", - "version": "5.1.2", + "hash": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "packageName": "strip-ansi", + "version": "7.0.1", }, - "name": "npm:string-width@5.1.2", + "name": "npm:strip-ansi", "type": "npm", }, "npm:strip-ansi-cjs": { @@ -1147,20 +1455,11 @@ __metadata: "name": "npm:strip-ansi@6.0.1", "type": "npm", }, - "npm:strip-ansi@7.0.1": { - "data": { - "hash": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "packageName": "strip-ansi", - "version": "7.0.1", - }, - "name": "npm:strip-ansi@7.0.1", - "type": "npm", - }, "npm:wrap-ansi": { "data": { - "hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "packageName": "wrap-ansi", - "version": "7.0.0", + "version": "8.1.0", }, "name": "npm:wrap-ansi", "type": "npm", @@ -1174,13 +1473,13 @@ __metadata: "name": "npm:wrap-ansi-cjs", "type": "npm", }, - "npm:wrap-ansi@8.1.0": { + "npm:wrap-ansi@7.0.0": { "data": { - "hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "packageName": "wrap-ansi", - "version": "8.1.0", + "version": "7.0.0", }, - "name": "npm:wrap-ansi@8.1.0", + "name": "npm:wrap-ansi@7.0.0", "type": "npm", }, } @@ -1202,7 +1501,7 @@ __metadata: )); const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(graph.externalNodes).toMatchInlineSnapshot(` { @@ -1215,13 +1514,13 @@ __metadata: "name": "npm:@isaacs/cliui", "type": "npm", }, - "npm:ansi-regex@5.0.1": { + "npm:ansi-regex": { "data": { "hash": "2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b", "packageName": "ansi-regex", "version": "5.0.1", }, - "name": "npm:ansi-regex@5.0.1", + "name": "npm:ansi-regex", "type": "npm", }, "npm:ansi-regex@6.0.1": { @@ -1233,13 +1532,13 @@ __metadata: "name": "npm:ansi-regex@6.0.1", "type": "npm", }, - "npm:ansi-styles@4.3.0": { + "npm:ansi-styles": { "data": { "hash": "513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4", "packageName": "ansi-styles", "version": "4.3.0", }, - "name": "npm:ansi-styles@4.3.0", + "name": "npm:ansi-styles", "type": "npm", }, "npm:ansi-styles@6.2.1": { @@ -1287,13 +1586,13 @@ __metadata: "name": "npm:eastasianwidth", "type": "npm", }, - "npm:emoji-regex@8.0.0": { + "npm:emoji-regex": { "data": { "hash": "d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192", "packageName": "emoji-regex", "version": "8.0.0", }, - "name": "npm:emoji-regex@8.0.0", + "name": "npm:emoji-regex", "type": "npm", }, "npm:emoji-regex@9.2.2": { @@ -1314,6 +1613,15 @@ __metadata: "name": "npm:is-fullwidth-code-point", "type": "npm", }, + "npm:string-width": { + "data": { + "hash": "7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193", + "packageName": "string-width", + "version": "5.1.2", + }, + "name": "npm:string-width", + "type": "npm", + }, "npm:string-width-cjs": { "data": { "hash": "e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb", @@ -1332,15 +1640,6 @@ __metadata: "name": "npm:string-width@4.2.3", "type": "npm", }, - "npm:string-width@5.1.2": { - "data": { - "hash": "7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193", - "packageName": "string-width", - "version": "5.1.2", - }, - "name": "npm:string-width@5.1.2", - "type": "npm", - }, "npm:strip-ansi-cjs": { "data": { "hash": "f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c", @@ -1370,9 +1669,9 @@ __metadata: }, "npm:wrap-ansi": { "data": { - "hash": "a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b", + "hash": "371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238", "packageName": "wrap-ansi", - "version": "7.0.0", + "version": "8.1.0", }, "name": "npm:wrap-ansi", "type": "npm", @@ -1386,13 +1685,13 @@ __metadata: "name": "npm:wrap-ansi-cjs", "type": "npm", }, - "npm:wrap-ansi@8.1.0": { + "npm:wrap-ansi@7.0.0": { "data": { - "hash": "371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238", + "hash": "a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b", "packageName": "wrap-ansi", - "version": "8.1.0", + "version": "7.0.0", }, - "name": "npm:wrap-ansi@8.1.0", + "name": "npm:wrap-ansi@7.0.0", "type": "npm", }, } @@ -1405,6 +1704,18 @@ __metadata: }); describe('invalid resolved', () => { + beforeEach(() => { + const fileSys = { + 'node_modules/@octokit/request-error/package.json': + '{"version": "3.0.3"}', + 'node_modules/@octokit/types/package.json': '{"version": "9.2.0"}', + 'node_modules/@octokit/webhooks-types/package.json': + '{"version": "5.8.0"}', + 'node_modules/@octokit/webhooks/package.json': '{"version": "9.26.0"}', + }; + vol.fromJSON(fileSys, '/root'); + }); + it('should parse yarn.lock with invalid resolved field', () => { const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 @@ -1440,8 +1751,21 @@ __metadata: "@octokit/webhooks-types" "5.8.0" aggregate-error "^3.1.0" `; + + const packageJson: PackageJson = { + name: '@my-ns/example', + version: '0.0.1', + type: 'commonjs', + dependencies: { + '@octokit/request-error': '^3', + '@octokit/types': '^9', + '@octokit/webhooks-types': '5.8.0', + '@octokit/webhooks': '^9.8.4', + }, + }; + const builder = new ProjectGraphBuilder(); - parseYarnLockfile(lockFile, builder); + parseYarnLockfile(lockFile, packageJson, builder); const graph = builder.getUpdatedProjectGraph(); expect(graph.externalNodes).toMatchInlineSnapshot(` { 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 271d5246a4506..3de74ed6373ac 100644 --- a/packages/nx/src/plugins/js/lock-file/yarn-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/yarn-parser.ts @@ -1,6 +1,6 @@ import { getHoistedPackageVersion } from './utils/package-json'; import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder'; -import { satisfies, Range } from 'semver'; +import { satisfies, Range, gt } from 'semver'; import { NormalizedPackageJson } from './utils/package-json'; import { ProjectGraph, @@ -37,6 +37,7 @@ type YarnDependency = { export function parseYarnLockfile( lockFileContent: string, + packageJson: NormalizedPackageJson, builder: ProjectGraphBuilder ) { const { parseSyml } = require('@yarnpkg/parsers'); @@ -44,7 +45,7 @@ export function parseYarnLockfile( // we use key => node map to avoid duplicate work when parsing keys const keyMap = new Map(); - addNodes(data, builder, keyMap); + addNodes(data, packageJson, builder, keyMap); addDependencies(data, builder, keyMap); } @@ -59,11 +60,18 @@ function getPackageNames(keys: string): string[] { function addNodes( { __metadata, ...dependencies }: YarnLockFile, + packageJson: NormalizedPackageJson, builder: ProjectGraphBuilder, keyMap: Map ) { const isBerry = !!__metadata; const nodes: Map> = new Map(); + const combinedDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + ...packageJson.peerDependencies, + ...packageJson.optionalDependencies, + }; Object.entries(dependencies).forEach(([keys, snapshot]) => { // ignore workspace projects & patches @@ -103,23 +111,22 @@ function addNodes( }; keyMap.set(key, node); + // use actual version so we can detect it later based on npm package's version + const mapKey = + snapshot.version && version !== snapshot.version + ? snapshot.version + : version; if (!nodes.has(packageName)) { - nodes.set(packageName, new Map([[version, node]])); + nodes.set(packageName, new Map([[mapKey, node]])); } else { - nodes.get(packageName).set(version, node); + nodes.get(packageName).set(mapKey, node); } }); }); }); for (const [packageName, versionMap] of nodes.entries()) { - let hoistedNode: ProjectGraphExternalNode; - if (versionMap.size === 1) { - hoistedNode = versionMap.values().next().value; - } else { - const hoistedVersion = getHoistedVersion(packageName); - hoistedNode = versionMap.get(hoistedVersion); - } + const hoistedNode = findHoistedNode(packageName, versionMap, combinedDeps); if (hoistedNode) { hoistedNode.name = `npm:${packageName}`; } @@ -130,6 +137,44 @@ function addNodes( } } +function findHoistedNode( + packageName: string, + versionMap: Map, + combinedDeps: Record +): ProjectGraphExternalNode { + const hoistedVersion = getHoistedVersion(packageName); + if (hoistedVersion) { + return versionMap.get(hoistedVersion); + } + const rootVersionSpecifier = combinedDeps[packageName]; + if (!rootVersionSpecifier) { + return; + } + const versions = Array.from(versionMap.keys()).sort((a, b) => + gt(a, b) ? -1 : 1 + ); + // take the highest version found + if (rootVersionSpecifier === '*') { + return versionMap.get(versions[0]); + } + // take version that satisfies the root version specifier + let version = versions.find((v) => satisfies(v, rootVersionSpecifier)); + if (!version) { + // try to find alias version + version = versions.find( + (v) => + versionMap.get(v).name === `npm:${packageName}@${rootVersionSpecifier}` + ); + } + if (!version) { + // try to find tarball package + version = versions.find((v) => versionMap.get(v).data.version !== v); + } + if (version) { + return versionMap.get(version); + } +} + function findVersion( packageName: string, key: string,