Files
2026-DEV-BUT3/node_modules/resolve/lib/exports-resolve.js
T
2026-04-25 15:28:39 +02:00

274 lines
9.4 KiB
JavaScript

'use strict';
var objectKeys = require('object-keys');
var $Error = require('es-errors');
// Check if an exports map key looks like a subpath (starts with '.')
function isSubpathKey(key) {
return key.length > 0 && key.charAt(0) === '.';
}
// Normalize the exports field into a map of subpath -> target
function normalizeExports(exportsField) {
if (typeof exportsField === 'string') {
return { __proto__: null, '.': exportsField };
}
if (Array.isArray(exportsField)) {
return { __proto__: null, '.': exportsField };
}
if (typeof exportsField === 'object' && exportsField !== null) {
var keys = objectKeys(exportsField);
if (keys.length === 0) {
return { __proto__: null };
}
// If any key starts with '.', it's a subpath map
// If no key starts with '.', it's a conditions object for '.'
var hasSubpath = false;
for (var i = 0; !hasSubpath && i < keys.length; i++) {
if (isSubpathKey(keys[i])) {
hasSubpath = true;
}
}
// Copy to new object with null prototype
var result = { __proto__: null };
for (var j = 0; j < keys.length; j++) {
result[keys[j]] = exportsField[keys[j]];
}
if (hasSubpath) {
return result;
}
return { __proto__: null, '.': result };
}
return null;
}
// Resolve a target value through conditions
// conditions: array of condition strings, or null (broken: string/array only)
function resolveTarget(target, conditions) {
if (typeof target === 'string') {
return target;
}
if (target === null) {
return null;
}
if (Array.isArray(target)) {
for (var i = 0; i < target.length; i++) {
var resolved = resolveTarget(target[i], conditions);
if (resolved !== null && typeof resolved !== 'undefined') {
return resolved;
}
}
return null;
}
if (typeof target === 'object') {
// If no conditions supported (broken category), can't resolve objects
if (conditions === null) {
return null;
}
var keys = objectKeys(target);
for (var j = 0; j < keys.length; j++) {
var key = keys[j];
for (var k = 0; k < conditions.length; k++) {
if (key === conditions[k]) {
var result = resolveTarget(target[key], conditions);
if (result != null) {
return result;
}
}
}
}
return null;
}
return null;
}
// Validate a resolved path
function validateTarget(target) {
if (typeof target !== 'string') {
return false;
}
if (target.slice(0, 2) !== './') {
return false;
}
if (target.indexOf('/node_modules/') !== -1) {
return false;
}
// Check for '..' path traversal
var parts = target.split('/');
for (var i = 0; i < parts.length; i++) {
if (parts[i] === '..') {
return false;
}
}
return true;
}
// Find the best pattern match for a subpath among keys with '*'
function findPatternMatch(subpath, exportsMap, allowPatternTrailers) {
var keys = objectKeys(exportsMap);
var bestKey = null;
var bestPrefixLen = -1;
var bestMatch = '';
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var starIndex = key.indexOf('*');
// Key must have exactly one '*'
if (starIndex !== -1 && key.indexOf('*', starIndex + 1) === -1) {
var prefix = key.slice(0, starIndex);
var suffix = key.slice(starIndex + 1);
// Pattern trailers: if suffix is non-empty after *, need allowPatternTrailers
if (suffix.length === 0 || allowPatternTrailers) {
if (
subpath.length >= prefix.length + suffix.length
&& subpath.slice(0, prefix.length) === prefix
&& (suffix.length === 0 || subpath.slice(subpath.length - suffix.length) === suffix)
) {
// Longest prefix wins
if (prefix.length > bestPrefixLen) {
bestPrefixLen = prefix.length;
bestKey = key;
bestMatch = subpath.slice(prefix.length, subpath.length - suffix.length);
}
}
}
}
}
if (bestKey !== null) {
return {
__proto__: null, key: bestKey, match: bestMatch
};
}
return null;
}
// Find directory slash match (for categories that support it)
function findDirSlashMatch(subpath, exportsMap) {
var keys = objectKeys(exportsMap);
var bestKey = null;
var bestPrefixLen = -1;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.charAt(key.length - 1) === '/') {
if (subpath.slice(0, key.length) === key && key.length > bestPrefixLen) {
bestPrefixLen = key.length;
bestKey = key;
}
}
}
if (bestKey !== null) {
return {
__proto__: null, key: bestKey, remainder: subpath.slice(bestKey.length)
};
}
return null;
}
// Replace '*' in target string with match value
function substitutePattern(target, match) {
if (typeof target === 'string') {
return target.split('*').join(match);
}
if (Array.isArray(target)) {
var result = [];
for (var i = 0; i < target.length; i++) {
result.push(substitutePattern(target[i], match));
}
return result;
}
if (typeof target === 'object' && target !== null) {
var obj = { __proto__: null };
var keys = objectKeys(target);
for (var j = 0; j < keys.length; j++) {
obj[keys[j]] = substitutePattern(target[keys[j]], match);
}
return obj;
}
return target;
}
// Main exports resolution function
// exportsField: the value of package.json "exports"
// subpath: the subpath to resolve (e.g., "." or "./foo/bar")
// conditions: array of condition strings, or null for broken category
// options: { patterns: boolean, patternTrailers: boolean, dirSlash: boolean }
// Returns: resolved relative path string, or null if no exports field
// Throws: when exports field exists but subpath is not exported
module.exports = function resolveExports(exportsField, subpath, conditions, options) {
if (typeof exportsField === 'undefined') {
return null;
}
var exportsMap = normalizeExports(exportsField);
if (!exportsMap) {
return null;
}
var allowPatterns = options && options.patterns;
var allowPatternTrailers = options && options.patternTrailers;
var allowDirSlash = options && options.dirSlash;
// 1. Exact key match
if (typeof exportsMap[subpath] !== 'undefined') {
var resolved = resolveTarget(exportsMap[subpath], conditions);
if (resolved !== null && typeof resolved !== 'undefined') {
if (!validateTarget(resolved)) {
var invalidError = new $Error('Invalid "exports" target "' + resolved + '" for subpath "' + subpath + '"');
invalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
throw invalidError;
}
return resolved;
}
// Target exists but resolved to null (explicitly not exported)
var notExportedError = new $Error('Package subpath "' + subpath + '" is not defined by "exports"');
notExportedError.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED';
throw notExportedError;
}
// 2. Pattern match (keys with '*')
if (allowPatterns) {
var patternResult = findPatternMatch(subpath, exportsMap, allowPatternTrailers);
if (patternResult) {
var substituted = substitutePattern(exportsMap[patternResult.key], patternResult.match);
var patternResolved = resolveTarget(substituted, conditions);
if (patternResolved !== null && typeof patternResolved !== 'undefined') {
if (!validateTarget(patternResolved)) {
var patternInvalidError = new $Error('Invalid "exports" target "' + patternResolved + '" for subpath "' + subpath + '"');
patternInvalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
throw patternInvalidError;
}
return patternResolved;
}
}
}
// 3. Directory slash match (for older categories)
if (allowDirSlash) {
var dirResult = findDirSlashMatch(subpath, exportsMap);
if (dirResult) {
var dirTarget = resolveTarget(exportsMap[dirResult.key], conditions);
if (dirTarget !== null && typeof dirTarget !== 'undefined' && typeof dirTarget === 'string') {
var dirResolved = dirTarget + dirResult.remainder;
if (!validateTarget(dirResolved)) {
var dirInvalidError = new $Error('Invalid "exports" target "' + dirResolved + '" for subpath "' + subpath + '"');
dirInvalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
throw dirInvalidError;
}
return dirResolved;
}
}
}
var err = new $Error('Package subpath "' + subpath + '" is not defined by "exports"');
err.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED';
throw err;
};