feat(core): add prune lock file support for npm 2 and 3 (#13120)

This commit is contained in:
Miroslav Jonaš 2022-11-12 22:01:03 +01:00 committed by GitHub
parent b142f385c9
commit fc8de9a4d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 3412 additions and 2649 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,9 @@
exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v1 data to external nodes 1`] = `
Object {
"data": Object {
"hash": "c22918aa52c9fdce78bf82451d8114e00b6d4375a8e5b1d1d96c6cf4755dbe1a",
"hash": "abdb9ef454702b3fae2f8002be40a8c2903514143b45592292ac8920e35cb60a",
"packageName": "yargs",
"version": "17.5.1",
"version": "17.6.2",
},
"name": "npm:yargs",
"type": "npm",
@ -16,7 +16,7 @@ exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v1 da
Array [
Object {
"source": "npm:yargs",
"target": "npm:cliui",
"target": "npm:cliui@8.0.1",
"type": "static",
},
Object {
@ -55,9 +55,9 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v2 data to external nodes 1`] = `
Object {
"data": Object {
"hash": "c22918aa52c9fdce78bf82451d8114e00b6d4375a8e5b1d1d96c6cf4755dbe1a",
"hash": "abdb9ef454702b3fae2f8002be40a8c2903514143b45592292ac8920e35cb60a",
"packageName": "yargs",
"version": "17.5.1",
"version": "17.6.2",
},
"name": "npm:yargs",
"type": "npm",
@ -68,7 +68,7 @@ exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v2 da
Array [
Object {
"source": "npm:yargs",
"target": "npm:cliui",
"target": "npm:cliui@8.0.1",
"type": "static",
},
Object {
@ -107,9 +107,9 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v3 data to external nodes 1`] = `
Object {
"data": Object {
"hash": "c22918aa52c9fdce78bf82451d8114e00b6d4375a8e5b1d1d96c6cf4755dbe1a",
"hash": "abdb9ef454702b3fae2f8002be40a8c2903514143b45592292ac8920e35cb60a",
"packageName": "yargs",
"version": "17.5.1",
"version": "17.6.2",
},
"name": "npm:yargs",
"type": "npm",
@ -120,7 +120,7 @@ exports[`lock-file mapLockFileDataToExternalNodes npm should map lock file v3 da
Array [
Object {
"source": "npm:yargs",
"target": "npm:cliui",
"target": "npm:cliui@8.0.1",
"type": "static",
},
Object {
@ -156,12 +156,204 @@ Array [
]
`;
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex lock file v1 1`] = `
Object {
"data": Object {
"hash": "0e69266e94bbe89cb6c7555e1ab56fb9d4c0e4fe2cbb674b92fc1ee200053526",
"packageName": "nx",
"version": "15.0.13",
},
"name": "npm:nx",
"type": "npm",
}
`;
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex lock file v1 2`] = `
Array [
Object {
"source": "npm:nx",
"target": "npm:@nrwl/cli",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@nrwl/tao",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@parcel/watcher",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@yarnpkg/lockfile",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@yarnpkg/parsers",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@zkochan/js-yaml",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:axios",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:chalk",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:chokidar",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cli-cursor",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cli-spinners",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cliui",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:dotenv",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:enquirer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:fast-glob",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:figures",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:flat",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:fs-extra",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:glob",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:ignore",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:js-yaml@4.1.0",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:jsonc-parser",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:minimatch@3.0.5",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:npm-run-path",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:open",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:semver",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:string-width",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:strong-log-transformer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tar-stream",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tmp",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tsconfig-paths",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tslib",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:v8-compile-cache",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:yargs",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:yargs-parser",
"type": "static",
},
]
`;
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex lock file v2 1`] = `
Object {
"data": Object {
"hash": "8da365ef8d327d868461fda5c863f779c2f75acfe6a591e733b31a2882763af3",
"hash": "64ba4128469ecb7d465d8dbed1ccd86be85c38177e85406f2cd6055f303758fa",
"packageName": "nx",
"version": "14.7.5",
"version": "15.0.13",
},
"name": "npm:nx",
"type": "npm",
@ -187,7 +379,27 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:chalk@4.1.0",
"target": "npm:@yarnpkg/lockfile",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@yarnpkg/parsers",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@zkochan/js-yaml",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:axios",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:chalk",
"type": "static",
},
Object {
@ -252,7 +464,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:js-yaml",
"target": "npm:js-yaml@4.1.0",
"type": "static",
},
Object {
@ -262,7 +474,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:minimatch@3.0.5",
"target": "npm:minimatch",
"type": "static",
},
Object {
@ -285,6 +497,11 @@ Array [
"target": "npm:string-width",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:strong-log-transformer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tar-stream",
@ -317,7 +534,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:yargs-parser@21.0.1",
"target": "npm:yargs-parser",
"type": "static",
},
]
@ -326,9 +543,9 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex lock file v3 1`] = `
Object {
"data": Object {
"hash": "8da365ef8d327d868461fda5c863f779c2f75acfe6a591e733b31a2882763af3",
"hash": "b661da6137e80c92cb5fa75b96780e12868eb81969dddaee67113a99f4ffd159",
"packageName": "nx",
"version": "14.7.5",
"version": "15.0.13",
},
"name": "npm:nx",
"type": "npm",
@ -354,169 +571,22 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:chalk@4.1.0",
"target": "npm:@yarnpkg/lockfile",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:chokidar",
"target": "npm:@yarnpkg/parsers",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cli-cursor",
"target": "npm:@zkochan/js-yaml",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cli-spinners",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:cliui",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:dotenv",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:enquirer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:fast-glob",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:figures",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:flat",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:fs-extra",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:glob",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:ignore",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:js-yaml",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:jsonc-parser",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:minimatch@3.0.5",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:npm-run-path",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:open",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:semver",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:string-width",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tar-stream",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tmp",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tsconfig-paths",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tslib",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:v8-compile-cache",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:yargs",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:yargs-parser@21.0.1",
"type": "static",
},
]
`;
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex npm lock file v1 1`] = `
Object {
"data": Object {
"hash": "62016caa192e50e3af6e4730fa1427455ad431a0e11ad807215c56bee04a8c8e",
"packageName": "nx",
"version": "14.7.5",
},
"name": "npm:nx",
"type": "npm",
}
`;
exports[`lock-file mapLockFileDataToExternalNodes npm should map successfully complex npm lock file v1 2`] = `
Array [
Object {
"source": "npm:nx",
"target": "npm:@nrwl/cli",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@nrwl/tao",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@parcel/watcher",
"target": "npm:axios",
"type": "static",
},
Object {
@ -619,6 +689,11 @@ Array [
"target": "npm:string-width",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:strong-log-transformer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tar-stream",
@ -651,7 +726,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:yargs-parser@21.0.1",
"target": "npm:yargs-parser",
"type": "static",
},
]
@ -660,9 +735,9 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes pnpm should map lock file data to external nodes 1`] = `
Object {
"data": Object {
"hash": "c22918aa52c9fdce78bf82451d8114e00b6d4375a8e5b1d1d96c6cf4755dbe1a",
"hash": "abdb9ef454702b3fae2f8002be40a8c2903514143b45592292ac8920e35cb60a",
"packageName": "yargs",
"version": "17.5.1",
"version": "17.6.2",
},
"name": "npm:yargs",
"type": "npm",
@ -712,9 +787,9 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes pnpm should map successfully complex lock file 1`] = `
Object {
"data": Object {
"hash": "1e61b9db38c17534ce427d40ce84f9c03b4c439253f659e9a48513a10a52b465",
"hash": "21f7648b65146104c6644fcde7073e2edc7e707731b2a38db83cd2882cb274df",
"packageName": "nx",
"version": "14.7.5",
"version": "15.0.13",
},
"name": "npm:nx",
"type": "npm",
@ -738,6 +813,26 @@ Array [
"target": "npm:@parcel/watcher",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@yarnpkg/lockfile",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@yarnpkg/parsers",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:@zkochan/js-yaml",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:axios",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:chalk",
@ -760,7 +855,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:cliui",
"target": "npm:cliui@7.0.4",
"type": "static",
},
Object {
@ -838,6 +933,11 @@ Array [
"target": "npm:string-width",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:strong-log-transformer",
"type": "static",
},
Object {
"source": "npm:nx",
"target": "npm:tar-stream",
@ -855,7 +955,7 @@ Array [
},
Object {
"source": "npm:nx",
"target": "npm:tslib",
"target": "npm:tslib@2.4.1",
"type": "static",
},
Object {
@ -879,7 +979,7 @@ Array [
exports[`lock-file mapLockFileDataToExternalNodes pnpm should map successfully complex lock file 3`] = `
Object {
"data": Object {
"hash": "6c5961e1a6ab89174727587860e480508f945407ce4c2cc7c9f1e46f181e6468",
"hash": "601ac9f28fb76d7e6d1d15600097ca3d8f636990b659c73cf44d3e4030e68c56",
"packageName": "@phenomnomnominal/tsquery",
"version": "4.1.1",
},

View File

@ -14,6 +14,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/@ampproject/remapping",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@ -33,6 +34,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/typescript",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
@ -59,6 +61,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/@ampproject/remapping",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@ -85,6 +88,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/typescript",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
@ -111,6 +115,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/@ampproject/remapping",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
@ -137,6 +142,7 @@ Object {
"dev": true,
"optional": undefined,
"path": "node_modules/typescript",
"peer": undefined,
},
],
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",

View File

@ -5,7 +5,7 @@ Object {
"@ampproject/remapping@2.2.0": Object {
"dependencies": Object {
"@jridgewell/gen-mapping": "0.1.1",
"@jridgewell/trace-mapping": "0.3.15",
"@jridgewell/trace-mapping": "0.3.17",
},
"dev": true,
"engines": Object {
@ -16,7 +16,7 @@ Object {
"dependencyDetails": Object {
"dependencies": Object {
"@jridgewell/gen-mapping": "0.1.1",
"@jridgewell/trace-mapping": "0.3.15",
"@jridgewell/trace-mapping": "0.3.17",
},
},
"isDependency": false,
@ -36,7 +36,7 @@ Object {
exports[`pnpm LockFile utility lock file with inline specifiers should parse lockfile (IS) 2`] = `
Object {
"typescript@4.8.3": Object {
"typescript@4.8.4": Object {
"dev": true,
"engines": Object {
"node": ">=4.2.0",
@ -47,15 +47,15 @@ Object {
"dependencyDetails": Object {},
"isDependency": false,
"isDevDependency": true,
"key": "/typescript/4.8.3",
"key": "/typescript/4.8.4",
"specifier": "~4.8.2",
},
],
"resolution": Object {
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
},
"rootVersion": true,
"version": "4.8.3",
"version": "4.8.4",
},
}
`;
@ -65,7 +65,7 @@ Object {
"@ampproject/remapping@2.2.0": Object {
"dependencies": Object {
"@jridgewell/gen-mapping": "0.1.1",
"@jridgewell/trace-mapping": "0.3.15",
"@jridgewell/trace-mapping": "0.3.17",
},
"dev": true,
"engines": Object {
@ -76,7 +76,7 @@ Object {
"dependencyDetails": Object {
"dependencies": Object {
"@jridgewell/gen-mapping": "0.1.1",
"@jridgewell/trace-mapping": "0.3.15",
"@jridgewell/trace-mapping": "0.3.17",
},
},
"isDependency": false,
@ -96,7 +96,7 @@ Object {
exports[`pnpm LockFile utility standard lock file should parse lockfile correctly 2`] = `
Object {
"typescript@4.8.3": Object {
"typescript@4.8.4": Object {
"dev": true,
"engines": Object {
"node": ">=4.2.0",
@ -107,15 +107,15 @@ Object {
"dependencyDetails": Object {},
"isDependency": false,
"isDevDependency": true,
"key": "/typescript/4.8.3",
"key": "/typescript/4.8.4",
"specifier": "~4.8.2",
},
],
"resolution": Object {
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
},
"rootVersion": true,
"version": "4.8.3",
"version": "4.8.4",
},
}
`;

View File

@ -11,7 +11,7 @@ import {
lockFileV1 as npmLockFileV1,
} from './__fixtures__/npm.lock';
import {
lockFileYargsOnly as pnpmLockFileYargsOnly,
lockFileYargsAndDevkit as pnpmLockFileYargsAndDevkit,
lockFile as pnpmLockFile,
} from './__fixtures__/pnpm.lock';
import {
@ -131,7 +131,7 @@ describe('lock-file', () => {
expect(partialGraph.dependencies['npm:nx']).toMatchSnapshot();
});
it('should map successfully complex npm lock file v1', () => {
it('should map successfully complex lock file v1', () => {
const lockFileData = parseNpmLockFile(npmLockFileV1);
const partialGraph = mapLockFileDataToPartialGraph(lockFileData, 'npm');
@ -156,7 +156,7 @@ describe('lock-file', () => {
});
it('should map lock file data to external nodes', () => {
const lockFileData = parsePnpmLockFile(pnpmLockFileYargsOnly);
const lockFileData = parsePnpmLockFile(pnpmLockFileYargsAndDevkit);
const partialGraph = mapLockFileDataToPartialGraph(
lockFileData,

View File

@ -164,16 +164,17 @@ export function writeLockFile(
export function pruneLockFile(
lockFile: LockFileData,
packages: string[],
projectName?: string,
packageManager: PackageManager = detectPackageManager(workspaceRoot)
): LockFileData {
if (packageManager === 'yarn') {
return pruneYarnLockFile(lockFile, packages);
return pruneYarnLockFile(lockFile, packages, projectName);
}
if (packageManager === 'pnpm') {
return prunePnpmLockFile(lockFile, packages);
return prunePnpmLockFile(lockFile, packages, projectName);
}
if (packageManager === 'npm') {
return pruneNpmLockFile(lockFile, packages);
return pruneNpmLockFile(lockFile, packages, projectName);
}
throw Error(`Unknown package manager: ${packageManager}`);
}

View File

@ -1,5 +1,19 @@
import { parseNpmLockFile, stringifyNpmLockFile } from './npm';
import { lockFileV2, lockFileV1, lockFileV3 } from './__fixtures__/npm.lock';
import {
parseNpmLockFile,
pruneNpmLockFile,
stringifyNpmLockFile,
} from './npm';
import {
lockFileV2,
lockFileV1,
lockFileV3,
lockFileV3JustTypescript,
lockFileV3YargsAndDevkitOnly,
lockFileV2JustTypescript,
lockFileV1JustTypescript,
lockFileV1YargsAndDevkitOnly,
lockFileV2YargsAndDevkitOnly,
} from './__fixtures__/npm.lock';
describe('npm LockFile utility', () => {
describe('v3', () => {
@ -15,9 +29,9 @@ describe('npm LockFile utility', () => {
},
rootPackage: {
devDependencies: {
'@nrwl/cli': '14.7.5',
'@nrwl/workspace': '14.7.5',
nx: '14.7.5',
'@nrwl/cli': '15.0.13',
'@nrwl/workspace': '15.0.13',
nx: '15.0.13',
prettier: '^2.6.2',
typescript: '~4.8.2',
},
@ -26,7 +40,7 @@ describe('npm LockFile utility', () => {
version: '0.0.0',
},
});
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(324);
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(339);
expect(
parsedLockFile.dependencies['@ampproject/remapping']
).toMatchSnapshot();
@ -85,6 +99,34 @@ describe('npm LockFile utility', () => {
it('should match the original file on stringification', () => {
expect(stringifyNpmLockFile(parsedLockFile)).toEqual(lockFileV3);
});
it('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
).length
).toEqual(136);
});
it('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
).toEqual(lockFileV3JustTypescript);
});
it('should correctly prune lockfile with multiple packages', () => {
expect(
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
)
).toEqual(lockFileV3YargsAndDevkitOnly);
});
});
describe('v2', () => {
@ -100,9 +142,9 @@ describe('npm LockFile utility', () => {
},
rootPackage: {
devDependencies: {
'@nrwl/cli': '14.7.5',
'@nrwl/workspace': '14.7.5',
nx: '14.7.5',
'@nrwl/cli': '15.0.13',
'@nrwl/workspace': '15.0.13',
nx: '15.0.13',
prettier: '^2.6.2',
typescript: '~4.8.2',
},
@ -111,7 +153,7 @@ describe('npm LockFile utility', () => {
version: '0.0.0',
},
});
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(324);
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(339);
expect(
parsedLockFile.dependencies['@ampproject/remapping']
).toMatchSnapshot();
@ -170,6 +212,36 @@ describe('npm LockFile utility', () => {
it('should match the original file on stringification', () => {
expect(stringifyNpmLockFile(parsedLockFile)).toEqual(lockFileV2);
});
it('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
).length
).toEqual(136);
});
it('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
).toEqual(lockFileV2JustTypescript);
});
it('should correctly prune lockfile with multiple packages', () => {
const pruned = pruneNpmLockFile(parsedLockFile, [
'yargs',
'@nrwl/devkit',
]);
expect(stringifyNpmLockFile(pruned)).toEqual(
lockFileV2YargsAndDevkitOnly
);
});
});
describe('v1', () => {
@ -184,7 +256,7 @@ describe('npm LockFile utility', () => {
version: '0.0.0',
},
});
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(324);
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(339);
expect(
parsedLockFile.dependencies['@ampproject/remapping']
).toMatchSnapshot();
@ -243,5 +315,33 @@ describe('npm LockFile utility', () => {
it('should match the original file on stringification', () => {
expect(stringifyNpmLockFile(parsedLockFile)).toEqual(lockFileV1);
});
xit('should prune the lock file', () => {
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['typescript']).dependencies
).length
).toEqual(1);
expect(
Object.keys(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
.dependencies
).length
).toEqual(136);
});
xit('should correctly prune lockfile with single package', () => {
expect(
stringifyNpmLockFile(pruneNpmLockFile(parsedLockFile, ['typescript']))
).toEqual(lockFileV1JustTypescript);
});
xit('should correctly prune lockfile with multiple packages', () => {
expect(
stringifyNpmLockFile(
pruneNpmLockFile(parsedLockFile, ['yargs', '@nrwl/devkit'])
)
).toEqual(lockFileV1YargsAndDevkitOnly);
});
});
});

View File

@ -1,10 +1,6 @@
import { satisfies } from 'semver';
import {
LockFileData,
PackageDependency,
PackageVersions,
} from './lock-file-type';
import { sortObject, hashString } from './utils';
import { LockFileData, PackageDependency } from './lock-file-type';
import { sortObject, hashString, TransitiveLookupFunctionInput } from './utils';
type PackageMeta = {
path: string;
@ -22,6 +18,7 @@ type NpmDependency = {
requires?: Record<string, string>;
dependencies?: Record<string, NpmDependency>;
dev?: boolean;
peer?: boolean;
devOptional?: boolean;
optional?: boolean;
};
@ -146,10 +143,12 @@ function mapPackageDependency(
lockfileVersion: number,
isRootVersion?: boolean
) {
const { dev, peer, optional } = value;
const packageMeta = {
path: packagePath,
dev: value.dev,
optional: value.optional,
dev,
peer,
optional,
};
if (!mappedPackages[packageName][key]) {
// const packageDependencies = lockfileVersion === 1 ? requires : dependencies;
@ -262,17 +261,29 @@ export function stringifyNpmLockFile(lockFileData: LockFileData): string {
// remapping the package back to package-lock format
function unmapPackage(packages: Dependencies, dependency: PackageDependency) {
const { packageMeta, rootVersion, version, resolved, integrity, ...value } =
dependency;
const {
packageMeta,
rootVersion,
version,
resolved,
integrity,
dev,
peer,
optional,
...value
} = dependency;
// we need to decompose value, to achieve particular field ordering
for (let i = 0; i < packageMeta.length; i++) {
const { path } = packageMeta[i];
const { path, dev, peer, optional } = packageMeta[i];
// we are sorting the properties to get as close as possible to the original package-lock.json
packages[path] = {
version,
resolved,
integrity,
dev,
peer,
optional,
...value,
};
}
@ -286,7 +297,7 @@ function unmapDependencies(
const { version, resolved, integrity, devOptional } = value;
for (let i = 0; i < packageMeta.length; i++) {
const { path, dev, optional } = packageMeta[i];
const { path, dev, optional, peer } = packageMeta[i];
const projectPath = path.split('node_modules/').slice(1);
const requires = unmapDependencyRequires(value);
@ -303,6 +314,7 @@ function unmapDependencies(
dev,
devOptional,
optional,
peer,
requires,
...innerDeps[packageName],
};
@ -373,27 +385,55 @@ function sortDependencies(
/**
* Returns matching version of the dependency
*/
export function transitiveDependencyNpmLookup(
packageName: string,
parentPackage: string,
versions: PackageVersions,
version: string
): PackageDependency {
const nestedVersion = Object.values(versions).find((v) =>
v.packageMeta.some(
(p) =>
p.path.indexOf(`${parentPackage}/node_modules/${packageName}`) !== -1
)
);
export function transitiveDependencyNpmLookup({
packageName,
parentPackages,
versions,
version,
}: TransitiveLookupFunctionInput): PackageDependency {
const packageDependencies = Object.values(versions);
if (nestedVersion) {
return nestedVersion;
for (let i = 0; i < packageDependencies.length; i++) {
if (satisfies(packageDependencies[i].version, version)) {
const packageMeta = packageDependencies[i].packageMeta.find((p) =>
isPathMatching(p.path, packageName, parentPackages)
);
if (packageMeta) {
return {
...packageDependencies[i],
packageMeta: [packageMeta],
};
}
}
}
// otherwise search for the matching version
// otherwise return the root version
return Object.values(versions).find((v) => v.rootVersion);
}
function isPathMatching(
path: string,
packageName: string,
parentPackages: string[]
): boolean {
const packages = path.split(/\/?node_modules\//).slice(1);
if (packages[packages.length - 1] !== packageName) {
return false;
}
const locations = parentPackages
.map((p) => packages.indexOf(p))
.filter((p) => p !== -1);
if (locations.length === 0) {
return false;
}
for (let i = 0; i < locations.length - 2; i++) {
if (locations[i] > locations[i + 1]) {
return false;
}
}
return true;
}
/**
* Prunes the lock file data based on the list of packages and their transitive dependencies
*
@ -402,11 +442,199 @@ export function transitiveDependencyNpmLookup(
*/
export function pruneNpmLockFile(
lockFileData: LockFileData,
packages: string[]
packages: string[],
projectName?: string
): LockFileData {
// todo(meeroslav): This functionality has not been implemented yet
console.warn(
'Pruning package-lock.json is not yet implemented. Returning entire lock file'
);
return lockFileData;
// NPM V1 does not track full dependency list in the lock file,
// so we can't reuse the lock file to generate a new one
if (lockFileData.lockFileMetadata.metadata.lockfileVersion === 1) {
console.warn(
`npm v7 is required to prune lockfile. Please upgrade to npm v7 or run "npm i --package-lock-only" to generate pruned lockfile.
Returning entire lock file.`
);
return lockFileData;
}
const dependencies = pruneDependencies(lockFileData.dependencies, packages);
const lockFileMetadata = {
...lockFileData.lockFileMetadata,
...pruneRootPackage(lockFileData, packages, projectName),
};
let prunedLockFileData: LockFileData;
prunedLockFileData = { dependencies, lockFileMetadata, hash: '' };
return prunedLockFileData;
}
function pruneRootPackage(
lockFileData: LockFileData,
packages: string[],
projectName?: string
): Record<string, any> {
if (lockFileData.lockFileMetadata.metadata.lockfileVersion === 1) {
return undefined;
}
const rootPackage = {
name: projectName || lockFileData.lockFileMetadata.rootPackage.name,
version: lockFileData.lockFileMetadata.rootPackage.version,
...(lockFileData.lockFileMetadata.rootPackage.license && {
license: lockFileData.lockFileMetadata.rootPackage.license,
}),
dependencies: {} as Record<string, string>,
};
for (const packageName of packages) {
const version = Object.values(lockFileData.dependencies[packageName]).find(
(v) => v.rootVersion
).version;
rootPackage.dependencies[packageName] = version;
}
rootPackage.dependencies = sortObject(rootPackage.dependencies);
return { rootPackage };
}
// iterate over packages to collect the affected tree of dependencies
function pruneDependencies(
dependencies: LockFileData['dependencies'],
packages: string[]
): LockFileData['dependencies'] {
const result: LockFileData['dependencies'] = {};
packages.forEach((packageName) => {
if (dependencies[packageName]) {
const [key, { packageMeta, dev, peer, optional, ...value }] =
Object.entries(dependencies[packageName]).find(
([_, v]) => v.rootVersion
);
result[packageName] = result[packageName] || {};
result[packageName][key] = Object.assign(value, {
packageMeta: [{ path: `node_modules/${packageName}` }],
});
pruneTransitiveDependencies(
[packageName],
dependencies,
result,
result[packageName][key]
);
} else {
console.warn(
`Could not find ${packageName} in the lock file. Skipping...`
);
}
});
return result;
}
// find all transitive dependencies of already pruned packages
// and adds them to the collection
// recursively prune their dependencies
function pruneTransitiveDependencies(
parentPackages: string[],
dependencies: LockFileData['dependencies'],
prunedDeps: LockFileData['dependencies'],
value: PackageDependency,
modifier?: 'dev' | 'optional' | 'peer'
): void {
if (!value.dependencies && !value.peerDependencies) {
return;
}
Object.entries({
...value.dependencies,
...value.devDependencies,
...value.peerDependencies,
...value.optionalDependencies,
}).forEach(([packageName, version]: [string, string]) => {
const versions = dependencies[packageName];
if (versions) {
const dependency = transitiveDependencyNpmLookup({
packageName,
parentPackages,
versions,
version,
});
if (dependency) {
// dev/optional/peer dependencies can be changed during the pruning process
// so we need to update them
if (!prunedDeps[packageName]) {
prunedDeps[packageName] = {};
}
const key = `${packageName}@${dependency.version}`;
if (prunedDeps[packageName][key]) {
const currentMeta = prunedDeps[packageName][key].packageMeta;
if (
!currentMeta.find((p) => p.path === dependency.packageMeta[0].path)
) {
const packageMeta = setPackageMetaModifiers(
packageName,
dependency,
value,
modifier
);
currentMeta.push(packageMeta);
currentMeta.sort();
}
} else {
const packageMeta = setPackageMetaModifiers(
packageName,
dependency,
value,
modifier
);
dependency.packageMeta = [packageMeta];
prunedDeps[packageName][key] = dependency;
// recurively collect dependencies
pruneTransitiveDependencies(
[...parentPackages, packageName],
dependencies,
prunedDeps,
prunedDeps[packageName][key],
getModifier(packageMeta)
);
}
}
}
});
}
function getModifier(
packageMeta: PackageMeta
): 'dev' | 'optional' | 'peer' | undefined {
if (packageMeta.dev) {
return 'dev';
} else if (packageMeta.optional) {
return 'optional';
} else if (packageMeta.peer) {
return 'peer';
}
}
function setPackageMetaModifiers(
packageName: string,
dependency: PackageDependency,
parent: PackageDependency,
modifier?: 'dev' | 'optional' | 'peer'
): PackageMeta {
const packageMeta: PackageMeta = { path: dependency.packageMeta[0].path };
if (parent.devDependencies?.[packageName]) {
packageMeta.dev = true;
} else if (parent.optionalDependencies?.[packageName]) {
packageMeta.optional = true;
} else if (parent.peerDependencies?.[packageName]) {
packageMeta.peer = true;
} else if (modifier === 'dev') {
packageMeta.dev = true;
} else if (modifier === 'optional') {
packageMeta.optional = true;
}
// peer is carried over from the parent
if (modifier === 'peer') {
packageMeta.peer = true;
}
return packageMeta;
}

View File

@ -10,7 +10,7 @@ describe('pnpm LockFile utility', () => {
it('should parse lockfile correctly', () => {
expect(parsedLockFile.lockFileMetadata).toEqual({ lockfileVersion: 5.4 });
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(324);
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(339);
expect(
parsedLockFile.dependencies['@ampproject/remapping']
).toMatchSnapshot();
@ -78,7 +78,7 @@ describe('pnpm LockFile utility', () => {
).toBeUndefined();
expect(
(
parsedLockFile.dependencies['typescript']['typescript@4.8.3']
parsedLockFile.dependencies['typescript']['typescript@4.8.4']
.packageMeta[0] as any
).specifier
).toEqual('~4.8.2');
@ -94,7 +94,7 @@ describe('pnpm LockFile utility', () => {
).toEqual(false);
expect(
(
parsedLockFile.dependencies['typescript']['typescript@4.8.3']
parsedLockFile.dependencies['typescript']['typescript@4.8.4']
.packageMeta[0] as any
).isDevDependency
).toEqual(true);
@ -112,7 +112,7 @@ describe('pnpm LockFile utility', () => {
expect(parsedLockFile.lockFileMetadata).toEqual({
lockfileVersion: '5.4-inlineSpecifiers',
});
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(324);
expect(Object.keys(parsedLockFile.dependencies).length).toEqual(339);
expect(
parsedLockFile.dependencies['@ampproject/remapping']
).toMatchSnapshot();
@ -170,7 +170,7 @@ describe('pnpm LockFile utility', () => {
).toBeUndefined();
expect(
(
parsedLockFile.dependencies['typescript']['typescript@4.8.3']
parsedLockFile.dependencies['typescript']['typescript@4.8.4']
.packageMeta[0] as any
).specifier
).toEqual('~4.8.2');
@ -186,7 +186,7 @@ describe('pnpm LockFile utility', () => {
).toEqual(false);
expect(
(
parsedLockFile.dependencies['typescript']['typescript@4.8.3']
parsedLockFile.dependencies['typescript']['typescript@4.8.4']
.packageMeta[0] as any
).isDevDependency
).toEqual(true);

View File

@ -4,7 +4,12 @@ import {
PackageVersions,
} from './lock-file-type';
import { load, dump } from '@zkochan/js-yaml';
import { sortObject, hashString, isRootVersion } from './utils';
import {
sortObject,
hashString,
isRootVersion,
TransitiveLookupFunctionInput,
} from './utils';
import { satisfies } from 'semver';
type PackageMeta = {
@ -276,12 +281,10 @@ function unmapLockFile(lockFileData: LockFileData): PnpmLockFile {
/**
* Returns matching version of the dependency
*/
export function transitiveDependencyPnpmLookup(
packageName: string,
parentPackage: string,
versions: PackageVersions,
version: string
): PackageDependency {
export function transitiveDependencyPnpmLookup({
versions,
version,
}: TransitiveLookupFunctionInput): PackageDependency {
// pnpm's dependencies always point to the exact version so this block is only for insurrance
return Object.values(versions).find((v) => satisfies(v.version, version));
}
@ -294,11 +297,59 @@ export function transitiveDependencyPnpmLookup(
*/
export function prunePnpmLockFile(
lockFileData: LockFileData,
packages: string[]
packages: string[],
projectName?: string
): LockFileData {
// todo(meeroslav): This functionality has not been implemented yet
console.warn(
'Pruning pnpm-lock.yaml is not yet implemented. Returning entire lock file'
`Pruning pnpm is not yet supported.
Returning entire lock file.`
);
return lockFileData;
// const dependencies = pruneDependencies(
// lockFileData.dependencies,
// packages,
// projectName
// );
// const prunedLockFileData = {
// lockFileMetadata: lockFileData.lockFileMetadata,
// dependencies,
// hash: '',
// };
// return prunedLockFileData;
}
// iterate over packages to collect the affected tree of dependencies
function pruneDependencies(
dependencies: LockFileData['dependencies'],
packages: string[],
projectName?: string
): LockFileData['dependencies'] {
const result: LockFileData['dependencies'] = {};
packages.forEach((packageName) => {
if (dependencies[packageName]) {
const [key, value] = Object.entries(dependencies[packageName]).find(
([_, v]) => v.rootVersion
);
result[packageName] = result[packageName] || {};
result[packageName][key] = value;
pruneTransitiveDependencies([packageName], dependencies, result, value);
} else {
console.warn(
`Could not find ${packageName} in the lock file. Skipping...`
);
}
});
return result;
}
// find all transitive dependencies of already pruned packages
// and adds them to the collection
// recursively prune their dependencies
function pruneTransitiveDependencies(
parentPackages: string[],
dependencies: LockFileData['dependencies'],
prunedDeps: LockFileData['dependencies'],
value: PackageDependency
): void {}

View File

@ -83,11 +83,15 @@ export function getNodeName(
return rootVersion ? `npm:${dep}` : `npm:${dep}@${version}`;
}
export type TransitiveLookupFunctionInput = {
packageName: string;
parentPackages: string[];
versions: PackageVersions;
version: string;
};
type TransitiveLookupFunction = (
packageName: string,
parentPackage: string,
versions: PackageVersions,
version: string
data: TransitiveLookupFunctionInput
) => PackageDependency;
export function mapExternalNodes(
@ -127,7 +131,7 @@ export function mapExternalNodes(
if (combinedDependencies) {
const nodeDependencies = [];
const transitiveDeps = mapTransitiveDependencies(
packageName,
[packageName],
lockFileData.dependencies,
combinedDependencies,
versionCache,
@ -152,7 +156,7 @@ export function mapExternalNodes(
// Finds the maching version of each dependency of the package and
// maps each {package}:{versionRange} pair to "npm:{package}@{version}" (when transitive) or "npm:{package}" (when root)
function mapTransitiveDependencies(
parentPackage: string,
parentPackages: string[],
packages: Record<string, PackageVersions>,
dependencies: Record<string, string>,
versionCache: Record<string, string>,
@ -164,35 +168,37 @@ function mapTransitiveDependencies(
const result: string[] = [];
Object.keys(dependencies).forEach((packageName) => {
const versions = packages[packageName];
// some of the peer dependencies might not be installed,
// we don't have them as nodes in externalNodes
// so there's no need to map them as dependencies
if (!packages[packageName]) {
if (!versions) {
return;
}
// fix for pnpm versions that might have suffixes - `1.2.3_@babel+core@4.5.6`
const cleanVersion = dependencies[packageName].split('_')[0];
const key = `${packageName}@${cleanVersion}`;
const version = dependencies[packageName].split('_')[0];
const key = `${packageName}@${version}`;
// if we already processed this dependency, use the version from the cache
if (versionCache[key]) {
result.push(versionCache[key]);
} else {
const version = packages[packageName][`${packageName}@${cleanVersion}`]
? cleanVersion
: transitiveLookupFn(
const matchedVersion = versions[`${packageName}@${version}`]
? version
: transitiveLookupFn({
packageName,
parentPackage,
packages[packageName],
cleanVersion
)?.version;
parentPackages,
versions,
version,
})?.version;
// for some peer dependencies, we won't find installed version so we'll just ignore these
if (version) {
if (matchedVersion) {
const nodeName = getNodeName(
packageName,
version,
packages[packageName][`${packageName}@${version}`]?.rootVersion
matchedVersion,
versions[`${packageName}@${matchedVersion}`]?.rootVersion
);
result.push(nodeName);
versionCache[key] = nodeName;

View File

@ -5,8 +5,12 @@ import {
PackageDependency,
PackageVersions,
} from './lock-file-type';
import { sortObject, hashString, isRootVersion } from './utils';
import { satisfies } from 'semver';
import {
sortObject,
hashString,
isRootVersion,
TransitiveLookupFunctionInput,
} from './utils';
type LockFileDependencies = Record<
string,
@ -155,12 +159,11 @@ function unmapPackages(
/**
* Returns matching version of the dependency
*/
export function transitiveDependencyYarnLookup(
packageName: string,
parentPackage: string,
versions: PackageVersions,
version: string
): PackageDependency {
export function transitiveDependencyYarnLookup({
packageName,
versions,
version,
}: TransitiveLookupFunctionInput): PackageDependency {
return Object.values(versions).find((v) =>
v.packageMeta.some(
(p) =>
@ -178,7 +181,8 @@ export function transitiveDependencyYarnLookup(
*/
export function pruneYarnLockFile(
lockFileData: LockFileData,
packages: string[]
packages: string[],
projectName?: string
): LockFileData {
const isBerry = !!lockFileData.lockFileMetadata?.__metadata;
const prunedDependencies = pruneDependencies(
@ -195,7 +199,8 @@ export function pruneYarnLockFile(
workspacePackages: pruneWorkspacePackages(
workspacePackages,
prunedDependencies,
packages
packages,
projectName
),
},
dependencies: prunedDependencies,
@ -257,7 +262,8 @@ function pruneTransitiveDependencies(
version
);
if (dependencyTriplet) {
const [key, { packageMeta, ...value }, metaVersion] = dependencyTriplet;
const [key, { packageMeta, ...depValue }, metaVersion] =
dependencyTriplet;
if (!prunedDeps[packageName]) {
prunedDeps[packageName] = {};
}
@ -270,7 +276,7 @@ function pruneTransitiveDependencies(
}
} else {
prunedDeps[packageName][key] = {
...value,
...depValue,
packageMeta: [metaVersion],
};
// recurively collect dependencies
@ -289,7 +295,8 @@ function pruneTransitiveDependencies(
function pruneWorkspacePackages(
workspacePackages: LockFileDependencies,
prunedDependencies: LockFileData['dependencies'],
packages: string[]
packages: string[],
projectName?: string
): LockFileDependencies {
const result: LockFileDependencies = {};