UNPKG

@soos-io/api-client

Version:

This is the SOOS API Client for registered clients leveraging the various integrations to the SOOS platform. Register for a free trial today at https://app.soos.io/register

662 lines (661 loc) 35.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GeneratedScanTypes = void 0; const tslib_1 = require("tslib"); const enums_1 = require("./../enums"); const SOOSAnalysisApiClient_1 = tslib_1.__importDefault(require("../api/SOOSAnalysisApiClient")); const SOOSProjectsApiClient_1 = tslib_1.__importDefault(require("../api/SOOSProjectsApiClient")); const SOOSUserApiClient_1 = tslib_1.__importDefault(require("../api/SOOSUserApiClient")); const constants_1 = require("../constants"); const enums_2 = require("../enums"); const logging_1 = require("../logging"); const utilities_1 = require("../utilities"); const FileSystem = tslib_1.__importStar(require("fs")); const Path = tslib_1.__importStar(require("path")); const form_data_1 = tslib_1.__importDefault(require("form-data")); const Glob = tslib_1.__importStar(require("glob")); const SOOSHooksApiClient_1 = tslib_1.__importDefault(require("../api/SOOSHooksApiClient")); const SOOSAttributionApiClient_1 = tslib_1.__importDefault(require("../api/SOOSAttributionApiClient")); const contributingDeveloperEnvironmentVariables = [ "Build_RequestedFor", "Build_RequestedForEmail", "CODEBUILD_BUILD_INITIATOR", "CODEBUILD_INITIATOR", "bamboo_planRepository_1_username", "bamboo_ManualBuildTriggerReason_userName", "BITBUCKET_STEP_TRIGGERER_UUID", "CIRCLE_USERNAME", "CIRCLE_PR_USERNAME", "CI_COMMITTER_USERNAME", "CI_COMMITTER_EMAIL", "GITHUB_ACTOR", "GITHUB_TRIGGERING_ACTOR", "CHANGE_AUTHOR", "CHANGE_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "SOOS_CONTRIBUTING_DEVELOPER", "TEAMCITY_BUILD_TRIGGEREDBY_USERNAME", "TRAVIS_JOB_RESTARTED_BY", "SOOS_CONTRIBUTING_DEVELOPER", ]; const GeneratedScanTypes = [enums_2.ScanType.CSA, enums_2.ScanType.SBOM, enums_2.ScanType.SCA]; exports.GeneratedScanTypes = GeneratedScanTypes; const NoneColor = "\x1b[32m"; const InfoColor = "\x1b[34m"; const LowColor = "\x1b[90m"; const MediumColor = "\x1b[33m"; const HighColor = "\x1b[31m"; const CriticalColor = "\x1b[31m"; const ResetColor = "\x1b[0m"; class AnalysisService { analysisApiClient; attributionApiClient; projectsApiClient; userApiClient; hooksApiClient; constructor(analysisApiClient, attributionApiClient, projectsApiClient, userApiClient, hooksApiClient) { this.analysisApiClient = analysisApiClient; this.attributionApiClient = attributionApiClient; this.projectsApiClient = projectsApiClient; this.userApiClient = userApiClient; this.hooksApiClient = hooksApiClient; } static create(apiKey, apiURL) { const analysisApiClient = new SOOSAnalysisApiClient_1.default(apiKey, apiURL); const attributionApiClient = new SOOSAttributionApiClient_1.default(apiKey, apiURL); const projectsApiClient = new SOOSProjectsApiClient_1.default(apiKey, apiURL.replace("api.", "api-projects.")); const userApiClient = new SOOSUserApiClient_1.default(apiKey, apiURL.replace("api.", "api-user.")); const hooksApiClient = new SOOSHooksApiClient_1.default(apiKey, apiURL.replace("api.", "api-hooks.")); return new AnalysisService(analysisApiClient, attributionApiClient, projectsApiClient, userApiClient, hooksApiClient); } logStatusMessage(message) { if (message) { switch (message.severity) { case enums_2.SeverityEnum.Unknown: case enums_2.SeverityEnum.None: case enums_2.SeverityEnum.Info: case enums_2.SeverityEnum.Low: logging_1.soosLogger.info(message.message); break; case enums_2.SeverityEnum.Medium: case enums_2.SeverityEnum.High: logging_1.soosLogger.warn(message.message); break; case enums_2.SeverityEnum.Critical: logging_1.soosLogger.error(message.message); break; } if (message.url) { const linkText = message.linkText ? `[${message.linkText}]` : ""; logging_1.soosLogger.info(`${linkText}(${message.url})`); } } } async setupScan({ clientId, projectName, branchName, commitHash, buildVersion, buildUri, branchUri, integrationType, operatingEnvironment, integrationName, appVersion, scriptVersion, contributingDeveloperAudit, scanType, toolName, toolVersion, commandLine, scanMode, }) { logging_1.soosLogger.info("Checking SOOS App status..."); const applicationStatus = await this.userApiClient.getApplicationStatus(clientId); this.logStatusMessage(applicationStatus.statusMessage); this.logStatusMessage(applicationStatus.clientMessage); logging_1.soosLogger.logLineSeparator(); logging_1.soosLogger.info(`Creating scan for project '${projectName}'...`); if (branchName) { logging_1.soosLogger.info(`Branch Name: ${branchName}`); } if (contributingDeveloperAudit === undefined || contributingDeveloperAudit.length === 0) { contributingDeveloperAudit = contributingDeveloperEnvironmentVariables .map((ev) => { const environmentVariableValue = process.env[ev]; return environmentVariableValue && environmentVariableValue.length > 0 ? { source: enums_2.ContributingDeveloperSource.EnvironmentVariable, sourceName: ev, contributingDeveloperId: environmentVariableValue, } : null; }) .filter((a) => a !== null); } const result = await this.analysisApiClient.createScan({ clientId: clientId, projectName: projectName, commitHash: commitHash, branch: branchName, buildVersion: buildVersion, buildUri: buildUri, branchUri: branchUri, integrationType: integrationType, operatingEnvironment: operatingEnvironment, integrationName: integrationName, appVersion: appVersion, scriptVersion: scriptVersion, contributingDeveloperAudit: contributingDeveloperAudit, scanType: scanType, toolName: toolName, toolVersion: toolVersion, commandLine: commandLine, scanMode: scanMode, }); logging_1.soosLogger.info(`Project Hash: ${result.projectHash}`); logging_1.soosLogger.info(`Branch Hash: ${result.branchHash}`); logging_1.soosLogger.info(`Scan Id: ${result.analysisId}`); logging_1.soosLogger.info("Scan created successfully."); return result; } async startScan({ clientId, projectHash, analysisId, scanType, }) { logging_1.soosLogger.info(`Starting ${scanType} Analysis Scan`); await this.analysisApiClient.startScan({ clientId: clientId, projectHash: projectHash, analysisId: analysisId, }); logging_1.soosLogger.info("Analysis scan started successfully... waiting for the scan"); } async waitForScanToFinish({ scanStatusUrl, scanUrl, scanType, }) { await (0, utilities_1.sleep)(constants_1.SOOS_CONSTANTS.Status.DelayTime); const scanStatus = await this.analysisApiClient.getScanStatus({ scanStatusUrl: scanStatusUrl, }); if (!scanStatus.isComplete) { logging_1.soosLogger.info(`${utilities_1.StringUtilities.fromCamelToTitleCase(scanStatus.status)}...`); return await this.waitForScanToFinish({ scanStatusUrl, scanUrl, scanType }); } if (scanStatus.errors.length > 0) { logging_1.soosLogger.group("Errors:"); logging_1.soosLogger.warn(JSON.stringify(scanStatus.errors, null, 2)); logging_1.soosLogger.groupEnd(); } const output = this.getFinalScanStatusMessage(scanType, scanStatus, scanUrl, true); logging_1.soosLogger.logLineSeparator(); output.map((o) => logging_1.soosLogger.always(o)); logging_1.soosLogger.logLineSeparator(); return scanStatus.status; } getColorBySeverity(severity, colorize) { if (!severity) { return ""; } switch (severity.toLocaleLowerCase()) { default: case "unknown": return ""; case "none": return colorize ? NoneColor : ""; case "info": return colorize ? InfoColor : ""; case "low": return colorize ? LowColor : ""; case "medium": return colorize ? MediumColor : ""; case "high": return colorize ? HighColor : ""; case "critical": return colorize ? CriticalColor : ""; } } getResetColor(colorize) { return colorize ? ResetColor : ""; } getFinalScanStatusMessage(scanType, scanStatus, scanUrl, colorize = false) { const isGeneratedScanType = GeneratedScanTypes.includes(scanType); const output = []; output.push(`Scan ${scanStatus.isSuccess ? `${this.getColorBySeverity("none", colorize)}passed${this.getResetColor(colorize)}` : `${this.getColorBySeverity("high", colorize)}failed${this.getResetColor(colorize)}`}${scanStatus.isSuccess ? " with:" : " because of:"}`); const maxLengthOfIssueText = 26; const padChar = " "; if (isGeneratedScanType) { const vulnerabilityCount = scanStatus.issues?.Vulnerability?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(vulnerabilityCount, "Vulnerability:", "Vulnerabilities:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Vulnerability?.maxSeverity, colorize)}${vulnerabilityCount}${this.getResetColor(colorize)}`); } const violationCount = scanStatus.issues?.Violation?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(violationCount, "Violation:", "Violations:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Violation?.maxSeverity, colorize)}${violationCount}${this.getResetColor(colorize)}`); if (scanType === enums_2.ScanType.DAST) { const dastCount = scanStatus.issues?.Dast?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(dastCount, "Web Vulnerability:", "Web Vulnerabilities:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Dast?.maxSeverity, colorize)}${dastCount}${this.getResetColor(colorize)}`); } if (scanType === enums_2.ScanType.SAST) { const sastCount = scanStatus.issues?.Sast?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(sastCount, "Code Issue:", "Code Issues:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Sast?.maxSeverity, colorize)}${sastCount}${this.getResetColor(colorize)}`); } if (isGeneratedScanType) { const unknownPackageCount = scanStatus.issues?.UnknownPackage?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(unknownPackageCount, "Unknown Package:", "Unknown Packages:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.UnknownPackage?.maxSeverity, colorize)}${unknownPackageCount}${this.getResetColor(colorize)}`); const dependencyTypoCount = scanStatus.issues?.DependencyTypo?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(dependencyTypoCount, "Dependency Typo:", "Dependency Typos:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.DependencyTypo?.maxSeverity, colorize)}${dependencyTypoCount}${this.getResetColor(colorize)}`); const dependencySubstitutionCount = scanStatus.issues?.DependencySubstitution?.count ?? 0; output.push(`${utilities_1.StringUtilities.pluralizeWord(dependencySubstitutionCount, "Dependency Substitution:", "Dependency Substitutions:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.DependencySubstitution?.maxSeverity, colorize)}${dependencySubstitutionCount}${this.getResetColor(colorize)}`); } output.push(`Scan Report: ${scanUrl}`); return output; } async waitForAttributionToFinish({ clientId, projectHash, branchHash, scanId, attributionId, }) { const attributionStatus = await this.attributionApiClient.getAttributionStatus({ clientId, projectHash, branchHash, scanId, attributionId, }); if (attributionStatus.status === enums_1.AttributionStatusEnum.Requested || attributionStatus.status === enums_1.AttributionStatusEnum.InProgress) { logging_1.soosLogger.info(`Waiting for export to complete (${attributionStatus.status})...`); await (0, utilities_1.sleep)(constants_1.SOOS_CONSTANTS.Status.DelayTime); return await this.waitForAttributionToFinish({ clientId, projectHash, branchHash, scanId, attributionId, }); } if (attributionStatus.status === enums_1.AttributionStatusEnum.CompletedWithProblems || attributionStatus.status === enums_1.AttributionStatusEnum.Failed) { logging_1.soosLogger.warn(JSON.stringify(attributionStatus.message)); } return attributionStatus; } async generateFormattedOutput({ clientId, projectHash, projectName, branchHash, analysisId, format, fileType, includeDependentProjects, includeVulnerabilities, includeOriginalSbom, workingDirectory, }) { logging_1.soosLogger.info(`Generating ${format} report as ${fileType} for ${projectName}...`); const attributionStatus = await this.attributionApiClient.createAttributionRequest({ clientId: clientId, projectHash: projectHash, branchHash: branchHash, scanId: analysisId, format, fileType, includeDependentProjects, includeVulnerabilities, includeOriginalSbom, }); const finalAttributionStatus = await this.waitForAttributionToFinish({ clientId, projectHash, branchHash, scanId: analysisId, attributionId: attributionStatus.id, }); if (finalAttributionStatus.status === enums_1.AttributionStatusEnum.Completed || finalAttributionStatus.status === enums_1.AttributionStatusEnum.CompletedWithProblems) { const output = await this.attributionApiClient.getScanAttribution({ clientId, projectHash, branchHash, scanId: analysisId, attributionId: finalAttributionStatus.id, }); if (finalAttributionStatus.filename) { logging_1.soosLogger.info(`${format} report generated successfully.`); const outputFile = Path.join(workingDirectory, finalAttributionStatus.filename); logging_1.soosLogger.info(`Writing ${format} report to ${outputFile}`); FileSystem.writeFileSync(outputFile, output); } else { logging_1.soosLogger.error(`${format} report was not generated. Verify a working directory was provided and try again.`); } } else { logging_1.soosLogger.error(`${format} report generation failed.`); } } async updateScanStatus({ clientId, projectHash, branchHash, scanType, analysisId, status, message, scanStatusUrl, }) { if (!(0, utilities_1.isNil)(scanStatusUrl)) { const scanStatus = await this.analysisApiClient.getScanStatus({ scanStatusUrl: scanStatusUrl, }); if (scanStatus.isComplete) return; } await this.analysisApiClient.updateScanStatus({ clientId: clientId, projectHash: projectHash, branchHash: branchHash, scanType: scanType, scanId: analysisId, status: status, message: message, }); if (status === enums_2.ScanStatus.Incomplete || status === enums_2.ScanStatus.Error) logging_1.soosLogger.error(message); } async findAnalysisFiles(scanType, path, pattern, filesToExclude = null, directoriesToExclude = null, maxFiles = 0) { process.chdir(path); logging_1.soosLogger.info(`Searching for ${scanType} files from ${path}...`); const files = Glob.sync(pattern, { ignore: [ ...(filesToExclude || []), ...(directoriesToExclude || []), constants_1.SOOS_CONSTANTS.Files.SoosDirectoryExclusionGlobPattern, ], nocase: true, }); const matchingFiles = files.map((x) => Path.resolve(x)); logging_1.soosLogger.info(`${matchingFiles.length} files found matching pattern '${pattern}'.`); matchingFiles.flat().map((filePath) => { const filename = Path.basename(filePath); const fileStats = FileSystem.statSync(filePath); const fileSize = (0, utilities_1.formatBytes)(fileStats.size); logging_1.soosLogger.info(`Found ${scanType} file '${filename}' (${fileSize}) at location '${filePath}'.`); }); if (maxFiles < 1) { return { filePaths: matchingFiles, hasMoreThanMaximumFiles: false }; } const hasMoreThanMaximumFiles = matchingFiles.length > maxFiles; const filesToUpload = matchingFiles.slice(0, maxFiles); if (hasMoreThanMaximumFiles) { const filesToSkip = matchingFiles.slice(maxFiles); const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(matchingFiles.length, "file was", "files were"); const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file"); logging_1.soosLogger.info(`The maximum number of ${scanType} files per scan is ${maxFiles}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following files will not be included in the scan: \n`, filesToSkip.map((file) => ` "${Path.basename(file)}": "${file}"`).join("\n")); } return { filePaths: filesToUpload, hasMoreThanMaximumFiles }; } async findManifestsAndHashableFiles({ clientId, projectHash, filesToExclude, directoriesToExclude, sourceCodePath, packageManagers, fileMatchType, }) { const supportedScanFileFormats = await this.analysisApiClient.getSupportedScanFileFormats({ clientId: clientId, }); const runFileHashing = fileMatchType === enums_1.FileMatchTypeEnum.FileHash || fileMatchType === enums_1.FileMatchTypeEnum.ManifestAndFileHash; const runManifestMatching = fileMatchType === enums_1.FileMatchTypeEnum.Manifest || fileMatchType === enums_1.FileMatchTypeEnum.ManifestAndFileHash; const filteredPackageManagers = packageManagers.length === 0 ? supportedScanFileFormats : supportedScanFileFormats.filter((packageManagerScanFileFormats) => packageManagers.some((pm) => utilities_1.StringUtilities.areEqual(pm, packageManagerScanFileFormats.packageManager, { sensitivity: "base", }))); const settings = await this.projectsApiClient.getProjectSettings({ clientId: clientId, projectHash, }); const manifestFormats = !runManifestMatching ? [] : filteredPackageManagers.flatMap((fpm) => { return { packageManager: fpm.packageManager, manifests: fpm.manifests?.map((sm) => { return { isLockFile: sm.isLockFile, includeWithLockFiles: sm.includeWithLockFiles, supportsLockFiles: sm.SupportsLockFiles, pattern: sm.pattern, }; }) ?? [], }; }); if (runManifestMatching) { logging_1.soosLogger.debug(`Running manifest file matching for ${manifestFormats.length} manifest formats.`); } const manifestFiles = !runManifestMatching ? [] : this.searchForManifestFiles({ packageManagerManifests: manifestFormats, useLockFile: settings.useLockFile ?? false, filesToExclude, directoriesToExclude, sourceCodePath, }); const archiveHashFormats = !runFileHashing ? [] : filteredPackageManagers.flatMap((fpm) => { const hashableFiles = fpm.hashableFiles ?? []; return !hashableFiles.some((hf) => hf.archiveFileExtensions) ? [] : { packageManager: fpm.packageManager, fileFormats: hashableFiles.map((hf) => { return { hashAlgorithms: hf.hashAlgorithms, patterns: hf.archiveFileExtensions?.filter((afe) => !(0, utilities_1.isNil)(afe)) ?? [], }; }) ?? [], }; }); if (runFileHashing) { logging_1.soosLogger.debug(`Running file hash matching for ${archiveHashFormats.length} archive file formats.`); } const archiveFileHashManifests = !runFileHashing || !archiveHashFormats.some((ahf) => ahf.fileFormats) ? [] : this.searchForHashableFiles({ hashableFileFormats: archiveHashFormats.filter((ahf) => ahf.fileFormats), sourceCodePath, filesToExclude, directoriesToExclude, }); const contentHashFormats = !runFileHashing ? [] : filteredPackageManagers.flatMap((fpm) => { const hashableFiles = fpm.hashableFiles ?? []; return !hashableFiles.some((hf) => hf.archiveContentFileExtensions) ? [] : { packageManager: fpm.packageManager, fileFormats: hashableFiles.map((hf) => { return { hashAlgorithms: hf.hashAlgorithms, patterns: hf.archiveContentFileExtensions?.filter((afe) => !(0, utilities_1.isNil)(afe)) ?? [], }; }) ?? [], }; }); if (runFileHashing) { logging_1.soosLogger.debug(`Running file hash matching for ${contentHashFormats.length} file formats.`); } const contentFileHashManifests = !runFileHashing || !contentHashFormats.some((chf) => chf.fileFormats) ? [] : this.searchForHashableFiles({ hashableFileFormats: contentHashFormats.filter((chf) => chf.fileFormats), sourceCodePath, filesToExclude, directoriesToExclude, }); const hashManifests = archiveFileHashManifests .concat(contentFileHashManifests) .filter((hm) => hm.fileHashes.length > 0); if (runFileHashing && hashManifests) { for (const soosHashesManifest of hashManifests) { if (soosHashesManifest.fileHashes.length > 0) { const hashManifestFileName = `${soosHashesManifest.packageManager}${constants_1.SOOS_CONSTANTS.SCA.SoosFileHashesManifest}`; const hashManifestPath = Path.join(sourceCodePath, hashManifestFileName); logging_1.soosLogger.info(`Generating SOOS hashes manifest: ${hashManifestPath}`); FileSystem.writeFileSync(hashManifestPath, JSON.stringify(soosHashesManifest, null, 2)); manifestFiles.push({ packageManager: soosHashesManifest.packageManager, name: hashManifestFileName, path: hashManifestPath, }); } } } return { manifestFiles, hashManifests }; } searchForManifestFiles({ packageManagerManifests, useLockFile, filesToExclude, directoriesToExclude, sourceCodePath, }) { const currentDirectory = process.cwd(); logging_1.soosLogger.info(`Setting current directory to project path '${sourceCodePath}'.`); process.chdir(sourceCodePath); logging_1.soosLogger.info(`Lock file setting is ${useLockFile ? "on, ignoring non-lock files" : "off, ignoring lock files"}.`); const manifestFiles = packageManagerManifests.reduce((accumulator, packageManagerManifests) => { const matches = packageManagerManifests.manifests .filter((manifest) => useLockFile === manifest.isLockFile || (useLockFile && !manifest.isLockFile && !manifest.supportsLockFiles) || (useLockFile && !manifest.isLockFile && manifest.includeWithLockFiles)) .map((manifest) => { const manifestGlobPattern = manifest.pattern.startsWith(".") ? `*${manifest.pattern}` : manifest.pattern; const pattern = `**/${manifestGlobPattern}`; const files = Glob.sync(pattern, { ignore: [ ...(filesToExclude || []), ...directoriesToExclude, constants_1.SOOS_CONSTANTS.SCA.SoosPackageDirToExclude, ], nocase: true, }); const absolutePathFiles = files.map((x) => Path.resolve(x)); const matchingFilesMessage = `${absolutePathFiles.length} files found matching pattern '${pattern}'.`; if (absolutePathFiles.length > 0) { logging_1.soosLogger.info(matchingFilesMessage); } else { logging_1.soosLogger.debug(matchingFilesMessage); } return absolutePathFiles; }); return accumulator.concat(matches.flat().map((filePath) => { const filename = Path.basename(filePath); const fileStats = FileSystem.statSync(filePath); const fileSize = (0, utilities_1.formatBytes)(fileStats.size); logging_1.soosLogger.info(`Found manifest file '${filename}' (${fileSize}) at location '${filePath}'.`); return { packageManager: packageManagerManifests.packageManager, name: filename, path: filePath, }; })); }, []); process.chdir(currentDirectory); logging_1.soosLogger.info(`Setting current directory back to '${currentDirectory}'.\n`); logging_1.soosLogger.info(`${manifestFiles.length} manifest files found.`); return manifestFiles; } searchForHashableFiles({ hashableFileFormats, sourceCodePath, filesToExclude, directoriesToExclude, }) { const currentDirectory = process.cwd(); logging_1.soosLogger.info(`Setting current directory to project path '${sourceCodePath}'.`); process.chdir(sourceCodePath); const fileHashes = hashableFileFormats.reduce((accumulator, fileFormatToHash) => { const matches = fileFormatToHash.fileFormats.flatMap((fileFormat) => { return fileFormat.patterns.flatMap((matchPattern) => { const manifestGlobPattern = matchPattern.startsWith(".") ? `*${matchPattern}` : matchPattern; const pattern = `**/${manifestGlobPattern}`; const files = Glob.sync(pattern, { ignore: [ ...(filesToExclude || []), ...directoriesToExclude, constants_1.SOOS_CONSTANTS.SCA.SoosPackageDirToExclude, ], nocase: true, }); const absolutePathFiles = files.map((x) => Path.resolve(x)); const matchingFilesMessage = `${absolutePathFiles.length} files found matching pattern '${matchPattern}'.`; if (absolutePathFiles.length > 0) { logging_1.soosLogger.info(matchingFilesMessage); } else { logging_1.soosLogger.debug(matchingFilesMessage); } return absolutePathFiles.flat().map((filePath) => { const filename = Path.basename(filePath); const fileDigests = fileFormat.hashAlgorithms.map((ha) => { const digest = (0, utilities_1.generateFileHash)(ha.hashAlgorithm, ha.bufferEncoding, ha.digestEncoding, filePath); logging_1.soosLogger.debug(`Found '${filePath}' (${ha.hashAlgorithm}:${digest})`); return { digest: digest, hashAlgorithm: ha.hashAlgorithm, }; }); return { digests: fileDigests.map((d) => { return { hashAlgorithm: d.hashAlgorithm, digest: d.digest, }; }), filename: filename, path: filePath, }; }); }); }); return accumulator.concat({ packageManager: fileFormatToHash.packageManager, fileHashes: matches, }); }, []); process.chdir(currentDirectory); logging_1.soosLogger.info(`Setting current directory back to '${currentDirectory}'.\n`); return fileHashes; } async getAnalysisFilesAsFormData(analysisFilePaths, workingDirectory) { const analysisFiles = analysisFilePaths.map((filePath) => { return { name: Path.basename(filePath), path: filePath, }; }); const formData = analysisFiles.reduce((formDataAcc, analysisFile, index) => { const fileParts = analysisFile.path.replace(workingDirectory, "").split(Path.sep); const parentFolder = fileParts.length >= 2 ? fileParts.slice(0, fileParts.length - 1).join(Path.sep) : ""; const suffix = index > 0 ? index : ""; const fileReadStream = FileSystem.createReadStream(analysisFile.path); formDataAcc.append(`file${suffix}`, fileReadStream); formDataAcc.append(`parentFolder${suffix}`, parentFolder); return formDataAcc; }, new form_data_1.default()); return formData; } async addManifestFilesToScan({ clientId, projectHash, branchHash, analysisId, scanType, scanStatusUrl, manifestFiles, }) { const filesToUpload = manifestFiles.slice(0, constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests); const hasMoreThanMaximumManifests = manifestFiles.length > constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests; if (hasMoreThanMaximumManifests) { const filesToSkip = manifestFiles.slice(constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests); const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(manifestFiles.length, "file was", "files were"); const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file"); logging_1.soosLogger.info(`The maximum number of manifest per scan is ${constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following manifests will not be included in the scan: \n`, filesToSkip.map((file) => ` "${file.name}": "${file.path}"`).join("\n")); } const manifestsByPackageManager = filesToUpload.reduce((accumulator, file) => { const packageManagerFiles = accumulator[file.packageManager] ?? []; return { ...accumulator, [file.packageManager]: packageManagerFiles.concat(file), }; }, {}); let allUploadsFailed = true; for (const [packageManager, files] of Object.entries(manifestsByPackageManager)) { try { const manifestUploadResponse = await this.uploadManifestFiles({ clientId: clientId, projectHash, branchHash, analysisId, manifestFiles: files.map((f) => f.path), hasMoreThanMaximumManifests, }); logging_1.soosLogger.info(`${packageManager} Manifest Files: \n`, ` ${manifestUploadResponse.message} \n`, manifestUploadResponse.manifests ?.map((m) => ` ${m.name}: ${m.statusMessage}`) .join("\n")); allUploadsFailed = false; } catch (e) { logging_1.soosLogger.warn(e instanceof Error ? e.message : e); } } if (allUploadsFailed) { await this.updateScanStatus({ clientId, projectHash, branchHash, scanType, analysisId: analysisId, status: enums_2.ScanStatus.Incomplete, message: `Error uploading manifests.`, scanStatusUrl, }); throw new Error("Error uploading manifests."); } } async uploadManifestFiles({ clientId, projectHash, branchHash, analysisId, manifestFiles, hasMoreThanMaximumManifests, }) { const formData = await this.getAnalysisFilesAsFormData(manifestFiles, process.cwd()); const response = await this.analysisApiClient.uploadManifestFiles({ clientId, projectHash, branchHash, analysisId, manifestFiles: formData, hasMoreThanMaximumManifests, }); return response; } } exports.default = AnalysisService;