Skip to content

Commit f43f91d

Browse files
authored
Merge pull request #270 from snyk/feat/add-npm-yarn-alias-support
feat: add alias support
2 parents ca73d78 + 0107642 commit f43f91d

File tree

22 files changed

+6412
-63
lines changed

22 files changed

+6412
-63
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as cloneDeep from 'lodash.clonedeep';
2+
export const rewriteAliasesInNpmLockV1 = (lockfileContent: string): string => {
3+
const jsonLockfile = JSON.parse(lockfileContent);
4+
for (const pkg in jsonLockfile.dependencies) {
5+
if (jsonLockfile.dependencies[pkg].version.startsWith('npm:')) {
6+
const aliasName = jsonLockfile.dependencies[pkg].version.substring(
7+
4,
8+
jsonLockfile.dependencies[pkg].version.lastIndexOf('@'),
9+
);
10+
jsonLockfile.dependencies[aliasName] = cloneDeep(
11+
jsonLockfile.dependencies[pkg],
12+
);
13+
jsonLockfile.dependencies[aliasName].version = jsonLockfile.dependencies[
14+
pkg
15+
].version.substring(
16+
jsonLockfile.dependencies[pkg].version.lastIndexOf('@') + 1,
17+
jsonLockfile.dependencies[pkg].version.length,
18+
);
19+
delete jsonLockfile.dependencies[pkg];
20+
}
21+
}
22+
23+
return JSON.stringify(jsonLockfile);
24+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { NpmLockPkg } from '../dep-graph-builders/npm-lock-v2/extract-npm-lock-v2-pkgs';
2+
3+
export const rewriteAliasesInNpmLockV2 = (
4+
lockfilePackages: Record<string, NpmLockPkg>,
5+
): Record<string, NpmLockPkg> => {
6+
// 1. Rewrite top level "" packages in "".dependencies
7+
const rootPkg = lockfilePackages[''];
8+
const lockFileToReturn: Record<string, NpmLockPkg> = lockfilePackages;
9+
if (rootPkg && rootPkg.dependencies) {
10+
const dependencies = rootPkg.dependencies;
11+
for (const pkgName in rootPkg.dependencies) {
12+
if (rootPkg.dependencies[pkgName].startsWith('npm:')) {
13+
const aliasName = rootPkg.dependencies[pkgName].substring(
14+
4,
15+
rootPkg.dependencies[pkgName].lastIndexOf('@'),
16+
);
17+
const aliasVersion = rootPkg.dependencies[pkgName].substring(
18+
rootPkg.dependencies[pkgName].lastIndexOf('@') + 1,
19+
rootPkg.dependencies[pkgName].length,
20+
);
21+
dependencies[aliasName] = aliasVersion;
22+
} else {
23+
dependencies[pkgName] = rootPkg.dependencies[pkgName];
24+
}
25+
}
26+
lockFileToReturn[''].dependencies = dependencies;
27+
}
28+
29+
// 2. Rewrite alias packages
30+
for (const pkgName in lockfilePackages) {
31+
if (pkgName != '' && lockfilePackages[pkgName].name) {
32+
lockFileToReturn[`node_modules/${lockfilePackages[pkgName].name}`] =
33+
lockfilePackages[pkgName];
34+
delete lockFileToReturn[pkgName];
35+
}
36+
}
37+
38+
return lockFileToReturn;
39+
};

lib/aliasesPreprocessors/pkgJson.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { parsePkgJson } from '../dep-graph-builders/util';
2+
3+
export const rewriteAliasesPkgJson = (packageJsonContent: string): string => {
4+
const pkgJsonPreprocessed = parsePkgJson(packageJsonContent);
5+
pkgJsonPreprocessed.dependencies = rewriteAliases(
6+
pkgJsonPreprocessed.dependencies,
7+
);
8+
pkgJsonPreprocessed.devDependencies = rewriteAliases(
9+
pkgJsonPreprocessed.devDependencies,
10+
);
11+
pkgJsonPreprocessed.optionalDependencies = rewriteAliases(
12+
pkgJsonPreprocessed.optionalDependencies,
13+
);
14+
pkgJsonPreprocessed.peerDependencies = rewriteAliases(
15+
pkgJsonPreprocessed.peerDependencies,
16+
);
17+
return JSON.stringify(pkgJsonPreprocessed);
18+
};
19+
20+
export const rewriteAliases = (
21+
dependencies: Record<string, string> | undefined,
22+
): Record<string, string> | undefined => {
23+
if (!dependencies) {
24+
return undefined;
25+
}
26+
const newDependencies: Record<string, string> = {};
27+
for (const key in dependencies) {
28+
const value = dependencies[key];
29+
if (value.startsWith('npm:')) {
30+
newDependencies[value.substring(4, value.lastIndexOf('@'))] =
31+
value.substring(value.lastIndexOf('@') + 1, value.length);
32+
} else {
33+
newDependencies[key] = value;
34+
}
35+
}
36+
return newDependencies;
37+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const rewriteAliasesInYarnLockV1 = (lockfileContent: string): string => {
2+
const regex = /^(\s*)"(.+?@npm:)([^"]+)":/gm;
3+
4+
const lockfilePreprocessed = lockfileContent.replace(regex, '$1"$3":');
5+
6+
return lockfilePreprocessed;
7+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { NormalisedPkgs } from '../dep-graph-builders/types';
2+
import * as cloneDeep from 'lodash.clonedeep';
3+
export const rewriteAliasesInYarnLockV2 = (
4+
lockfileNormalisedPkgs: NormalisedPkgs,
5+
): NormalisedPkgs => {
6+
const lockfileNormalisedPkgsPreprocessed: NormalisedPkgs = cloneDeep(
7+
lockfileNormalisedPkgs,
8+
);
9+
for (const pkg in lockfileNormalisedPkgsPreprocessed) {
10+
const pkgSplit = pkg.substring(0, pkg.lastIndexOf('@'));
11+
const resolutionSplit =
12+
lockfileNormalisedPkgsPreprocessed[pkg].resolution?.split(
13+
/@npm[:%3A]/,
14+
)[0];
15+
16+
if (
17+
!pkg.startsWith('v2@workspace') &&
18+
resolutionSplit &&
19+
pkgSplit != resolutionSplit
20+
) {
21+
const newPkg = lockfileNormalisedPkgsPreprocessed[pkg];
22+
delete lockfileNormalisedPkgsPreprocessed[pkg];
23+
const newKey = pkg.replace(pkgSplit, resolutionSplit);
24+
lockfileNormalisedPkgsPreprocessed[newKey] = newPkg;
25+
}
26+
if (pkg.startsWith('v2@workspace')) {
27+
const newDependencies: Record<string, string> = {};
28+
for (const key in lockfileNormalisedPkgsPreprocessed[pkg].dependencies) {
29+
const value = lockfileNormalisedPkgsPreprocessed[pkg].dependencies[key];
30+
if (value.startsWith('npm:')) {
31+
newDependencies[value.substring(4, value.lastIndexOf('@'))] =
32+
value.substring(value.lastIndexOf('@') + 1, value.length);
33+
} else {
34+
newDependencies[key] = value;
35+
}
36+
}
37+
lockfileNormalisedPkgsPreprocessed[pkg].dependencies = newDependencies;
38+
}
39+
}
40+
41+
return lockfileNormalisedPkgsPreprocessed;
42+
};

lib/dep-graph-builders/npm-lock-v2/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import * as semver from 'semver';
2121
import * as micromatch from 'micromatch';
2222
import * as pathUtil from 'path';
2323
import { eventLoopSpinner } from 'event-loop-spinner';
24+
import { rewriteAliasesPkgJson } from '../../aliasesPreprocessors/pkgJson';
25+
import { rewriteAliasesInNpmLockV2 } from '../../aliasesPreprocessors/npm-lock-v2';
2426

2527
export { extractPkgsFromNpmLockV2 };
2628

@@ -38,8 +40,14 @@ export const parseNpmLockV2Project = async (
3840
pruneNpmStrictOutOfSync,
3941
} = options;
4042

41-
const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);
42-
const pkgs = extractPkgsFromNpmLockV2(pkgLockContent);
43+
const pkgJson: PackageJsonBase = parsePkgJson(
44+
options.honorAliases
45+
? rewriteAliasesPkgJson(pkgJsonContent)
46+
: pkgJsonContent,
47+
);
48+
const pkgs = options.honorAliases
49+
? rewriteAliasesInNpmLockV2(extractPkgsFromNpmLockV2(pkgLockContent))
50+
: extractPkgsFromNpmLockV2(pkgLockContent);
4351

4452
const depgraph = await buildDepGraphNpmLockV2(pkgs, pkgJson, {
4553
includeDevDeps,

lib/dep-graph-builders/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export type NormalisedPkgs = Record<
2424
PkgIdentifier,
2525
{
2626
version: string; // This is resolved version
27+
resolution?: string;
2728
dependencies: Record<string, string>;
2829
optionalDependencies: Record<string, string>;
2930
}
@@ -35,6 +36,7 @@ export type DepGraphBuildOptions = {
3536
strictOutOfSync: boolean;
3637
includePeerDeps?: boolean;
3738
pruneNpmStrictOutOfSync?: boolean;
39+
honorAliases?: boolean;
3840
};
3941

4042
export type LockFileParseOptions = {
@@ -57,6 +59,7 @@ export type YarnLockV2ProjectParseOptions = {
5759
includeOptionalDeps: boolean;
5860
strictOutOfSync: boolean;
5961
pruneWithinTopLevelDeps: boolean;
62+
honorAliases?: boolean;
6063
};
6164

6265
/*
@@ -73,6 +76,7 @@ export type YarnLockV1ProjectParseOptions = {
7376
includePeerDeps: boolean;
7477
strictOutOfSync: boolean;
7578
pruneLevel: PruneLevel;
79+
honorAliases?: boolean;
7680
};
7781

7882
export type Yarn1DepGraphBuildOptions = {

lib/dep-graph-builders/yarn-lock-v1/simple.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { buildDepGraphYarnLockV1Simple } from '.';
2+
import { rewriteAliasesPkgJson } from '../../aliasesPreprocessors/pkgJson';
3+
import { rewriteAliasesInYarnLockV1 } from '../../aliasesPreprocessors/yarn-lock-v1';
4+
25
import { PackageJsonBase, YarnLockV1ProjectParseOptions } from '../types';
36
import { parsePkgJson } from '../util';
47
import { buildDepGraphYarnLockV1SimpleCyclesPruned } from './build-depgraph-simple-pruned';
@@ -15,11 +18,18 @@ export const parseYarnLockV1Project = async (
1518
includePeerDeps,
1619
pruneLevel,
1720
strictOutOfSync,
21+
honorAliases,
1822
} = options;
1923

20-
const pkgs = extractPkgsFromYarnLockV1(yarnLockContent);
24+
const pkgs = extractPkgsFromYarnLockV1(
25+
honorAliases
26+
? rewriteAliasesInYarnLockV1(yarnLockContent)
27+
: yarnLockContent,
28+
);
2129

22-
const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);
30+
const pkgJson: PackageJsonBase = parsePkgJson(
31+
honorAliases ? rewriteAliasesPkgJson(pkgJsonContent) : pkgJsonContent,
32+
);
2333

2434
const depGraph =
2535
pruneLevel === 'cycles'

lib/dep-graph-builders/yarn-lock-v2/simple.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
} from '../types';
88
import { buildDepGraphYarnLockV2Simple } from './build-depgraph-simple';
99
import { DepGraph } from '@snyk/dep-graph';
10+
import { rewriteAliasesPkgJson } from '../../aliasesPreprocessors/pkgJson';
11+
import { rewriteAliasesInYarnLockV2 } from '../../aliasesPreprocessors/yarn-lock-v2';
1012

1113
export const parseYarnLockV2Project = async (
1214
pkgJsonContent: string,
@@ -19,11 +21,16 @@ export const parseYarnLockV2Project = async (
1921
includeOptionalDeps,
2022
strictOutOfSync,
2123
pruneWithinTopLevelDeps,
24+
honorAliases,
2225
} = options;
2326

24-
const pkgs = extractPkgsFromYarnLockV2(yarnLockContent);
27+
const pkgs = honorAliases
28+
? rewriteAliasesInYarnLockV2(extractPkgsFromYarnLockV2(yarnLockContent))
29+
: extractPkgsFromYarnLockV2(yarnLockContent);
2530

26-
const pkgJson: PackageJsonBase = parsePkgJson(pkgJsonContent);
31+
const pkgJson: PackageJsonBase = parsePkgJson(
32+
honorAliases ? rewriteAliasesPkgJson(pkgJsonContent) : pkgJsonContent,
33+
);
2734

2835
const depgraph = await buildDepGraphYarnLockV2Simple(
2936
pkgs,

lib/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ import {
7070
getPnpmLockfileVersion,
7171
NodeLockfileVersion,
7272
} from './utils';
73+
import { rewriteAliasesInNpmLockV1 } from './aliasesPreprocessors/npm-lock-v1';
74+
import { rewriteAliasesPkgJson } from './aliasesPreprocessors/pkgJson';
7375
export {
7476
parseNpmLockV2Project,
7577
extractPkgsFromYarnLockV1,
@@ -156,6 +158,7 @@ async function buildDepTreeFromFiles(
156158
lockFilePath: string,
157159
includeDev = false,
158160
strictOutOfSync = true,
161+
honorAliases?: boolean,
159162
): Promise<PkgTree> {
160163
if (!root || !manifestFilePath || !lockFilePath) {
161164
throw new Error('Missing required parameters for buildDepTreeFromFiles()');
@@ -192,8 +195,12 @@ async function buildDepTreeFromFiles(
192195
}
193196

194197
return await buildDepTree(
195-
manifestFileContents,
196-
lockFileContents,
198+
honorAliases
199+
? rewriteAliasesPkgJson(manifestFileContents)
200+
: manifestFileContents,
201+
honorAliases
202+
? rewriteAliasesInNpmLockV1(lockFileContents)
203+
: lockFileContents,
197204
includeDev,
198205
lockFileType,
199206
strictOutOfSync,

0 commit comments

Comments
 (0)