UNPKG

snyk-go-plugin

Version:
176 lines 8.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildGraph = exports.buildDepGraphFromImportsAndModules = exports.getDepGraph = void 0; const path = require("path"); const dep_graph_1 = require("@snyk/dep-graph"); const helpers_1 = require("./helpers"); const custom_error_1 = require("./errors/custom-error"); const version_1 = require("./version"); const sub_process_1 = require("./sub-process"); const package_url_1 = require("./package-url"); async function getDepGraph(root, targetFile, options = {}) { var _a, _b; const { args: additionalArgs = [], configuration } = options; const includeGoStandardLibraryDeps = (_a = configuration === null || configuration === void 0 ? void 0 : configuration.includeGoStandardLibraryDeps) !== null && _a !== void 0 ? _a : false; // Determine stdlib version const stdlibVersion = includeGoStandardLibraryDeps ? await (0, helpers_1.resolveStdlibVersion)(root, targetFile) : 'unknown'; const includePackageUrls = (_b = configuration === null || configuration === void 0 ? void 0 : configuration.includePackageUrls) !== null && _b !== void 0 ? _b : true; return buildDepGraphFromImportsAndModules(root, targetFile, { stdlibVersion, additionalArgs, includeGoStandardLibraryDeps, includePackageUrls, }); } exports.getDepGraph = getDepGraph; async function buildDepGraphFromImportsAndModules(root = '.', targetFile = 'go.mod', options = {}) { var _a, _b; // TODO(BST-657): parse go.mod file to obtain root module name and go version const projectName = path.basename(root); // The correct name should come from the `go list` command const projectVersion = '0.0.0'; // TODO(BST-657): try `git describe`? options = { stdlibVersion: 'unknown', additionalArgs: [], includeGoStandardLibraryDeps: false, includePackageUrls: false, ...options, }; let rootPkg = createPkgInfo(projectName, projectVersion, options); let depGraphBuilder = new dep_graph_1.DepGraphBuilder({ name: 'gomodules' }, rootPkg); let goDepsOutput; const args = [ 'list', ...((_a = options.additionalArgs) !== null && _a !== void 0 ? _a : []), '-json', '-deps', './...', ]; try { const goModAbsolutPath = path.resolve(root, path.dirname(targetFile)); goDepsOutput = await (0, sub_process_1.runGo)(args, { cwd: goModAbsolutPath }); } catch (err) { if (/cannot find main module, but found/.test(err)) { return depGraphBuilder.build(); } if (/does not contain main module/.test(err)) { return depGraphBuilder.build(); } const userError = new custom_error_1.CustomError(err); userError.userMessage = `'go ${args.join(' ')}' command failed with error: ${userError.message}`; throw userError; } if (goDepsOutput.includes('matched no packages')) { return depGraphBuilder.build(); } const goDepsString = `[${goDepsOutput.replace(/}\r?\n{/g, '},{')}]`; const goDeps = JSON.parse(goDepsString); const packagesByName = {}; for (const gp of goDeps) { packagesByName[gp.ImportPath] = gp; // ImportPath is the fully qualified name } const localPackages = goDeps.filter((gp) => !gp.DepOnly); const localPackageWithMainModule = localPackages.find((localPackage) => !!(localPackage.Module && localPackage.Module.Main)); if ((_b = localPackageWithMainModule === null || localPackageWithMainModule === void 0 ? void 0 : localPackageWithMainModule.Module) === null || _b === void 0 ? void 0 : _b.Path) { rootPkg = createPkgInfo(localPackageWithMainModule.Module.Path, projectVersion, options); depGraphBuilder = new dep_graph_1.DepGraphBuilder({ name: 'gomodules' }, rootPkg); } const topLevelDeps = extractAllImports(localPackages); const childrenChain = new Map(); const ancestorsChain = new Map(); buildGraph(depGraphBuilder, topLevelDeps, packagesByName, 'root-node', childrenChain, ancestorsChain, options); return depGraphBuilder.build(); } exports.buildDepGraphFromImportsAndModules = buildDepGraphFromImportsAndModules; function buildGraph(depGraphBuilder, depPackages, packagesByName, currentParent, childrenChain, ancestorsChain, options, visited) { var _a; const depPackagesLen = depPackages.length; for (let i = depPackagesLen - 1; i >= 0; i--) { const localVisited = visited || new Set(); const packageImport = depPackages[i]; let version = 'unknown'; // ---------- Standard library handling ---------- if (isStandardLibraryPackage(packagesByName[packageImport])) { if (!options.includeGoStandardLibraryDeps) { continue; // skip when flag disabled } // All standard library packages are prefixed with "std/" const stdPackageName = `std/${packageImport}`; // create synthetic node and connect, then continue loop const stdNode = createPkgInfo(stdPackageName, options.stdlibVersion || version, options); depGraphBuilder.addPkgNode(stdNode, stdPackageName); depGraphBuilder.connectDep(currentParent, stdPackageName); continue; } // ---------- External package handling ---------- const pkgMeta = packagesByName[packageImport]; if (!pkgMeta || !pkgMeta.DepOnly) { continue; // skip local or root-module packages } const pkg = pkgMeta; const goModule = ((_a = pkg.Module) === null || _a === void 0 ? void 0 : _a.Replace) || pkg.Module; if (goModule === null || goModule === void 0 ? void 0 : goModule.Version) { // get hash (prefixed with #) or version (with v prefix removed) version = (0, version_1.toSnykVersion)((0, version_1.parseVersion)(goModule.Version)); } if (currentParent && packageImport) { const newNode = createPkgInfo(packageImport, version, options, goModule); const currentChildren = childrenChain.get(currentParent) || []; const currentAncestors = ancestorsChain.get(currentParent) || []; const isAncestorOrChild = currentChildren.includes(packageImport) || currentAncestors.includes(packageImport); // @TODO boost: breaking cycles, re-work once dep-graph lib can handle cycles if (packageImport === currentParent || isAncestorOrChild) { continue; } if (localVisited.has(packageImport)) { const prunedId = `${packageImport}:pruned`; depGraphBuilder.addPkgNode(newNode, prunedId, { labels: { pruned: 'true' }, }); depGraphBuilder.connectDep(currentParent, prunedId); continue; } depGraphBuilder.addPkgNode(newNode, packageImport); depGraphBuilder.connectDep(currentParent, packageImport); localVisited.add(packageImport); childrenChain.set(currentParent, [...currentChildren, packageImport]); ancestorsChain.set(packageImport, [...currentAncestors, currentParent]); const transitives = packagesByName[packageImport].Imports || []; if (transitives.length > 0) { buildGraph(depGraphBuilder, transitives, packagesByName, packageImport, childrenChain, ancestorsChain, options, localVisited); } } } } exports.buildGraph = buildGraph; function extractAllImports(goDeps) { const goDepsImports = new Set(); for (const pkg of goDeps) { if (pkg.Imports) { for (const imp of pkg.Imports) { goDepsImports.add(imp); } } } return Array.from(goDepsImports); } function isStandardLibraryPackage(pkgName) { // Go Standard Library Packages are marked as Standard: true return (pkgName === null || pkgName === void 0 ? void 0 : pkgName.Standard) === true; } function createPkgInfo(name, version, options, goModule) { let purl; if (options.includePackageUrls) { purl = goModule ? // If we are dealing with a GoModule, the purl should be constructed from its values, because details can differ // from `name` and `version`. (0, package_url_1.createGoPurl)(goModule, name) : // Otherwise create a simple purl that matches the `name` and `version` attributes. (0, package_url_1.createGoPurl)({ Path: name, Version: version }); } return { name, version, purl }; } //# sourceMappingURL=dep-graph.js.map