UNPKG

snyk-docker-plugin

Version:
247 lines (210 loc) 6.31 kB
import * as Debug from "debug"; import { mkdir, mkdtemp, rm, stat, writeFile } from "fs/promises"; import * as path from "path"; import { FilePathToContent, FilesByDirMap } from "./types"; const debug = Debug("snyk"); const nodeModulesRegex = /^(.*?)(?:[\\\/]node_modules)/; export { persistNodeModules, cleanupAppNodeModules, groupNodeAppFilesByDirectory, groupNodeModulesFilesByDirectory, }; interface ScanPaths { tempDir: string; tempProjectPath: string; manifestPath?: string; } async function createTempProjectDir( projectDir: string, ): Promise<{ tmpDir: string; tempProjectRoot: string }> { const tmpDir = await mkdtemp("snyk"); const tempProjectRoot = path.join(tmpDir, projectDir); await mkdir(tempProjectRoot, { recursive: true }); return { tmpDir, tempProjectRoot, }; } const manifestName: string = "package.json"; async function fileExists(path: string): Promise<boolean> { return await stat(path) .then(() => true) .catch(() => false); } async function createSyntheticManifest( tempRootManifestDir: string, ): Promise<void> { const tempRootManifestPath = path.join(tempRootManifestDir, manifestName); debug(`Creating an empty synthetic manifest file: ${tempRootManifestPath}`); try { await writeFile(tempRootManifestPath, "{}", "utf-8"); } catch (error) { debug( `Error while writing file ${tempRootManifestPath} : ${error.message}`, ); } } async function saveOnDisk( tempDir: string, modules: Set<string>, filePathToContent: FilePathToContent, ): Promise<void> { for (const module of modules) { const manifestContent = filePathToContent[module]; if (!manifestContent) { continue; } await createFile(path.join(tempDir, module), manifestContent); } } async function persistNodeModules( project: string, filePathToContent: FilePathToContent, fileNamesGroupedByDirectory: FilesByDirMap, ): Promise<ScanPaths> { const modules = fileNamesGroupedByDirectory.get(project); const tmpDir: string = ""; const tempProjectRoot: string = ""; if (!modules || modules.size === 0) { debug(`Empty application directory tree.`); return { tempDir: tmpDir, tempProjectPath: tempProjectRoot, }; } try { const { tmpDir, tempProjectRoot } = await createTempProjectDir(project); await saveOnDisk(tmpDir, modules, filePathToContent); const result: ScanPaths = { tempDir: tmpDir, tempProjectPath: tempProjectRoot, manifestPath: path.join( tempProjectRoot.substring(tmpDir.length), manifestName, ), }; const manifestFileExists = await fileExists( path.join(tempProjectRoot, manifestName), ); if (!manifestFileExists) { await createSyntheticManifest(tempProjectRoot); delete result.manifestPath; } return result; } catch (error) { debug( `Failed to copy the application manifest files locally: ${error.message}`, ); return { tempDir: tmpDir, tempProjectPath: tempProjectRoot, }; } } async function createFile(filePath, fileContent): Promise<void> { try { await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, fileContent, "utf-8"); } catch (error) { debug(`Error while creating file ${filePath} : ${error.message}`); } } function isYarnCacheDependency(filePath: string): boolean { if ( filePath.includes(".yarn/cache") || filePath.includes(".cache/yarn") || filePath.includes("yarn\\cache") || filePath.includes("cache\\yarn") || filePath.includes("Cache\\Yarn") || filePath.includes("Yarn\\Cache") ) { return true; } return false; } function isNpmCacheDependency(filePath: string): boolean { if (filePath.includes(".npm/") || filePath.includes("\\npm-cache")) { return true; } return false; } // TODO: Enable custom cache filtering if needed // function isCustomCache(filePath: string): boolean { // return (filePath.includes("cache") || filePath.includes("Cache")); // } function isPnpmCacheDependency(filePath: string): boolean { if ( filePath.includes("pnpm-store") || filePath.includes("pnpm/store") || filePath.includes("pnpm\\store") ) { return true; } return false; } function getNodeModulesParentDir(filePath: string): string | null { const nodeModulesParentDirMatch = nodeModulesRegex.exec(filePath); if (nodeModulesParentDirMatch && nodeModulesParentDirMatch.length > 1) { const nodeModulesParentDir = nodeModulesParentDirMatch[1]; if (nodeModulesParentDir === "") { return "/"; // ensuring the same behavior of path.dirname for '/' dir } return nodeModulesParentDir; } return null; } function groupNodeAppFilesByDirectory( filePathToContent: FilePathToContent, ): FilesByDirMap { const filesByDir: FilesByDirMap = new Map(); const filePaths = Object.keys(filePathToContent); for (const filePath of filePaths) { const directory = path.dirname(filePath); if (!filesByDir.has(directory)) { filesByDir.set(directory, new Set()); } filesByDir.get(directory)?.add(filePath); } return filesByDir; } function getGroupingDir(filePath: string): string { const nodeModulesParentDir = getNodeModulesParentDir(filePath); if (nodeModulesParentDir) { return nodeModulesParentDir; } return path.dirname(filePath); } function groupNodeModulesFilesByDirectory( filePathToContent: FilePathToContent, ): FilesByDirMap { const filesByDir: FilesByDirMap = new Map(); const filePaths = Object.keys(filePathToContent); for (const filePath of filePaths) { if (isNpmCacheDependency(filePath)) { continue; } if (isYarnCacheDependency(filePath)) { continue; } if (isPnpmCacheDependency(filePath)) { continue; } const directory = getGroupingDir(filePath); if (!filesByDir.has(directory)) { filesByDir.set(directory, new Set()); } filesByDir.get(directory)?.add(filePath); } return filesByDir; } async function cleanupAppNodeModules(appRootDir: string): Promise<void> { if (!appRootDir) { return; } try { await rm(appRootDir, { recursive: true }); } catch (error) { debug(`Error while removing ${appRootDir} : ${error.message}`); } }