snyk-nuget-plugin
Version:
Snyk CLI NuGet plugin
144 lines • 8.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FILTERED_DEPENDENCY_PREFIX = void 0;
exports.parse = parse;
const debugModule = require("debug");
const dep_graph_1 = require("@snyk/dep-graph");
const errors_1 = require("../../errors");
const debug = debugModule('snyk');
// Dependencies that starts with these are discarded
exports.FILTERED_DEPENDENCY_PREFIX = [
// `runtime` and `runtime.native` are a bit of a hot topic, see more https://github.com/dotnet/core/issues/7568.
// For our case, we are already creating the correct dependencies and their respective runtime version numbers based
// of our runtime resolution logic. So a dependency will already be `System.Net.Http@8.0.0` if running on .NET 8, thus
// removing the need for a `runtime.native.System.Net.Http@8.0.0` as well. From our investigation these runtime native
// dependencies are causing noise for the customers and are not of interested.
'runtime',
];
function recursivelyPopulateNodes(depGraphBuilder, resolvedPackages, parentID, dependencies, overrides, visited) {
if (!dependencies) {
return;
}
const visitedCopy = new Set(visited);
for (const [childName, childResolvedVersion] of Object.entries(dependencies)) {
const localVisited = visitedCopy || new Set();
// Ignore packages with specific prefixes, which for one reason or the other are no interesting and pollutes the
// graph. Refer to comments on the individual elements in the ignore list for more information.
if (exports.FILTERED_DEPENDENCY_PREFIX.some((prefix) => childName.startsWith(prefix))) {
debug(`${childName} matched a prefix we ignore, not adding to graph`);
continue;
}
// Find the actual resolved version and target for this package name
// NuGet may resolve to a different version than what's declared in transitive dependencies
// and use the lowercased name as NuGet packages are case-insensitive
const lowercaseChildName = childName.toLowerCase();
const resolvedPackage = resolvedPackages[lowercaseChildName];
if (!resolvedPackage) {
debug(`Child package ${childName} not found in lock file packages for framework.`);
continue;
}
const { name: actualPkgName, resolvedVersion: actualResolvedVersion, target: childPkgEntry, } = resolvedPackage;
if (childResolvedVersion !== actualResolvedVersion) {
debug(`Version mismatch for ${childName}: declared ${childResolvedVersion}, using resolved ${actualResolvedVersion}`);
}
const childID = `${actualPkgName}@${actualResolvedVersion}`;
let finalVersion = actualResolvedVersion;
// If we're looking at a runtime assembly version for self-contained dlls, overwrite the dependency version
// we've found in the graph with those from the runtime assembly, as they take precedence.
if (overrides.overrideVersion &&
+actualResolvedVersion.split('.')[0] < 6 &&
actualPkgName in overrides.overridesAssemblies &&
+overrides.overridesAssemblies[actualPkgName].split('.')[0] < 6) {
finalVersion = overrides.overrideVersion;
}
if (localVisited.has(childID)) {
const prunedID = `${childID}:pruned`;
depGraphBuilder.addPkgNode({ name: actualPkgName, version: finalVersion }, prunedID, {
labels: { pruned: 'true' },
});
depGraphBuilder.connectDep(parentID, prunedID);
debug(`Pruning duplicate dependency: ${parentID} -> ${childID}`);
continue;
}
depGraphBuilder.addPkgNode({ name: actualPkgName, version: finalVersion }, childID);
depGraphBuilder.connectDep(parentID, childID);
localVisited.add(childID);
debug(`Adding dependency: ${parentID} -> ${childID}`);
recursivelyPopulateNodes(depGraphBuilder, resolvedPackages, childID, childPkgEntry.dependencies, overrides, localVisited);
}
}
function buildDepGraph(projectName, targetFramework, projectAssets, overrides) {
const depGraphBuilder = new dep_graph_1.DepGraphBuilder({ name: 'nuget' }, {
name: projectName,
version: projectAssets.project.version,
});
if (!targetFramework) {
// This should ideally not happen if validateManifest and parse are called first
throw new errors_1.InvalidManifestError('Target framework not found in lock file metadata.');
}
const assetsTargetFrameworks = Object.keys(projectAssets.targets);
// Match the targetFramework as a prefix or a superstring of the entries in project.assets.json to handle this:
// [superstring] net6.0-windows10.0.19041.0 => net6.0-windows10.0.19041
// [prefix] net8.0-windows => net8.0-windows7.0
const partialMatches = assetsTargetFrameworks.filter((target) => target.startsWith(targetFramework) || targetFramework.startsWith(target));
// If no partial match could be found, use the first entry in project.assets.json as a last resort to avoid failing.
const assetsTargetFramework = partialMatches.length > 0 ? partialMatches[0] : assetsTargetFrameworks[0];
if (!assetsTargetFramework) {
// This should ideally not happen if validateManifest and parse are called first
throw new errors_1.InvalidManifestError(`No target framework found in project.assets.json dependencies, ${targetFramework} requested.`);
}
if (assetsTargetFramework !== targetFramework) {
debug(`Using ${assetsTargetFramework} instead of requested ${targetFramework} (partial matches: ${partialMatches.join(',')})`);
}
const allPackagesForFramework = projectAssets.targets[assetsTargetFramework];
const resolvedPackages = {};
for (const [key, target] of Object.entries(allPackagesForFramework)) {
const [name, version] = key.split('/');
// Use the lowercased name for lookups as NuGet packages are case-insensitive.
resolvedPackages[name.toLowerCase()] = {
name,
resolvedVersion: version,
target,
};
}
// Identify direct dependencies for the selected framework
const directDependencies = {};
projectAssets.projectFileDependencyGroups[assetsTargetFramework].forEach((dependency) => {
const dependencySplit = dependency.split(' ');
directDependencies[dependencySplit[0]] = dependencySplit[2];
});
debug(`Direct dependencies found in lock file for ${assetsTargetFramework}: '${Object.keys(directDependencies)}'`);
if (Object.keys(directDependencies).length === 0) {
debug('No direct dependencies found in project.assets.json for the selected framework.');
// Return a graph with just the root if no direct dependencies
return depGraphBuilder.build();
}
// Start recursive population from direct dependencies
recursivelyPopulateNodes(depGraphBuilder, resolvedPackages, 'root-node', directDependencies, // Pass the direct dependencies object
overrides);
return depGraphBuilder.build();
}
function validateManifest(manifest) {
if (!manifest.project) {
throw new errors_1.InvalidManifestError('Project field was not found in project.assets.json');
}
if (!manifest.project.frameworks) {
throw new errors_1.InvalidManifestError('No frameworks were found in project.assets.json');
}
if (!manifest.project.frameworks ||
Object.keys(manifest.project.frameworks).length === 0) {
throw new errors_1.InvalidManifestError('0 frameworks were found in project.assets.json');
}
if (!manifest.targets) {
throw new errors_1.InvalidManifestError('No targets were found in project.assets.json');
}
if (!manifest.targets || Object.keys(manifest.targets).length === 0) {
throw new errors_1.InvalidManifestError('0 targets were found in project.assets.json');
}
}
function parse(projectName, targetFramework, projectAssets, overrides) {
debug('Trying to parse project.assets.json manifest with v3 depGraph builder');
validateManifest(projectAssets);
return buildDepGraph(projectName, targetFramework, projectAssets, overrides);
}
//# sourceMappingURL=dotnet-core-v3-parser.js.map