nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
1,398 lines (1,305 loc) • 43.8 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/modules/esm/resolve.js
import * as __hoisted_internal_util__ from "nstdlib/lib/internal/util";
import * as internalFS from "nstdlib/lib/internal/fs/utils";
import { BuiltinModule } from "nstdlib/lib/internal/bootstrap/realm";
import { realpathSync } from "nstdlib/lib/fs";
import { getOptionValue } from "nstdlib/lib/internal/options";
import { sep, posix as __posix__, resolve } from "nstdlib/lib/path";
import {
URL,
pathToFileURL,
fileURLToPath,
isURL,
URLParse,
} from "nstdlib/lib/internal/url";
import { getCWDURL, setOwnProperty } from "nstdlib/lib/internal/util";
import { canParse as URLCanParse } from "nstdlib/stub/binding/url";
import { legacyMainResolve as FSLegacyMainResolve } from "nstdlib/stub/binding/fs";
import { codes as __codes__ } from "nstdlib/lib/internal/errors";
import { Module as CJSModule } from "nstdlib/lib/internal/modules/cjs/loader";
import { getConditionsSet } from "nstdlib/lib/internal/modules/esm/utils";
import * as packageJsonReader from "nstdlib/lib/internal/modules/package_json_reader";
import * as internalFsBinding from "nstdlib/stub/binding/fs";
import { defaultGetFormatWithoutErrors } from "nstdlib/lib/internal/modules/esm/get_format";
let _module_exports_ = {};
export { _module_exports_ as default };
// Do not eagerly grab .manifest, it may be in TDZ
const { relative: relativePosixPath } = __posix__;
const preserveSymlinks = getOptionValue("--preserve-symlinks");
const preserveSymlinksMain = getOptionValue("--preserve-symlinks-main");
const inputTypeFlag = getOptionValue("--input-type");
const {
ERR_INPUT_TYPE_NOT_ALLOWED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_MODULE_SPECIFIER,
ERR_INVALID_PACKAGE_CONFIG,
ERR_INVALID_PACKAGE_TARGET,
ERR_MODULE_NOT_FOUND,
ERR_PACKAGE_IMPORT_NOT_DEFINED,
ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_RESOLVE_REQUEST,
} = __codes__;
/**
* @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
*/
const emittedPackageWarnings = new Set();
/**
* Emits a deprecation warning for the use of a deprecated trailing slash pattern mapping in the "exports" field
* module resolution of a package.
* @param {string} match - The deprecated trailing slash pattern mapping.
* @param {string} pjsonUrl - The URL of the package.json file.
* @param {string} base - The URL of the module that imported the package.
*/
function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
if (process.noDeprecation) {
return;
}
const pjsonPath = fileURLToPath(pjsonUrl);
if (emittedPackageWarnings.has(pjsonPath + "|" + match)) {
return;
}
emittedPackageWarnings.add(pjsonPath + "|" + match);
process.emitWarning(
`Use of deprecated trailing slash pattern mapping "${match}" in the ` +
`"exports" field module resolution of the package at ${pjsonPath}${
base ? ` imported from ${fileURLToPath(base)}` : ""
}. Mapping specifiers ending in "/" is no longer supported.`,
"DeprecationWarning",
"DEP0155",
);
}
const doubleSlashRegEx = /[/\\][/\\]/;
/**
* Emits a deprecation warning for invalid segment in module resolution.
* @param {string} target - The target module.
* @param {string} request - The requested module.
* @param {string} match - The matched module.
* @param {string} pjsonUrl - The package.json URL.
* @param {boolean} internal - Whether the module is in the "imports" or "exports" field.
* @param {string} base - The base URL.
* @param {boolean} isTarget - Whether the target is a module.
*/
function emitInvalidSegmentDeprecation(
target,
request,
match,
pjsonUrl,
internal,
base,
isTarget,
) {
if (process.noDeprecation) {
return;
}
const pjsonPath = fileURLToPath(pjsonUrl);
const double =
RegExp.prototype.exec.call(
doubleSlashRegEx,
isTarget ? target : request,
) !== null;
process.emitWarning(
`Use of deprecated ${
double ? "double slash" : "leading or trailing slash matching"
} resolving "${target}" for module ` +
`request "${request}" ${
request !== match ? `matched to "${match}" ` : ""
}in the "${internal ? "imports" : "exports"}" field module resolution of the package at ${
pjsonPath
}${base ? ` imported from ${fileURLToPath(base)}` : ""}.`,
"DeprecationWarning",
"DEP0166",
);
}
/**
* Emits a deprecation warning if the given URL is a module and
* the package.json file does not define a "main" or "exports" field.
* @param {URL} url - The URL of the module being resolved.
* @param {URL} packageJSONUrl - The URL of the package.json file for the module.
* @param {string | URL} [base] - The base URL for the module being resolved.
* @param {string} [main] - The "main" field from the package.json file.
*/
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
if (process.noDeprecation) {
return;
}
const format = defaultGetFormatWithoutErrors(url);
if (format !== "module") {
return;
}
const path = fileURLToPath(url);
const pkgPath = fileURLToPath(new URL(".", packageJSONUrl));
const basePath = fileURLToPath(base);
if (!main) {
process.emitWarning(
`No "main" or "exports" field defined in the package.json for ${
pkgPath
} resolving the main entry point "${String.prototype.slice.call(
path,
pkgPath.length,
)}", imported from ${
basePath
}.\nDefault "index" lookups for the main are deprecated for ES modules.`,
"DeprecationWarning",
"DEP0151",
);
} else if (resolve(pkgPath, main) !== path) {
process.emitWarning(
`Package ${pkgPath} has a "main" field set to "${main}", ` +
`excluding the full filename and extension to the resolved file at "${String.prototype.slice.call(
path,
pkgPath.length,
)}", imported from ${
basePath
}.\n Automatic extension resolution of the "main" field is ` +
"deprecated for ES modules.",
"DeprecationWarning",
"DEP0151",
);
}
}
const realpathCache = new Map();
const legacyMainResolveExtensions = [
"",
".js",
".json",
".node",
"/index.js",
"/index.json",
"/index.node",
"./index.js",
"./index.json",
"./index.node",
];
const legacyMainResolveExtensionsIndexes = {
// 0-6: when packageConfig.main is defined
kResolvedByMain: 0,
kResolvedByMainJs: 1,
kResolvedByMainJson: 2,
kResolvedByMainNode: 3,
kResolvedByMainIndexJs: 4,
kResolvedByMainIndexJson: 5,
kResolvedByMainIndexNode: 6,
// 7-9: when packageConfig.main is NOT defined,
// or when the previous case didn't found the file
kResolvedByPackageAndJs: 7,
kResolvedByPackageAndJson: 8,
kResolvedByPackageAndNode: 9,
};
/**
* Legacy CommonJS main resolution:
* 1. let M = pkg_url + (json main field)
* 2. TRY(M, M.js, M.json, M.node)
* 3. TRY(M/index.js, M/index.json, M/index.node)
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
* 5. NOT_FOUND
* @param {URL} packageJSONUrl
* @param {import('typings/internalBinding/modules').PackageConfig} packageConfig
* @param {string | URL | undefined} base
* @returns {URL}
*/
function legacyMainResolve(packageJSONUrl, packageConfig, base) {
const packageJsonUrlString = packageJSONUrl.href;
if (typeof packageJsonUrlString !== "string") {
throw new ERR_INVALID_ARG_TYPE("packageJSONUrl", ["URL"], packageJSONUrl);
}
const baseStringified = isURL(base) ? base.href : base;
const resolvedOption = FSLegacyMainResolve(
packageJsonUrlString,
packageConfig.main,
baseStringified,
);
const baseUrl =
resolvedOption <=
legacyMainResolveExtensionsIndexes.kResolvedByMainIndexNode
? `./${packageConfig.main}`
: "";
const resolvedUrl = new URL(
baseUrl + legacyMainResolveExtensions[resolvedOption],
packageJSONUrl,
);
emitLegacyIndexDeprecation(
resolvedUrl,
packageJSONUrl,
base,
packageConfig.main,
);
return resolvedUrl;
}
const encodedSepRegEx = /%2F|%5C/i;
/**
* Finalizes the resolution of a module specifier by checking if the resolved pathname contains encoded "/" or "\\"
* characters, checking if the resolved pathname is a directory or file, and resolving any symlinks if necessary.
* @param {URL} resolved - The resolved URL object.
* @param {string | URL | undefined} base - The base URL object.
* @param {boolean} preserveSymlinks - Whether to preserve symlinks or not.
* @returns {URL} - The finalized URL object.
* @throws {ERR_INVALID_MODULE_SPECIFIER} - If the resolved pathname contains encoded "/" or "\\" characters.
* @throws {ERR_UNSUPPORTED_DIR_IMPORT} - If the resolved pathname is a directory.
* @throws {ERR_MODULE_NOT_FOUND} - If the resolved pathname is not a file.
*/
function finalizeResolution(resolved, base, preserveSymlinks) {
if (RegExp.prototype.exec.call(encodedSepRegEx, resolved.pathname) !== null) {
throw new ERR_INVALID_MODULE_SPECIFIER(
resolved.pathname,
'must not include encoded "/" or "\\" characters',
fileURLToPath(base),
);
}
let path;
try {
path = fileURLToPath(resolved);
} catch (err) {
const { setOwnProperty } = __hoisted_internal_util__;
setOwnProperty(err, "input", `${resolved}`);
setOwnProperty(err, "module", `${base}`);
throw err;
}
const stats = internalFsBinding.internalModuleStat(
String.prototype.endsWith.call(path, "/")
? String.prototype.slice.call(path, -1)
: path,
);
// Check for stats.isDirectory()
if (stats === 1) {
throw new ERR_UNSUPPORTED_DIR_IMPORT(
path,
fileURLToPath(base),
String(resolved),
);
} else if (stats !== 0) {
// Check for !stats.isFile()
if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) {
process.send({ "watch:require": [path || resolved.pathname] });
}
throw new ERR_MODULE_NOT_FOUND(
path || resolved.pathname,
base && fileURLToPath(base),
resolved,
);
}
if (!preserveSymlinks) {
const real = realpathSync(path, {
[internalFS.realpathCacheKey]: realpathCache,
});
const { search, hash } = resolved;
resolved = pathToFileURL(
real + (String.prototype.endsWith.call(path, sep) ? "/" : ""),
);
resolved.search = search;
resolved.hash = hash;
}
return resolved;
}
/**
* Returns an error object indicating that the specified import is not defined.
* @param {string} specifier - The import specifier that is not defined.
* @param {URL} packageJSONUrl - The URL of the package.json file, or null if not available.
* @param {string | URL | undefined} base - The base URL to use for resolving relative URLs.
* @returns {ERR_PACKAGE_IMPORT_NOT_DEFINED} - The error object.
*/
function importNotDefined(specifier, packageJSONUrl, base) {
return new ERR_PACKAGE_IMPORT_NOT_DEFINED(
specifier,
packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)),
fileURLToPath(base),
);
}
/**
* Returns an error object indicating that the specified subpath was not exported by the package.
* @param {string} subpath - The subpath that was not exported.
* @param {URL} packageJSONUrl - The URL of the package.json file.
* @param {string | URL | undefined} [base] - The base URL to use for resolving the subpath.
* @returns {ERR_PACKAGE_PATH_NOT_EXPORTED} - The error object.
*/
function exportsNotFound(subpath, packageJSONUrl, base) {
return new ERR_PACKAGE_PATH_NOT_EXPORTED(
fileURLToPath(new URL(".", packageJSONUrl)),
subpath,
base && fileURLToPath(base),
);
}
/**
* Throws an error indicating that the given request is not a valid subpath match for the specified pattern.
* @param {string} request - The request that failed to match the pattern.
* @param {string} match - The pattern that the request was compared against.
* @param {URL} packageJSONUrl - The URL of the package.json file being resolved.
* @param {boolean} internal - Whether the resolution is for an "imports" or "exports" field in package.json.
* @param {string | URL | undefined} base - The base URL for the resolution.
* @throws {ERR_INVALID_MODULE_SPECIFIER} When the request is not a valid match for the pattern.
*/
function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) {
const reason = `request is not a valid match in pattern "${match}" for the "${
internal ? "imports" : "exports"
}" resolution of ${fileURLToPath(packageJSONUrl)}`;
throw new ERR_INVALID_MODULE_SPECIFIER(
request,
reason,
base && fileURLToPath(base),
);
}
/**
* Creates an error object for an invalid package target.
* @param {string} subpath - The subpath.
* @param {import('internal/modules/esm/package_config.js').PackageTarget} target - The target.
* @param {URL} packageJSONUrl - The URL of the package.json file.
* @param {boolean} internal - Whether the package is internal.
* @param {string | URL | undefined} base - The base URL.
* @returns {ERR_INVALID_PACKAGE_TARGET} - The error object.
*/
function invalidPackageTarget(subpath, target, packageJSONUrl, internal, base) {
if (typeof target === "object" && target !== null) {
target = JSONStringify(target, null, "");
} else {
target = `${target}`;
}
return new ERR_INVALID_PACKAGE_TARGET(
fileURLToPath(new URL(".", packageJSONUrl)),
subpath,
target,
internal,
base && fileURLToPath(base),
);
}
const invalidSegmentRegEx =
/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
const deprecatedInvalidSegmentRegEx =
/(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
const invalidPackageNameRegEx = /^\.|%|\\/;
const patternRegEx = /\*/g;
/**
* Resolves the package target string to a URL object.
* @param {string} target - The target string to resolve.
* @param {string} subpath - The subpath to append to the resolved URL.
* @param {RegExpMatchArray} match - The matched string array from the import statement.
* @param {string} packageJSONUrl - The URL of the package.json file.
* @param {string} base - The base URL to resolve the target against.
* @param {RegExp} pattern - The pattern to replace in the target string.
* @param {boolean} internal - Whether the target is internal to the package.
* @param {boolean} isPathMap - Whether the target is a path map.
* @param {string[]} conditions - The import conditions.
* @returns {URL} - The resolved URL object.
* @throws {ERR_INVALID_PACKAGE_TARGET} - If the target is invalid.
* @throws {ERR_INVALID_SUBPATH} - If the subpath is invalid.
*/
function resolvePackageTargetString(
target,
subpath,
match,
packageJSONUrl,
base,
pattern,
internal,
isPathMap,
conditions,
) {
if (subpath !== "" && !pattern && target[target.length - 1] !== "/") {
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
if (!String.prototype.startsWith.call(target, "./")) {
if (
internal &&
!String.prototype.startsWith.call(target, "../") &&
!String.prototype.startsWith.call(target, "/")
) {
// No need to convert target to string, since it's already presumed to be
if (!URLCanParse(target)) {
const exportTarget = pattern
? RegExp.prototype[Symbol.replace].call(
patternRegEx,
target,
() => subpath,
)
: target + subpath;
return packageResolve(exportTarget, packageJSONUrl, conditions);
}
}
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
if (
RegExp.prototype.exec.call(
invalidSegmentRegEx,
String.prototype.slice.call(target, 2),
) !== null
) {
if (
RegExp.prototype.exec.call(
deprecatedInvalidSegmentRegEx,
String.prototype.slice.call(target, 2),
) === null
) {
if (!isPathMap) {
const request = pattern
? String.prototype.replace.call(match, "*", () => subpath)
: match + subpath;
const resolvedTarget = pattern
? RegExp.prototype[Symbol.replace].call(
patternRegEx,
target,
() => subpath,
)
: target;
emitInvalidSegmentDeprecation(
resolvedTarget,
request,
match,
packageJSONUrl,
internal,
base,
true,
);
}
} else {
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
}
const resolved = new URL(target, packageJSONUrl);
const resolvedPath = resolved.pathname;
const packagePath = new URL(".", packageJSONUrl).pathname;
if (!String.prototype.startsWith.call(resolvedPath, packagePath)) {
throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
if (subpath === "") {
return resolved;
}
if (RegExp.prototype.exec.call(invalidSegmentRegEx, subpath) !== null) {
const request = pattern
? String.prototype.replace.call(match, "*", () => subpath)
: match + subpath;
if (
RegExp.prototype.exec.call(deprecatedInvalidSegmentRegEx, subpath) ===
null
) {
if (!isPathMap) {
const resolvedTarget = pattern
? RegExp.prototype[Symbol.replace].call(
patternRegEx,
target,
() => subpath,
)
: target;
emitInvalidSegmentDeprecation(
resolvedTarget,
request,
match,
packageJSONUrl,
internal,
base,
false,
);
}
} else {
throwInvalidSubpath(request, match, packageJSONUrl, internal, base);
}
}
if (pattern) {
return new URL(
RegExp.prototype[Symbol.replace].call(
patternRegEx,
resolved.href,
() => subpath,
),
);
}
return new URL(subpath, resolved);
}
/**
* Checks if the given key is a valid array index.
* @param {string} key - The key to check.
* @returns {boolean} - Returns `true` if the key is a valid array index, else `false`.
*/
function isArrayIndex(key) {
const keyNum = +key;
if (`${keyNum}` !== key) {
return false;
}
return keyNum >= 0 && keyNum < 0xffff_ffff;
}
/**
* Resolves the target of a package based on the provided parameters.
* @param {string} packageJSONUrl - The URL of the package.json file.
* @param {import('internal/modules/esm/package_config.js').PackageTarget} target - The target to resolve.
* @param {string} subpath - The subpath to resolve.
* @param {string} packageSubpath - The subpath of the package to resolve.
* @param {string} base - The base path to resolve.
* @param {RegExp} pattern - The pattern to match.
* @param {boolean} internal - Whether the package is internal.
* @param {boolean} isPathMap - Whether the package is a path map.
* @param {Set<string>} conditions - The conditions to match.
* @returns {URL | null | undefined} - The resolved target, or null if not found, or undefined if not resolvable.
*/
function resolvePackageTarget(
packageJSONUrl,
target,
subpath,
packageSubpath,
base,
pattern,
internal,
isPathMap,
conditions,
) {
if (typeof target === "string") {
return resolvePackageTargetString(
target,
subpath,
packageSubpath,
packageJSONUrl,
base,
pattern,
internal,
isPathMap,
conditions,
);
} else if (Array.isArray(target)) {
if (target.length === 0) {
return null;
}
let lastException;
for (let i = 0; i < target.length; i++) {
const targetItem = target[i];
let resolveResult;
try {
resolveResult = resolvePackageTarget(
packageJSONUrl,
targetItem,
subpath,
packageSubpath,
base,
pattern,
internal,
isPathMap,
conditions,
);
} catch (e) {
lastException = e;
if (e.code === "ERR_INVALID_PACKAGE_TARGET") {
continue;
}
throw e;
}
if (resolveResult === undefined) {
continue;
}
if (resolveResult === null) {
lastException = null;
continue;
}
return resolveResult;
}
if (lastException == null) {
return lastException;
}
throw lastException;
} else if (typeof target === "object" && target !== null) {
const keys = Object.getOwnPropertyNames(target);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (isArrayIndex(key)) {
throw new ERR_INVALID_PACKAGE_CONFIG(
fileURLToPath(packageJSONUrl),
base,
'"exports" cannot contain numeric property keys.',
);
}
}
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key === "default" || conditions.has(key)) {
const conditionalTarget = target[key];
const resolveResult = resolvePackageTarget(
packageJSONUrl,
conditionalTarget,
subpath,
packageSubpath,
base,
pattern,
internal,
isPathMap,
conditions,
);
if (resolveResult === undefined) {
continue;
}
return resolveResult;
}
}
return undefined;
} else if (target === null) {
return null;
}
throw invalidPackageTarget(
packageSubpath,
target,
packageJSONUrl,
internal,
base,
);
}
/**
* Is the given exports object using the shorthand syntax?
* @param {import('internal/modules/esm/package_config.js').PackageConfig['exports']} exports
* @param {URL} packageJSONUrl The URL of the package.json file.
* @param {string | URL | undefined} base The base URL.
*/
function isConditionalExportsMainSugar(_module_exports_, packageJSONUrl, base) {
if (typeof exports === "string" || Array.isArray(exports)) {
return true;
}
if (typeof exports !== "object" || exports === null) {
return false;
}
const keys = Object.getOwnPropertyNames(exports);
let isConditionalSugar = false;
let i = 0;
for (let j = 0; j < keys.length; j++) {
const key = keys[j];
const curIsConditionalSugar = key === "" || key[0] !== ".";
if (i++ === 0) {
isConditionalSugar = curIsConditionalSugar;
} else if (isConditionalSugar !== curIsConditionalSugar) {
throw new ERR_INVALID_PACKAGE_CONFIG(
fileURLToPath(packageJSONUrl),
base,
"\"exports\" cannot contain some keys starting with '.' and some not." +
" The exports object must either be an object of package subpath keys" +
" or an object of main entry condition name keys only.",
);
}
}
return isConditionalSugar;
}
/**
* Resolves the exports of a package.
* @param {URL} packageJSONUrl - The URL of the package.json file.
* @param {string} packageSubpath - The subpath of the package to resolve.
* @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig - The package metadata.
* @param {string | URL | undefined} base - The base path to resolve from.
* @param {Set<string>} conditions - An array of conditions to match.
* @returns {URL} - The resolved package target.
*/
function packageExportsResolve(
packageJSONUrl,
packageSubpath,
packageConfig,
base,
conditions,
) {
let { exports } = packageConfig;
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
exports = { ".": exports };
}
if (
Object.prototype.hasOwnProperty.call(exports, packageSubpath) &&
!String.prototype.includes.call(packageSubpath, "*") &&
!String.prototype.endsWith.call(packageSubpath, "/")
) {
const target = exports[packageSubpath];
const resolveResult = resolvePackageTarget(
packageJSONUrl,
target,
"",
packageSubpath,
base,
false,
false,
false,
conditions,
);
if (resolveResult == null) {
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
return resolveResult;
}
let bestMatch = "";
let bestMatchSubpath;
const keys = Object.getOwnPropertyNames(exports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = String.prototype.indexOf.call(key, "*");
if (
patternIndex !== -1 &&
String.prototype.startsWith.call(
packageSubpath,
String.prototype.slice.call(key, 0, patternIndex),
)
) {
// When this reaches EOL, this can throw at the top of the whole function:
//
// if (StringPrototypeEndsWith(packageSubpath, '/'))
// throwInvalidSubpath(packageSubpath)
//
// To match "imports" and the spec.
if (String.prototype.endsWith.call(packageSubpath, "/")) {
emitTrailingSlashPatternDeprecation(
packageSubpath,
packageJSONUrl,
base,
);
}
const patternTrailer = String.prototype.slice.call(key, patternIndex + 1);
if (
packageSubpath.length >= key.length &&
String.prototype.endsWith.call(packageSubpath, patternTrailer) &&
patternKeyCompare(bestMatch, key) === 1 &&
String.prototype.lastIndexOf.call(key, "*") === patternIndex
) {
bestMatch = key;
bestMatchSubpath = String.prototype.slice.call(
packageSubpath,
patternIndex,
packageSubpath.length - patternTrailer.length,
);
}
}
}
if (bestMatch) {
const target = exports[bestMatch];
const resolveResult = resolvePackageTarget(
packageJSONUrl,
target,
bestMatchSubpath,
bestMatch,
base,
true,
false,
String.prototype.endsWith.call(packageSubpath, "/"),
conditions,
);
if (resolveResult == null) {
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
return resolveResult;
}
throw exportsNotFound(packageSubpath, packageJSONUrl, base);
}
/**
* Compares two strings that may contain a wildcard character ('*') and returns a value indicating their order.
* @param {string} a - The first string to compare.
* @param {string} b - The second string to compare.
* @returns {number} - A negative number if `a` should come before `b`, a positive number if `a` should come after `b`,
* or 0 if they are equal.
*/
function patternKeyCompare(a, b) {
const aPatternIndex = String.prototype.indexOf.call(a, "*");
const bPatternIndex = String.prototype.indexOf.call(b, "*");
const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
if (baseLenA > baseLenB) {
return -1;
}
if (baseLenB > baseLenA) {
return 1;
}
if (aPatternIndex === -1) {
return 1;
}
if (bPatternIndex === -1) {
return -1;
}
if (a.length > b.length) {
return -1;
}
if (b.length > a.length) {
return 1;
}
return 0;
}
/**
* Resolves the given import name for a package.
* @param {string} name - The name of the import to resolve.
* @param {string | URL | undefined} base - The base URL to resolve the import from.
* @param {Set<string>} conditions - An object containing the import conditions.
* @throws {ERR_INVALID_MODULE_SPECIFIER} If the import name is not valid.
* @throws {ERR_PACKAGE_IMPORT_NOT_DEFINED} If the import name cannot be resolved.
* @returns {URL} The resolved import URL.
*/
function packageImportsResolve(name, base, conditions) {
if (
name === "#" ||
String.prototype.startsWith.call(name, "#/") ||
String.prototype.endsWith.call(name, "/")
) {
const reason = "is not a valid internal imports specifier name";
throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
}
let packageJSONUrl;
const packageConfig = packageJsonReader.getPackageScopeConfig(base);
if (packageConfig.exists) {
packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
const imports = packageConfig.imports;
if (imports) {
if (
Object.prototype.hasOwnProperty.call(imports, name) &&
!String.prototype.includes.call(name, "*")
) {
const resolveResult = resolvePackageTarget(
packageJSONUrl,
imports[name],
"",
name,
base,
false,
true,
false,
conditions,
);
if (resolveResult != null) {
return resolveResult;
}
} else {
let bestMatch = "";
let bestMatchSubpath;
const keys = Object.getOwnPropertyNames(imports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const patternIndex = String.prototype.indexOf.call(key, "*");
if (
patternIndex !== -1 &&
String.prototype.startsWith.call(
name,
String.prototype.slice.call(key, 0, patternIndex),
)
) {
const patternTrailer = String.prototype.slice.call(
key,
patternIndex + 1,
);
if (
name.length >= key.length &&
String.prototype.endsWith.call(name, patternTrailer) &&
patternKeyCompare(bestMatch, key) === 1 &&
String.prototype.lastIndexOf.call(key, "*") === patternIndex
) {
bestMatch = key;
bestMatchSubpath = String.prototype.slice.call(
name,
patternIndex,
name.length - patternTrailer.length,
);
}
}
}
if (bestMatch) {
const target = imports[bestMatch];
const resolveResult = resolvePackageTarget(
packageJSONUrl,
target,
bestMatchSubpath,
bestMatch,
base,
true,
true,
false,
conditions,
);
if (resolveResult != null) {
return resolveResult;
}
}
}
}
}
throw importNotDefined(name, packageJSONUrl, base);
}
/**
* Parse a package name from a specifier.
* @param {string} specifier - The import specifier.
* @param {string | URL | undefined} base - The parent URL.
*/
function parsePackageName(specifier, base) {
let separatorIndex = String.prototype.indexOf.call(specifier, "/");
let validPackageName = true;
let isScoped = false;
if (specifier[0] === "@") {
isScoped = true;
if (separatorIndex === -1 || specifier.length === 0) {
validPackageName = false;
} else {
separatorIndex = String.prototype.indexOf.call(
specifier,
"/",
separatorIndex + 1,
);
}
}
const packageName =
separatorIndex === -1
? specifier
: String.prototype.slice.call(specifier, 0, separatorIndex);
// Package name cannot have leading . and cannot have percent-encoding or
// \\ separators.
if (
RegExp.prototype.exec.call(invalidPackageNameRegEx, packageName) !== null
) {
validPackageName = false;
}
if (!validPackageName) {
throw new ERR_INVALID_MODULE_SPECIFIER(
specifier,
"is not a valid package name",
fileURLToPath(base),
);
}
const packageSubpath =
"." +
(separatorIndex === -1
? ""
: String.prototype.slice.call(specifier, separatorIndex));
return { packageName, packageSubpath, isScoped };
}
/**
* Resolves a package specifier to a URL.
* @param {string} specifier - The package specifier to resolve.
* @param {string | URL | undefined} base - The base URL to use for resolution.
* @param {Set<string>} conditions - An object containing the conditions for resolution.
* @returns {URL} - The resolved URL.
*/
function packageResolve(specifier, base, conditions) {
// TODO(@anonrig): Move this to a C++ function.
if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
return new URL("node:" + specifier);
}
const { packageName, packageSubpath, isScoped } = parsePackageName(
specifier,
base,
);
// ResolveSelf
const packageConfig = packageJsonReader.getPackageScopeConfig(base);
if (packageConfig.exists) {
const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
if (packageConfig.exports != null && packageConfig.name === packageName) {
return packageExportsResolve(
packageJSONUrl,
packageSubpath,
packageConfig,
base,
conditions,
);
}
}
let packageJSONUrl = new URL(
"./node_modules/" + packageName + "/package.json",
base,
);
let packageJSONPath = fileURLToPath(packageJSONUrl);
let lastPath;
do {
const stat = internalFsBinding.internalModuleStat(
String.prototype.slice.call(
packageJSONPath,
0,
packageJSONPath.length - 13,
),
);
// Check for !stat.isDirectory()
if (stat !== 1) {
lastPath = packageJSONPath;
packageJSONUrl = new URL(
(isScoped ? "../../../../node_modules/" : "../../../node_modules/") +
packageName +
"/package.json",
packageJSONUrl,
);
packageJSONPath = fileURLToPath(packageJSONUrl);
continue;
}
// Package match.
const packageConfig = packageJsonReader.read(packageJSONPath, {
__proto__: null,
specifier,
base,
isESM: true,
});
if (packageConfig.exports != null) {
return packageExportsResolve(
packageJSONUrl,
packageSubpath,
packageConfig,
base,
conditions,
);
}
if (packageSubpath === ".") {
return legacyMainResolve(packageJSONUrl, packageConfig, base);
}
return new URL(packageSubpath, packageJSONUrl);
// Cross-platform root check.
} while (packageJSONPath.length !== lastPath.length);
// eslint can't handle the above code.
// eslint-disable-next-line no-unreachable
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
}
/**
* Checks if a specifier is a bare specifier.
* @param {string} specifier - The specifier to check.
*/
function isBareSpecifier(specifier) {
return specifier[0] && specifier[0] !== "/" && specifier[0] !== ".";
}
/**
* Determines whether a specifier is a relative path.
* @param {string} specifier - The specifier to check.
*/
function isRelativeSpecifier(specifier) {
if (specifier[0] === ".") {
if (specifier.length === 1 || specifier[1] === "/") {
return true;
}
if (specifier[1] === ".") {
if (specifier.length === 2 || specifier[2] === "/") {
return true;
}
}
}
return false;
}
/**
* Determines whether a specifier should be treated as a relative or absolute path.
* @param {string} specifier - The specifier to check.
*/
function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
if (specifier === "") {
return false;
}
if (specifier[0] === "/") {
return true;
}
return isRelativeSpecifier(specifier);
}
/**
* Resolves a module specifier to a URL.
* @param {string} specifier - The module specifier to resolve.
* @param {string | URL | undefined} base - The base URL to resolve against.
* @param {Set<string>} conditions - An object containing environment conditions.
* @param {boolean} preserveSymlinks - Whether to preserve symlinks in the resolved URL.
*/
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
const protocol =
typeof base === "string"
? String.prototype.slice.call(
base,
0,
String.prototype.indexOf.call(base, ":") + 1,
)
: base.protocol;
const isData = protocol === "data:";
// Order swapped from spec for minor perf gain.
// Ok since relative URLs cannot parse as URLs.
let resolved;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
try {
resolved = new URL(specifier, base);
} catch (cause) {
const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base);
setOwnProperty(error, "cause", cause);
throw error;
}
} else if (protocol === "file:" && specifier[0] === "#") {
resolved = packageImportsResolve(specifier, base, conditions);
} else {
try {
resolved = new URL(specifier);
} catch (cause) {
if (isData && !BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base);
setOwnProperty(error, "cause", cause);
throw error;
}
resolved = packageResolve(specifier, base, conditions);
}
}
if (resolved.protocol !== "file:") {
return resolved;
}
return finalizeResolution(resolved, base, preserveSymlinks);
}
/**
* Try to resolve an import as a CommonJS module.
* @param {string} specifier - The specifier to resolve.
* @param {string} parentURL - The base URL.
* @returns {string | Buffer | false}
*/
function resolveAsCommonJS(specifier, parentURL) {
try {
const parent = fileURLToPath(parentURL);
const tmpModule = new CJSModule(parent, null);
tmpModule.paths = CJSModule._nodeModulePaths(parent);
let found = CJSModule._resolveFilename(specifier, tmpModule, false);
// If it is a relative specifier return the relative path
// to the parent
if (isRelativeSpecifier(specifier)) {
const foundURL = pathToFileURL(found).pathname;
found = relativePosixPath(
String.prototype.slice.call(
parentURL,
"file://".length,
String.prototype.lastIndexOf.call(parentURL, "/"),
),
foundURL,
);
// Add './' if the path does not start with '../'
// This should be a safe assumption because when loading
// esm modules there should be always a file specified so
// there should not be a specifier like '..' or '.'
if (!String.prototype.startsWith.call(found, "../")) {
found = `./${found}`;
}
} else if (isBareSpecifier(specifier)) {
// If it is a bare specifier return the relative path within the
// module
const i = String.prototype.indexOf.call(specifier, "/");
const pkg =
i === -1 ? specifier : String.prototype.slice.call(specifier, 0, i);
const needle = `${sep}node_modules${sep}${pkg}${sep}`;
const index = String.prototype.lastIndexOf.call(found, needle);
if (index !== -1) {
found =
pkg +
"/" +
Array.prototype.join.call(
Array.prototype.map.call(
String.prototype.split.call(
String.prototype.slice.call(found, index + needle.length),
sep,
),
// Escape URL-special characters to avoid generating a incorrect suggestion
encodeURIComponent,
),
"/",
);
} else {
found = `${pathToFileURL(found)}`;
}
}
return found;
} catch {
return false;
}
}
/**
* Validate user-input in `context` supplied by a custom loader.
* @param {string | URL | undefined} parentURL - The parent URL.
*/
function throwIfInvalidParentURL(parentURL) {
if (parentURL === undefined) {
return; // Main entry point, so no parent
}
if (typeof parentURL !== "string" && !isURL(parentURL)) {
throw new ERR_INVALID_ARG_TYPE("parentURL", ["string", "URL"], parentURL);
}
}
/**
* Resolves the given specifier using the provided context, which includes the parent URL and conditions.
* Attempts to resolve the specifier and returns the resulting URL and format.
* @param {string} specifier - The specifier to resolve.
* @param {object} [context={}] - The context object containing the parent URL and conditions.
* @param {string} [context.parentURL] - The URL of the parent module.
* @param {string[]} [context.conditions] - The conditions for resolving the specifier.
*/
function defaultResolve(specifier, context = {}) {
let { parentURL, conditions } = context;
throwIfInvalidParentURL(parentURL);
let parsedParentURL;
if (parentURL) {
parsedParentURL = URLParse(parentURL);
}
let parsed, protocol;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
parsed = URLParse(specifier, parsedParentURL);
} else {
parsed = URLParse(specifier);
}
if (parsed != null) {
// Avoid accessing the `protocol` property due to the lazy getters.
protocol = parsed.protocol;
if (protocol === "data:") {
return { __proto__: null, url: parsed.href };
}
}
protocol ??= parsed?.protocol;
if (protocol === "node:") {
return { __proto__: null, url: specifier };
}
const isMain = parentURL === undefined;
if (isMain) {
parentURL = getCWDURL().href;
// This is the initial entry point to the program, and --input-type has
// been passed as an option; but --input-type can only be used with
// --eval, --print or STDIN string input. It is not allowed with file
// input, to avoid user confusion over how expansive the effect of the
// flag should be (i.e. entry point only, package scope surrounding the
// entry point, etc.).
if (inputTypeFlag) {
throw new ERR_INPUT_TYPE_NOT_ALLOWED();
}
}
conditions = getConditionsSet(conditions);
let url;
try {
url = moduleResolve(
specifier,
parentURL,
conditions,
isMain ? preserveSymlinksMain : preserveSymlinks,
);
} catch (error) {
// Try to give the user a hint of what would have been the
// resolved CommonJS module
if (
error.code === "ERR_MODULE_NOT_FOUND" ||
error.code === "ERR_UNSUPPORTED_DIR_IMPORT"
) {
if (String.prototype.startsWith.call(specifier, "file://")) {
specifier = fileURLToPath(specifier);
}
decorateErrorWithCommonJSHints(error, specifier, parentURL);
}
throw error;
}
return {
__proto__: null,
// Do NOT cast `url` to a string: that will work even when there are real
// problems, silencing them
url: url.href,
format: defaultGetFormatWithoutErrors(url, context),
};
}
/**
* Decorates the given error with a hint for CommonJS modules.
* @param {Error} error - The error to decorate.
* @param {string} specifier - The specifier that was attempted to be imported.
* @param {string} parentURL - The URL of the parent module.
*/
function decorateErrorWithCommonJSHints(error, specifier, parentURL) {
const found = resolveAsCommonJS(specifier, parentURL);
if (found && found !== specifier) {
// Don't suggest the same input the user provided.
// Modify the stack and message string to include the hint
const endOfFirstLine = String.prototype.indexOf.call(error.stack, "\n");
const hint = `Did you mean to import ${JSONStringify(found)}?`;
error.stack =
String.prototype.slice.call(error.stack, 0, endOfFirstLine) +
"\n" +
hint +
String.prototype.slice.call(error.stack, endOfFirstLine);
error.message += `\n${hint}`;
}
}
_module_exports_ = {
decorateErrorWithCommonJSHints,
defaultResolve,
encodedSepRegEx,
packageExportsResolve,
packageImportsResolve,
throwIfInvalidParentURL,
legacyMainResolve,
};
// cycle
export { decorateErrorWithCommonJSHints };
export { defaultResolve };
export { encodedSepRegEx };
export { packageExportsResolve };
export { packageImportsResolve };
export { throwIfInvalidParentURL };
export { legacyMainResolve };