diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index db31feb93ee724..44c3f4c31fb352 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -55,7 +55,6 @@ const { StringPrototypeCharAt, StringPrototypeCharCodeAt, StringPrototypeEndsWith, - StringPrototypeLastIndexOf, StringPrototypeIndexOf, StringPrototypeRepeat, StringPrototypeSlice, @@ -68,7 +67,7 @@ const cjsParseCache = new SafeWeakMap(); // Set first due to cycle with ESM loader functions. module.exports = { - wrapSafe, Module, toRealPath, readPackageScope, cjsParseCache, + wrapSafe, Module, cjsParseCache, get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; }, initializeCJS, }; @@ -88,9 +87,7 @@ const { const { internalCompileFunction } = require('internal/vm'); const assert = require('internal/assert'); const fs = require('fs'); -const internalFS = require('internal/fs/utils'); const path = require('path'); -const { sep } = path; const { internalModuleStat } = internalBinding('fs'); const { safeGetenv } = internalBinding('credentials'); const { @@ -106,6 +103,7 @@ const { makeRequireFunction, normalizeReferrerURL, stripBOM, + toRealPath, } = require('internal/modules/helpers'); const packageJsonReader = require('internal/modules/package_json_reader'); const { getOptionValue, getEmbedderOptions } = require('internal/options'); @@ -403,15 +401,7 @@ function initializeCJS() { // -> a. // -> a/index. -/** - * @param {string} requestPath - * @return {PackageConfig} - */ -function readPackage(requestPath) { - return packageJsonReader.read(path.resolve(requestPath, 'package.json')); -} - -let _readPackage = readPackage; +let _readPackage = packageJsonReader.readPackage; ObjectDefineProperty(Module, '_readPackage', { __proto__: null, get() { return _readPackage; }, @@ -423,37 +413,6 @@ ObjectDefineProperty(Module, '_readPackage', { configurable: true, }); -/** - * Get the nearest parent package.json file from a given path. - * Return the package.json data and the path to the package.json file, or false. - * @param {string} checkPath The path to start searching from. - */ -function readPackageScope(checkPath) { - const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep); - let separatorIndex; - const enabledPermission = permission.isEnabled(); - do { - separatorIndex = StringPrototypeLastIndexOf(checkPath, sep); - checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex); - // Stop the search when the process doesn't have permissions - // to walk upwards - if (enabledPermission && !permission.has('fs.read', checkPath + sep)) { - return false; - } - if (StringPrototypeEndsWith(checkPath, sep + 'node_modules')) { - return false; - } - const pjson = _readPackage(checkPath + sep); - if (pjson.exists) { - return { - data: pjson, - path: checkPath, - }; - } - } while (separatorIndex > rootSeparatorIndex); - return false; -} - /** * Try to load a specifier as a package. * @param {string} requestPath The path to what we are trying to load @@ -498,14 +457,6 @@ function tryPackage(requestPath, exts, isMain, originalPath) { return actual; } -/** - * Cache for storing resolved real paths of modules. - * In order to minimize unnecessary lstat() calls, this cache is a list of known-real paths. - * Set to an empty Map to reset. - * @type {Map} - */ -const realpathCache = new SafeMap(); - /** * Check if the file exists and is not a directory if using `--preserve-symlinks` and `isMain` is false, keep symlinks * intact, otherwise resolve to the absolute realpath. @@ -521,17 +472,6 @@ function tryFile(requestPath, isMain) { return toRealPath(requestPath); } - -/** - * Resolves the path of a given `require` specifier, following symlinks. - * @param {string} requestPath The `require` specifier - */ -function toRealPath(requestPath) { - return fs.realpathSync(requestPath, { - [internalFS.realpathCacheKey]: realpathCache, - }); -} - /** * Given a path, check if the file exists with any of the set extensions. * @param {string} basePath The path and filename without extension @@ -593,7 +533,7 @@ function trySelfParentPath(parent) { function trySelf(parentPath, request) { if (!parentPath) { return false; } - const { data: pkg, path: pkgPath } = readPackageScope(parentPath); + const { data: pkg, path: pkgPath } = packageJsonReader.readPackageScope(parentPath); if (!pkg || pkg.exports == null || pkg.name === undefined) { return false; } @@ -1153,7 +1093,7 @@ Module._resolveFilename = function(request, parent, isMain, options) { if (request[0] === '#' && (parent?.filename || parent?.id === '')) { const parentPath = parent?.filename ?? process.cwd() + path.sep; - const pkg = readPackageScope(parentPath) || { __proto__: null }; + const pkg = packageJsonReader.readPackageScope(parentPath) || { __proto__: null }; if (pkg.data?.imports != null) { try { const { packageImportsResolve } = require('internal/modules/esm/resolve'); @@ -1450,7 +1390,7 @@ Module._extensions['.js'] = function(module, filename) { content = fs.readFileSync(filename, 'utf8'); } if (StringPrototypeEndsWith(filename, '.js')) { - const pkg = readPackageScope(filename) || { __proto__: null }; + const pkg = packageJsonReader.readPackageScope(filename) || { __proto__: null }; // Function require shouldn't be used in ES modules. if (pkg.data?.type === 'module') { const parent = moduleParentCache.get(module); diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index cc32e95c4eb413..7f2959cc469dc1 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -21,6 +21,8 @@ const { const { BuiltinModule } = require('internal/bootstrap/realm'); const { validateString } = require('internal/validators'); +const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched. +const internalFS = require('internal/fs/utils'); const path = require('path'); const { pathToFileURL, fileURLToPath, URL } = require('internal/url'); @@ -39,6 +41,23 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => { /** @typedef {import('internal/modules/cjs/loader.js').Module} Module */ +/** + * Cache for storing resolved real paths of modules. + * In order to minimize unnecessary lstat() calls, this cache is a list of known-real paths. + * Set to an empty Map to reset. + * @type {Map} + */ +const realpathCache = new SafeMap(); +/** + * Resolves the path of a given `require` specifier, following symlinks. + * @param {string} requestPath The `require` specifier + */ +function toRealPath(requestPath) { + return fs.realpathSync(requestPath, { + [internalFS.realpathCacheKey]: realpathCache, + }); +} + /** @type {Set} */ let cjsConditions; /** @@ -310,4 +329,5 @@ module.exports = { makeRequireFunction, normalizeReferrerURL, stripBOM, + toRealPath, }; diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index c4afd159ebdb63..65f5ce3551bbd0 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -4,12 +4,17 @@ const { JSONParse, ObjectPrototypeHasOwnProperty, SafeMap, + StringPrototypeEndsWith, + StringPrototypeIndexOf, + StringPrototypeLastIndexOf, + StringPrototypeSlice, } = primordials; const { ERR_INVALID_PACKAGE_CONFIG, } = require('internal/errors').codes; const { internalModuleReadJSON } = internalBinding('fs'); -const { toNamespacedPath } = require('path'); +const { resolve, sep, toNamespacedPath } = require('path'); +const permission = require('internal/process/permission'); const { kEmptyObject, setOwnProperty } = require('internal/util'); const { fileURLToPath, pathToFileURL } = require('internal/url'); @@ -111,4 +116,47 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { return result; } -module.exports = { read }; +/** + * @param {string} requestPath + * @return {PackageConfig} + */ +function readPackage(requestPath) { + return read(resolve(requestPath, 'package.json')); +} + +/** + * Get the nearest parent package.json file from a given path. + * Return the package.json data and the path to the package.json file, or false. + * @param {string} checkPath The path to start searching from. + */ +function readPackageScope(checkPath) { + const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep); + let separatorIndex; + const enabledPermission = permission.isEnabled(); + do { + separatorIndex = StringPrototypeLastIndexOf(checkPath, sep); + checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex); + // Stop the search when the process doesn't have permissions + // to walk upwards + if (enabledPermission && !permission.has('fs.read', checkPath + sep)) { + return false; + } + if (StringPrototypeEndsWith(checkPath, sep + 'node_modules')) { + return false; + } + const pjson = readPackage(checkPath + sep); + if (pjson.exists) { + return { + data: pjson, + path: checkPath, + }; + } + } while (separatorIndex > rootSeparatorIndex); + return false; +} + +module.exports = { + read, + readPackage, + readPackageScope, +}; diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 2e4dabd503a883..e5969bf7b75ea6 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -15,12 +15,13 @@ function resolveMainPath(main) { // Note extension resolution for the main entry point can be deprecated in a // future major. // Module._findPath is monkey-patchable here. - const { Module, toRealPath } = require('internal/modules/cjs/loader'); + const { Module } = require('internal/modules/cjs/loader'); let mainPath = Module._findPath(path.resolve(main), null, true); if (!mainPath) { return; } const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); if (!preserveSymlinksMain) { + const { toRealPath } = require('internal/modules/helpers'); mainPath = toRealPath(mainPath); } @@ -48,7 +49,7 @@ function shouldUseESMLoader(mainPath) { if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; } - const { readPackageScope } = require('internal/modules/cjs/loader'); + const { readPackageScope } = require('internal/modules/package_json_reader'); const pkg = readPackageScope(mainPath); // No need to guard `pkg` as it can only be an object or `false`. return pkg.data?.type === 'module' || getOptionValue('--experimental-default-type') === 'module';