Skip to content

Commit

Permalink
Merge pull request #605 from snyk/feat/packagelockv3
Browse files Browse the repository at this point in the history
fix: package-lock v3 missing sub-dependencies - UNIFY-258
  • Loading branch information
neil-snyk authored Sep 18, 2024
2 parents ce95eb7 + 31c1ba3 commit fc1a6bb
Show file tree
Hide file tree
Showing 14 changed files with 47,287 additions and 17,582 deletions.
152 changes: 128 additions & 24 deletions lib/analyzer/applications/node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { legacy } from "@snyk/dep-graph";
import { DepGraph, legacy } from "@snyk/dep-graph";
import * as Debug from "debug";
import * as path from "path";
import * as lockFileParser from "snyk-nodejs-lockfile-parser";
Expand All @@ -7,7 +7,14 @@ import { DepGraphFact, TestedFilesFact } from "../../facts";

const debug = Debug("snyk");

import { PkgTree } from "snyk-nodejs-lockfile-parser";
import { InvalidUserInputError } from "@snyk/composer-lockfile-parser/dist/errors";
import {
getNpmLockfileVersion,
getPnpmLockfileVersion,
getYarnLockfileVersion,
LockfileType,
NodeLockfileVersion,
} from "snyk-nodejs-lockfile-parser";
import { LogicalRoot } from "snyk-resolve-deps/dist/types";
import {
cleanupAppNodeModules,
Expand Down Expand Up @@ -145,29 +152,25 @@ async function depGraphFromManifestFiles(
const shouldBeStrictForManifestAndLockfileOutOfSync = false;

for (const pathPair of manifestFilePairs) {
// TODO: initially generate as DepGraph
let parserResult: PkgTree;
try {
parserResult = await lockFileParser.buildDepTree(
filePathToContent[pathPair.manifest],
filePathToContent[pathPair.lock],
shouldIncludeDevDependencies,
pathPair.lockType,
shouldBeStrictForManifestAndLockfileOutOfSync,
// Don't provide a default manifest file name, prefer the parser to infer it.
);
} catch (err) {
debug(
`An error occurred while analysing a pair of manifest and lock files: ${err.message}`,
);
continue;
}

const strippedLabelsParserResult = stripUndefinedLabels(parserResult);
const depGraph = await legacy.depTreeToGraph(
strippedLabelsParserResult,
pathPair.lockType,
const lockfileVersion = getLockFileVersion(
pathPair.lock,
filePathToContent[pathPair.lock],
);
const depGraph: DepGraph = shouldBuildDepTree(lockfileVersion)
? await buildDepGraphFromDepTree(
filePathToContent[pathPair.manifest],
filePathToContent[pathPair.lock],
pathPair.lockType,
shouldIncludeDevDependencies,
shouldBeStrictForManifestAndLockfileOutOfSync,
)
: await buildDepGraph(
filePathToContent[pathPair.manifest],
filePathToContent[pathPair.lock],
lockfileVersion,
shouldIncludeDevDependencies,
shouldBeStrictForManifestAndLockfileOutOfSync,
);

const depGraphFact: DepGraphFact = {
type: "depGraph",
Expand Down Expand Up @@ -246,3 +249,104 @@ function stripUndefinedLabels(
});
return parserResultWithProperLabels;
}

async function buildDepGraph(
manifestFileContents: string,
lockFileContents: string,
lockfileVersion: NodeLockfileVersion,
shouldIncludeDevDependencies: boolean,
shouldBeStrictForManifestAndLockfileOutOfSync: boolean,
): Promise<DepGraph> {
switch (lockfileVersion) {
case NodeLockfileVersion.YarnLockV1:
return await lockFileParser.parseYarnLockV1Project(
manifestFileContents,
lockFileContents,
{
includeDevDeps: shouldIncludeDevDependencies,
includeOptionalDeps: true,
includePeerDeps: false,
pruneLevel: "withinTopLevelDeps",
strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync,
},
);
case NodeLockfileVersion.YarnLockV2:
return await lockFileParser.parseYarnLockV2Project(
manifestFileContents,
lockFileContents,
{
includeDevDeps: shouldIncludeDevDependencies,
includeOptionalDeps: true,
pruneWithinTopLevelDeps: true,
strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync,
},
);
case NodeLockfileVersion.NpmLockV2:
case NodeLockfileVersion.NpmLockV3:
return await lockFileParser.parseNpmLockV2Project(
manifestFileContents,
lockFileContents,
{
includeDevDeps: shouldIncludeDevDependencies,
includeOptionalDeps: true,
pruneCycles: true,
strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync,
},
);
}
throw new Error(
"Failed to build dep graph from current project, unknown lockfile version : " +
lockfileVersion.toString() +
".",
);
}

async function buildDepGraphFromDepTree(
manifestFileContents: string,
lockFileContents: string,
lockfileType: LockfileType,
shouldIncludeDevDependencies: boolean,
shouldBeStrictForManifestAndLockfileOutOfSync: boolean,
) {
const parserResult = await lockFileParser.buildDepTree(
manifestFileContents,
lockFileContents,
shouldIncludeDevDependencies,
lockfileType,
shouldBeStrictForManifestAndLockfileOutOfSync,
// Don't provide a default manifest file name, prefer the parser to infer it.
);
const strippedLabelsParserResult = stripUndefinedLabels(parserResult);
return await legacy.depTreeToGraph(strippedLabelsParserResult, lockfileType);
}

export function getLockFileVersion(
lockFilePath: string,
lockFileContents: string,
): NodeLockfileVersion {
let lockfileVersion: NodeLockfileVersion;

if (lockFilePath.endsWith("package-lock.json")) {
lockfileVersion = getNpmLockfileVersion(lockFileContents);
} else if (lockFilePath.endsWith("yarn.lock")) {
lockfileVersion = getYarnLockfileVersion(lockFileContents);
} else if (lockFilePath.endsWith("pnpm-lock.yaml")) {
lockfileVersion = getPnpmLockfileVersion(lockFileContents);
} else {
throw new InvalidUserInputError(
`Unknown lockfile ${lockFilePath}. ` +
"Please provide either package-lock.json, yarn.lock or pnpm-lock.yaml",
);
}

return lockfileVersion;
}

export function shouldBuildDepTree(lockfileVersion: NodeLockfileVersion) {
return !(
lockfileVersion === NodeLockfileVersion.YarnLockV1 ||
lockfileVersion === NodeLockfileVersion.YarnLockV2 ||
lockfileVersion === NodeLockfileVersion.NpmLockV2 ||
lockfileVersion === NodeLockfileVersion.NpmLockV3
);
}
Loading

0 comments on commit fc1a6bb

Please sign in to comment.