Skip to content

Commit

Permalink
feat: add pruning to yarn lock v2 dep graph builder
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesPatrickGill committed May 18, 2023
1 parent 1341f41 commit ded8dde
Show file tree
Hide file tree
Showing 28 changed files with 688 additions and 74 deletions.
7 changes: 7 additions & 0 deletions lib/dep-graph-builders/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ export type ProjectParseOptions = DepGraphBuildOptions &
LockFileParseOptions & {
pruneCycles: boolean;
};

export type YarnLockV2ProjectParseOptions = {
includeDevDeps: boolean;
includeOptionalDeps: boolean;
strictOutOfSync: boolean;
pruneWithinTopLevelDeps: boolean;
};
79 changes: 58 additions & 21 deletions lib/dep-graph-builders/yarn-lock-v2/build-depgraph-simple.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { DepGraphBuilder } from '@snyk/dep-graph';
import { addPkgNodeToGraph, getTopLevelDeps, PkgNode } from '../util';
import type { DepGraphBuildOptions } from '../types';
import { getTopLevelDeps, PkgNode } from '../util';
import type { YarnLockV2ProjectParseOptions } from '../types';
import type { NormalisedPkgs, PackageJsonBase } from '../types';
import { getYarnLockV2ChildNode } from './utils';
import { eventLoopSpinner } from 'event-loop-spinner';

export const buildDepGraphYarnLockV2Simple = async (
extractedYarnLockV2Pkgs: NormalisedPkgs,
pkgJson: PackageJsonBase,
options: DepGraphBuildOptions,
options: YarnLockV2ProjectParseOptions,
) => {
const { includeDevDeps, strictOutOfSync, includeOptionalDeps } = options;
const {
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevelDeps,
} = options;

const depGraphBuilder = new DepGraphBuilder(
{ name: 'yarn' },
{ name: pkgJson.name, version: pkgJson.version },
);

const visitedMap: Set<string> = new Set();

const topLevelDeps = getTopLevelDeps(pkgJson, {
includeDevDeps,
});
Expand All @@ -34,11 +37,11 @@ export const buildDepGraphYarnLockV2Simple = async (
await dfsVisit(
depGraphBuilder,
rootNode,
visitedMap,
extractedYarnLockV2Pkgs,
strictOutOfSync,
includeOptionalDeps,
pkgJson.resolutions || {},
pruneWithinTopLevelDeps,
);

return depGraphBuilder.build();
Expand All @@ -53,19 +56,20 @@ export const buildDepGraphYarnLockV2Simple = async (
const dfsVisit = async (
depGraphBuilder: DepGraphBuilder,
node: PkgNode,
visitedMap: Set<string>,
extractedYarnLockV2Pkgs: NormalisedPkgs,
strictOutOfSync: boolean,
includeOptionalDeps: boolean,
resolutions: Record<string, string>,
pruneWithinTopLevel: boolean,
visited?: Set<string>,
): Promise<void> => {
visitedMap.add(node.id);

for (const [name, depInfo] of Object.entries(node.dependencies || {})) {
if (eventLoopSpinner.isStarving()) {
await eventLoopSpinner.spin();
}

const localVisited = visited || new Set<string>();

const childNode = getYarnLockV2ChildNode(
name,
depInfo,
Expand All @@ -76,19 +80,52 @@ const dfsVisit = async (
node,
);

if (!visitedMap.has(childNode.id)) {
addPkgNodeToGraph(depGraphBuilder, childNode, {});
await dfsVisit(
depGraphBuilder,
childNode,
visitedMap,
extractedYarnLockV2Pkgs,
strictOutOfSync,
includeOptionalDeps,
resolutions,
);
if (localVisited.has(childNode.id)) {
if (pruneWithinTopLevel) {
const prunedId = `${childNode.id}:pruned`;
depGraphBuilder.addPkgNode(
{ name: childNode.name, version: childNode.version },
prunedId,
{
labels: {
scope: node.isDev ? 'dev' : 'prod',
pruned: 'true',
...(node.missingLockFileEntry && {
missingLockFileEntry: 'true',
}),
},
},
);
depGraphBuilder.connectDep(node.id, prunedId);
} else {
depGraphBuilder.connectDep(node.id, childNode.id);
}
continue;
}

depGraphBuilder.addPkgNode(
{ name: childNode.name, version: childNode.version },
childNode.id,
{
labels: {
scope: node.isDev ? 'dev' : 'prod',
...(node.missingLockFileEntry && {
missingLockFileEntry: 'true',
}),
},
},
);
depGraphBuilder.connectDep(node.id, childNode.id);
localVisited.add(childNode.id);
await dfsVisit(
depGraphBuilder,
childNode,
extractedYarnLockV2Pkgs,
strictOutOfSync,
includeOptionalDeps,
resolutions,
pruneWithinTopLevel,
localVisited,
);
}
};
12 changes: 9 additions & 3 deletions lib/dep-graph-builders/yarn-lock-v2/simple.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { extractPkgsFromYarnLockV2 } from './extract-yarnlock-v2-pkgs';
import { parsePkgJson } from '../util';
import { PackageJsonBase, ProjectParseOptions } from '../types';
import { PackageJsonBase, YarnLockV2ProjectParseOptions } from '../types';
import { buildDepGraphYarnLockV2Simple } from './build-depgraph-simple';
import { DepGraph } from '@snyk/dep-graph';

export const parseYarnLockV2Project = async (
pkgJsonContent: string,
yarnLockContent: string,
options: ProjectParseOptions,
options: YarnLockV2ProjectParseOptions,
): Promise<DepGraph> => {
const { includeDevDeps, includeOptionalDeps, strictOutOfSync } = options;
const {
includeDevDeps,
includeOptionalDeps,
strictOutOfSync,
pruneWithinTopLevelDeps,
} = options;

const pkgs = extractPkgsFromYarnLockV2(yarnLockContent);

Expand All @@ -19,6 +24,7 @@ export const parseYarnLockV2Project = async (
includeDevDeps,
strictOutOfSync,
includeOptionalDeps,
pruneWithinTopLevelDeps,
});

return depgraph;
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import type {
PackageJsonBase,
NormalisedPkgs,
ProjectParseOptions,
YarnLockV2ProjectParseOptions,
} from './dep-graph-builders/types';
import {
getLockfileVersionFromFile,
Expand All @@ -75,6 +76,7 @@ export {
buildDepGraphYarnLockV2Simple,
PackageJsonBase,
ProjectParseOptions,
YarnLockV2ProjectParseOptions,
NormalisedPkgs,
NormalisedPkgs as YarnLockPackages,
getLockfileVersionFromFile,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "empty",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"empty@workspace:.":
version: 0.0.0-use.local
resolution: "empty@workspace:."
languageName: unknown
linkType: soft
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "repeat-node-in-different-top-level-chains",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0",
"d": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"repeat-node-in-different-top-level-chains@workspace:.":
version: 0.0.0-use.local
resolution: "empty@workspace:."
languageName: unknown
linkType: soft
dependencies:
a: 1.0.0
d: 1.0.0

"a@npm:1.0.0":
version: 1.0.0
resolution: "a@npm:1.0.0"
dependencies:
b: 1.0.0

"b@npm:1.0.0":
version: 1.0.0
resolution: "b@npm:1.0.0"
dependencies:
c: 1.0.0

"c@npm:1.0.0":
version: 1.0.0
resolution: "c@npm:1.0.0"

"d@npm:1.0.0":
version: 1.0.0
resolution: "d@npm:1.0.0"
dependencies:
b: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "repeat-node-within-top-level-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"repeat-node-within-top-level-chain@workspace:.":
version: 0.0.0-use.local
resolution: "empty@workspace:."
languageName: unknown
linkType: soft
dependencies:
a: 1.0.0

"a@npm:1.0.0":
version: 1.0.0
resolution: "a@npm:1.0.0"
dependencies:
b: 1.0.0
d: 1.0.0

"b@npm:1.0.0":
version: 1.0.0
resolution: "b@npm:1.0.0"
dependencies:
c: 1.0.0

"c@npm:1.0.0":
version: 1.0.0
resolution: "c@npm:1.0.0"

"d@npm:1.0.0":
version: 1.0.0
resolution: "d@npm:1.0.0"
dependencies:
b: 1.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "simple-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"simple-chain@workspace:.":
version: 0.0.0-use.local
resolution: "empty@workspace:."
languageName: unknown
linkType: soft
dependencies:
a: 1.0.0

"a@npm:1.0.0":
version: 1.0.0
resolution: "a@npm:1.0.0"
dependencies:
b: 1.0.0

"b@npm:1.0.0":
version: 1.0.0
resolution: "b@npm:1.0.0"

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "simple-cyclic-chain",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"a": "1.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"simple-cyclic-chain@workspace:.":
version: 0.0.0-use.local
resolution: "empty@workspace:."
languageName: unknown
linkType: soft
dependencies:
a: 1.0.0

"a@npm:1.0.0":
version: 1.0.0
resolution: "a@npm:1.0.0"
dependencies:
b: 1.0.0

"b@npm:1.0.0":
version: 1.0.0
resolution: "b@npm:1.0.0"
dependencies:
a: 1.0.0

Loading

0 comments on commit ded8dde

Please sign in to comment.