"Fossies" - the Fresh Open Source Software Archive

Member "cli-1.1280.1/src/lib/find-files.ts" (20 Feb 2024, 9346 Bytes) of package /linux/misc/snyk-cli-1.1280.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 import * as fs from 'fs';
    2 import * as pathLib from 'path';
    3 
    4 const sortBy = require('lodash.sortby');
    5 const groupBy = require('lodash.groupby');
    6 import { detectPackageManagerFromFile } from './detect';
    7 import * as debugModule from 'debug';
    8 
    9 const debug = debugModule('snyk:find-files');
   10 
   11 // TODO: use util.promisify once we move to node 8
   12 
   13 /**
   14  * Returns files inside given file path.
   15  *
   16  * @param path file path.
   17  */
   18 export async function readDirectory(path: string): Promise<string[]> {
   19   return await new Promise((resolve, reject) => {
   20     fs.readdir(path, (err, files) => {
   21       if (err) {
   22         reject(err);
   23       }
   24       resolve(files);
   25     });
   26   });
   27 }
   28 
   29 /**
   30  * Returns file stats object for given file path.
   31  *
   32  * @param path path to file or directory.
   33  */
   34 export async function getStats(path: string): Promise<fs.Stats> {
   35   return await new Promise((resolve, reject) => {
   36     fs.stat(path, (err, stats) => {
   37       if (err) {
   38         reject(err);
   39       }
   40       resolve(stats);
   41     });
   42   });
   43 }
   44 
   45 interface FindFilesRes {
   46   files: string[];
   47   allFilesFound: string[];
   48 }
   49 
   50 const ignoreFolders = ['node_modules', '.build'];
   51 
   52 /**
   53  * Find all files in given search path. Returns paths to files found.
   54  *
   55  * @param path file path to search.
   56  * @param ignore (optional) files to ignore. Will always ignore node_modules.
   57  * @param filter (optional) file names to find. If not provided all files are returned.
   58  * @param levelsDeep (optional) how many levels deep to search, defaults to two, this path and one sub directory.
   59  */
   60 export async function find(
   61   path: string,
   62   ignore: string[] = [],
   63   filter: string[] = [],
   64   levelsDeep = 4,
   65 ): Promise<FindFilesRes> {
   66   const found: string[] = [];
   67   const foundAll: string[] = [];
   68 
   69   // ensure we ignore find against node_modules path and .build folder for swift.
   70   if (path.endsWith('node_modules') || path.endsWith('/.build')) {
   71     return { files: found, allFilesFound: foundAll };
   72   }
   73 
   74   // ensure dependencies folders is always ignored
   75   for (const folder of ignoreFolders) {
   76     if (!ignore.includes(folder)) {
   77       ignore.push(folder);
   78     }
   79   }
   80 
   81   try {
   82     if (levelsDeep < 0) {
   83       return { files: found, allFilesFound: foundAll };
   84     } else {
   85       levelsDeep--;
   86     }
   87     const fileStats = await getStats(path);
   88     if (fileStats.isDirectory()) {
   89       const { files, allFilesFound } = await findInDirectory(
   90         path,
   91         ignore,
   92         filter,
   93         levelsDeep,
   94       );
   95       found.push(...files);
   96       foundAll.push(...allFilesFound);
   97     } else if (fileStats.isFile()) {
   98       const fileFound = findFile(path, filter);
   99       if (fileFound) {
  100         found.push(fileFound);
  101         foundAll.push(fileFound);
  102       }
  103     }
  104     const filteredOutFiles = foundAll.filter((f) => !found.includes(f));
  105     if (filteredOutFiles.length) {
  106       debug(
  107         `Filtered out ${filteredOutFiles.length}/${
  108           foundAll.length
  109         } files: ${filteredOutFiles.join(', ')}`,
  110       );
  111     }
  112     return { files: filterForDefaultManifests(found), allFilesFound: foundAll };
  113   } catch (err) {
  114     throw new Error(`Error finding files in path '${path}'.\n${err.message}`);
  115   }
  116 }
  117 
  118 function findFile(path: string, filter: string[] = []): string | null {
  119   if (filter.length > 0) {
  120     const filename = pathLib.basename(path);
  121     if (filter.includes(filename)) {
  122       return path;
  123     }
  124   } else {
  125     return path;
  126   }
  127   return null;
  128 }
  129 
  130 async function findInDirectory(
  131   path: string,
  132   ignore: string[] = [],
  133   filter: string[] = [],
  134   levelsDeep = 4,
  135 ): Promise<FindFilesRes> {
  136   const files = await readDirectory(path);
  137   const toFind = files
  138     .filter((file) => !ignore.includes(file))
  139     .map((file) => {
  140       const resolvedPath = pathLib.resolve(path, file);
  141       if (!fs.existsSync(resolvedPath)) {
  142         debug('File does not seem to exist, skipping: ', file);
  143         return { files: [], allFilesFound: [] };
  144       }
  145       return find(resolvedPath, ignore, filter, levelsDeep);
  146     });
  147 
  148   const found = await Promise.all(toFind);
  149   return {
  150     files: Array.prototype.concat.apply(
  151       [],
  152       found.map((f) => f.files),
  153     ),
  154     allFilesFound: Array.prototype.concat.apply(
  155       [],
  156       found.map((f) => f.allFilesFound),
  157     ),
  158   };
  159 }
  160 
  161 function filterForDefaultManifests(files: string[]): string[] {
  162   // take all the files in the same dir & filter out
  163   // based on package Manager
  164   if (files.length <= 1) {
  165     return files;
  166   }
  167 
  168   const filteredFiles: string[] = [];
  169 
  170   const beforeSort = files
  171     .filter(Boolean)
  172     .filter((p) => fs.existsSync(p))
  173     .map((p) => ({
  174       path: p,
  175       ...pathLib.parse(p),
  176       packageManager: detectProjectTypeFromFile(p),
  177     }));
  178   const sorted = sortBy(beforeSort, 'dir');
  179   const foundFiles = groupBy(sorted, 'dir');
  180 
  181   for (const directory of Object.keys(foundFiles)) {
  182     const filesInDirectory = foundFiles[directory];
  183     const beforeGroup = filesInDirectory.filter((p) => !!p.packageManager);
  184 
  185     const groupedFiles = groupBy(beforeGroup, 'packageManager');
  186 
  187     for (const packageManager of Object.keys(groupedFiles)) {
  188       const filesPerPackageManager = groupedFiles[packageManager];
  189 
  190       if (filesPerPackageManager.length <= 1) {
  191         const shouldSkip = shouldSkipAddingFile(
  192           packageManager,
  193           filesPerPackageManager[0].path,
  194           filteredFiles,
  195         );
  196         if (shouldSkip) {
  197           continue;
  198         }
  199         filteredFiles.push(filesPerPackageManager[0].path);
  200         continue;
  201       }
  202       const defaultManifestFileName = chooseBestManifest(
  203         filesPerPackageManager,
  204         packageManager,
  205       );
  206       if (defaultManifestFileName) {
  207         const shouldSkip = shouldSkipAddingFile(
  208           packageManager,
  209           filesPerPackageManager[0].path,
  210           filteredFiles,
  211         );
  212         if (shouldSkip) {
  213           continue;
  214         }
  215         filteredFiles.push(defaultManifestFileName);
  216       }
  217     }
  218   }
  219   return filteredFiles;
  220 }
  221 
  222 function detectProjectTypeFromFile(file: string): string | null {
  223   try {
  224     const packageManager = detectPackageManagerFromFile(file);
  225     if (['yarn', 'npm'].includes(packageManager)) {
  226       return 'node';
  227     }
  228     return packageManager;
  229   } catch (error) {
  230     return null;
  231   }
  232 }
  233 
  234 function shouldSkipAddingFile(
  235   packageManager: string,
  236   filePath: string,
  237   filteredFiles: string[],
  238 ): boolean {
  239   if (['gradle'].includes(packageManager) && filePath) {
  240     const rootGradleFile = filteredFiles
  241       .filter(
  242         (targetFile) =>
  243           targetFile.endsWith('build.gradle') ||
  244           targetFile.endsWith('build.gradle.kts'),
  245       )
  246       .filter((targetFile) => {
  247         const parsedPath = pathLib.parse(targetFile);
  248         const relativePath = pathLib.relative(parsedPath.dir, filePath);
  249         return !relativePath.startsWith(`..${pathLib.sep}`);
  250       });
  251     return !!rootGradleFile.length;
  252   }
  253   return false;
  254 }
  255 
  256 function chooseBestManifest(
  257   files: Array<{ base: string; path: string }>,
  258   projectType: string,
  259 ): string | null {
  260   switch (projectType) {
  261     case 'node': {
  262       const lockFile = files.filter((path) =>
  263         ['package-lock.json', 'yarn.lock'].includes(path.base),
  264       )[0];
  265       debug(
  266         `Encountered multiple node lockfiles files, defaulting to ${lockFile.path}`,
  267       );
  268       if (lockFile) {
  269         return lockFile.path;
  270       }
  271       const packageJson = files.filter((path) =>
  272         ['package.json'].includes(path.base),
  273       )[0];
  274       debug(
  275         `Encountered multiple npm manifest files, defaulting to ${packageJson.path}`,
  276       );
  277       return packageJson.path;
  278     }
  279     case 'rubygems': {
  280       const defaultManifest = files.filter((path) =>
  281         ['Gemfile.lock'].includes(path.base),
  282       )[0];
  283       debug(
  284         `Encountered multiple gem manifest files, defaulting to ${defaultManifest.path}`,
  285       );
  286       return defaultManifest.path;
  287     }
  288     case 'cocoapods': {
  289       const defaultManifest = files.filter((path) =>
  290         ['Podfile'].includes(path.base),
  291       )[0];
  292       debug(
  293         `Encountered multiple cocoapods manifest files, defaulting to ${defaultManifest.path}`,
  294       );
  295       return defaultManifest.path;
  296     }
  297     case 'pip': {
  298       const defaultManifest = files.filter((path) =>
  299         ['Pipfile'].includes(path.base),
  300       )[0];
  301       debug(
  302         `Encountered multiple pip manifest files, defaulting to ${defaultManifest.path}`,
  303       );
  304       return defaultManifest.path;
  305     }
  306     case 'gradle': {
  307       const defaultManifest = files.filter((path) =>
  308         ['build.gradle'].includes(path.base),
  309       )[0];
  310       debug(
  311         `Encountered multiple gradle manifest files, defaulting to ${defaultManifest.path}`,
  312       );
  313       return defaultManifest.path;
  314     }
  315     case 'poetry': {
  316       const defaultManifest = files.filter((path) =>
  317         ['pyproject.toml'].includes(path.base),
  318       )[0];
  319       debug(
  320         `Encountered multiple poetry manifest files, defaulting to ${defaultManifest.path}`,
  321       );
  322       return defaultManifest.path;
  323     }
  324     case 'hex': {
  325       const defaultManifest = files.filter((path) =>
  326         ['mix.exs'].includes(path.base),
  327       )[0];
  328       debug(
  329         `Encountered multiple hex manifest files, defaulting to ${defaultManifest.path}`,
  330       );
  331       return defaultManifest.path;
  332     }
  333     default: {
  334       return null;
  335     }
  336   }
  337 }