resolver.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. 'use strict';
  2. // The Resolver is currently experimental and might be exposed to users in the future.
  3. const pa = require('path');
  4. const fs = require('fs');
  5. const {
  6. VMError
  7. } = require('./bridge');
  8. const { VMScript } = require('./script');
  9. // This should match. Note that '\', '%' are invalid characters
  10. // 1. name/.*
  11. // 2. @scope/name/.*
  12. const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^/\\%]+)(\/.*)?$/;
  13. // See https://tc39.es/ecma262/#integer-index
  14. function isArrayIndex(key) {
  15. const keyNum = +key;
  16. if (`${keyNum}` !== key) return false;
  17. return keyNum >= 0 && keyNum < 0xFFFFFFFF;
  18. }
  19. class Resolver {
  20. constructor(builtinModules, globalPaths, hostRequire) {
  21. this.builtinModules = builtinModules;
  22. this.globalPaths = globalPaths;
  23. this.hostRequire = hostRequire;
  24. }
  25. init(vm) {
  26. }
  27. pathResolve(path) {
  28. return pa.resolve(path);
  29. }
  30. pathIsRelative(path) {
  31. if (path === '' || path[0] !== '.') return false;
  32. if (path.length === 1) return true;
  33. const idx = path[1] === '.' ? 2 : 1;
  34. if (path.length <= idx) return false;
  35. return path[idx] === '/' || path[idx] === pa.sep;
  36. }
  37. pathIsAbsolute(path) {
  38. return pa.isAbsolute(path);
  39. }
  40. pathConcat(...paths) {
  41. return pa.join(...paths);
  42. }
  43. pathBasename(path) {
  44. return pa.basename(path);
  45. }
  46. pathDirname(path) {
  47. return pa.dirname(path);
  48. }
  49. lookupPaths(mod, id) {
  50. if (typeof id === 'string') throw new Error('Id is not a string');
  51. if (this.pathIsRelative(id)) return [mod.path || '.'];
  52. return [...mod.paths, ...this.globalPaths];
  53. }
  54. getBuiltinModulesList() {
  55. return Object.getOwnPropertyNames(this.builtinModules);
  56. }
  57. loadBuiltinModule(vm, id) {
  58. const handler = this.builtinModules[id];
  59. return handler && handler(this, vm, id);
  60. }
  61. loadJS(vm, mod, filename) {
  62. throw new VMError(`Access denied to require '${filename}'`, 'EDENIED');
  63. }
  64. loadJSON(vm, mod, filename) {
  65. throw new VMError(`Access denied to require '${filename}'`, 'EDENIED');
  66. }
  67. loadNode(vm, mod, filename) {
  68. throw new VMError(`Access denied to require '${filename}'`, 'EDENIED');
  69. }
  70. registerModule(mod, filename, path, parent, direct) {
  71. }
  72. resolve(mod, x, options, ext, direct) {
  73. if (typeof x !== 'string') throw new Error('Id is not a string');
  74. if (x.startsWith('node:') || this.builtinModules[x]) {
  75. // a. return the core module
  76. // b. STOP
  77. return x;
  78. }
  79. return this.resolveFull(mod, x, options, ext, direct);
  80. }
  81. resolveFull(mod, x, options, ext, direct) {
  82. // 7. THROW "not found"
  83. throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND');
  84. }
  85. // NODE_MODULES_PATHS(START)
  86. genLookupPaths(path) {
  87. // 1. let PARTS = path split(START)
  88. // 2. let I = count of PARTS - 1
  89. // 3. let DIRS = []
  90. const dirs = [];
  91. // 4. while I >= 0,
  92. while (true) {
  93. const name = this.pathBasename(path);
  94. // a. if PARTS[I] = "node_modules" CONTINUE
  95. if (name !== 'node_modules') {
  96. // b. DIR = path join(PARTS[0 .. I] + "node_modules")
  97. // c. DIRS = DIR + DIRS // Note: this seems wrong. Should be DIRS + DIR
  98. dirs.push(this.pathConcat(path, 'node_modules'));
  99. }
  100. const dir = this.pathDirname(path);
  101. if (dir == path) break;
  102. // d. let I = I - 1
  103. path = dir;
  104. }
  105. return dirs;
  106. // This is done later on
  107. // 5. return DIRS + GLOBAL_FOLDERS
  108. }
  109. }
  110. class DefaultResolver extends Resolver {
  111. constructor(builtinModules, checkPath, globalPaths, pathContext, customResolver, hostRequire, compiler, strict) {
  112. super(builtinModules, globalPaths, hostRequire);
  113. this.checkPath = checkPath;
  114. this.pathContext = pathContext;
  115. this.customResolver = customResolver;
  116. this.compiler = compiler;
  117. this.strict = strict;
  118. this.packageCache = {__proto__: null};
  119. this.scriptCache = {__proto__: null};
  120. }
  121. isPathAllowed(path) {
  122. return this.checkPath(path);
  123. }
  124. pathTestIsDirectory(path) {
  125. try {
  126. const stat = fs.statSync(path, {__proto__: null, throwIfNoEntry: false});
  127. return stat && stat.isDirectory();
  128. } catch (e) {
  129. return false;
  130. }
  131. }
  132. pathTestIsFile(path) {
  133. try {
  134. const stat = fs.statSync(path, {__proto__: null, throwIfNoEntry: false});
  135. return stat && stat.isFile();
  136. } catch (e) {
  137. return false;
  138. }
  139. }
  140. readFile(path) {
  141. return fs.readFileSync(path, {encoding: 'utf8'});
  142. }
  143. readFileWhenExists(path) {
  144. return this.pathTestIsFile(path) ? this.readFile(path) : undefined;
  145. }
  146. readScript(filename) {
  147. let script = this.scriptCache[filename];
  148. if (!script) {
  149. script = new VMScript(this.readFile(filename), {filename, compiler: this.compiler});
  150. this.scriptCache[filename] = script;
  151. }
  152. return script;
  153. }
  154. checkAccess(mod, filename) {
  155. if (!this.isPathAllowed(filename)) {
  156. throw new VMError(`Module '${filename}' is not allowed to be required. The path is outside the border!`, 'EDENIED');
  157. }
  158. }
  159. loadJS(vm, mod, filename) {
  160. filename = this.pathResolve(filename);
  161. this.checkAccess(mod, filename);
  162. if (this.pathContext(filename, 'js') === 'sandbox') {
  163. const script = this.readScript(filename);
  164. vm.run(script, {filename, strict: this.strict, module: mod, wrapper: 'none', dirname: mod.path});
  165. } else {
  166. const m = this.hostRequire(filename);
  167. mod.exports = vm.readonly(m);
  168. }
  169. }
  170. loadJSON(vm, mod, filename) {
  171. filename = this.pathResolve(filename);
  172. this.checkAccess(mod, filename);
  173. const json = this.readFile(filename);
  174. mod.exports = vm._jsonParse(json);
  175. }
  176. loadNode(vm, mod, filename) {
  177. filename = this.pathResolve(filename);
  178. this.checkAccess(mod, filename);
  179. if (this.pathContext(filename, 'node') === 'sandbox') throw new VMError('Native modules can be required only with context set to \'host\'.');
  180. const m = this.hostRequire(filename);
  181. mod.exports = vm.readonly(m);
  182. }
  183. // require(X) from module at path Y
  184. resolveFull(mod, x, options, ext, direct) {
  185. // Note: core module handled by caller
  186. const extList = Object.getOwnPropertyNames(ext);
  187. const path = mod.path || '.';
  188. // 5. LOAD_PACKAGE_SELF(X, dirname(Y))
  189. let f = this.loadPackageSelf(x, path, extList);
  190. if (f) return f;
  191. // 4. If X begins with '#'
  192. if (x[0] === '#') {
  193. // a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
  194. f = this.loadPackageImports(x, path, extList);
  195. if (f) return f;
  196. }
  197. // 2. If X begins with '/'
  198. if (this.pathIsAbsolute(x)) {
  199. // a. set Y to be the filesystem root
  200. f = this.loadAsFileOrDirecotry(x, extList);
  201. if (f) return f;
  202. // c. THROW "not found"
  203. throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND');
  204. // 3. If X begins with './' or '/' or '../'
  205. } else if (this.pathIsRelative(x)) {
  206. if (typeof options === 'object' && options !== null) {
  207. const paths = options.paths;
  208. if (Array.isArray(paths)) {
  209. for (let i = 0; i < paths.length; i++) {
  210. // a. LOAD_AS_FILE(Y + X)
  211. // b. LOAD_AS_DIRECTORY(Y + X)
  212. f = this.loadAsFileOrDirecotry(this.pathConcat(paths[i], x), extList);
  213. if (f) return f;
  214. }
  215. } else if (paths === undefined) {
  216. // a. LOAD_AS_FILE(Y + X)
  217. // b. LOAD_AS_DIRECTORY(Y + X)
  218. f = this.loadAsFileOrDirecotry(this.pathConcat(path, x), extList);
  219. if (f) return f;
  220. } else {
  221. throw new VMError('Invalid options.paths option.');
  222. }
  223. } else {
  224. // a. LOAD_AS_FILE(Y + X)
  225. // b. LOAD_AS_DIRECTORY(Y + X)
  226. f = this.loadAsFileOrDirecotry(this.pathConcat(path, x), extList);
  227. if (f) return f;
  228. }
  229. // c. THROW "not found"
  230. throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND');
  231. }
  232. let dirs;
  233. if (typeof options === 'object' && options !== null) {
  234. const paths = options.paths;
  235. if (Array.isArray(paths)) {
  236. dirs = [];
  237. for (let i = 0; i < paths.length; i++) {
  238. const lookups = this.genLookupPaths(paths[i]);
  239. for (let j = 0; j < lookups.length; j++) {
  240. if (!dirs.includes(lookups[j])) dirs.push(lookups[j]);
  241. }
  242. if (i === 0) {
  243. const globalPaths = this.globalPaths;
  244. for (let j = 0; j < globalPaths.length; j++) {
  245. if (!dirs.includes(globalPaths[j])) dirs.push(globalPaths[j]);
  246. }
  247. }
  248. }
  249. } else if (paths === undefined) {
  250. dirs = [...mod.paths, ...this.globalPaths];
  251. } else {
  252. throw new VMError('Invalid options.paths option.');
  253. }
  254. } else {
  255. dirs = [...mod.paths, ...this.globalPaths];
  256. }
  257. // 6. LOAD_NODE_MODULES(X, dirname(Y))
  258. f = this.loadNodeModules(x, dirs, extList);
  259. if (f) return f;
  260. f = this.customResolver(this, x, path, extList);
  261. if (f) return f;
  262. return super.resolveFull(mod, x, options, ext, direct);
  263. }
  264. loadAsFileOrDirecotry(x, extList) {
  265. // a. LOAD_AS_FILE(X)
  266. const f = this.loadAsFile(x, extList);
  267. if (f) return f;
  268. // b. LOAD_AS_DIRECTORY(X)
  269. return this.loadAsDirectory(x, extList);
  270. }
  271. tryFile(x) {
  272. x = this.pathResolve(x);
  273. return this.isPathAllowed(x) && this.pathTestIsFile(x) ? x : undefined;
  274. }
  275. tryWithExtension(x, extList) {
  276. for (let i = 0; i < extList.length; i++) {
  277. const ext = extList[i];
  278. if (ext !== this.pathBasename(ext)) continue;
  279. const f = this.tryFile(x + ext);
  280. if (f) return f;
  281. }
  282. return undefined;
  283. }
  284. readPackage(path) {
  285. const packagePath = this.pathResolve(this.pathConcat(path, 'package.json'));
  286. const cache = this.packageCache[packagePath];
  287. if (cache !== undefined) return cache;
  288. if (!this.isPathAllowed(packagePath)) return undefined;
  289. const content = this.readFileWhenExists(packagePath);
  290. if (!content) {
  291. this.packageCache[packagePath] = false;
  292. return false;
  293. }
  294. let parsed;
  295. try {
  296. parsed = JSON.parse(content);
  297. } catch (e) {
  298. e.path = packagePath;
  299. e.message = 'Error parsing ' + packagePath + ': ' + e.message;
  300. throw e;
  301. }
  302. const filtered = {
  303. name: parsed.name,
  304. main: parsed.main,
  305. exports: parsed.exports,
  306. imports: parsed.imports,
  307. type: parsed.type
  308. };
  309. this.packageCache[packagePath] = filtered;
  310. return filtered;
  311. }
  312. readPackageScope(path) {
  313. while (true) {
  314. const dir = this.pathDirname(path);
  315. if (dir === path) break;
  316. const basename = this.pathBasename(dir);
  317. if (basename === 'node_modules') break;
  318. const pack = this.readPackage(dir);
  319. if (pack) return {data: pack, scope: dir};
  320. path = dir;
  321. }
  322. return {data: undefined, scope: undefined};
  323. }
  324. // LOAD_AS_FILE(X)
  325. loadAsFile(x, extList) {
  326. // 1. If X is a file, load X as its file extension format. STOP
  327. const f = this.tryFile(x);
  328. if (f) return f;
  329. // 2. If X.js is a file, load X.js as JavaScript text. STOP
  330. // 3. If X.json is a file, parse X.json to a JavaScript Object. STOP
  331. // 4. If X.node is a file, load X.node as binary addon. STOP
  332. return this.tryWithExtension(x, extList);
  333. }
  334. // LOAD_INDEX(X)
  335. loadIndex(x, extList) {
  336. // 1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
  337. // 2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
  338. // 3. If X/index.node is a file, load X/index.node as binary addon. STOP
  339. return this.tryWithExtension(this.pathConcat(x, 'index'), extList);
  340. }
  341. // LOAD_AS_DIRECTORY(X)
  342. loadAsPackage(x, pack, extList) {
  343. // 1. If X/package.json is a file,
  344. // already done.
  345. if (pack) {
  346. // a. Parse X/package.json, and look for "main" field.
  347. // b. If "main" is a falsy value, GOTO 2.
  348. if (typeof pack.main === 'string') {
  349. // c. let M = X + (json main field)
  350. const m = this.pathConcat(x, pack.main);
  351. // d. LOAD_AS_FILE(M)
  352. let f = this.loadAsFile(m, extList);
  353. if (f) return f;
  354. // e. LOAD_INDEX(M)
  355. f = this.loadIndex(m, extList);
  356. if (f) return f;
  357. // f. LOAD_INDEX(X) DEPRECATED
  358. f = this.loadIndex(x, extList);
  359. if (f) return f;
  360. // g. THROW "not found"
  361. throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND');
  362. }
  363. }
  364. // 2. LOAD_INDEX(X)
  365. return this.loadIndex(x, extList);
  366. }
  367. // LOAD_AS_DIRECTORY(X)
  368. loadAsDirectory(x, extList) {
  369. // 1. If X/package.json is a file,
  370. const pack = this.readPackage(x);
  371. return this.loadAsPackage(x, pack, extList);
  372. }
  373. // LOAD_NODE_MODULES(X, START)
  374. loadNodeModules(x, dirs, extList) {
  375. // 1. let DIRS = NODE_MODULES_PATHS(START)
  376. // This step is already done.
  377. // 2. for each DIR in DIRS:
  378. for (let i = 0; i < dirs.length; i++) {
  379. const dir = dirs[i];
  380. // a. LOAD_PACKAGE_EXPORTS(X, DIR)
  381. let f = this.loadPackageExports(x, dir, extList);
  382. if (f) return f;
  383. // b. LOAD_AS_FILE(DIR/X)
  384. f = this.loadAsFile(dir + '/' + x, extList);
  385. if (f) return f;
  386. // c. LOAD_AS_DIRECTORY(DIR/X)
  387. f = this.loadAsDirectory(dir + '/' + x, extList);
  388. if (f) return f;
  389. }
  390. return undefined;
  391. }
  392. // LOAD_PACKAGE_IMPORTS(X, DIR)
  393. loadPackageImports(x, dir, extList) {
  394. // 1. Find the closest package scope SCOPE to DIR.
  395. const {data, scope} = this.readPackageScope(dir);
  396. // 2. If no scope was found, return.
  397. if (!data) return undefined;
  398. // 3. If the SCOPE/package.json "imports" is null or undefined, return.
  399. if (typeof data.imports !== 'object' || data.imports === null || Array.isArray(data.imports)) return undefined;
  400. // 4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
  401. // ["node", "require"]) defined in the ESM resolver.
  402. // PACKAGE_IMPORTS_RESOLVE(specifier, parentURL, conditions)
  403. // 1. Assert: specifier begins with "#".
  404. // 2. If specifier is exactly equal to "#" or starts with "#/", then
  405. if (x === '#' || x.startsWith('#/')) {
  406. // a. Throw an Invalid Module Specifier error.
  407. throw new VMError(`Invalid module specifier '${x}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  408. }
  409. // 3. Let packageURL be the result of LOOKUP_PACKAGE_SCOPE(parentURL).
  410. // Note: packageURL === parentURL === scope
  411. // 4. If packageURL is not null, then
  412. // Always true
  413. // a. Let pjson be the result of READ_PACKAGE_JSON(packageURL).
  414. // pjson === data
  415. // b. If pjson.imports is a non-null Object, then
  416. // Already tested
  417. // x. Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( specifier, pjson.imports, packageURL, true, conditions).
  418. const match = this.packageImportsExportsResolve(x, data.imports, scope, true, ['node', 'require'], extList);
  419. // y. If resolved is not null or undefined, return resolved.
  420. if (!match) {
  421. // 5. Throw a Package Import Not Defined error.
  422. throw new VMError(`Package import not defined for '${x}'`, 'ERR_PACKAGE_IMPORT_NOT_DEFINED');
  423. }
  424. // END PACKAGE_IMPORTS_RESOLVE
  425. // 5. RESOLVE_ESM_MATCH(MATCH).
  426. return this.resolveEsmMatch(match, x, extList);
  427. }
  428. // LOAD_PACKAGE_EXPORTS(X, DIR)
  429. loadPackageExports(x, dir, extList) {
  430. // 1. Try to interpret X as a combination of NAME and SUBPATH where the name
  431. // may have a @scope/ prefix and the subpath begins with a slash (`/`).
  432. const res = x.match(EXPORTS_PATTERN);
  433. // 2. If X does not match this pattern or DIR/NAME/package.json is not a file,
  434. // return.
  435. if (!res) return undefined;
  436. const scope = this.pathConcat(dir, res[1]);
  437. const pack = this.readPackage(scope);
  438. if (!pack) return undefined;
  439. // 3. Parse DIR/NAME/package.json, and look for "exports" field.
  440. // 4. If "exports" is null or undefined, return.
  441. if (!pack.exports) return undefined;
  442. // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
  443. // `package.json` "exports", ["node", "require"]) defined in the ESM resolver.
  444. const match = this.packageExportsResolve(scope, '.' + (res[2] || ''), pack.exports, ['node', 'require'], extList);
  445. // 6. RESOLVE_ESM_MATCH(MATCH)
  446. return this.resolveEsmMatch(match, x, extList);
  447. }
  448. // LOAD_PACKAGE_SELF(X, DIR)
  449. loadPackageSelf(x, dir, extList) {
  450. // 1. Find the closest package scope SCOPE to DIR.
  451. const {data, scope} = this.readPackageScope(dir);
  452. // 2. If no scope was found, return.
  453. if (!data) return undefined;
  454. // 3. If the SCOPE/package.json "exports" is null or undefined, return.
  455. if (!data.exports) return undefined;
  456. // 4. If the SCOPE/package.json "name" is not the first segment of X, return.
  457. if (x !== data.name && !x.startsWith(data.name + '/')) return undefined;
  458. // 5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
  459. // "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
  460. // defined in the ESM resolver.
  461. const match = this.packageExportsResolve(scope, '.' + x.slice(data.name.length), data.exports, ['node', 'require'], extList);
  462. // 6. RESOLVE_ESM_MATCH(MATCH)
  463. return this.resolveEsmMatch(match, x, extList);
  464. }
  465. // RESOLVE_ESM_MATCH(MATCH)
  466. resolveEsmMatch(match, x, extList) {
  467. // 1. let { RESOLVED, EXACT } = MATCH
  468. const resolved = match;
  469. const exact = true;
  470. // 2. let RESOLVED_PATH = fileURLToPath(RESOLVED)
  471. const resolvedPath = resolved;
  472. let f;
  473. // 3. If EXACT is true,
  474. if (exact) {
  475. // a. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension
  476. // format. STOP
  477. f = this.tryFile(resolvedPath);
  478. // 4. Otherwise, if EXACT is false,
  479. } else {
  480. // a. LOAD_AS_FILE(RESOLVED_PATH)
  481. // b. LOAD_AS_DIRECTORY(RESOLVED_PATH)
  482. f = this.loadAsFileOrDirecotry(resolvedPath, extList);
  483. }
  484. if (f) return f;
  485. // 5. THROW "not found"
  486. throw new VMError(`Cannot find module '${x}'`, 'ENOTFOUND');
  487. }
  488. // PACKAGE_EXPORTS_RESOLVE(packageURL, subpath, exports, conditions)
  489. packageExportsResolve(packageURL, subpath, rexports, conditions, extList) {
  490. // 1. If exports is an Object with both a key starting with "." and a key not starting with ".", throw an Invalid Package Configuration error.
  491. let hasDots = false;
  492. if (typeof rexports === 'object' && !Array.isArray(rexports)) {
  493. const keys = Object.getOwnPropertyNames(rexports);
  494. if (keys.length > 0) {
  495. hasDots = keys[0][0] === '.';
  496. for (let i = 0; i < keys.length; i++) {
  497. if (hasDots !== (keys[i][0] === '.')) {
  498. throw new VMError('Invalid package configuration', 'ERR_INVALID_PACKAGE_CONFIGURATION');
  499. }
  500. }
  501. }
  502. }
  503. // 2. If subpath is equal to ".", then
  504. if (subpath === '.') {
  505. // a. Let mainExport be undefined.
  506. let mainExport = undefined;
  507. // b. If exports is a String or Array, or an Object containing no keys starting with ".", then
  508. if (typeof rexports === 'string' || Array.isArray(rexports) || !hasDots) {
  509. // x. Set mainExport to exports.
  510. mainExport = rexports;
  511. // c. Otherwise if exports is an Object containing a "." property, then
  512. } else if (hasDots) {
  513. // x. Set mainExport to exports["."].
  514. mainExport = rexports['.'];
  515. }
  516. // d. If mainExport is not undefined, then
  517. if (mainExport) {
  518. // x. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, mainExport, "", false, false, conditions).
  519. const resolved = this.packageTargetResolve(packageURL, mainExport, '', false, false, conditions, extList);
  520. // y. If resolved is not null or undefined, return resolved.
  521. if (resolved) return resolved;
  522. }
  523. // 3. Otherwise, if exports is an Object and all keys of exports start with ".", then
  524. } else if (hasDots) {
  525. // a. Let matchKey be the string "./" concatenated with subpath.
  526. // Note: Here subpath starts already with './'
  527. // b. Let resolved be the result of PACKAGE_IMPORTS_EXPORTS_RESOLVE( matchKey, exports, packageURL, false, conditions).
  528. const resolved = this.packageImportsExportsResolve(subpath, rexports, packageURL, false, conditions, extList);
  529. // c. If resolved is not null or undefined, return resolved.
  530. if (resolved) return resolved;
  531. }
  532. // 4. Throw a Package Path Not Exported error.
  533. throw new VMError(`Package path '${subpath}' is not exported`, 'ERR_PACKAGE_PATH_NOT_EXPORTED');
  534. }
  535. // PACKAGE_IMPORTS_EXPORTS_RESOLVE(matchKey, matchObj, packageURL, isImports, conditions)
  536. packageImportsExportsResolve(matchKey, matchObj, packageURL, isImports, conditions, extList) {
  537. // 1. If matchKey is a key of matchObj and does not contain "*", then
  538. let target = matchObj[matchKey];
  539. if (target && matchKey.indexOf('*') === -1) {
  540. // a. Let target be the value of matchObj[matchKey].
  541. // b. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, "", false, isImports, conditions).
  542. return this.packageTargetResolve(packageURL, target, '', false, isImports, conditions, extList);
  543. }
  544. // 2. Let expansionKeys be the list of keys of matchObj containing only a single "*",
  545. // sorted by the sorting function PATTERN_KEY_COMPARE which orders in descending order of specificity.
  546. const expansionKeys = Object.getOwnPropertyNames(matchObj);
  547. let bestKey = '';
  548. let bestSubpath;
  549. // 3. For each key expansionKey in expansionKeys, do
  550. for (let i = 0; i < expansionKeys.length; i++) {
  551. const expansionKey = expansionKeys[i];
  552. if (matchKey.length < expansionKey.length) continue;
  553. // a. Let patternBase be the substring of expansionKey up to but excluding the first "*" character.
  554. const star = expansionKey.indexOf('*');
  555. if (star === -1) continue; // Note: expansionKeys was not filtered
  556. const patternBase = expansionKey.slice(0, star);
  557. // b. If matchKey starts with but is not equal to patternBase, then
  558. if (matchKey.startsWith(patternBase) && expansionKey.indexOf('*', star + 1) === -1) { // Note: expansionKeys was not filtered
  559. // 1. Let patternTrailer be the substring of expansionKey from the index after the first "*" character.
  560. const patternTrailer = expansionKey.slice(star + 1);
  561. // 2. If patternTrailer has zero length, or if matchKey ends with patternTrailer and the length of matchKey is greater than or
  562. // equal to the length of expansionKey, then
  563. if (matchKey.endsWith(patternTrailer) && this.patternKeyCompare(bestKey, expansionKey) === 1) { // Note: expansionKeys was not sorted
  564. // a. Let target be the value of matchObj[expansionKey].
  565. target = matchObj[expansionKey];
  566. // b. Let subpath be the substring of matchKey starting at the index of the length of patternBase up to the length of
  567. // matchKey minus the length of patternTrailer.
  568. bestKey = expansionKey;
  569. bestSubpath = matchKey.slice(patternBase.length, matchKey.length - patternTrailer.length);
  570. }
  571. }
  572. }
  573. if (bestSubpath) { // Note: expansionKeys was not sorted
  574. // c. Return the result of PACKAGE_TARGET_RESOLVE(packageURL, target, subpath, true, isImports, conditions).
  575. return this.packageTargetResolve(packageURL, target, bestSubpath, true, isImports, conditions, extList);
  576. }
  577. // 4. Return null.
  578. return null;
  579. }
  580. // PATTERN_KEY_COMPARE(keyA, keyB)
  581. patternKeyCompare(keyA, keyB) {
  582. // 1. Assert: keyA ends with "/" or contains only a single "*".
  583. // 2. Assert: keyB ends with "/" or contains only a single "*".
  584. // 3. Let baseLengthA be the index of "*" in keyA plus one, if keyA contains "*", or the length of keyA otherwise.
  585. const baseAStar = keyA.indexOf('*');
  586. const baseLengthA = baseAStar === -1 ? keyA.length : baseAStar + 1;
  587. // 4. Let baseLengthB be the index of "*" in keyB plus one, if keyB contains "*", or the length of keyB otherwise.
  588. const baseBStar = keyB.indexOf('*');
  589. const baseLengthB = baseBStar === -1 ? keyB.length : baseBStar + 1;
  590. // 5. If baseLengthA is greater than baseLengthB, return -1.
  591. if (baseLengthA > baseLengthB) return -1;
  592. // 6. If baseLengthB is greater than baseLengthA, return 1.
  593. if (baseLengthB > baseLengthA) return 1;
  594. // 7. If keyA does not contain "*", return 1.
  595. if (baseAStar === -1) return 1;
  596. // 8. If keyB does not contain "*", return -1.
  597. if (baseBStar === -1) return -1;
  598. // 9. If the length of keyA is greater than the length of keyB, return -1.
  599. if (keyA.length > keyB.length) return -1;
  600. // 10. If the length of keyB is greater than the length of keyA, return 1.
  601. if (keyB.length > keyA.length) return 1;
  602. // 11. Return 0.
  603. return 0;
  604. }
  605. // PACKAGE_TARGET_RESOLVE(packageURL, target, subpath, pattern, internal, conditions)
  606. packageTargetResolve(packageURL, target, subpath, pattern, internal, conditions, extList) {
  607. // 1. If target is a String, then
  608. if (typeof target === 'string') {
  609. // a. If pattern is false, subpath has non-zero length and target does not end with "/", throw an Invalid Module Specifier error.
  610. if (!pattern && subpath.length > 0 && !target.endsWith('/')) {
  611. throw new VMError(`Invalid package specifier '${subpath}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  612. }
  613. // b. If target does not start with "./", then
  614. if (!target.startsWith('./')) {
  615. // 1. If internal is true and target does not start with "../" or "/" and is not a valid URL, then
  616. if (internal && !target.startsWith('../') && !target.startsWith('/')) {
  617. let isURL = false;
  618. try {
  619. // eslint-disable-next-line no-new
  620. new URL(target);
  621. isURL = true;
  622. } catch (e) {}
  623. if (!isURL) {
  624. // a. If pattern is true, then
  625. if (pattern) {
  626. // 1. Return PACKAGE_RESOLVE(target with every instance of "*" replaced by subpath, packageURL + "/").
  627. return this.packageResolve(target.replace(/\*/g, subpath), packageURL, conditions, extList);
  628. }
  629. // b. Return PACKAGE_RESOLVE(target + subpath, packageURL + "/").
  630. return this.packageResolve(this.pathConcat(target, subpath), packageURL, conditions, extList);
  631. }
  632. }
  633. // Otherwise, throw an Invalid Package Target error.
  634. throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET');
  635. }
  636. target = decodeURI(target);
  637. // c. If target split on "/" or "\" contains any ".", ".." or "node_modules" segments after the first segment, case insensitive
  638. // and including percent encoded variants, throw an Invalid Package Target error.
  639. if (target.split(/[/\\]/).slice(1).findIndex(x => x === '.' || x === '..' || x.toLowerCase() === 'node_modules') !== -1) {
  640. throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET');
  641. }
  642. // d. Let resolvedTarget be the URL resolution of the concatenation of packageURL and target.
  643. const resolvedTarget = this.pathConcat(packageURL, target);
  644. // e. Assert: resolvedTarget is contained in packageURL.
  645. subpath = decodeURI(subpath);
  646. // f. If subpath split on "/" or "\" contains any ".", ".." or "node_modules" segments, case insensitive and including percent
  647. // encoded variants, throw an Invalid Module Specifier error.
  648. if (subpath.split(/[/\\]/).findIndex(x => x === '.' || x === '..' || x.toLowerCase() === 'node_modules') !== -1) {
  649. throw new VMError(`Invalid package specifier '${subpath}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  650. }
  651. // g. If pattern is true, then
  652. if (pattern) {
  653. // 1. Return the URL resolution of resolvedTarget with every instance of "*" replaced with subpath.
  654. return resolvedTarget.replace(/\*/g, subpath);
  655. }
  656. // h. Otherwise,
  657. // 1. Return the URL resolution of the concatenation of subpath and resolvedTarget.
  658. return this.pathConcat(resolvedTarget, subpath);
  659. // 3. Otherwise, if target is an Array, then
  660. } else if (Array.isArray(target)) {
  661. // a. If target.length is zero, return null.
  662. if (target.length === 0) return null;
  663. let lastException = undefined;
  664. // b. For each item targetValue in target, do
  665. for (let i = 0; i < target.length; i++) {
  666. const targetValue = target[i];
  667. // 1. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions),
  668. // continuing the loop on any Invalid Package Target error.
  669. let resolved;
  670. try {
  671. resolved = this.packageTargetResolve(packageURL, targetValue, subpath, pattern, internal, conditions, extList);
  672. } catch (e) {
  673. if (e.code !== 'ERR_INVALID_PACKAGE_TARGET') throw e;
  674. lastException = e;
  675. continue;
  676. }
  677. // 2. If resolved is undefined, continue the loop.
  678. // 3. Return resolved.
  679. if (resolved !== undefined) return resolved;
  680. if (resolved === null) {
  681. lastException = null;
  682. }
  683. }
  684. // c. Return or throw the last fallback resolution null return or error.
  685. if (lastException === undefined || lastException === null) return lastException;
  686. throw lastException;
  687. // 2. Otherwise, if target is a non-null Object, then
  688. } else if (typeof target === 'object' && target !== null) {
  689. const keys = Object.getOwnPropertyNames(target);
  690. // a. If exports contains any index property keys, as defined in ECMA-262 6.1.7 Array Index, throw an Invalid Package Configuration error.
  691. for (let i = 0; i < keys.length; i++) {
  692. const p = keys[i];
  693. if (isArrayIndex(p)) throw new VMError(`Invalid package configuration for '${subpath}'`, 'ERR_INVALID_PACKAGE_CONFIGURATION');
  694. }
  695. // b. For each property p of target, in object insertion order as,
  696. for (let i = 0; i < keys.length; i++) {
  697. const p = keys[i];
  698. // 1. If p equals "default" or conditions contains an entry for p, then
  699. if (p === 'default' || conditions.includes(p)) {
  700. // a. Let targetValue be the value of the p property in target.
  701. const targetValue = target[p];
  702. // b. Let resolved be the result of PACKAGE_TARGET_RESOLVE( packageURL, targetValue, subpath, pattern, internal, conditions).
  703. const resolved = this.packageTargetResolve(packageURL, targetValue, subpath, pattern, internal, conditions, extList);
  704. // c. If resolved is equal to undefined, continue the loop.
  705. // d. Return resolved.
  706. if (resolved !== undefined) return resolved;
  707. }
  708. }
  709. // c. Return undefined.
  710. return undefined;
  711. // 4. Otherwise, if target is null, return null.
  712. } else if (target == null) {
  713. return null;
  714. }
  715. // Otherwise throw an Invalid Package Target error.
  716. throw new VMError(`Invalid package target for '${subpath}'`, 'ERR_INVALID_PACKAGE_TARGET');
  717. }
  718. // PACKAGE_RESOLVE(packageSpecifier, parentURL)
  719. packageResolve(packageSpecifier, parentURL, conditions, extList) {
  720. // 1. Let packageName be undefined.
  721. let packageName = undefined;
  722. // 2. If packageSpecifier is an empty string, then
  723. if (packageSpecifier === '') {
  724. // a. Throw an Invalid Module Specifier error.
  725. throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  726. }
  727. // 3. If packageSpecifier is a Node.js builtin module name, then
  728. if (this.builtinModules[packageSpecifier]) {
  729. // a. Return the string "node:" concatenated with packageSpecifier.
  730. return 'node:' + packageSpecifier;
  731. }
  732. let idx = packageSpecifier.indexOf('/');
  733. // 5. Otherwise,
  734. if (packageSpecifier[0] === '@') {
  735. // a. If packageSpecifier does not contain a "/" separator, then
  736. if (idx === -1) {
  737. // x. Throw an Invalid Module Specifier error.
  738. throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  739. }
  740. // b. Set packageName to the substring of packageSpecifier until the second "/" separator or the end of the string.
  741. idx = packageSpecifier.indexOf('/', idx + 1);
  742. }
  743. // else
  744. // 4. If packageSpecifier does not start with "@", then
  745. // a. Set packageName to the substring of packageSpecifier until the first "/" separator or the end of the string.
  746. packageName = idx === -1 ? packageSpecifier : packageSpecifier.slice(0, idx);
  747. // 6. If packageName starts with "." or contains "\" or "%", then
  748. if (idx !== 0 && (packageName[0] === '.' || packageName.indexOf('\\') >= 0 || packageName.indexOf('%') >= 0)) {
  749. // a. Throw an Invalid Module Specifier error.
  750. throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  751. }
  752. // 7. Let packageSubpath be "." concatenated with the substring of packageSpecifier from the position at the length of packageName.
  753. const packageSubpath = '.' + packageSpecifier.slice(packageName.length);
  754. // 8. If packageSubpath ends in "/", then
  755. if (packageSubpath[packageSubpath.length - 1] === '/') {
  756. // a. Throw an Invalid Module Specifier error.
  757. throw new VMError(`Invalid package specifier '${packageSpecifier}'`, 'ERR_INVALID_MODULE_SPECIFIER');
  758. }
  759. // 9. Let selfUrl be the result of PACKAGE_SELF_RESOLVE(packageName, packageSubpath, parentURL).
  760. const selfUrl = this.packageSelfResolve(packageName, packageSubpath, parentURL);
  761. // 10. If selfUrl is not undefined, return selfUrl.
  762. if (selfUrl) return selfUrl;
  763. // 11. While parentURL is not the file system root,
  764. let packageURL;
  765. while (true) {
  766. // a. Let packageURL be the URL resolution of "node_modules/" concatenated with packageSpecifier, relative to parentURL.
  767. packageURL = this.pathResolve(this.pathConcat(parentURL, 'node_modules', packageSpecifier));
  768. // b. Set parentURL to the parent folder URL of parentURL.
  769. const parentParentURL = this.pathDirname(parentURL);
  770. // c. If the folder at packageURL does not exist, then
  771. if (this.isPathAllowed(packageURL) && this.pathTestIsDirectory(packageURL)) break;
  772. // 1. Continue the next loop iteration.
  773. if (parentParentURL === parentURL) {
  774. // 12. Throw a Module Not Found error.
  775. throw new VMError(`Cannot find module '${packageSpecifier}'`, 'ENOTFOUND');
  776. }
  777. parentURL = parentParentURL;
  778. }
  779. // d. Let pjson be the result of READ_PACKAGE_JSON(packageURL).
  780. const pack = this.readPackage(packageURL);
  781. // e. If pjson is not null and pjson.exports is not null or undefined, then
  782. if (pack && pack.exports) {
  783. // 1. Return the result of PACKAGE_EXPORTS_RESOLVE(packageURL, packageSubpath, pjson.exports, defaultConditions).
  784. return this.packageExportsResolve(packageURL, packageSubpath, pack.exports, conditions, extList);
  785. }
  786. // f. Otherwise, if packageSubpath is equal to ".", then
  787. if (packageSubpath === '.') {
  788. // 1. If pjson.main is a string, then
  789. // a. Return the URL resolution of main in packageURL.
  790. return this.loadAsPackage(packageSubpath, pack, extList);
  791. }
  792. // g. Otherwise,
  793. // 1. Return the URL resolution of packageSubpath in packageURL.
  794. return this.pathConcat(packageURL, packageSubpath);
  795. }
  796. }
  797. exports.Resolver = Resolver;
  798. exports.DefaultResolver = DefaultResolver;