UNPKG

snyk-docker-plugin

Version:
359 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.shouldBuildDepTree = exports.getLockFileVersion = exports.detectLockFile = 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) { // First, try to build dep graph from pnpm virtual store if present const pnpmResult = await tryBuildDepGraphFromPnpmStore(project, filePathToContent, fileNamesGroupedByDirectory); if (pnpmResult) { scanResults.push(pnpmResult); continue; } // Fallback to snyk-resolve-deps for non-pnpm projects 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; } /** * Builds a dependency graph from pnpm's .pnpm directory. * snyk-resolve-deps doesn't understand pnpm's virtual store structure, * so we parse the package.json files directly. */ async function tryBuildDepGraphFromPnpmStore(project, filePathToContent, fileNamesGroupedByDirectory) { const projectFiles = fileNamesGroupedByDirectory.get(project); if (!projectFiles) { return null; } // Find all package.json files inside .pnpm directories const pnpmPackageJsons = Array.from(projectFiles).filter((f) => f.includes("/node_modules/.pnpm/") && f.endsWith("/package.json")); if (pnpmPackageJsons.length === 0) { return null; } // Find the root package.json (parent of node_modules/.pnpm) const pnpmMatch = pnpmPackageJsons[0].match(/^(.+?)\/node_modules\/\.pnpm\//); if (!pnpmMatch) { return null; } const rootManifestPath = path.posix.join(pnpmMatch[1], "package.json"); const rootManifestContent = filePathToContent[rootManifestPath]; if (!rootManifestContent) { return null; } let rootPkg; try { rootPkg = JSON.parse(rootManifestContent); } catch (_a) { return null; } if (!rootPkg.name) { return null; } debug(`Building pnpm dep graph for ${rootPkg.name} from .pnpm directory`); // Parse all packages from .pnpm and add them as direct dependencies const builder = new dep_graph_1.DepGraphBuilder({ name: "pnpm" }, { name: rootPkg.name, version: rootPkg.version || "0.0.0" }); const seen = new Set(); for (const pkgJsonPath of pnpmPackageJsons) { const content = filePathToContent[pkgJsonPath]; if (!content) { continue; } try { const pkg = JSON.parse(content); if (!pkg.name || !pkg.version) { continue; } const nodeId = `${pkg.name}@${pkg.version}`; if (seen.has(nodeId)) { continue; } seen.add(nodeId); builder.addPkgNode({ name: pkg.name, version: pkg.version }, nodeId); builder.connectDep(builder.rootNodeId, nodeId); } catch (_b) { // Skip unparseable files } } if (seen.size === 0) { return null; } const depGraph = builder.build(); debug(`Built pnpm dep graph with ${depGraph.getPkgs().length} packages`); return { facts: [ { type: "depGraph", data: depGraph }, { type: "testedFiles", data: rootManifestPath }, ], identity: { type: "pnpm", targetFile: rootManifestPath, }, }; } 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 detectLockFile(directoryPath, filesInDirectory) { const lockFiles = [ { filename: "package-lock.json", type: lockFileParser.LockfileType.npm }, { filename: "yarn.lock", type: lockFileParser.LockfileType.yarn }, { filename: "pnpm-lock.yaml", type: lockFileParser.LockfileType.pnpm }, ]; for (const { filename, type } of lockFiles) { const lockPath = path.join(directoryPath, filename); if (filesInDirectory.has(lockPath)) { return { path: lockPath, type }; } } return null; } exports.detectLockFile = detectLockFile; 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"); if (!filesInDirectory.has(expectedManifest)) { continue; } // TODO: correlate filtering action with expected lockfile types const lockFile = detectLockFile(directoryPath, filesInDirectory); if (!lockFile) { continue; } manifestLockPathPairs.push({ manifest: expectedManifest, lock: lockFile.path, lockType: lockFile.type, }); } 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 hasManifestFile = filesInDirectory.has(expectedManifest); const hasLockFile = detectLockFile(directoryPath, filesInDirectory) !== null; 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, }); case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV5: case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV6: case snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV9: return await lockFileParser.parsePnpmProject(manifestFileContents, lockFileContents, { includeDevDeps: shouldIncludeDevDependencies, includeOptionalDeps: true, includePeerDeps: false, pruneWithinTopLevelDeps: true, strictOutOfSync: shouldBeStrictForManifestAndLockfileOutOfSync, }, lockfileVersion); } 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 || lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV5 || lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV6 || lockfileVersion === snyk_nodejs_lockfile_parser_1.NodeLockfileVersion.PnpmLockV9); } exports.shouldBuildDepTree = shouldBuildDepTree; //# sourceMappingURL=node.js.map