UNPKG

dependency-cruiser

Version:

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

170 lines (156 loc) 6.04 kB
import { readFileSync } from "node:fs"; import { join } from "node:path"; import memoize from "lodash/memoize.js"; import has from "lodash/has.js"; import { resolve } from "./resolve.mjs"; import { isScoped, isRelativeModuleName } from "./module-classifiers.mjs"; /** * Returns the 'root' of the package - the spot where we can probably find * the package.json of that package, and the reference we'd usualy have * in our own package.json to declare the dependency. * * Some samples: * - for unscoped packages this would be everything up to the first '/': * - 'lodash/fp' => 'lodash' * - 'mypackage/some/module' => 'mypackage' * - for scoped packages this would be everything up to the second '/', * if it's there (which should normaly be the case, but nevertheless we * can't assume that) * - @angular/common => @angular/common * - @somescope/some/module => @somescope/some * - @imdoingweirdvoodoo => @imdoingweirdvoodoo * - for local packages it doesn't make sense to call this function, but * if consumers insist it'll return the local package name unchanged: * - './myThing' => './myThing' * - './some/other/thing' => './some/other/thing' * * At this time we don't take situations into account where the caller includes * a node module through a local path (which could make sense if you're on * non-commonJS and are still using node_modules) e.g. '../node_modules/lodash/fp' * * @param {string} pModule a module name * @return {string} the module name root */ export function getPackageRoot(pModule) { if (!Boolean(pModule) || isRelativeModuleName(pModule)) { return pModule; } let lPathElements = pModule.split("/"); if (isScoped(pModule)) { // '@imdoingweirdvoodoo' if (lPathElements.length === 1) { return pModule; } // @scope/package // @scope/package/some/thing return `${lPathElements[0]}/${lPathElements[1]}`; } // lodash // lodash/fp return lPathElements[0]; } /** * returns the contents of the package.json of the given pModule as it would * resolve from pBaseDirectory * * e.g. to get the package.json of `lodash` that is required bya * `src/report/something.js` use `getPackageJSON('lodash', 'src/report/');` * * The pBaseDirectory parameter is necessary because dependency-cruiser/ this module * will have a different base dir, and will hence resolve either to the * wrong package or not to a package at all. * * @param {string} pModuleName The name of the module to get the package.json of * @param {string} pFileDirectory The folder the module resides in. Defaults to the current working directory * @param {any} pResolveOptions options for the resolver * @return {Record<string, any>} The package.json as a javascript object, or * null if either module or package.json could * not be found */ function bareGetPackageJson(pModuleName, pFileDirectory, pResolveOptions) { let lReturnValue = null; try { const lPackageJsonFilename = resolve( join(getPackageRoot(pModuleName), "package.json"), pFileDirectory ? pFileDirectory : process.cwd(), { ...pResolveOptions, // if a module has exports fields _and_ does not expose package.json // in those exports, enhanced-resolve (nor node!) will not be able to // resolve the package.json if it actually heeds those exports fields. // We can instruct enhanced-resolve to ignore them, however, by passing // it the empty array for exports fields (overriding anything in // the pResvolveOptions) exportsFields: [], // we don't need to try any extensions; we already // know it as we have passed the complete module name to resolve => // override whatever the default is with [""] ('use no extensions please') extensions: [""], }, // we need a separate caching context so as not to **** up the regular // cruise, which might actually want to utilize the exportsFields // and an array of extensions "manifest-resolution" ); lReturnValue = JSON.parse(readFileSync(lPackageJsonFilename, "utf8")); } catch (pError) { // left empty on purpose } return lReturnValue; } export const getPackageJson = memoize( bareGetPackageJson, (pModuleName, pBaseDirectory) => `${pBaseDirectory}|${pModuleName}` ); /** * Tells whether the pModule as resolved to pBaseDirectory is deprecated * * @param {string} pModuleName The name of the module to get the deprecation status of * @param {string} pFileDirectory The folder the module resides in. * @param {any} pResolveOptions options for the resolver * @return {boolean} true when deprecated, false in all other cases */ export function dependencyIsDeprecated( pModuleName, pFileDirectory, pResolveOptions ) { let lReturnValue = false; let lPackageJson = getPackageJson( pModuleName, pFileDirectory, pResolveOptions ); if (Boolean(lPackageJson)) { lReturnValue = has(lPackageJson, "deprecated") && lPackageJson.deprecated; } return lReturnValue; } /** * Returns the license of pModule as resolved to pBaseDirectory - if any * * @param {string} pModuleName The name of the module to get the license of * @param {string} pFileDirectory The folder the module resides in. * @param {any} pResolveOptions options for the resolver * @return {string} The module's license string, or '' in case * there is no package.json or no license field */ export function getLicense(pModuleName, pFileDirectory, pResolveOptions) { let lReturnValue = ""; let lPackageJson = getPackageJson( pModuleName, pFileDirectory, pResolveOptions ); if ( Boolean(lPackageJson) && has(lPackageJson, "license") && typeof lPackageJson.license === "string" ) { lReturnValue = lPackageJson.license; } return lReturnValue; } export function clearCache() { getPackageJson.cache.clear(); }