UNPKG

dependency-cruiser

Version:

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

200 lines (176 loc) 5.65 kB
import { readFileSync, readdirSync, accessSync, statSync, constants, } from "node:fs"; import { join } from "node:path"; import { DEFAULT_CONFIG_FILE_NAME } from "../defaults.mjs"; const LIKELY_SOURCE_FOLDERS = ["src", "lib", "app", "bin", "sources"]; const LIKELY_TEST_FOLDERS = ["test", "spec", "tests", "specs", "bdd"]; const LIKELY_PACKAGES_FOLDERS = ["packages"]; const TSCONFIG_CANDIDATE_PATTERN = /.*tsconfig.*\.json$/i; const JSCONFIG_CANDIDATE_PATTERN = /.*jsconfig.*\.json$/i; const WEBPACK_CANDIDATE_PATTERN = /.*webpack.*\.(c?js|json5?|ts|ya?ml)$/i; const BABEL_CONFIG_CANDIDATE_PATTERN = /^\.babelrc$|.*babel.*\.json/i; /** * Read the package manifest ('package.json') and return it as a javascript object * * @param {import("fs").PathOrFileDescriptor} pManifestFileName - the file name where the package manifest (package.json) lives * @returns {Record<string,any>} - the contents of said manifest as a javascript object * @throws {ENOENT} when the manifest wasn't found * @throws {SyntaxError} when the manifest's json is invalid */ export function readManifest(pManifestFileName = "./package.json") { return JSON.parse(readFileSync(pManifestFileName, "utf8")); } /** * We could have used utl.fileExists - but that one is cached. * Not typically what we want for this util. * * @param {import("fs").PathLike} pFile * @returns {boolean} */ export function fileExists(pFile) { try { accessSync(pFile, constants.R_OK); } catch (pError) { return false; } return true; } /** * @returns {boolean} */ function babelIsConfiguredInManifest() { let lReturnValue = false; try { // @ts-expect-error defaultly tsc doesn't know about newfangled stuff like hasOwn lReturnValue = Object.hasOwn(readManifest(), "babel"); } catch (pError) { // silently ignore - we'll return false anyway then } return lReturnValue; } /** * @returns {boolean} */ export function isTypeModule() { let lReturnValue = false; try { lReturnValue = (readManifest()?.type ?? "commonjs") === "module"; } catch (pError) { // silently ignore - we'll return false anyway then } return lReturnValue; } /** * @param {string} pFolderName * @returns {string[]} Array of folder names */ function getFolderNames(pFolderName) { return readdirSync(pFolderName, "utf8").filter((pFileName) => statSync(join(pFolderName, pFileName)).isDirectory(), ); } /** * @param {RegExp} pPattern * @param {string=} pFolderName * @returns {string[]} */ function getMatchingFileNames(pPattern, pFolderName = process.cwd()) { return readdirSync(pFolderName, "utf8").filter( (pFileName) => statSync(join(pFolderName, pFileName)).isFile() && pPattern.test(pFileName), ); } /** * @param {string[]} pFolderNames * @returns {boolean} */ export function isLikelyMonoRepo(pFolderNames = getFolderNames(process.cwd())) { return pFolderNames.includes("packages"); } function getPackageManager(pManifest) { let lReturnValue = ""; try { const lManifest = pManifest ?? readManifest(); if (Object.hasOwn(lManifest, "packageManager")) { lReturnValue = lManifest.packageManager; } } catch (pError) { // silently ignore - we'll return the empty string anyway then } return lReturnValue; } /** * @param {Record<string,any>} pManifest * @returns {boolean} */ export function likelyUsesBun(pManifest) { const bunIsPackageManager = getPackageManager(pManifest).startsWith("bun"); return ( bunIsPackageManager || fileExists("bun.lockb") || fileExists("bunfig.toml") ); } export function hasTestsWithinSource(pTestLocations, pSourceLocations) { return pTestLocations.every((pTestLocation) => pSourceLocations.includes(pTestLocation), ); } export function getFolderCandidates(pCandidateFolderArray) { return (pFolderNames = getFolderNames(process.cwd())) => { return pFolderNames.filter((pFolderName) => pCandidateFolderArray.includes(pFolderName), ); }; } /** * @param {string[]|string} pLocations * @returns {string[]} */ export function toSourceLocationArray(pLocations) { if (!Array.isArray(pLocations)) { return pLocations.split(",").map((pFolder) => pFolder.trim()); } return pLocations; } /** * @returns {string[]} */ function getManifestFilesWithABabelConfig() { return babelIsConfiguredInManifest() ? ["package.json"] : []; } export const getBabelConfigCandidates = () => getManifestFilesWithABabelConfig().concat( getMatchingFileNames(BABEL_CONFIG_CANDIDATE_PATTERN), ); export const hasBabelConfigCandidates = () => getBabelConfigCandidates().length > 0; export const getTSConfigCandidates = (pFolderName = process.cwd()) => getMatchingFileNames(TSCONFIG_CANDIDATE_PATTERN, pFolderName); export const hasTSConfigCandidates = (pFolderName = process.cwd()) => getTSConfigCandidates(pFolderName).length > 0; export const getJSConfigCandidates = (pFolderName = process.cwd()) => getMatchingFileNames(JSCONFIG_CANDIDATE_PATTERN, pFolderName); export const hasJSConfigCandidates = (pFolderName = process.cwd()) => getJSConfigCandidates(pFolderName).length > 0; export const getWebpackConfigCandidates = () => getMatchingFileNames(WEBPACK_CANDIDATE_PATTERN); export const hasWebpackConfigCandidates = () => getWebpackConfigCandidates().length > 0; export const getSourceFolderCandidates = getFolderCandidates( LIKELY_SOURCE_FOLDERS, ); export const getTestFolderCandidates = getFolderCandidates(LIKELY_TEST_FOLDERS); export const getMonoRepoPackagesCandidates = getFolderCandidates( LIKELY_PACKAGES_FOLDERS, ); /** * @returns {string} */ export function getDefaultConfigFileName() { return isTypeModule() ? ".dependency-cruiser.cjs" : DEFAULT_CONFIG_FILE_NAME; }