UNPKG

resolve-package-path

Version:

a special purpose fast memoizing way to resolve a node modules package.json

244 lines 10.7 kB
'use strict'; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; // credit goes to https://github.com/davecombs // extracted in part from: https://github.com/stefanpenner/hash-for-dep/blob/15b2ebcf22024ceb2eb7907f8c412ae40f87b15e/lib/resolve-package-path.js#L1 // const fs = require("fs"); const path = require("path"); const pathRoot = require("path-root"); const rethrow_unless_code_1 = __importDefault(require("./rethrow-unless-code")); /* * Define a regex that will match against the 'name' value passed into * resolvePackagePath. The regex corresponds to the following test: * Match any of the following 3 alternatives: * * 1) dot, then optional second dot, then / or nothing i.e. . ./ .. ../ OR * 2) / i.e. / OR * 3) (A-Za-z colon - [optional]), then / or \ i.e. optional drive letter + colon, then / or \ * * Basically, the three choices mean "explicitly relative or absolute path, on either * Unix/Linux or Windows" */ const ABSOLUTE_OR_RELATIVE_PATH_REGEX = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/; const shouldPreserveSymlinks = require("./should-preserve-symlinks"); const PRESERVE_SYMLINKS = shouldPreserveSymlinks(process); /* * Resolve the real path for a file. Return null if does not * exist or is not a file or FIFO, return the real path otherwise. * * Cache the result in the passed-in cache for performance, * keyed on the filePath passed in. * * NOTE: Because this is a private method, it does not attempt to normalize * the path passed in - it assumes the caller has done that. * * @private * @method _getRealFilePath * @param {Cache} realFilePathCache the Cache object to cache the real (resolved) * path in, keyed by filePath. See lib/cache.js and lib/cache-group.js * @param {String} filePath the path to the file of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealFilePath(realFilePathCache, filePath) { if (realFilePathCache.has(filePath)) { return realFilePathCache.get(filePath); // could be null } let realPath = null; // null = 'FILE NOT FOUND' try { const stat = fs.statSync(filePath); // I don't know if Node would handle having the filePath actually // be a FIFO, but as the following is also part of the node-resolution // algorithm in resolve.sync(), we'll do the same check here. if (stat.isFile() || stat.isFIFO()) { if (PRESERVE_SYMLINKS) { realPath = filePath; } else { realPath = fs.realpathSync(filePath); } } } catch (e) { (0, rethrow_unless_code_1.default)(e, 'ENOENT'); } realFilePathCache.set(filePath, realPath); return realPath; } /* * Resolve the real path for a directory, return null if does not * exist or is not a directory, return the real path otherwise. * * @param {Cache} realDirectoryPathCache the Cache object to cache the real (resolved) * path in, keyed by directoryPath. See lib/cache.js and lib/cache-group.js * @param {String} directoryPath the path to the directory of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealDirectoryPath(realDirectoryPathCache, directoryPath) { if (realDirectoryPathCache.has(directoryPath)) { return realDirectoryPathCache.get(directoryPath); // could be null } let realPath = null; try { const stat = fs.statSync(directoryPath); if (stat.isDirectory()) { if (PRESERVE_SYMLINKS) { realPath = directoryPath; } else { realPath = fs.realpathSync(directoryPath); } } } catch (e) { (0, rethrow_unless_code_1.default)(e, 'ENOENT', 'ENOTDIR'); } realDirectoryPathCache.set(directoryPath, realPath); return realPath; } /* * Given a package 'name' and starting directory, resolve to a real (existing) file path. * * Do it similar to how it is done in resolve.sync() - travel up the directory hierarchy, * attaching 'node-modules' to each directory and seeing if the directory exists and * has the relevant 'package.json' file we're searching for. It is *much* faster than * resolve.sync(), because we don't test that the requested name is a directory. * This routine assumes that it is only called when we don't already have * the cached entry. * * NOTE: it is valid for 'name' to be an absolute or relative path. * Because this is an internal routine, we'll require that 'dir' be non-empty * if this is called, to make things simpler (see resolvePackagePath). * * @param realFilePathCache the cache containing the real paths corresponding to * various file and directory paths (which may or may not be already resolved). * * @param name the 'name' of the module, i.e. x in require(x), but with * '/package.json' on the end. It is NOT referring to a directory (so we don't * have to do the directory checks that resolve.sync does). * NOTE: because this is an internal routine, for speed it does not check * that '/package.json' is actually the end of the name. * * @param dir the directory (MUST BE non-empty, and valid) to start from, appending the name to the * directory and checking that the file exists. Go up the directory hierarchy from there. * if name is itself an absolute path, * * @result the path to the actual package.json file that's found, or null if not. */ function _findPackagePath(realFilePathCache, name, dir) { const fsRoot = pathRoot(dir); let currPath = dir; while (currPath !== fsRoot) { // when testing for 'node_modules', need to allow names like NODE_MODULES, // which can occur with case-insensitive OSes. let endsWithNodeModules = path.basename(currPath).toLowerCase() === 'node_modules'; let filePath = path.join(currPath, endsWithNodeModules ? '' : 'node_modules', name); let realPath = _getRealFilePath(realFilePathCache, filePath); if (realPath) { return realPath; } if (endsWithNodeModules) { // go up past the ending node_modules directory so the next dirname // goes up past that (if ending in node_modules, going up just one // directory below will then add 'node_modules' on the next loop and // re-process this same node_modules directory. currPath = path.dirname(currPath); } currPath = path.dirname(currPath); } return null; } /* * Resolve the path to the nearest `package.json` from the given initial search * directory. * * @param {Cache} findUpCache - a cache of memoized results that is * prioritized to avoid I/O. * * @param {string} initialSearchDir - the normalized path to start searching * from. * * @return {string | null} - the deepest directory on the path to root from * `initialSearchDir` that contains a {{package.json}}, or `null` if no such * directory exists. */ function _findUpPackagePath(findUpCache, initialSearchDir) { let previous; let dir = initialSearchDir; let maybePackageJsonPath; let result = null; do { if (findUpCache.has(dir)) { result = findUpCache.get(dir); break; } maybePackageJsonPath = path.join(dir, 'package.json'); if (fs.existsSync(maybePackageJsonPath)) { result = maybePackageJsonPath; break; } previous = dir; dir = path.dirname(dir); } while (dir !== previous); findUpCache.set(initialSearchDir, result); return result; } /* * Resolve the path to a module's package.json file, if it exists. The * name and dir are as in hashForDep and ModuleEntry.locate. * * @param caches an instance of CacheGroup where information will be cached * during processing. * * @param name the 'name' of the module. The name may also be a path, * either relative or absolute. The path must be to a module DIRECTORY, NOT to the * package.json file in the directory, as we attach 'package.json' here. * * @param dir (optional) the root directory to run the path resolution from. * if dir is not provided, __dirname for this module is used instead. * * @return the realPath corresponding to the module's package.json file, or null * if that file is not found or is not a file. * * Note: 'name' is expected in the format expected for require(x), i.e., it is * resolved using the Node path-normalization rules. */ function resolvePackagePath(caches, name, dir) { if (typeof name !== 'string' || name.length === 0) { throw new TypeError("resolvePackagePath: 'name' must be a non-zero-length string."); } // Perform tests similar to those in resolve.sync(). let basedir = dir || __dirname; // Ensure that basedir is an absolute path at this point. If it does not refer to // a real directory, go up the path until a real directory is found, or return an error. // BUG!: this will never throw an exception, at least on Unix/Linux. If the path is // relative, path.resolve() will make it absolute by putting the current directory // before it, so it won't fail. If the path is already absolute, / will always be // valid, so again it won't fail. let absoluteStart = path.resolve(basedir); while (_getRealDirectoryPath(caches.REAL_DIRECTORY_PATH, absoluteStart) === null) { absoluteStart = path.dirname(absoluteStart); } if (!absoluteStart) { let error = new TypeError("resolvePackagePath: 'dir' or one of the parent directories in its path must refer to a valid directory."); error.code = 'MODULE_NOT_FOUND'; throw error; } if (ABSOLUTE_OR_RELATIVE_PATH_REGEX.test(name)) { let res = path.resolve(absoluteStart, name); return _getRealFilePath(caches.REAL_FILE_PATH, path.join(res, 'package.json')); // XXX Do we need to handle the core(x) case too? Not sure. } else { return _findPackagePath(caches.REAL_FILE_PATH, path.join(name, 'package.json'), absoluteStart); } } resolvePackagePath._findPackagePath = _findPackagePath; resolvePackagePath._findUpPackagePath = _findUpPackagePath; resolvePackagePath._getRealFilePath = _getRealFilePath; resolvePackagePath._getRealDirectoryPath = _getRealDirectoryPath; module.exports = resolvePackagePath; //# sourceMappingURL=resolve-package-path.js.map