snyk-docker-plugin
Version:
Snyk CLI docker plugin
254 lines • 12.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shouldBuildDepTree = exports.getLockFileVersion = exports.nodeFilesToScannedProjects = void 0;
const dep_graph_1 = require("@snyk/dep-graph");
const Debug = require("debug");
const path = require("path");
const lockFileParser = require("snyk-nodejs-lockfile-parser");
const resolveDeps = require("snyk-resolve-deps");
const debug = Debug("snyk");
const errors_1 = require("@snyk/composer-lockfile-parser/dist/errors");
const snyk_nodejs_lockfile_parser_1 = require("snyk-nodejs-lockfile-parser");
const node_modules_utils_1 = require("./node-modules-utils");
async function nodeFilesToScannedProjects(filePathToContent, shouldIncludeNodeModules) {
const scanResults = [];
/**
* TODO: Add support for Yarn workspaces!
* https://github.com/snyk/nodejs-lockfile-parser/blob/af8ba81930e950156b539281ecf41c1bc63dacf4/test/lib/yarn-workflows.test.ts#L7-L17
*
* When building the ScanResult ensure the workspace is stored in scanResult.identity.args:
* args: {
* rootWorkspace: <path-of-workspace>,
* };
*/
if (Object.keys(filePathToContent).length === 0) {
return [];
}
const fileNamesGroupedByDirectory = (0, node_modules_utils_1.groupNodeAppFilesByDirectory)(filePathToContent);
const manifestFilePairs = findManifestLockPairsInSameDirectory(fileNamesGroupedByDirectory);
if (manifestFilePairs.length !== 0) {
scanResults.push(...(await depGraphFromManifestFiles(filePathToContent, manifestFilePairs)));
}
if (shouldIncludeNodeModules) {
const appNodeModulesGroupedByDirectory = (0, node_modules_utils_1.groupNodeModulesFilesByDirectory)(filePathToContent);
const nodeProjects = findManifestNodeModulesFilesInSameDirectory(appNodeModulesGroupedByDirectory);
if (nodeProjects.length !== 0) {
scanResults.push(...(await depGraphFromNodeModules(filePathToContent, nodeProjects, appNodeModulesGroupedByDirectory)));
}
}
return scanResults;
}
exports.nodeFilesToScannedProjects = nodeFilesToScannedProjects;
async function depGraphFromNodeModules(filePathToContent, nodeProjects, fileNamesGroupedByDirectory) {
const scanResults = [];
for (const project of nodeProjects) {
const { tempDir, tempProjectPath, manifestPath } = await (0, node_modules_utils_1.persistNodeModules)(project, filePathToContent, fileNamesGroupedByDirectory);
if (!tempDir) {
continue;
}
if (!tempProjectPath) {
await (0, node_modules_utils_1.cleanupAppNodeModules)(tempDir);
continue;
}
try {
const pkgTree = await resolveDeps(tempProjectPath, {
dev: false,
noFromArrays: true,
});
if (pkgTree.numDependencies === 0) {
continue;
}
const depGraph = await dep_graph_1.legacy.depTreeToGraph(pkgTree, pkgTree.type || "npm");
scanResults.push({
facts: [
{
type: "depGraph",
data: depGraph,
},
{
type: "testedFiles",
data: manifestPath
? manifestPath
: path.join(project, "node_modules"),
},
],
identity: {
type: depGraph.pkgManager.name,
targetFile: manifestPath
? manifestPath
: path.join(project, "node_modules"),
},
});
}
catch (error) {
debug(`An error occurred while analysing node_modules dir: ${error.message}`);
}
finally {
await (0, node_modules_utils_1.cleanupAppNodeModules)(tempDir);
}
}
return scanResults;
}
async function depGraphFromManifestFiles(filePathToContent, manifestFilePairs) {
const scanResults = [];
const shouldIncludeDevDependencies = false;
const shouldBeStrictForManifestAndLockfileOutOfSync = false;
for (const pathPair of manifestFilePairs) {
let depGraph;
try {
const lockfileVersion = getLockFileVersion(pathPair.lock, filePathToContent[pathPair.lock]);
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);
}
catch (err) {
debug(`An error occurred while analysing a pair of manifest and lock files: ${err.message}`);
continue;
}
const depGraphFact = {
type: "depGraph",
data: depGraph,
};
const testedFilesFact = {
type: "testedFiles",
data: [path.basename(pathPair.manifest), path.basename(pathPair.lock)],
};
scanResults.push({
facts: [depGraphFact, testedFilesFact],
identity: {
type: depGraph.pkgManager.name,
targetFile: pathPair.manifest,
},
});
}
return scanResults;
}
function findManifestLockPairsInSameDirectory(fileNamesGroupedByDirectory) {
const manifestLockPathPairs = [];
for (const directoryPath of fileNamesGroupedByDirectory.keys()) {
if (directoryPath.includes("node_modules")) {
continue;
}
const filesInDirectory = fileNamesGroupedByDirectory.get(directoryPath);
if (!filesInDirectory || filesInDirectory.size < 1) {
// missing manifest files
continue;
}
const expectedManifest = path.join(directoryPath, "package.json");
const expectedNpmLockFile = path.join(directoryPath, "package-lock.json");
const expectedYarnLockFile = path.join(directoryPath, "yarn.lock");
const hasManifestFile = filesInDirectory.has(expectedManifest);
const hasLockFile = filesInDirectory.has(expectedNpmLockFile) ||
filesInDirectory.has(expectedYarnLockFile);
if (hasManifestFile && hasLockFile) {
manifestLockPathPairs.push({
manifest: expectedManifest,
// TODO: correlate filtering action with expected lockfile types
lock: filesInDirectory.has(expectedNpmLockFile)
? expectedNpmLockFile
: expectedYarnLockFile,
lockType: filesInDirectory.has(expectedNpmLockFile)
? lockFileParser.LockfileType.npm
: lockFileParser.LockfileType.yarn,
});
}
}
return manifestLockPathPairs;
}
function findManifestNodeModulesFilesInSameDirectory(fileNamesGroupedByDirectory) {
const nodeProjects = [];
for (const directoryPath of fileNamesGroupedByDirectory.keys()) {
const filesInDirectory = fileNamesGroupedByDirectory.get(directoryPath);
if (!filesInDirectory || filesInDirectory.size < 1) {
// missing manifest files
continue;
}
const expectedManifest = path.join(directoryPath, "package.json");
const expectedNpmLockFile = path.join(directoryPath, "package-lock.json");
const expectedYarnLockFile = path.join(directoryPath, "yarn.lock");
const hasManifestFile = filesInDirectory.has(expectedManifest);
const hasLockFile = filesInDirectory.has(expectedNpmLockFile) ||
filesInDirectory.has(expectedYarnLockFile);
if (hasManifestFile && hasLockFile) {
continue;
}
nodeProjects.push(directoryPath);
}
return nodeProjects;
}
function stripUndefinedLabels(parserResult) {
const optionalLabels = parserResult.labels;
const mandatoryLabels = {};
if (optionalLabels) {
for (const currentLabelName of Object.keys(optionalLabels)) {
if (optionalLabels[currentLabelName] !== undefined) {
mandatoryLabels[currentLabelName] = optionalLabels[currentLabelName];
}
}
}
const parserResultWithProperLabels = Object.assign({}, parserResult, {
labels: mandatoryLabels,
});
return parserResultWithProperLabels;
}
async function buildDepGraph(manifestFileContents, lockFileContents, lockfileVersion, shouldIncludeDevDependencies, shouldBeStrictForManifestAndLockfileOutOfSync) {
switch (lockfileVersion) {
case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.YarnLockV1:
return await lockFileParser.parseYarnLockV1Project(manifestFileContents, lockFileContents, {
includeDevDeps: shouldIncludeDevDependencies,
includeOptionalDeps: true,
includePeerDeps: false,
pruneLevel: "withinTopLevelDeps",
strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync,
});
case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.YarnLockV2:
return await lockFileParser.parseYarnLockV2Project(manifestFileContents, lockFileContents, {
includeDevDeps: shouldIncludeDevDependencies,
includeOptionalDeps: true,
pruneWithinTopLevelDeps: true,
strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync,
});
case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.NpmLockV2:
case snyk_nodejs_lockfile_parser_1.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, lockFileContents, lockfileType, shouldIncludeDevDependencies, shouldBeStrictForManifestAndLockfileOutOfSync) {
const parserResult = await lockFileParser.buildDepTree(manifestFileContents, lockFileContents, shouldIncludeDevDependencies, lockfileType, shouldBeStrictForManifestAndLockfileOutOfSync);
const strippedLabelsParserResult = stripUndefinedLabels(parserResult);
return await dep_graph_1.legacy.depTreeToGraph(strippedLabelsParserResult, lockfileType);
}
function getLockFileVersion(lockFilePath, lockFileContents) {
let lockfileVersion;
if (lockFilePath.endsWith("package-lock.json")) {
lockfileVersion = (0, snyk_nodejs_lockfile_parser_1.getNpmLockfileVersion)(lockFileContents);
}
else if (lockFilePath.endsWith("yarn.lock")) {
lockfileVersion = (0, snyk_nodejs_lockfile_parser_1.getYarnLockfileVersion)(lockFileContents);
}
else if (lockFilePath.endsWith("pnpm-lock.yaml")) {
lockfileVersion = (0, snyk_nodejs_lockfile_parser_1.getPnpmLockfileVersion)(lockFileContents);
}
else {
throw new errors_1.InvalidUserInputError(`Unknown lockfile ${lockFilePath}. ` +
"Please provide either package-lock.json, yarn.lock or pnpm-lock.yaml");
}
return lockfileVersion;
}
exports.getLockFileVersion = getLockFileVersion;
function shouldBuildDepTree(lockfileVersion) {
return !(lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.YarnLockV1 ||
lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.YarnLockV2 ||
lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.NpmLockV2 ||
lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.NpmLockV3);
}
exports.shouldBuildDepTree = shouldBuildDepTree;
//# sourceMappingURL=node.js.map