UNPKG

snyk-nuget-plugin

Version:
314 lines 15.9 kB
"use strict"; 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