1'use strict';
2
3const {
4  StringPrototypeEndsWith,
5} = primordials;
6
7const { getOptionValue } = require('internal/options');
8const path = require('path');
9
10/**
11 * Get the absolute path to the main entry point.
12 * @param {string} main - Entry point path
13 */
14function resolveMainPath(main) {
15  const defaultType = getOptionValue('--experimental-default-type');
16  /** @type {string} */
17  let mainPath;
18  if (defaultType === 'module') {
19    if (getOptionValue('--preserve-symlinks-main')) { return; }
20    mainPath = path.resolve(main);
21  } else {
22    // Extension searching for the main entry point is supported only in legacy mode.
23    // Module._findPath is monkey-patchable here.
24    const { Module } = require('internal/modules/cjs/loader');
25    mainPath = Module._findPath(path.resolve(main), null, true);
26  }
27  if (!mainPath) { return; }
28
29  const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
30  if (!preserveSymlinksMain) {
31    const { toRealPath } = require('internal/modules/helpers');
32    try {
33      mainPath = toRealPath(mainPath);
34    } catch (err) {
35      if (defaultType === 'module' && err?.code === 'ENOENT') {
36        const { decorateErrorWithCommonJSHints } = require('internal/modules/esm/resolve');
37        const { getCWDURL } = require('internal/util');
38        decorateErrorWithCommonJSHints(err, mainPath, getCWDURL());
39      }
40      throw err;
41    }
42  }
43
44  return mainPath;
45}
46
47/**
48 * Determine whether the main entry point should be loaded through the ESM Loader.
49 * @param {string} mainPath - Absolute path to the main entry point
50 */
51function shouldUseESMLoader(mainPath) {
52  if (getOptionValue('--experimental-default-type') === 'module') { return true; }
53
54  /**
55   * @type {string[]} userLoaders A list of custom loaders registered by the user
56   * (or an empty list when none have been registered).
57   */
58  const userLoaders = getOptionValue('--experimental-loader');
59  /**
60   * @type {string[]} userImports A list of preloaded modules registered by the user
61   * (or an empty list when none have been registered).
62   */
63  const userImports = getOptionValue('--import');
64  if (userLoaders.length > 0 || userImports.length > 0) {
65    return true;
66  }
67  const esModuleSpecifierResolution =
68    getOptionValue('--experimental-specifier-resolution');
69  if (esModuleSpecifierResolution === 'node') {
70    return true;
71  }
72  // Determine the module format of the entry point.
73  if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; }
74  if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; }
75
76  const { readPackageScope } = require('internal/modules/package_json_reader');
77  const pkg = readPackageScope(mainPath);
78  // No need to guard `pkg` as it can only be an object or `false`.
79  return pkg.data?.type === 'module' || getOptionValue('--experimental-default-type') === 'module';
80}
81
82/**
83 * Run the main entry point through the ESM Loader.
84 * @param {string} mainPath - Absolute path for the main entry point
85 */
86function runMainESM(mainPath) {
87  const { loadESM } = require('internal/process/esm_loader');
88  const { pathToFileURL } = require('internal/url');
89  const main = pathToFileURL(mainPath).href;
90
91  handleMainPromise(loadESM((esmLoader) => {
92    return esmLoader.import(main, undefined, { __proto__: null });
93  }));
94}
95
96/**
97 * Handle process exit events around the main entry point promise.
98 * @param {Promise} promise - Main entry point promise
99 */
100async function handleMainPromise(promise) {
101  const {
102    handleProcessExit,
103  } = require('internal/modules/esm/handle_process_exit');
104  process.on('exit', handleProcessExit);
105  try {
106    return await promise;
107  } finally {
108    process.off('exit', handleProcessExit);
109  }
110}
111
112/**
113 * Parse the CLI main entry point string and run it.
114 * For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed
115 * by `require('module')`) even when the entry point is ESM.
116 * This monkey-patchable code is bypassed under `--experimental-default-type=module`.
117 * Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
118 * @param {string} main - First positional CLI argument, such as `'entry.js'` from `node entry.js`
119 */
120function executeUserEntryPoint(main = process.argv[1]) {
121  const resolvedMain = resolveMainPath(main);
122  const useESMLoader = shouldUseESMLoader(resolvedMain);
123  if (useESMLoader) {
124    runMainESM(resolvedMain || main);
125  } else {
126    // Module._load is the monkey-patchable CJS module loader.
127    const { Module } = require('internal/modules/cjs/loader');
128    Module._load(main, null, true);
129  }
130}
131
132module.exports = {
133  executeUserEntryPoint,
134  handleMainPromise,
135};
136