var isCore = require('is-core-module'); var fs = require('fs'); var path = require('path'); var $Error = require('es-errors'); var $TypeError = require('es-errors/type'); var getHomedir = require('./homedir'); var caller = require('./caller'); var nodeModulesPaths = require('./node-modules-paths'); var normalizeOptions = require('./normalize-options'); var resolveExports = require('./exports-resolve'); var parsePackageSpecifier = require('./parse-package-specifier'); var getExportsCategory = require('./get-exports-category'); var getCategoryInfo = require('node-exports-info/getCategoryInfo'); var getCategoriesForRange = require('node-exports-info/getCategoriesForRange'); var selectMostRestrictive = require('./select-most-restrictive'); var realpathFS = process.platform !== 'win32' && fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; var relativePathRegex = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/; var windowsDriveRegex = /^\w:[/\\]*$/; var nodeModulesRegex = /[/\\]node_modules[/\\]*$/; var homedir = getHomedir(); function defaultPaths() { return [ path.join(homedir, '.node_modules'), path.join(homedir, '.node_libraries') ]; } var defaultIsFile = function isFile(file) { try { var stat = fs.statSync(file, { throwIfNoEntry: false }); } catch (e) { if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; throw e; } return !!stat && (stat.isFile() || stat.isFIFO()); }; var defaultIsDir = function isDirectory(dir) { try { var stat = fs.statSync(dir, { throwIfNoEntry: false }); } catch (e) { if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; throw e; } return !!stat && stat.isDirectory(); }; var defaultRealpathSync = function realpathSync(x) { try { return realpathFS(x); } catch (realpathErr) { if (realpathErr.code !== 'ENOENT') { throw realpathErr; } } return x; }; function maybeRealpathSync(realpathSync, x, opts) { if (!opts || !opts.preserveSymlinks) { return realpathSync(x); } return x; } function defaultReadPackageSync(readFileSync, pkgfile) { return JSON.parse(readFileSync(pkgfile)); } function getPackageCandidates(x, start, opts) { var dirs = nodeModulesPaths(start, opts, x); for (var i = 0; i < dirs.length; i++) { dirs[i] = path.join(dirs[i], x); } return dirs; } function findConsumerPackageSync(startDir, isFile, readPackageSync, readFileSync) { var dir = path.resolve(startDir); while (true) { var pkgfile = path.join(dir, 'package.json'); if (isFile(pkgfile)) { try { return readPackageSync(readFileSync, pkgfile); } catch (e) { if (!(e instanceof SyntaxError)) { throw e; } } } var parentDir = path.dirname(dir); if (parentDir === dir) { return null; } dir = parentDir; } } function findPackageWithDirSync(startDir, isFile, readPackageSync, readFileSync) { var dir = path.resolve(startDir); while (true) { // Stop at node_modules boundaries - can't self-reference across node_modules if (nodeModulesRegex.test(dir)) { return null; } var pkgfile = path.join(dir, 'package.json'); if (isFile(pkgfile)) { try { var pkg = readPackageSync(readFileSync, pkgfile); return { __proto__: null, pkg: pkg, dir: dir }; } catch (e) { if (!(e instanceof SyntaxError)) { throw e; } } } var parentDir = path.dirname(dir); if (parentDir === dir) { return null; } dir = parentDir; } } module.exports = function resolveSync(x, options) { if (typeof x !== 'string') { throw new $TypeError('Path must be a string.'); } var opts = normalizeOptions(x, options); var isFile = opts.isFile || defaultIsFile; var isDirectory = opts.isDirectory || defaultIsDir; var readFileSync = opts.readFileSync || fs.readFileSync; var realpathSync = opts.realpathSync || defaultRealpathSync; var readPackageSync = opts.readPackageSync || defaultReadPackageSync; if (opts.readFileSync && opts.readPackageSync) { throw new $TypeError('`readFileSync` and `readPackageSync` are mutually exclusive.'); } var packageIterator = opts.packageIterator; var extensions = opts.extensions || ['.js']; var includeCoreModules = opts.includeCoreModules !== false; var basedir = opts.basedir || path.dirname(caller()); var parent = opts.filename || basedir; opts.paths = opts.paths || defaultPaths(); // Determine exports category var exportsCategory = getExportsCategory(opts); if (exportsCategory === 'engines') { // Read consumer's package.json to get engines.node var consumerPkg = findConsumerPackageSync(basedir, isFile, readPackageSync, readFileSync); if (consumerPkg && consumerPkg.engines && consumerPkg.engines.node) { var categories = getCategoriesForRange(consumerPkg.engines.node); exportsCategory = selectMostRestrictive(categories); } else { exportsCategory = null; } } var useExports = exportsCategory !== null && exportsCategory !== 'pre-exports'; // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory var absoluteStart = maybeRealpathSync(realpathSync, path.resolve(basedir), opts); if (opts.basedir && !isDirectory(absoluteStart)) { var dirError = new $TypeError('Provided basedir "' + opts.basedir + '" is not a directory' + (opts.preserveSymlinks ? '' : ', or a symlink to a directory')); dirError.code = 'INVALID_BASEDIR'; throw dirError; } if (relativePathRegex.test(x)) { var res = path.resolve(absoluteStart, x); if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; var m = loadAsFileSync(res) || loadAsDirectorySync(res); if (m) return maybeRealpathSync(realpathSync, m, opts); } else if (includeCoreModules && isCore(x)) { return x; } else if (useExports) { // Try self-reference resolution first var selfRef = resolveSelfReferenceSync(x, absoluteStart); if (selfRef) return maybeRealpathSync(realpathSync, selfRef, opts); var nE = loadNodeModulesWithExportsSync(x, absoluteStart); if (nE) return maybeRealpathSync(realpathSync, nE, opts); } else { var n = loadNodeModulesSync(x, absoluteStart); if (n) return maybeRealpathSync(realpathSync, n, opts); } var err = new $Error("Cannot find module '" + x + "' from '" + parent + "'"); err.code = 'MODULE_NOT_FOUND'; throw err; function resolveSelfReferenceSync(x, startDir) { var parsed = parsePackageSpecifier(x); var pkgInfo = findPackageWithDirSync(startDir, isFile, readPackageSync, readFileSync); if (!pkgInfo || !pkgInfo.pkg || pkgInfo.pkg.name !== parsed.name) { return null; // Not a self-reference } var pkg = pkgInfo.pkg; var pkgDir = pkgInfo.dir; if (opts.packageFilter) { pkg = opts.packageFilter(pkg, path.join(pkgDir, 'package.json'), pkgDir); } // If package has exports field, resolve via exports if (typeof pkg.exports !== 'undefined') { var categoryInfo = getCategoryInfo(exportsCategory, 'require'); var conditions = opts.conditions || categoryInfo.conditions; var resolved = resolveExports(pkg.exports, parsed.subpath, conditions, categoryInfo.flags); if (resolved) { var resolvedPath = path.resolve(pkgDir, resolved); if (isFile(resolvedPath)) { return resolvedPath; } } // exports field exists but didn't resolve - this is an error per Node semantics return undefined; } // No exports field - fall back to traditional resolution for self-reference // (Note: this matches Node's behavior where self-ref without exports uses main) if (parsed.subpath === '.') { return loadAsDirectorySync(pkgDir); } var subPath = path.join(pkgDir, parsed.subpath.slice(1)); var sm = loadAsFileSync(subPath); if (sm) return sm; return loadAsDirectorySync(subPath); } function loadAsFileSync(x) { var pkg = loadpkg(path.dirname(x)); if (pkg && pkg.dir && pkg.pkg && opts.pathFilter) { var rfile = path.relative(pkg.dir, x); var r = opts.pathFilter(pkg.pkg, x, rfile); if (r) { x = path.resolve(pkg.dir, r); // eslint-disable-line no-param-reassign } } if (isFile(x)) { return x; } for (var i = 0; i < extensions.length; i++) { var file = x + extensions[i]; if (isFile(file)) { return file; } } } function loadpkg(dir) { if (dir === '' || dir === '/') return; if (process.platform === 'win32' && windowsDriveRegex.test(dir)) { return; } if (nodeModulesRegex.test(dir)) return; var pkgfile = path.join(isDirectory(dir) ? maybeRealpathSync(realpathSync, dir, opts) : dir, 'package.json'); if (!isFile(pkgfile)) { return loadpkg(path.dirname(dir)); } var pkg; try { pkg = readPackageSync(readFileSync, pkgfile); } catch (e) { if (!(e instanceof SyntaxError)) { throw e; } } if (pkg && opts.packageFilter) { pkg = opts.packageFilter(pkg, pkgfile, dir); } return { pkg: pkg, dir: dir }; } function loadAsDirectorySync(x) { var pkgfile = path.join(isDirectory(x) ? maybeRealpathSync(realpathSync, x, opts) : x, '/package.json'); if (isFile(pkgfile)) { try { var pkg = readPackageSync(readFileSync, pkgfile); } catch (e) {} if (pkg && opts.packageFilter) { pkg = opts.packageFilter(pkg, pkgfile, x); } if (pkg && pkg.main) { if (typeof pkg.main !== 'string') { var mainError = new $TypeError('package “' + pkg.name + '” `main` must be a string'); mainError.code = 'INVALID_PACKAGE_MAIN'; throw mainError; } if (pkg.main === '.' || pkg.main === './') { pkg.main = 'index'; } try { var mainPath = path.resolve(x, pkg.main); var m = loadAsFileSync(mainPath); if (m) return m; var n = loadAsDirectorySync(mainPath); if (n) return n; var checkIndex = loadAsFileSync(path.resolve(x, 'index')); if (checkIndex) return checkIndex; } catch (e) { } var incorrectMainError = new $Error("Cannot find module '" + path.resolve(x, pkg.main) + "'. Please verify that the package.json has a valid \"main\" entry"); incorrectMainError.code = 'INCORRECT_PACKAGE_MAIN'; throw incorrectMainError; } } return loadAsFileSync(path.join(x, '/index')); } function loadNodeModulesSync(x, start) { var thunk = function () { return getPackageCandidates(x, start, opts); }; var dirs = packageIterator ? packageIterator(x, start, thunk, opts) : thunk(); for (var i = 0; i < dirs.length; i++) { var dir = dirs[i]; if (isDirectory(path.dirname(dir))) { var m = loadAsFileSync(dir); if (m) return m; var n = loadAsDirectorySync(dir); if (n) return n; } } } function loadNodeModulesWithExportsSync(x, start) { var parsed = parsePackageSpecifier(x); var categoryInfo = getCategoryInfo(exportsCategory, 'require'); var conditions = opts.conditions || categoryInfo.conditions; // Get candidate directories for the package name var thunk = function () { return getPackageCandidates(parsed.name, start, opts); }; var dirs = packageIterator ? packageIterator(parsed.name, start, thunk, opts) : thunk(); for (var i = 0; i < dirs.length; i++) { var pkgDir = dirs[i]; if (isDirectory(pkgDir)) { var pkgfile = path.join(pkgDir, 'package.json'); if (isFile(pkgfile)) { var pkg; try { pkg = readPackageSync(readFileSync, pkgfile); } catch (e) { if (!(e instanceof SyntaxError)) { throw e; } pkg = null; } if (pkg) { if (opts.packageFilter) { pkg = opts.packageFilter(pkg, pkgfile, pkgDir); } // If package has exports field, use exports resolution if (typeof pkg.exports !== 'undefined') { var resolved = resolveExports(pkg.exports, parsed.subpath, conditions, categoryInfo.flags); if (resolved) { var resolvedPath = path.resolve(pkgDir, resolved); if (isFile(resolvedPath)) { return resolvedPath; } } // exports field exists but didn't resolve - this is an error per Node semantics // (don't fall through to main/index) return undefined; } // No exports field, fall back to traditional resolution if (parsed.subpath === '.') { var result = loadAsDirectorySync(pkgDir); if (result) { return result; } } else { var subPath = path.join(pkgDir, parsed.subpath.slice(1)); var sm = loadAsFileSync(subPath); if (sm) { return sm; } var sn = loadAsDirectorySync(subPath); if (sn) { return sn; } } } } else if (parsed.subpath === '.') { // No package.json, fall back to file/directory resolution var m = loadAsFileSync(pkgDir); if (m) { return m; } var n = loadAsDirectorySync(pkgDir); if (n) { return n; } } else { // No package.json, fall back to file/directory resolution for subpath var fullPath = path.join(pkgDir, parsed.subpath.slice(1)); var m2 = loadAsFileSync(fullPath); if (m2) { return m2; } var n2 = loadAsDirectorySync(fullPath); if (n2) { return n2; } } } } } };