snyk-nuget-plugin
Version:
Snyk CLI NuGet plugin
314 lines • 15.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDepGraphFromFiles = buildDepGraphFromFiles;
exports.buildDepTreeFromFiles = buildDepTreeFromFiles;
const fs = require("fs");
const path = require("path");
const csProjParser = require("./parsers/csproj-parser");
const debugModule = require("debug");
const depsParser = require("dotnet-deps-parser");
const dotnetCoreV3Parser = require("./parsers/dotnet-core-v3-parser");
const dotnetCoreParser = require("./parsers/dotnet-core-parser");
const dotnetFrameworkParser = require("./parsers/dotnet-framework-parser");
const projectJsonParser = require("./parsers/project-json-parser");
const packagesConfigParser = require("./parsers/packages-config-parser");
const errors_1 = require("../errors");
const types_1 = require("./types");
const dotnet = require("./cli/dotnet");
const nugetFrameworksParser = require("./csharp/nugetframeworks_parser");
const runtime_assembly_v2_1 = require("./runtime-assembly-v2");
const debug = debugModule('snyk');
const PROJECTSDK = 'Microsoft.NET.Sdk';
const PROJECT_ASSETS_FILENAME = 'project.assets.json';
const PARSERS = {
'dotnet-core': {
depParser: dotnetCoreParser,
fileContentParser: JSON,
},
'dotnet-core-v3': {
depParser: dotnetCoreV3Parser,
fileContentParser: JSON,
},
'packages.config': {
depParser: dotnetFrameworkParser,
fileContentParser: packagesConfigParser,
},
'project.json': {
depParser: dotnetFrameworkParser,
fileContentParser: projectJsonParser,
},
};
function getPackagesFolder(packagesFolder, projectRootFolder) {
if (packagesFolder) {
return path.resolve(process.cwd(), packagesFolder);
}
return path.resolve(projectRootFolder, 'packages');
}
function getRootName({ root, projectRootFolder, projectNamePrefix, fileContentPath, manifestType, }) {
let name = path.basename(root || projectRootFolder || '');
// Fallback (e.g. when root is `/` and basename is ''): derive name from manifest path.
if (!name && fileContentPath) {
let dir = path.dirname(fileContentPath);
if (manifestType === types_1.ManifestType.DOTNET_CORE &&
path.basename(dir).toLowerCase() === 'obj') {
// project.assets.json files often live inside an `obj/` build-artifact directory: in this case use the parent dir
dir = path.dirname(dir);
}
name = path.basename(dir);
}
return (projectNamePrefix || '') + (name || 'root');
}
function getFileContents(fileContentPath) {
try {
debug(`Parsing content of ${fileContentPath}`);
return fs.readFileSync(fileContentPath, 'utf-8');
}
catch (error) {
throw new errors_1.FileNotProcessableError(error);
}
}
async function resolveAssetsFilePath(root, targetFile) {
const expectedAssetsFile = path.resolve(root, targetFile);
if (fs.existsSync(expectedAssetsFile)) {
return expectedAssetsFile;
}
const targetFileDir = path.dirname(expectedAssetsFile);
const projectDir = path.dirname(targetFileDir);
let projectFile = null;
try {
const files = fs.readdirSync(projectDir);
const projectFiles = files.filter((file) => file.endsWith('.csproj') ||
file.endsWith('.vbproj') ||
file.endsWith('.fsproj'));
if (projectFiles.length > 0) {
projectFile = path.resolve(projectDir, projectFiles[0]);
}
}
catch (error) {
throw new errors_1.FileNotProcessableError(`Failed to scan project directory for .*proj files: ${error}`);
}
if (!projectFile) {
throw new errors_1.FileNotProcessableError(`Could not find any .csproj, .vbproj, or .fsproj files in directory: ${projectDir}`);
}
// Use MSBuild to get the correct BaseIntermediateOutputPath
const baseIntermediatePath = await dotnet.getBaseIntermediateOutputPath(projectFile);
if (!baseIntermediatePath) {
throw new errors_1.CliCommandError(`Could not determine BaseIntermediateOutputPath for project: ${projectFile}`);
}
const resolvedPath = path.isAbsolute(baseIntermediatePath)
? baseIntermediatePath
: path.resolve(projectDir, baseIntermediatePath);
const assetsFile = path.resolve(resolvedPath, PROJECT_ASSETS_FILENAME);
if (!fs.existsSync(assetsFile)) {
throw new errors_1.FileNotProcessableError(`project.assets.json not found at resolved path: ${assetsFile}. ` +
`Ensure 'dotnet restore' has been run successfully.`);
}
return assetsFile;
}
async function getResultsWithoutPublish(decidedTargetFrameworks, projectPath, safeRoot, nugetFrameworksParserLocation, resolvedProjectName, projectAssets) {
const parser = PARSERS['dotnet-core-v3'];
const projectFolder = projectPath ? path.dirname(projectPath) : safeRoot;
// Check if any target frameworks need runtime assembly overrides
const needsRuntimeOverrides = decidedTargetFrameworks.some((framework) => !framework.includes('netstandard') && !framework.includes('netcoreapp'));
const overridesAssemblies = {};
// Only load runtime overrides if we have frameworks that need them (exclude netstandard and netcoreapp)
if (needsRuntimeOverrides) {
const { sdkVersion, sdkPath } = await (0, runtime_assembly_v2_1.extractSdkInfo)(projectFolder);
const localRuntimes = await dotnet.execute(['--list-runtimes'], projectFolder);
const runtimeVersion = (0, runtime_assembly_v2_1.findLatestMatchingVersion)(localRuntimes, sdkVersion);
try {
const overridesPath = `${path.dirname(sdkPath)}${runtime_assembly_v2_1.PACKS_PATH}${runtimeVersion}/${runtime_assembly_v2_1.PACKAGE_OVERRIDES_FILE}`;
const overridesText = fs.readFileSync(overridesPath, 'utf-8');
for (const pkg of overridesText.split('\n')) {
if (pkg) {
const [name, version] = pkg.split('|');
// Trim any carriage return
overridesAssemblies[name] = version.trim();
}
}
}
catch (err) {
throw new errors_1.FileNotProcessableError(`Failed to read PackageOverrides.txt, error: ${err}`);
}
}
// Loop through all TargetFrameworks supplied and generate a dependency graph for each.
const results = [];
for (const decidedTargetFramework of decidedTargetFrameworks) {
// Parse the TargetFramework using Nuget.Frameworks itself, instead of trying to reinvent the wheel, thus ensuring
// we have maximum context to use later when building the depGraph.
const response = await dotnet.run(nugetFrameworksParserLocation, [
decidedTargetFramework,
]);
const targetFrameworkInfo = JSON.parse(response);
if (targetFrameworkInfo.IsUnsupported) {
throw new errors_1.InvalidManifestError(`dotnet was not able to parse the target framework ${decidedTargetFramework}, it was reported unsupported by the dotnet runtime`);
}
const overrides = {
overridesAssemblies,
// .NET Standard and .NET Core App frameworks don't need runtime assembly overrides
// as they don't provide specific runtime assembly information that can be read more precisely
// than what's available in the project.assets.json file.
overrideVersion: decidedTargetFramework.includes('netstandard') ||
decidedTargetFramework.includes('netcoreapp')
? undefined
: targetFrameworkInfo.Version.split('.').slice(0, -1).join('.'),
};
let targetFramework = decidedTargetFramework;
if (targetFrameworkInfo.Framework === '.NETStandard') {
targetFramework = targetFrameworkInfo.DotNetFrameworkName;
}
const depGraph = parser.depParser.parse(resolvedProjectName, targetFramework, projectAssets, overrides);
results.push({
dependencyGraph: depGraph,
targetFramework: decidedTargetFramework,
});
}
return results;
}
async function buildDepGraphFromFiles(root, targetFile, manifestType, useProjectNameFromAssetsFile, projectNamePrefix, targetFramework) {
const safeRoot = root || '.';
const safeTargetFile = targetFile || '.';
// Resolve the correct assets file path using MSBuild if needed
const fileContentPath = await resolveAssetsFilePath(safeRoot, safeTargetFile);
const fileContent = getFileContents(fileContentPath);
const parser = PARSERS['dotnet-core-v3'];
const projectAssets = parser.fileContentParser.parse(fileContent);
if (!projectAssets.project?.frameworks) {
throw new errors_1.FileNotProcessableError(`unable to detect any target framework in manifest file ${safeTargetFile}, a valid one is needed to continue down this path.`);
}
// Scan all 'frameworks' detected in the project.assets.json file, and use the targetAlias if detected and
// otherwise the raw key name, as it's not guaranteed that all framework objects contains a targetAlias.
const targetFrameworks = Object.entries(projectAssets.project.frameworks).map(([key, value]) => ('targetAlias' in value ? value.targetAlias : key));
if (targetFrameworks.length <= 0) {
throw new errors_1.FileNotProcessableError(`unable to detect a target framework in ${safeTargetFile}, a valid one is needed to continue down this path.`);
}
if (targetFramework && !targetFrameworks.includes(targetFramework)) {
console.warn(`\x1b[33m⚠ WARNING\x1b[0m: Supplied targetframework \x1b[1m${targetFramework}\x1b[0m was not detected in the supplied
manifest file. Available targetFrameworks detected was \x1b[1m${targetFrameworks.join(',')}\x1b[0m.
Will attempt to build dependency graph anyway, but the operation might fail.`);
}
let resolvedProjectName = getRootName({
root,
projectRootFolder: safeRoot,
projectNamePrefix,
fileContentPath,
manifestType,
});
const projectNameFromManifestFile = projectAssets?.project?.restore?.projectName;
if (manifestType === types_1.ManifestType.DOTNET_CORE &&
useProjectNameFromAssetsFile) {
if (projectNameFromManifestFile) {
resolvedProjectName = projectNameFromManifestFile;
}
else {
debug(`project.assets.json file doesn't contain a value for 'projectName'. Using default value: ${resolvedProjectName}`);
}
}
// If a specific targetFramework has been requested, only query that, otherwise try to do them all
// Passing a const value as the project sdk. Why? The targetFile it's project.assets.json, which gets generated
// only for the sdk style projects. The assets file won't get generated for projects which rely on packages.config.
// The reason behind deciding to call this method is because maybe in the future we want to not support some specific
// target frameworks.
const decidedTargetFrameworks = targetFramework
? [targetFramework]
: targetFrameworks.filter((framework) => depsParser.isSupportedByV3GraphGeneration(framework, PROJECTSDK));
if (decidedTargetFrameworks.length == 0) {
throw new errors_1.InvalidManifestError(`Was not able to find any supported TargetFrameworks to scan, aborting`);
}
// Ensure `dotnet` is installed on the system or fail trying.
await dotnet.validate();
// Write a .NET Framework Parser to a temporary directory for validating TargetFrameworks.
const nugetFrameworksParserLocation = nugetFrameworksParser.generate();
await dotnet.restore(nugetFrameworksParserLocation);
// Access the path of this project, to ensure we get the right .csproj file, in case of multiples present
const projectPath = projectAssets.project.restore.projectPath;
if (!projectPath) {
console.warn(`\x1b[33m⚠ WARNING\x1b[0m: Could not detect any projectPath in the project assets file, if your solution contains multiple projects in the same folder, this operation might fail.`);
}
return getResultsWithoutPublish(decidedTargetFrameworks, projectPath, safeRoot, nugetFrameworksParserLocation, resolvedProjectName, projectAssets);
}
async function buildDepTreeFromFiles(root, targetFile, packagesFolderPath, manifestType, useProjectNameFromAssetsFile, projectNamePrefix) {
const safeRoot = root || '.';
const safeTargetFile = targetFile || '.';
const fileContentPath = path.resolve(safeRoot, safeTargetFile);
const fileContent = getFileContents(fileContentPath);
const projectRootFolder = path.resolve(fileContentPath, '../../');
const packagesFolder = getPackagesFolder(packagesFolderPath, projectRootFolder);
if (manifestType === types_1.ManifestType.PROJECT_JSON) {
let json;
try {
json = JSON.parse(fileContent);
}
catch (err) {
throw new errors_1.FileNotProcessableError(`Failed to parse project.json: ${err}`);
}
const hasAnyRequiredProp = [
'dependencies',
'frameworks',
'runtimes',
'supports',
].some((prop) => prop in json);
if (!hasAnyRequiredProp) {
throw new errors_1.NotSupportedEcosystem('project.json file is not a valid project.json file');
}
}
const tree = {
dependencies: {},
meta: {},
name: getRootName({
root,
projectRootFolder,
projectNamePrefix,
fileContentPath,
manifestType,
}),
packageFormatVersion: 'nuget:0.0.0',
version: '0.0.0',
};
let targetFrameworks;
try {
if (manifestType === types_1.ManifestType.DOTNET_CORE) {
targetFrameworks =
csProjParser.getTargetFrameworksFromProjFile(projectRootFolder);
}
else {
// .csproj is in the same directory as packages.config or project.json
const fileContentParentDirectory = path.resolve(fileContentPath, '../');
targetFrameworks = csProjParser.getTargetFrameworksFromProjFile(fileContentParentDirectory);
// finally, for the .NETFramework project, try to assume the framework using dotnet-deps-parser
if (targetFrameworks.length <= 0) {
// currently only process packages.config files
if (manifestType === types_1.ManifestType.PACKAGES_CONFIG) {
const minimumTargetFramework = await packagesConfigParser.getMinimumTargetFramework(fileContent);
if (minimumTargetFramework) {
targetFrameworks = [minimumTargetFramework];
}
}
}
}
}
catch (error) {
return Promise.reject(error);
}
// Only supports the first targetFramework we find.
// Use the newer `buildDepGraphFromFiles` for better support for multiple target frameworks.
const targetFramework = targetFrameworks.length > 0 ? targetFrameworks[0].original : undefined;
tree.meta = {
targetFramework: targetFramework,
};
const parser = PARSERS[manifestType];
const manifest = await parser.fileContentParser.parse(fileContent, tree);
if (manifestType === types_1.ManifestType.DOTNET_CORE &&
useProjectNameFromAssetsFile) {
const projectName = manifest?.project?.restore?.projectName;
if (projectName) {
tree.name = projectName;
}
else {
debug("project.assets.json file doesn't contain a value for 'projectName'. Using default value: " +
tree.name);
}
}
return parser.depParser.parse(tree, manifest, targetFramework, packagesFolder);
}
//# sourceMappingURL=index.js.map