UNPKG

dependency-cruiser

Version:

Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.

130 lines (119 loc) 4.21 kB
import { readdirSync, statSync } from "node:fs"; import { join, normalize } from "node:path"; // eslint-disable-next-line n/prefer-global/process import { platform } from "node:process"; import picomatch from "picomatch"; import { scannableExtensions } from "./transpile/meta.mjs"; import { filenameMatchesPattern } from "#graph-utl/match-facade.mjs"; import getExtension from "#utl/get-extension.mjs"; import pathToPosix from "#utl/path-to-posix.mjs"; /** * @import {IStrictCruiseOptions} from "../../types/options.mjs"; */ /** * @param {IStrictCruiseOptions} pOptions * @returns {string[]} */ function getScannableExtensions(pOptions) { return scannableExtensions.concat(pOptions.extraExtensionsToScan); } function fileIsScannable(pOptions, pPathToFile) { return getScannableExtensions(pOptions).includes(getExtension(pPathToFile)); } function shouldBeIncluded(pFullPathToFile, pOptions) { return ( !pOptions?.includeOnly?.path || filenameMatchesPattern(pFullPathToFile, pOptions.includeOnly.path) ); } function shouldBeExcluded(pFullPathToFile, pOptions) { return ( (pOptions?.exclude?.path && filenameMatchesPattern(pFullPathToFile, pOptions.exclude.path)) || (pOptions?.doNotFollow?.path && filenameMatchesPattern(pFullPathToFile, pOptions.doNotFollow.path)) ); } /** * @param {string} pDirectoryName * @param {IStrictCruiseOptions} pOptions options that * @returns {string[]} */ function gatherScannableFilesFromDirectory(pDirectoryName, pOptions) { return readdirSync(join(pOptions.baseDir, pDirectoryName)) .map((pFileName) => join(pDirectoryName, pFileName)) .flatMap((pFullPathToFile) => { const lPosixPath = pathToPosix(pFullPathToFile); if (shouldBeExcluded(lPosixPath, pOptions)) { return []; } const lStat = statSync(join(pOptions.baseDir, pFullPathToFile), { throwIfNoEntry: false, }); if (lStat) { if (lStat.isDirectory()) { return gatherScannableFilesFromDirectory(pFullPathToFile, pOptions); } if ( fileIsScannable(pOptions, pFullPathToFile) && shouldBeIncluded(lPosixPath, pOptions) ) { return lPosixPath; } } return []; }); } function expandGlob(pBaseDirectory, pScannedGlob) { const isMatch = picomatch(pathToPosix(pScannedGlob.glob), { windows: platform === "win32", }); return readdirSync(join(pBaseDirectory, pScannedGlob.base), { recursive: true, }) .filter((pFile) => isMatch(pFile)) .map((pFile) => pathToPosix(join(pScannedGlob.base, pFile))); } /** * Returns an array of strings, representing paths to files to be gathered * * If an entry in the array passed is a (path to a) directory, it recursively * scans that directory for files with a scannable extension. * If an entry is a path to a file it just adds it. * * Files and directories are assumed to be either absolute, or relative to the * current working directory. * * @param {string[]} pFileDirectoryAndGlobArray globs and/ or paths to files or * directories to be gathered * @param {IStrictCruiseOptions} pOptions options that * influence what needs to be gathered/ scanned * notably useful attributes: * - exclude - regexp of what to exclude * - includeOnly - regexp what to include * @return {string[]} paths to files to be gathered. */ export default function gatherInitialSources( pFileDirectoryAndGlobArray, pOptions, ) { const lOptions = { baseDir: process.cwd(), ...pOptions }; return pFileDirectoryAndGlobArray .flatMap((pFileDirectoryOrGlob) => { const lScannedGlob = picomatch.scan(pFileDirectoryOrGlob, { windows: platform === "win32", }); if (lScannedGlob.isGlob) { return expandGlob(lOptions.baseDir, lScannedGlob); } return normalize(pFileDirectoryOrGlob); }) .flatMap((pFileOrDirectory) => { if (statSync(join(lOptions.baseDir, pFileOrDirectory)).isDirectory()) { return gatherScannableFilesFromDirectory(pFileOrDirectory, lOptions); } // Not a glob anymore, and not a directory => it's a file return pathToPosix(pFileOrDirectory); }) .sort(); }