import { fileURLToPath, pathToFileURL } from "./url.ts";import { 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, NodeError,} from "./internal/errors.ts";
const { hasOwn } = Object;
export const encodedSepRegEx = /%2F|%2C/i;
function throwInvalidSubpath( subpath: string, packageJSONUrl: string, internal: boolean, base: string,) { const reason = `request is not a valid subpath for the "${ internal ? "imports" : "exports" }" resolution of ${fileURLToPath(packageJSONUrl)}`; throw new ERR_INVALID_MODULE_SPECIFIER( subpath, reason, base && fileURLToPath(base), );}
function throwInvalidPackageTarget( subpath: string, target: any, packageJSONUrl: string, internal: boolean, base: string,) { if (typeof target === "object" && target !== null) { target = JSON.stringify(target, null, ""); } else { target = `${target}`; } throw new ERR_INVALID_PACKAGE_TARGET( fileURLToPath(new URL(".", packageJSONUrl)), subpath, target, internal, base && fileURLToPath(base), );}
function throwImportNotDefined( specifier: string, packageJSONUrl: URL | undefined, base: string | URL,): TypeError & { code: string } { throw new ERR_PACKAGE_IMPORT_NOT_DEFINED( specifier, packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)), fileURLToPath(base), );}
function throwExportsNotFound( subpath: string, packageJSONUrl: string, base?: string,): Error & { code: string } { throw new ERR_PACKAGE_PATH_NOT_EXPORTED( subpath, fileURLToPath(new URL(".", packageJSONUrl)), base && fileURLToPath(base), );}
function patternKeyCompare(a: string, b: string): number { const aPatternIndex = a.indexOf("*"); const bPatternIndex = b.indexOf("*"); 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;}
function fileExists(url: string | URL): boolean { try { const info = Deno.statSync(url); return info.isFile; } catch { return false; }}
function tryStatSync(path: string): { isDirectory: boolean } { try { const info = Deno.statSync(path); return { isDirectory: info.isDirectory }; } catch { return { isDirectory: false }; }}
function legacyMainResolve( packageJSONUrl: URL, packageConfig: PackageConfig, base: string | URL,): URL { let guess; if (packageConfig.main !== undefined) { if ( fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl)) ) { return guess; } else if ( fileExists(guess = new URL(`./${packageConfig.main}.js`, packageJSONUrl)) ) { } else if ( fileExists( guess = new URL(`./${packageConfig.main}.json`, packageJSONUrl), ) ) { } else if ( fileExists( guess = new URL(`./${packageConfig.main}.node`, packageJSONUrl), ) ) { } else if ( fileExists( guess = new URL(`./${packageConfig.main}/index.js`, packageJSONUrl), ) ) { } else if ( fileExists( guess = new URL(`./${packageConfig.main}/index.json`, packageJSONUrl), ) ) { } else if ( fileExists( guess = new URL(`./${packageConfig.main}/index.node`, packageJSONUrl), ) ) { } else guess = undefined; if (guess) { return guess; } } if (fileExists(guess = new URL("./index.js", packageJSONUrl))) { } else if (fileExists(guess = new URL("./index.json", packageJSONUrl))) { } else if (fileExists(guess = new URL("./index.node", packageJSONUrl))) { } else guess = undefined; if (guess) { return guess; } throw new ERR_MODULE_NOT_FOUND( fileURLToPath(new URL(".", packageJSONUrl)), fileURLToPath(base), );}
function parsePackageName( specifier: string, base: string | URL,): { packageName: string; packageSubpath: string; isScoped: boolean } { let separatorIndex = specifier.indexOf("/"); let validPackageName = true; let isScoped = false; if (specifier[0] === "@") { isScoped = true; if (separatorIndex === -1 || specifier.length === 0) { validPackageName = false; } else { separatorIndex = specifier.indexOf("/", separatorIndex + 1); } }
const packageName = separatorIndex === -1 ? specifier : specifier.slice(0, separatorIndex);
for (let i = 0; i < packageName.length; i++) { if (packageName[i] === "%" || packageName[i] === "\\") { validPackageName = false; break; } }
if (!validPackageName) { throw new ERR_INVALID_MODULE_SPECIFIER( specifier, "is not a valid package name", fileURLToPath(base), ); }
const packageSubpath = "." + (separatorIndex === -1 ? "" : specifier.slice(separatorIndex));
return { packageName, packageSubpath, isScoped };}
function packageResolve( specifier: string, base: string, conditions: Set<string>,): URL | undefined { const { packageName, packageSubpath, isScoped } = parsePackageName( specifier, base, );
const packageConfig = getPackageScopeConfig(base); if (packageConfig.exists) { const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); if ( packageConfig.name === packageName && packageConfig.exports !== undefined && packageConfig.exports !== null ) { return packageExportsResolve( packageJSONUrl.toString(), packageSubpath, packageConfig, base, conditions, ); } }
let packageJSONUrl = new URL( "./node_modules/" + packageName + "/package.json", base, ); let packageJSONPath = fileURLToPath(packageJSONUrl); let lastPath; do { const stat = tryStatSync( packageJSONPath.slice(0, packageJSONPath.length - 13), ); if (!stat.isDirectory) { lastPath = packageJSONPath; packageJSONUrl = new URL( (isScoped ? "../../../../node_modules/" : "../../../node_modules/") + packageName + "/package.json", packageJSONUrl, ); packageJSONPath = fileURLToPath(packageJSONUrl); continue; }
const packageConfig = getPackageConfig(packageJSONPath, specifier, base); if (packageConfig.exports !== undefined && packageConfig.exports !== null) { return packageExportsResolve( packageJSONUrl.toString(), packageSubpath, packageConfig, base, conditions, ); } if (packageSubpath === ".") { return legacyMainResolve(packageJSONUrl, packageConfig, base); } return new URL(packageSubpath, packageJSONUrl); } while (packageJSONPath.length !== lastPath.length);
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));}
const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/;const patternRegEx = /\*/g;
function resolvePackageTargetString( target: string, subpath: string, match: string, packageJSONUrl: string, base: string, pattern: boolean, internal: boolean, conditions: Set<string>,): URL | undefined { if (subpath !== "" && !pattern && target[target.length - 1] !== "/") { throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); }
if (!target.startsWith("./")) { if ( internal && !target.startsWith("../") && !target.startsWith("/") ) { let isURL = false; try { new URL(target); isURL = true; } catch { } if (!isURL) { const exportTarget = pattern ? target.replace(patternRegEx, () => subpath) : target + subpath; return packageResolve(exportTarget, packageJSONUrl, conditions); } } throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); }
if (invalidSegmentRegEx.test(target.slice(2))) { throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); }
const resolved = new URL(target, packageJSONUrl); const resolvedPath = resolved.pathname; const packagePath = new URL(".", packageJSONUrl).pathname;
if (!resolvedPath.startsWith(packagePath)) { throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); }
if (subpath === "") return resolved;
if (invalidSegmentRegEx.test(subpath)) { const request = pattern ? match.replace("*", () => subpath) : match + subpath; throwInvalidSubpath(request, packageJSONUrl, internal, base); }
if (pattern) { return new URL(resolved.href.replace(patternRegEx, () => subpath)); } return new URL(subpath, resolved);}
function isArrayIndex(key: string): boolean { const keyNum = +key; if (`${keyNum}` !== key) return false; return keyNum >= 0 && keyNum < 0xFFFF_FFFF;}
function resolvePackageTarget( packageJSONUrl: string, target: any, subpath: string, packageSubpath: string, base: string, pattern: boolean, internal: boolean, conditions: Set<string>,): URL | undefined { if (typeof target === "string") { return resolvePackageTargetString( target, subpath, packageSubpath, packageJSONUrl, base, pattern, internal, conditions, ); } else if (Array.isArray(target)) { if (target.length === 0) { return undefined; }
let lastException; for (let i = 0; i < target.length; i++) { const targetItem = target[i]; let resolved; try { resolved = resolvePackageTarget( packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern, internal, conditions, ); } catch (e: unknown) { lastException = e; if (e instanceof NodeError && e.code === "ERR_INVALID_PACKAGE_TARGET") { continue; } throw e; } if (resolved === undefined) { continue; } if (resolved === null) { lastException = null; continue; } return resolved; } if (lastException === undefined || lastException === null) { return undefined; } 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 resolved = resolvePackageTarget( packageJSONUrl, conditionalTarget, subpath, packageSubpath, base, pattern, internal, conditions, ); if (resolved === undefined) { continue; } return resolved; } } return undefined; } else if (target === null) { return undefined; } throwInvalidPackageTarget( packageSubpath, target, packageJSONUrl, internal, base, );}
export function packageExportsResolve( packageJSONUrl: string, packageSubpath: string, packageConfig: PackageConfig, base: string, conditions: Set<string>, ): URL { let exports = packageConfig.exports; if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) { exports = { ".": exports }; }
if ( hasOwn(exports, packageSubpath) && !packageSubpath.includes("*") && !packageSubpath.endsWith("/") ) { const target = exports[packageSubpath]; const resolved = resolvePackageTarget( packageJSONUrl, target, "", packageSubpath, base, false, false, conditions, ); if (resolved === null || resolved === undefined) { throwExportsNotFound(packageSubpath, packageJSONUrl, base); } return resolved!; }
let bestMatch = ""; let bestMatchSubpath = ""; const keys = Object.getOwnPropertyNames(exports); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const patternIndex = key.indexOf("*"); if ( patternIndex !== -1 && packageSubpath.startsWith(key.slice(0, patternIndex)) ) { if (packageSubpath.endsWith("/")) { } const patternTrailer = key.slice(patternIndex + 1); if ( packageSubpath.length >= key.length && packageSubpath.endsWith(patternTrailer) && patternKeyCompare(bestMatch, key) === 1 && key.lastIndexOf("*") === patternIndex ) { bestMatch = key; bestMatchSubpath = packageSubpath.slice( patternIndex, packageSubpath.length - patternTrailer.length, ); } } }
if (bestMatch) { const target = exports[bestMatch]; const resolved = resolvePackageTarget( packageJSONUrl, target, bestMatchSubpath, bestMatch, base, true, false, conditions, ); if (resolved === null || resolved === undefined) { throwExportsNotFound(packageSubpath, packageJSONUrl, base); } return resolved!; }
throwExportsNotFound(packageSubpath, packageJSONUrl, base);}
export interface PackageConfig { pjsonPath: string; exists: boolean; name?: string; main?: string; exports?: any; imports?: any; type?: string;}
const packageJSONCache = new Map();
function getPackageConfig( path: string, specifier: string | URL, base?: string | URL,): PackageConfig { const existing = packageJSONCache.get(path); if (existing !== undefined) { return existing; }
let source: string | undefined; try { source = new TextDecoder().decode( Deno.readFileSync(path), ); } catch { }
if (source === undefined) { const packageConfig = { pjsonPath: path, exists: false, main: undefined, name: undefined, type: "none", exports: undefined, imports: undefined, }; packageJSONCache.set(path, packageConfig); return packageConfig; }
let packageJSON; try { packageJSON = JSON.parse(source); } catch (error) { throw new ERR_INVALID_PACKAGE_CONFIG( path, (base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier), error.message, ); }
let { imports, main, name, type } = packageJSON; const { exports } = packageJSON; if (typeof imports !== "object" || imports === null) imports = undefined; if (typeof main !== "string") main = undefined; if (typeof name !== "string") name = undefined; if (type !== "module" && type !== "commonjs") type = "none";
const packageConfig = { pjsonPath: path, exists: true, main, name, type, exports, imports, }; packageJSONCache.set(path, packageConfig); return packageConfig;}
function getPackageScopeConfig(resolved: URL | string): PackageConfig { let packageJSONUrl = new URL("./package.json", resolved); while (true) { const packageJSONPath = packageJSONUrl.pathname; if (packageJSONPath.endsWith("node_modules/package.json")) { break; } const packageConfig = getPackageConfig( fileURLToPath(packageJSONUrl), resolved, ); if (packageConfig.exists) return packageConfig;
const lastPackageJSONUrl = packageJSONUrl; packageJSONUrl = new URL("../package.json", packageJSONUrl);
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break; } const packageJSONPath = fileURLToPath(packageJSONUrl); const packageConfig = { pjsonPath: packageJSONPath, exists: false, main: undefined, name: undefined, type: "none", exports: undefined, imports: undefined, }; packageJSONCache.set(packageJSONPath, packageConfig); return packageConfig;}
export function packageImportsResolve( name: string, base: string, conditions: Set<string>, ): URL { if ( name === "#" || name.startsWith("#/") || name.startsWith("/") ) { const reason = "is not a valid internal imports specifier name"; throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base)); } let packageJSONUrl; const packageConfig = getPackageScopeConfig(base); if (packageConfig.exists) { packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); const imports = packageConfig.imports; if (imports) { if ( hasOwn(imports, name) && !name.includes("*") ) { const resolved = resolvePackageTarget( packageJSONUrl.toString(), imports[name], "", name, base, false, true, conditions, ); if (resolved !== null && resolved !== undefined) { return resolved; } } else { let bestMatch = ""; let bestMatchSubpath = ""; const keys = Object.getOwnPropertyNames(imports); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const patternIndex = key.indexOf("*"); if ( patternIndex !== -1 && name.startsWith( key.slice(0, patternIndex), ) ) { const patternTrailer = key.slice(patternIndex + 1); if ( name.length >= key.length && name.endsWith(patternTrailer) && patternKeyCompare(bestMatch, key) === 1 && key.lastIndexOf("*") === patternIndex ) { bestMatch = key; bestMatchSubpath = name.slice( patternIndex, name.length - patternTrailer.length, ); } } }
if (bestMatch) { const target = imports[bestMatch]; const resolved = resolvePackageTarget( packageJSONUrl.toString(), target, bestMatchSubpath, bestMatch, base, true, true, conditions, ); if (resolved !== null && resolved !== undefined) { return resolved; } } } } } throwImportNotDefined(name, packageJSONUrl, base);}
function isConditionalExportsMainSugar( exports: any, packageJSONUrl: string, base: string,): boolean { 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) { const message = "\"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."; throw new ERR_INVALID_PACKAGE_CONFIG( fileURLToPath(packageJSONUrl), base, message, ); } } return isConditionalSugar;}