• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayIsArray,
5  ArrayPrototypeJoin,
6  ArrayPrototypeShift,
7  JSONParse,
8  JSONStringify,
9  ObjectFreeze,
10  ObjectGetOwnPropertyNames,
11  ObjectPrototypeHasOwnProperty,
12  RegExp,
13  RegExpPrototypeExec,
14  RegExpPrototypeSymbolReplace,
15  SafeMap,
16  SafeSet,
17  String,
18  StringPrototypeEndsWith,
19  StringPrototypeIncludes,
20  StringPrototypeIndexOf,
21  StringPrototypeLastIndexOf,
22  StringPrototypeReplace,
23  StringPrototypeSlice,
24  StringPrototypeSplit,
25  StringPrototypeStartsWith,
26} = primordials;
27const internalFS = require('internal/fs/utils');
28const { BuiltinModule } = require('internal/bootstrap/loaders');
29const {
30  realpathSync,
31  statSync,
32  Stats,
33} = require('fs');
34const { getOptionValue } = require('internal/options');
35const pendingDeprecation = getOptionValue('--pending-deprecation');
36// Do not eagerly grab .manifest, it may be in TDZ
37const policy = getOptionValue('--experimental-policy') ?
38  require('internal/process/policy') :
39  null;
40const { sep, relative, resolve } = require('path');
41const preserveSymlinks = getOptionValue('--preserve-symlinks');
42const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
43const experimentalNetworkImports =
44  getOptionValue('--experimental-network-imports');
45const typeFlag = getOptionValue('--input-type');
46const { URL, pathToFileURL, fileURLToPath } = require('internal/url');
47const {
48  ERR_INPUT_TYPE_NOT_ALLOWED,
49  ERR_INVALID_ARG_VALUE,
50  ERR_INVALID_MODULE_SPECIFIER,
51  ERR_INVALID_PACKAGE_CONFIG,
52  ERR_INVALID_PACKAGE_TARGET,
53  ERR_MANIFEST_DEPENDENCY_MISSING,
54  ERR_MODULE_NOT_FOUND,
55  ERR_PACKAGE_IMPORT_NOT_DEFINED,
56  ERR_PACKAGE_PATH_NOT_EXPORTED,
57  ERR_UNSUPPORTED_DIR_IMPORT,
58  ERR_NETWORK_IMPORT_DISALLOWED,
59} = require('internal/errors').codes;
60
61const { Module: CJSModule } = require('internal/modules/cjs/loader');
62const packageJsonReader = require('internal/modules/package_json_reader');
63const { getPackageConfig, getPackageScopeConfig } = require('internal/modules/esm/package_config');
64
65/**
66 * @typedef {import('internal/modules/esm/package_config.js').PackageConfig} PackageConfig
67 */
68
69
70const userConditions = getOptionValue('--conditions');
71const noAddons = getOptionValue('--no-addons');
72const addonConditions = noAddons ? [] : ['node-addons'];
73
74const DEFAULT_CONDITIONS = ObjectFreeze([
75  'node',
76  'import',
77  ...addonConditions,
78  ...userConditions,
79]);
80
81const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
82
83const emittedPackageWarnings = new SafeSet();
84
85function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
86  const pjsonPath = fileURLToPath(pjsonUrl);
87  if (emittedPackageWarnings.has(pjsonPath + '|' + match))
88    return;
89  emittedPackageWarnings.add(pjsonPath + '|' + match);
90  process.emitWarning(
91    `Use of deprecated trailing slash pattern mapping "${match}" in the ` +
92    `"exports" field module resolution of the package at ${pjsonPath}${
93      base ? ` imported from ${fileURLToPath(base)}` :
94        ''}. Mapping specifiers ending in "/" is no longer supported.`,
95    'DeprecationWarning',
96    'DEP0155',
97  );
98}
99
100const doubleSlashRegEx = /[/\\][/\\]/;
101
102function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, internal, base, isTarget) {
103  if (!pendingDeprecation) { return; }
104  const pjsonPath = fileURLToPath(pjsonUrl);
105  const double = RegExpPrototypeExec(doubleSlashRegEx, isTarget ? target : request) !== null;
106  process.emitWarning(
107    `Use of deprecated ${double ? 'double slash' :
108      'leading or trailing slash matching'} resolving "${target}" for module ` +
109      `request "${request}" ${request !== match ? `matched to "${match}" ` : ''
110      }in the "${internal ? 'imports' : 'exports'}" field module resolution of the package at ${
111        pjsonPath}${base ? ` imported from ${fileURLToPath(base)}` : ''}.`,
112    'DeprecationWarning',
113    'DEP0166',
114  );
115}
116
117/**
118 * @param {URL} url
119 * @param {URL} packageJSONUrl
120 * @param {string | URL | undefined} base
121 * @param {string} [main]
122 * @returns {void}
123 */
124function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
125  const format = defaultGetFormatWithoutErrors(url);
126  if (format !== 'module')
127    return;
128  const path = fileURLToPath(url);
129  const pkgPath = fileURLToPath(new URL('.', packageJSONUrl));
130  const basePath = fileURLToPath(base);
131  if (!main) {
132    process.emitWarning(
133      `No "main" or "exports" field defined in the package.json for ${pkgPath
134      } resolving the main entry point "${
135        StringPrototypeSlice(path, pkgPath.length)}", imported from ${basePath
136      }.\nDefault "index" lookups for the main are deprecated for ES modules.`,
137      'DeprecationWarning',
138      'DEP0151',
139    );
140  } else if (resolve(pkgPath, main) !== path) {
141    process.emitWarning(
142      `Package ${pkgPath} has a "main" field set to "${main}", ` +
143      `excluding the full filename and extension to the resolved file at "${
144        StringPrototypeSlice(path, pkgPath.length)}", imported from ${
145        basePath}.\n Automatic extension resolution of the "main" field is ` +
146      'deprecated for ES modules.',
147      'DeprecationWarning',
148      'DEP0151',
149    );
150  }
151}
152
153/**
154 * @param {string[]} [conditions]
155 * @returns {Set<string>}
156 */
157function getConditionsSet(conditions) {
158  if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
159    if (!ArrayIsArray(conditions)) {
160      throw new ERR_INVALID_ARG_VALUE('conditions', conditions,
161                                      'expected an array');
162    }
163    return new SafeSet(conditions);
164  }
165  return DEFAULT_CONDITIONS_SET;
166}
167
168const realpathCache = new SafeMap();
169
170/**
171 * @param {string | URL} path
172 * @returns {import('fs').Stats}
173 */
174const tryStatSync =
175  (path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats();
176
177/**
178 * @param {string | URL} url
179 * @returns {boolean}
180 */
181function fileExists(url) {
182  return statSync(url, { throwIfNoEntry: false })?.isFile() ?? false;
183}
184
185/**
186 * Legacy CommonJS main resolution:
187 * 1. let M = pkg_url + (json main field)
188 * 2. TRY(M, M.js, M.json, M.node)
189 * 3. TRY(M/index.js, M/index.json, M/index.node)
190 * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
191 * 5. NOT_FOUND
192 * @param {URL} packageJSONUrl
193 * @param {PackageConfig} packageConfig
194 * @param {string | URL | undefined} base
195 * @returns {URL}
196 */
197function legacyMainResolve(packageJSONUrl, packageConfig, base) {
198  let guess;
199  if (packageConfig.main !== undefined) {
200    // Note: fs check redundances will be handled by Descriptor cache here.
201    if (fileExists(guess = new URL(`./${packageConfig.main}`,
202                                   packageJSONUrl))) {
203      return guess;
204    } else if (fileExists(guess = new URL(`./${packageConfig.main}.js`,
205                                          packageJSONUrl)));
206    else if (fileExists(guess = new URL(`./${packageConfig.main}.json`,
207                                        packageJSONUrl)));
208    else if (fileExists(guess = new URL(`./${packageConfig.main}.node`,
209                                        packageJSONUrl)));
210    else if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`,
211                                        packageJSONUrl)));
212    else if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`,
213                                        packageJSONUrl)));
214    else if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`,
215                                        packageJSONUrl)));
216    else guess = undefined;
217    if (guess) {
218      emitLegacyIndexDeprecation(guess, packageJSONUrl, base,
219                                 packageConfig.main);
220      return guess;
221    }
222    // Fallthrough.
223  }
224  if (fileExists(guess = new URL('./index.js', packageJSONUrl)));
225  // So fs.
226  else if (fileExists(guess = new URL('./index.json', packageJSONUrl)));
227  else if (fileExists(guess = new URL('./index.node', packageJSONUrl)));
228  else guess = undefined;
229  if (guess) {
230    emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main);
231    return guess;
232  }
233  // Not found.
234  throw new ERR_MODULE_NOT_FOUND(
235    fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
236}
237
238/**
239 * @param {URL} search
240 * @returns {URL | undefined}
241 */
242function resolveExtensionsWithTryExactName(search) {
243  if (fileExists(search)) return search;
244  return resolveExtensions(search);
245}
246
247const extensions = ['.js', '.json', '.node', '.mjs'];
248
249/**
250 * @param {URL} search
251 * @returns {URL | undefined}
252 */
253function resolveExtensions(search) {
254  for (let i = 0; i < extensions.length; i++) {
255    const extension = extensions[i];
256    const guess = new URL(`${search.pathname}${extension}`, search);
257    if (fileExists(guess)) return guess;
258  }
259  return undefined;
260}
261
262/**
263 * @param {URL} search
264 * @returns {URL | undefined}
265 */
266function resolveDirectoryEntry(search) {
267  const dirPath = fileURLToPath(search);
268  const pkgJsonPath = resolve(dirPath, 'package.json');
269  if (fileExists(pkgJsonPath)) {
270    const pkgJson = packageJsonReader.read(pkgJsonPath);
271    if (pkgJson.containsKeys) {
272      const { main } = JSONParse(pkgJson.string);
273      if (main != null) {
274        const mainUrl = pathToFileURL(resolve(dirPath, main));
275        return resolveExtensionsWithTryExactName(mainUrl);
276      }
277    }
278  }
279  return resolveExtensions(new URL('index', search));
280}
281
282const encodedSepRegEx = /%2F|%5C/i;
283/**
284 * @param {URL} resolved
285 * @param {string | URL | undefined} base
286 * @param {boolean} preserveSymlinks
287 * @returns {URL | undefined}
288 */
289function finalizeResolution(resolved, base, preserveSymlinks) {
290  if (RegExpPrototypeExec(encodedSepRegEx, resolved.pathname) !== null)
291    throw new ERR_INVALID_MODULE_SPECIFIER(
292      resolved.pathname, 'must not include encoded "/" or "\\" characters',
293      fileURLToPath(base));
294
295  let path = fileURLToPath(resolved);
296  if (getOptionValue('--experimental-specifier-resolution') === 'node') {
297    let file = resolveExtensionsWithTryExactName(resolved);
298
299    // Directory
300    if (file === undefined) {
301      file = StringPrototypeEndsWith(path, '/') ?
302        (resolveDirectoryEntry(resolved) || resolved) : resolveDirectoryEntry(new URL(`${resolved}/`));
303
304      if (file === resolved) return file;
305
306      if (file === undefined) {
307        throw new ERR_MODULE_NOT_FOUND(
308          resolved.pathname, fileURLToPath(base), 'module');
309      }
310    }
311    // If `preserveSymlinks` is false, `resolved` is returned and `path`
312    // is used only to check that the resolved path exists.
313    resolved = file;
314    path = fileURLToPath(resolved);
315  }
316
317  const stats = tryStatSync(StringPrototypeEndsWith(path, '/') ?
318    StringPrototypeSlice(path, -1) : path);
319  if (stats.isDirectory()) {
320    const err = new ERR_UNSUPPORTED_DIR_IMPORT(path, fileURLToPath(base));
321    err.url = String(resolved);
322    throw err;
323  } else if (!stats.isFile()) {
324    if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) {
325      process.send({ 'watch:require': [path || resolved.pathname] });
326    }
327    throw new ERR_MODULE_NOT_FOUND(
328      path || resolved.pathname, base && fileURLToPath(base), 'module');
329  }
330
331  if (!preserveSymlinks) {
332    const real = realpathSync(path, {
333      [internalFS.realpathCacheKey]: realpathCache,
334    });
335    const { search, hash } = resolved;
336    resolved =
337        pathToFileURL(real + (StringPrototypeEndsWith(path, sep) ? '/' : ''));
338    resolved.search = search;
339    resolved.hash = hash;
340  }
341
342  return resolved;
343}
344
345/**
346 * @param {string} specifier
347 * @param {URL} packageJSONUrl
348 * @param {string | URL | undefined} base
349 */
350function importNotDefined(specifier, packageJSONUrl, base) {
351  return new ERR_PACKAGE_IMPORT_NOT_DEFINED(
352    specifier, packageJSONUrl && fileURLToPath(new URL('.', packageJSONUrl)),
353    fileURLToPath(base));
354}
355
356/**
357 * @param {string} subpath
358 * @param {URL} packageJSONUrl
359 * @param {string | URL | undefined} base
360 */
361function exportsNotFound(subpath, packageJSONUrl, base) {
362  return new ERR_PACKAGE_PATH_NOT_EXPORTED(
363    fileURLToPath(new URL('.', packageJSONUrl)), subpath,
364    base && fileURLToPath(base));
365}
366
367/**
368 *
369 * @param {string} request
370 * @param {string} match
371 * @param {URL} packageJSONUrl
372 * @param {boolean} internal
373 * @param {string | URL | undefined} base
374 */
375function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) {
376  const reason = `request is not a valid match in pattern "${match}" for the "${
377    internal ? 'imports' : 'exports'}" resolution of ${
378    fileURLToPath(packageJSONUrl)}`;
379  throw new ERR_INVALID_MODULE_SPECIFIER(request, reason,
380                                         base && fileURLToPath(base));
381}
382
383function invalidPackageTarget(
384  subpath, target, packageJSONUrl, internal, base) {
385  if (typeof target === 'object' && target !== null) {
386    target = JSONStringify(target, null, '');
387  } else {
388    target = `${target}`;
389  }
390  return new ERR_INVALID_PACKAGE_TARGET(
391    fileURLToPath(new URL('.', packageJSONUrl)), subpath, target,
392    internal, base && fileURLToPath(base));
393}
394
395const 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;
396const 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;
397const invalidPackageNameRegEx = /^\.|%|\\/;
398const patternRegEx = /\*/g;
399
400/**
401 *
402 * @param {string} target
403 * @param {*} subpath
404 * @param {*} match
405 * @param {*} packageJSONUrl
406 * @param {*} base
407 * @param {*} pattern
408 * @param {*} internal
409 * @param {*} isPathMap
410 * @param {*} conditions
411 * @returns {URL}
412 */
413function resolvePackageTargetString(
414  target,
415  subpath,
416  match,
417  packageJSONUrl,
418  base,
419  pattern,
420  internal,
421  isPathMap,
422  conditions,
423) {
424
425  if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
426    throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
427
428  if (!StringPrototypeStartsWith(target, './')) {
429    if (internal && !StringPrototypeStartsWith(target, '../') &&
430        !StringPrototypeStartsWith(target, '/')) {
431      let isURL = false;
432      try {
433        new URL(target);
434        isURL = true;
435      } catch {
436        // Continue regardless of error.
437      }
438      if (!isURL) {
439        const exportTarget = pattern ?
440          RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
441          target + subpath;
442        return packageResolve(
443          exportTarget, packageJSONUrl, conditions);
444      }
445    }
446    throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
447  }
448
449  if (RegExpPrototypeExec(invalidSegmentRegEx, StringPrototypeSlice(target, 2)) !== null) {
450    if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, StringPrototypeSlice(target, 2)) === null) {
451      if (!isPathMap) {
452        const request = pattern ?
453          StringPrototypeReplace(match, '*', () => subpath) :
454          match + subpath;
455        const resolvedTarget = pattern ?
456          RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
457          target;
458        emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, internal, base, true);
459      }
460    } else {
461      throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
462    }
463  }
464
465  const resolved = new URL(target, packageJSONUrl);
466  const resolvedPath = resolved.pathname;
467  const packagePath = new URL('.', packageJSONUrl).pathname;
468
469  if (!StringPrototypeStartsWith(resolvedPath, packagePath))
470    throw invalidPackageTarget(match, target, packageJSONUrl, internal, base);
471
472  if (subpath === '') return resolved;
473
474  if (RegExpPrototypeExec(invalidSegmentRegEx, subpath) !== null) {
475    const request = pattern ? StringPrototypeReplace(match, '*', () => subpath) : match + subpath;
476    if (RegExpPrototypeExec(deprecatedInvalidSegmentRegEx, subpath) === null) {
477      if (!isPathMap) {
478        const resolvedTarget = pattern ?
479          RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
480          target;
481        emitInvalidSegmentDeprecation(resolvedTarget, request, match, packageJSONUrl, internal, base, false);
482      }
483    } else {
484      throwInvalidSubpath(request, match, packageJSONUrl, internal, base);
485    }
486  }
487
488  if (pattern) {
489    return new URL(
490      RegExpPrototypeSymbolReplace(patternRegEx, resolved.href, () => subpath),
491    );
492  }
493
494  return new URL(subpath, resolved);
495}
496
497/**
498 * @param {string} key
499 * @returns {boolean}
500 */
501function isArrayIndex(key) {
502  const keyNum = +key;
503  if (`${keyNum}` !== key) return false;
504  return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
505}
506
507/**
508 *
509 * @param {*} packageJSONUrl
510 * @param {string|[string]} target
511 * @param {*} subpath
512 * @param {*} packageSubpath
513 * @param {*} base
514 * @param {*} pattern
515 * @param {*} internal
516 * @param {*} isPathMap
517 * @param {*} conditions
518 * @returns {URL|null}
519 */
520function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
521                              base, pattern, internal, isPathMap, conditions) {
522  if (typeof target === 'string') {
523    return resolvePackageTargetString(
524      target, subpath, packageSubpath, packageJSONUrl, base, pattern, internal,
525      isPathMap, conditions);
526  } else if (ArrayIsArray(target)) {
527    if (target.length === 0) {
528      return null;
529    }
530
531    let lastException;
532    for (let i = 0; i < target.length; i++) {
533      const targetItem = target[i];
534      let resolveResult;
535      try {
536        resolveResult = resolvePackageTarget(
537          packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
538          internal, isPathMap, conditions);
539      } catch (e) {
540        lastException = e;
541        if (e.code === 'ERR_INVALID_PACKAGE_TARGET') {
542          continue;
543        }
544        throw e;
545      }
546      if (resolveResult === undefined) {
547        continue;
548      }
549      if (resolveResult === null) {
550        lastException = null;
551        continue;
552      }
553      return resolveResult;
554    }
555    if (lastException === undefined || lastException === null)
556      return lastException;
557    throw lastException;
558  } else if (typeof target === 'object' && target !== null) {
559    const keys = ObjectGetOwnPropertyNames(target);
560    for (let i = 0; i < keys.length; i++) {
561      const key = keys[i];
562      if (isArrayIndex(key)) {
563        throw new ERR_INVALID_PACKAGE_CONFIG(
564          fileURLToPath(packageJSONUrl), base,
565          '"exports" cannot contain numeric property keys.');
566      }
567    }
568    for (let i = 0; i < keys.length; i++) {
569      const key = keys[i];
570      if (key === 'default' || conditions.has(key)) {
571        const conditionalTarget = target[key];
572        const resolveResult = resolvePackageTarget(
573          packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
574          pattern, internal, isPathMap, conditions);
575        if (resolveResult === undefined)
576          continue;
577        return resolveResult;
578      }
579    }
580    return undefined;
581  } else if (target === null) {
582    return null;
583  }
584  throw invalidPackageTarget(packageSubpath, target, packageJSONUrl, internal,
585                             base);
586}
587
588/**
589 *
590 * @param {import('internal/modules/esm/package_config.js').Exports} exports
591 * @param {URL} packageJSONUrl
592 * @param {string | URL | undefined} base
593 * @returns {boolean}
594 */
595function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
596  if (typeof exports === 'string' || ArrayIsArray(exports)) return true;
597  if (typeof exports !== 'object' || exports === null) return false;
598
599  const keys = ObjectGetOwnPropertyNames(exports);
600  let isConditionalSugar = false;
601  let i = 0;
602  for (let j = 0; j < keys.length; j++) {
603    const key = keys[j];
604    const curIsConditionalSugar = key === '' || key[0] !== '.';
605    if (i++ === 0) {
606      isConditionalSugar = curIsConditionalSugar;
607    } else if (isConditionalSugar !== curIsConditionalSugar) {
608      throw new ERR_INVALID_PACKAGE_CONFIG(
609        fileURLToPath(packageJSONUrl), base,
610        '"exports" cannot contain some keys starting with \'.\' and some not.' +
611        ' The exports object must either be an object of package subpath keys' +
612        ' or an object of main entry condition name keys only.');
613    }
614  }
615  return isConditionalSugar;
616}
617
618/**
619 * @param {URL} packageJSONUrl
620 * @param {string} packageSubpath
621 * @param {PackageConfig} packageConfig
622 * @param {string | URL | undefined} base
623 * @param {Set<string>} conditions
624 * @returns {URL}
625 */
626function packageExportsResolve(
627  packageJSONUrl, packageSubpath, packageConfig, base, conditions) {
628  let exports = packageConfig.exports;
629  if (isConditionalExportsMainSugar(exports, packageJSONUrl, base))
630    exports = { '.': exports };
631
632  if (ObjectPrototypeHasOwnProperty(exports, packageSubpath) &&
633      !StringPrototypeIncludes(packageSubpath, '*') &&
634      !StringPrototypeEndsWith(packageSubpath, '/')) {
635    const target = exports[packageSubpath];
636    const resolveResult = resolvePackageTarget(
637      packageJSONUrl, target, '', packageSubpath, base, false, false, false,
638      conditions,
639    );
640
641    if (resolveResult == null) {
642      throw exportsNotFound(packageSubpath, packageJSONUrl, base);
643    }
644
645    return resolveResult;
646  }
647
648  let bestMatch = '';
649  let bestMatchSubpath;
650  const keys = ObjectGetOwnPropertyNames(exports);
651  for (let i = 0; i < keys.length; i++) {
652    const key = keys[i];
653    const patternIndex = StringPrototypeIndexOf(key, '*');
654    if (patternIndex !== -1 &&
655        StringPrototypeStartsWith(packageSubpath,
656                                  StringPrototypeSlice(key, 0, patternIndex))) {
657      // When this reaches EOL, this can throw at the top of the whole function:
658      //
659      // if (StringPrototypeEndsWith(packageSubpath, '/'))
660      //   throwInvalidSubpath(packageSubpath)
661      //
662      // To match "imports" and the spec.
663      if (StringPrototypeEndsWith(packageSubpath, '/'))
664        emitTrailingSlashPatternDeprecation(packageSubpath, packageJSONUrl,
665                                            base);
666      const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
667      if (packageSubpath.length >= key.length &&
668          StringPrototypeEndsWith(packageSubpath, patternTrailer) &&
669          patternKeyCompare(bestMatch, key) === 1 &&
670          StringPrototypeLastIndexOf(key, '*') === patternIndex) {
671        bestMatch = key;
672        bestMatchSubpath = StringPrototypeSlice(
673          packageSubpath, patternIndex,
674          packageSubpath.length - patternTrailer.length);
675      }
676    }
677  }
678
679  if (bestMatch) {
680    const target = exports[bestMatch];
681    const resolveResult = resolvePackageTarget(
682      packageJSONUrl,
683      target,
684      bestMatchSubpath,
685      bestMatch,
686      base,
687      true,
688      false,
689      StringPrototypeEndsWith(packageSubpath, '/'),
690      conditions);
691
692    if (resolveResult == null) {
693      throw exportsNotFound(packageSubpath, packageJSONUrl, base);
694    }
695    return resolveResult;
696  }
697
698  throw exportsNotFound(packageSubpath, packageJSONUrl, base);
699}
700
701function patternKeyCompare(a, b) {
702  const aPatternIndex = StringPrototypeIndexOf(a, '*');
703  const bPatternIndex = StringPrototypeIndexOf(b, '*');
704  const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1;
705  const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1;
706  if (baseLenA > baseLenB) return -1;
707  if (baseLenB > baseLenA) return 1;
708  if (aPatternIndex === -1) return 1;
709  if (bPatternIndex === -1) return -1;
710  if (a.length > b.length) return -1;
711  if (b.length > a.length) return 1;
712  return 0;
713}
714
715/**
716 * @param {string} name
717 * @param {string | URL | undefined} base
718 * @param {Set<string>} conditions
719 * @returns {URL}
720 */
721function packageImportsResolve(name, base, conditions) {
722  if (name === '#' || StringPrototypeStartsWith(name, '#/') ||
723      StringPrototypeEndsWith(name, '/')) {
724    const reason = 'is not a valid internal imports specifier name';
725    throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
726  }
727  let packageJSONUrl;
728  const packageConfig = getPackageScopeConfig(base);
729  if (packageConfig.exists) {
730    packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
731    const imports = packageConfig.imports;
732    if (imports) {
733      if (ObjectPrototypeHasOwnProperty(imports, name) &&
734          !StringPrototypeIncludes(name, '*')) {
735        const resolveResult = resolvePackageTarget(
736          packageJSONUrl, imports[name], '', name, base, false, true, false,
737          conditions,
738        );
739        if (resolveResult != null) {
740          return resolveResult;
741        }
742      } else {
743        let bestMatch = '';
744        let bestMatchSubpath;
745        const keys = ObjectGetOwnPropertyNames(imports);
746        for (let i = 0; i < keys.length; i++) {
747          const key = keys[i];
748          const patternIndex = StringPrototypeIndexOf(key, '*');
749          if (patternIndex !== -1 &&
750              StringPrototypeStartsWith(name,
751                                        StringPrototypeSlice(key, 0,
752                                                             patternIndex))) {
753            const patternTrailer = StringPrototypeSlice(key, patternIndex + 1);
754            if (name.length >= key.length &&
755                StringPrototypeEndsWith(name, patternTrailer) &&
756                patternKeyCompare(bestMatch, key) === 1 &&
757                StringPrototypeLastIndexOf(key, '*') === patternIndex) {
758              bestMatch = key;
759              bestMatchSubpath = StringPrototypeSlice(
760                name, patternIndex, name.length - patternTrailer.length);
761            }
762          }
763        }
764
765        if (bestMatch) {
766          const target = imports[bestMatch];
767          const resolveResult = resolvePackageTarget(packageJSONUrl, target,
768                                                     bestMatchSubpath,
769                                                     bestMatch, base, true,
770                                                     true, false, conditions);
771          if (resolveResult != null) {
772            return resolveResult;
773          }
774        }
775      }
776    }
777  }
778  throw importNotDefined(name, packageJSONUrl, base);
779}
780
781/**
782 * @param {URL} url
783 * @returns {import('internal/modules/esm/package_config.js').PackageType}
784 */
785function getPackageType(url) {
786  const packageConfig = getPackageScopeConfig(url);
787  return packageConfig.type;
788}
789
790/**
791 * @param {string} specifier
792 * @param {string | URL | undefined} base
793 * @returns {{ packageName: string, packageSubpath: string, isScoped: boolean }}
794 */
795function parsePackageName(specifier, base) {
796  let separatorIndex = StringPrototypeIndexOf(specifier, '/');
797  let validPackageName = true;
798  let isScoped = false;
799  if (specifier[0] === '@') {
800    isScoped = true;
801    if (separatorIndex === -1 || specifier.length === 0) {
802      validPackageName = false;
803    } else {
804      separatorIndex = StringPrototypeIndexOf(
805        specifier, '/', separatorIndex + 1);
806    }
807  }
808
809  const packageName = separatorIndex === -1 ?
810    specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
811
812  // Package name cannot have leading . and cannot have percent-encoding or
813  // \\ separators.
814  if (RegExpPrototypeExec(invalidPackageNameRegEx, packageName) !== null)
815    validPackageName = false;
816
817  if (!validPackageName) {
818    throw new ERR_INVALID_MODULE_SPECIFIER(
819      specifier, 'is not a valid package name', fileURLToPath(base));
820  }
821
822  const packageSubpath = '.' + (separatorIndex === -1 ? '' :
823    StringPrototypeSlice(specifier, separatorIndex));
824
825  return { packageName, packageSubpath, isScoped };
826}
827
828/**
829 * @param {string} specifier
830 * @param {string | URL | undefined} base
831 * @param {Set<string>} conditions
832 * @returns {resolved: URL, format? : string}
833 */
834function packageResolve(specifier, base, conditions) {
835  if (BuiltinModule.canBeRequiredByUsers(specifier) &&
836      BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
837    return new URL('node:' + specifier);
838  }
839
840  const { packageName, packageSubpath, isScoped } =
841    parsePackageName(specifier, base);
842
843  // ResolveSelf
844  const packageConfig = getPackageScopeConfig(base);
845  if (packageConfig.exists) {
846    const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
847    if (packageConfig.name === packageName &&
848        packageConfig.exports !== undefined && packageConfig.exports !== null) {
849      return packageExportsResolve(
850        packageJSONUrl, packageSubpath, packageConfig, base, conditions);
851    }
852  }
853
854  let packageJSONUrl =
855    new URL('./node_modules/' + packageName + '/package.json', base);
856  let packageJSONPath = fileURLToPath(packageJSONUrl);
857  let lastPath;
858  do {
859    const stat = tryStatSync(StringPrototypeSlice(packageJSONPath, 0,
860                                                  packageJSONPath.length - 13));
861    if (!stat.isDirectory()) {
862      lastPath = packageJSONPath;
863      packageJSONUrl = new URL((isScoped ?
864        '../../../../node_modules/' : '../../../node_modules/') +
865        packageName + '/package.json', packageJSONUrl);
866      packageJSONPath = fileURLToPath(packageJSONUrl);
867      continue;
868    }
869
870    // Package match.
871    const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
872    if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
873      return packageExportsResolve(
874        packageJSONUrl, packageSubpath, packageConfig, base, conditions);
875    }
876    if (packageSubpath === '.') {
877      return legacyMainResolve(
878        packageJSONUrl,
879        packageConfig,
880        base,
881      );
882    }
883
884    return new URL(packageSubpath, packageJSONUrl);
885    // Cross-platform root check.
886  } while (packageJSONPath.length !== lastPath.length);
887
888  // eslint can't handle the above code.
889  // eslint-disable-next-line no-unreachable
890  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));
891}
892
893/**
894 * @param {string} specifier
895 * @returns {boolean}
896 */
897function isBareSpecifier(specifier) {
898  return specifier[0] && specifier[0] !== '/' && specifier[0] !== '.';
899}
900
901function isRelativeSpecifier(specifier) {
902  if (specifier[0] === '.') {
903    if (specifier.length === 1 || specifier[1] === '/') return true;
904    if (specifier[1] === '.') {
905      if (specifier.length === 2 || specifier[2] === '/') return true;
906    }
907  }
908  return false;
909}
910
911function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
912  if (specifier === '') return false;
913  if (specifier[0] === '/') return true;
914  return isRelativeSpecifier(specifier);
915}
916
917/**
918 * @param {string} specifier
919 * @param {string | URL | undefined} base
920 * @param {Set<string>} conditions
921 * @param {boolean} preserveSymlinks
922 * @returns {url: URL, format?: string}
923 */
924function moduleResolve(specifier, base, conditions, preserveSymlinks) {
925  const isRemote = base.protocol === 'http:' ||
926    base.protocol === 'https:';
927  // Order swapped from spec for minor perf gain.
928  // Ok since relative URLs cannot parse as URLs.
929  let resolved;
930  if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
931    resolved = new URL(specifier, base);
932  } else if (!isRemote && specifier[0] === '#') {
933    resolved = packageImportsResolve(specifier, base, conditions);
934  } else {
935    try {
936      resolved = new URL(specifier);
937    } catch {
938      if (!isRemote) {
939        resolved = packageResolve(specifier, base, conditions);
940      }
941    }
942  }
943  if (resolved.protocol !== 'file:') {
944    return resolved;
945  }
946  return finalizeResolution(resolved, base, preserveSymlinks);
947}
948
949/**
950 * Try to resolve an import as a CommonJS module
951 * @param {string} specifier
952 * @param {string} parentURL
953 * @returns {boolean|string}
954 */
955function resolveAsCommonJS(specifier, parentURL) {
956  try {
957    const parent = fileURLToPath(parentURL);
958    const tmpModule = new CJSModule(parent, null);
959    tmpModule.paths = CJSModule._nodeModulePaths(parent);
960
961    let found = CJSModule._resolveFilename(specifier, tmpModule, false);
962
963    // If it is a relative specifier return the relative path
964    // to the parent
965    if (isRelativeSpecifier(specifier)) {
966      found = relative(parent, found);
967      // Add '.separator if the path does not start with '..separator'
968      // This should be a safe assumption because when loading
969      // esm modules there should be always a file specified so
970      // there should not be a specifier like '..' or '.'
971      if (!StringPrototypeStartsWith(found, `..${sep}`)) {
972        found = `.${sep}${found}`;
973      }
974    } else if (isBareSpecifier(specifier)) {
975      // If it is a bare specifier return the relative path within the
976      // module
977      const pkg = StringPrototypeSplit(specifier, '/')[0];
978      const index = StringPrototypeIndexOf(found, pkg);
979      if (index !== -1) {
980        found = StringPrototypeSlice(found, index);
981      }
982    }
983    // Normalize the path separator to give a valid suggestion
984    // on Windows
985    if (process.platform === 'win32') {
986      found = RegExpPrototypeSymbolReplace(new RegExp(`\\${sep}`, 'g'),
987                                           found, '/');
988    }
989    return found;
990  } catch {
991    return false;
992  }
993}
994
995// TODO(@JakobJingleheimer): de-dupe `specifier` & `parsed`
996function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
997  if (parsedParentURL) {
998    // Avoid accessing the `protocol` property due to the lazy getters.
999    const parentProtocol = parsedParentURL.protocol;
1000    if (
1001      parentProtocol === 'http:' ||
1002      parentProtocol === 'https:'
1003    ) {
1004      if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
1005        // Avoid accessing the `protocol` property due to the lazy getters.
1006        const parsedProtocol = parsed?.protocol;
1007        // data: and blob: disallowed due to allowing file: access via
1008        // indirection
1009        if (parsedProtocol &&
1010          parsedProtocol !== 'https:' &&
1011          parsedProtocol !== 'http:'
1012        ) {
1013          throw new ERR_NETWORK_IMPORT_DISALLOWED(
1014            specifier,
1015            parsedParentURL,
1016            'remote imports cannot import from a local location.',
1017          );
1018        }
1019
1020        return { url: parsed.href };
1021      }
1022      if (BuiltinModule.canBeRequiredByUsers(specifier) &&
1023          BuiltinModule.canBeRequiredWithoutScheme(specifier)) {
1024        throw new ERR_NETWORK_IMPORT_DISALLOWED(
1025          specifier,
1026          parsedParentURL,
1027          'remote imports cannot import from a local location.',
1028        );
1029      }
1030
1031      throw new ERR_NETWORK_IMPORT_DISALLOWED(
1032        specifier,
1033        parsedParentURL,
1034        'only relative and absolute specifiers are supported.',
1035      );
1036    }
1037  }
1038}
1039
1040
1041async function defaultResolve(specifier, context = {}) {
1042  let { parentURL, conditions } = context;
1043  if (parentURL && policy?.manifest) {
1044    const redirects = policy.manifest.getDependencyMapper(parentURL);
1045    if (redirects) {
1046      const { resolve, reaction } = redirects;
1047      const destination = resolve(specifier, new SafeSet(conditions));
1048      let missing = true;
1049      if (destination === true) {
1050        missing = false;
1051      } else if (destination) {
1052        const href = destination.href;
1053        return { url: href };
1054      }
1055      if (missing) {
1056        // Prevent network requests from firing if resolution would be banned.
1057        // Network requests can extract data by doing things like putting
1058        // secrets in query params
1059        reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(
1060          parentURL,
1061          specifier,
1062          ArrayPrototypeJoin([...conditions], ', ')),
1063        );
1064      }
1065    }
1066  }
1067
1068  let parsedParentURL;
1069  if (parentURL) {
1070    try {
1071      parsedParentURL = new URL(parentURL);
1072    } catch {
1073      // Ignore exception
1074    }
1075  }
1076
1077  let parsed;
1078  try {
1079    if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
1080      parsed = new URL(specifier, parsedParentURL);
1081    } else {
1082      parsed = new URL(specifier);
1083    }
1084
1085    // Avoid accessing the `protocol` property due to the lazy getters.
1086    const protocol = parsed.protocol;
1087    if (protocol === 'data:' ||
1088      (experimentalNetworkImports &&
1089        (
1090          protocol === 'https:' ||
1091          protocol === 'http:'
1092        )
1093      )
1094    ) {
1095      return { __proto__: null, url: parsed.href };
1096    }
1097  } catch {
1098    // Ignore exception
1099  }
1100
1101  // There are multiple deep branches that can either throw or return; instead
1102  // of duplicating that deeply nested logic for the possible returns, DRY and
1103  // check for a return. This seems the least gnarly.
1104  const maybeReturn = checkIfDisallowedImport(
1105    specifier,
1106    parsed,
1107    parsedParentURL,
1108  );
1109
1110  if (maybeReturn) return maybeReturn;
1111
1112  // This must come after checkIfDisallowedImport
1113  if (parsed && parsed.protocol === 'node:') return { url: specifier };
1114
1115
1116  const isMain = parentURL === undefined;
1117  if (isMain) {
1118    parentURL = pathToFileURL(`${process.cwd()}/`).href;
1119
1120    // This is the initial entry point to the program, and --input-type has
1121    // been passed as an option; but --input-type can only be used with
1122    // --eval, --print or STDIN string input. It is not allowed with file
1123    // input, to avoid user confusion over how expansive the effect of the
1124    // flag should be (i.e. entry point only, package scope surrounding the
1125    // entry point, etc.).
1126    if (typeFlag) throw new ERR_INPUT_TYPE_NOT_ALLOWED();
1127  }
1128
1129  conditions = getConditionsSet(conditions);
1130  let url;
1131  try {
1132    url = moduleResolve(
1133      specifier,
1134      parentURL,
1135      conditions,
1136      isMain ? preserveSymlinksMain : preserveSymlinks,
1137    );
1138  } catch (error) {
1139    // Try to give the user a hint of what would have been the
1140    // resolved CommonJS module
1141    if (error.code === 'ERR_MODULE_NOT_FOUND' ||
1142        error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
1143      if (StringPrototypeStartsWith(specifier, 'file://')) {
1144        specifier = fileURLToPath(specifier);
1145      }
1146      const found = resolveAsCommonJS(specifier, parentURL);
1147      if (found) {
1148        // Modify the stack and message string to include the hint
1149        const lines = StringPrototypeSplit(error.stack, '\n');
1150        const hint = `Did you mean to import ${found}?`;
1151        error.stack =
1152          ArrayPrototypeShift(lines) + '\n' +
1153          hint + '\n' +
1154          ArrayPrototypeJoin(lines, '\n');
1155        error.message += `\n${hint}`;
1156      }
1157    }
1158    throw error;
1159  }
1160
1161  return {
1162    // Do NOT cast `url` to a string: that will work even when there are real
1163    // problems, silencing them
1164    url: url.href,
1165    format: defaultGetFormatWithoutErrors(url, context),
1166  };
1167}
1168
1169module.exports = {
1170  DEFAULT_CONDITIONS,
1171  defaultResolve,
1172  encodedSepRegEx,
1173  getPackageScopeConfig,
1174  getPackageType,
1175  packageExportsResolve,
1176  packageImportsResolve,
1177};
1178
1179// cycle
1180const {
1181  defaultGetFormatWithoutErrors,
1182} = require('internal/modules/esm/get_format');
1183
1184if (policy) {
1185  const $defaultResolve = defaultResolve;
1186  module.exports.defaultResolve = async function defaultResolve(
1187    specifier,
1188    context,
1189  ) {
1190    const ret = await $defaultResolve(specifier, context);
1191    // This is a preflight check to avoid data exfiltration by query params etc.
1192    policy.manifest.mightAllow(ret.url, () =>
1193      new ERR_MANIFEST_DEPENDENCY_MISSING(
1194        context.parentURL,
1195        specifier,
1196        context.conditions,
1197      ),
1198    );
1199    return ret;
1200  };
1201}
1202