| 'use strict'; |
| |
| const { |
| RegExpPrototypeExec, |
| StringPrototypeIndexOf, |
| StringPrototypeSlice, |
| Symbol, |
| globalThis, |
| } = primordials; |
| |
| const path = require('path'); |
| |
| const { |
| codes: { |
| ERR_EVAL_ESM_CANNOT_PRINT, |
| ERR_INVALID_ARG_TYPE, |
| ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, |
| }, |
| } = require('internal/errors'); |
| const { pathToFileURL } = require('internal/url'); |
| const { exitCodes: { kGenericUserError } } = internalBinding('errors'); |
| const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); |
| |
| const { |
| executionAsyncId, |
| clearDefaultTriggerAsyncId, |
| clearAsyncIdStack, |
| hasAsyncIdStack, |
| afterHooksExist, |
| emitAfter, |
| popAsyncContext, |
| } = require('internal/async_hooks'); |
| const { containsModuleSyntax } = internalBinding('contextify'); |
| const { getOptionValue } = require('internal/options'); |
| const { |
| makeContextifyScript, runScriptInThisContext, |
| } = require('internal/vm'); |
| const { emitExperimentalWarning } = require('internal/util'); |
| // shouldAbortOnUncaughtToggle is a typed array for faster |
| // communication with JS. |
| const { shouldAbortOnUncaughtToggle } = internalBinding('util'); |
| |
| function tryGetCwd() { |
| try { |
| return process.cwd(); |
| } catch { |
| // getcwd(3) can fail if the current working directory has been deleted. |
| // Fall back to the directory name of the (absolute) executable path. |
| // It's not really correct but what are the alternatives? |
| return path.dirname(process.execPath); |
| } |
| } |
| |
| let evalIndex = 0; |
| function getEvalModuleUrl() { |
| return `${pathToFileURL(process.cwd())}/[eval${++evalIndex}]`; |
| } |
| |
| /** |
| * Evaluate an ESM entry point and return the promise that gets fulfilled after |
| * it finishes evaluation. |
| * @param {string} source Source code the ESM |
| * @param {boolean} print Whether the result should be printed. |
| * @returns {Promise} |
| */ |
| function evalModuleEntryPoint(source, print) { |
| if (print) { |
| throw new ERR_EVAL_ESM_CANNOT_PRINT(); |
| } |
| RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs. |
| return require('internal/modules/run_main').runEntryPointWithESMLoader( |
| (loader) => loader.eval(source, getEvalModuleUrl(), true), |
| ); |
| } |
| |
| function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) { |
| const origModule = globalThis.module; // Set e.g. when called from the REPL. |
| const module = createModule(name); |
| const baseUrl = pathToFileURL(module.filename).href; |
| |
| if (shouldUseModuleEntryPoint(name, body)) { |
| return getOptionValue('--experimental-strip-types') ? |
| evalTypeScriptModuleEntryPoint(body, print) : |
| evalModuleEntryPoint(body, print); |
| } |
| |
| const evalFunction = () => runScriptInContext(name, |
| body, |
| breakFirstLine, |
| print, |
| module, |
| baseUrl, |
| undefined, |
| origModule); |
| |
| if (shouldLoadESM) { |
| return require('internal/modules/run_main').runEntryPointWithESMLoader(evalFunction); |
| } |
| evalFunction(); |
| } |
| |
| const exceptionHandlerState = { |
| captureFn: null, |
| reportFlag: false, |
| }; |
| |
| function setUncaughtExceptionCaptureCallback(fn) { |
| if (fn === null) { |
| exceptionHandlerState.captureFn = fn; |
| shouldAbortOnUncaughtToggle[0] = 1; |
| process.report.reportOnUncaughtException = exceptionHandlerState.reportFlag; |
| return; |
| } |
| if (typeof fn !== 'function') { |
| throw new ERR_INVALID_ARG_TYPE('fn', ['Function', 'null'], fn); |
| } |
| if (exceptionHandlerState.captureFn !== null) { |
| throw new ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(); |
| } |
| exceptionHandlerState.captureFn = fn; |
| shouldAbortOnUncaughtToggle[0] = 0; |
| exceptionHandlerState.reportFlag = |
| process.report.reportOnUncaughtException === true; |
| process.report.reportOnUncaughtException = false; |
| } |
| |
| function hasUncaughtExceptionCaptureCallback() { |
| return exceptionHandlerState.captureFn !== null; |
| } |
| |
| function noop() {} |
| |
| // XXX(joyeecheung): for some reason this cannot be defined at the top-level |
| // and exported to be written to process._fatalException, it has to be |
| // returned as an *anonymous function* wrapped inside a factory function, |
| // otherwise it breaks the test-timers.setInterval async hooks test - |
| // this may indicate that node::errors::TriggerUncaughtException() should |
| // fix up the callback scope before calling into process._fatalException, |
| // or this function should take extra care of the async hooks before it |
| // schedules a setImmediate. |
| function createOnGlobalUncaughtException() { |
| // The C++ land node::errors::TriggerUncaughtException() will |
| // exit the process if it returns false, and continue execution if it |
| // returns true (which indicates that the exception is handled by the user). |
| return (er, fromPromise) => { |
| // It's possible that defaultTriggerAsyncId was set for a constructor |
| // call that threw and was never cleared. So clear it now. |
| clearDefaultTriggerAsyncId(); |
| |
| const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; |
| process.emit('uncaughtExceptionMonitor', er, type); |
| if (exceptionHandlerState.captureFn !== null) { |
| exceptionHandlerState.captureFn(er); |
| } else if (!process.emit('uncaughtException', er, type)) { |
| // If someone handled it, then great. Otherwise, die in C++ land |
| // since that means that we'll exit the process, emit the 'exit' event. |
| try { |
| if (!process._exiting) { |
| process._exiting = true; |
| process.exitCode = kGenericUserError; |
| process.emit('exit', kGenericUserError); |
| } |
| } catch { |
| // Nothing to be done about it at this point. |
| } |
| return false; |
| } |
| |
| // If we handled an error, then make sure any ticks get processed |
| // by ensuring that the next Immediate cycle isn't empty. |
| require('timers').setImmediate(noop); |
| |
| // Emit the after() hooks now that the exception has been handled. |
| if (afterHooksExist()) { |
| do { |
| const asyncId = executionAsyncId(); |
| if (asyncId === 0) |
| popAsyncContext(0); |
| else |
| emitAfter(asyncId); |
| } while (hasAsyncIdStack()); |
| } |
| // And completely empty the id stack, including anything that may be |
| // cached on the native side. |
| clearAsyncIdStack(); |
| |
| return true; |
| }; |
| } |
| |
| function readStdin(callback) { |
| process.stdin.setEncoding('utf8'); |
| |
| let code = ''; |
| process.stdin.on('data', (d) => { |
| code += d; |
| }); |
| |
| process.stdin.on('end', () => { |
| callback(code); |
| }); |
| } |
| |
| /** |
| * Adds the TS message to the error stack. |
| * |
| * At the 3rd line of the stack, the message is added. |
| * @param {string} originalStack The stack to decorate |
| * @param {string} newMessage the message to add to the error stack |
| * @returns {void} |
| */ |
| function decorateCJSErrorWithTSMessage(originalStack, newMessage) { |
| let index; |
| for (let i = 0; i < 3; i++) { |
| index = StringPrototypeIndexOf(originalStack, '\n', index + 1); |
| } |
| return StringPrototypeSlice(originalStack, 0, index) + |
| '\n' + newMessage + |
| StringPrototypeSlice(originalStack, index); |
| } |
| |
| /** |
| * |
| * Wrapper of evalScript |
| * |
| * This function wraps the evaluation of the source code in a try-catch block. |
| * If the source code fails to be evaluated, it will retry evaluating the source code |
| * with the TypeScript parser. |
| * |
| * If the source code fails to be evaluated with the TypeScript parser, |
| * it will rethrow the original error, adding the TypeScript error message to the stack. |
| * |
| * This way we don't change the behavior of the code, but we provide a better error message |
| * in case of a typescript error. |
| * @param {string} name The name of the file |
| * @param {string} source The source code to evaluate |
| * @param {boolean} breakFirstLine Whether to break on the first line |
| * @param {boolean} print If the result should be printed |
| * @param {boolean} shouldLoadESM If the code should be loaded as an ESM module |
| * @returns {void} |
| */ |
| function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) { |
| const origModule = globalThis.module; // Set e.g. when called from the REPL. |
| const module = createModule(name); |
| const baseUrl = pathToFileURL(module.filename).href; |
| |
| if (shouldUseModuleEntryPoint(name, source)) { |
| return evalTypeScriptModuleEntryPoint(source, print); |
| } |
| |
| let compiledScript; |
| // This variable can be modified if the source code is stripped. |
| let sourceToRun = source; |
| try { |
| compiledScript = compileScript(name, source, baseUrl); |
| } catch (originalError) { |
| try { |
| sourceToRun = stripTypeScriptModuleTypes(source, name, false); |
| // Retry the CJS/ESM syntax detection after stripping the types. |
| if (shouldUseModuleEntryPoint(name, sourceToRun)) { |
| return evalTypeScriptModuleEntryPoint(source, print); |
| } |
| // If the ContextifiedScript was successfully created, execute it. |
| // outside the try-catch block to avoid catching runtime errors. |
| compiledScript = compileScript(name, sourceToRun, baseUrl); |
| // Emit the experimental warning after the code was successfully evaluated. |
| emitExperimentalWarning('Type Stripping'); |
| } catch (tsError) { |
| // If it's invalid or unsupported TypeScript syntax, rethrow the original error |
| // with the TypeScript error message added to the stack. |
| if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' || tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') { |
| originalError.stack = decorateCJSErrorWithTSMessage(originalError.stack, tsError.message); |
| throw originalError; |
| } |
| |
| throw tsError; |
| } |
| } |
| |
| const evalFunction = () => runScriptInContext(name, |
| sourceToRun, |
| breakFirstLine, |
| print, |
| module, |
| baseUrl, |
| compiledScript, |
| origModule); |
| |
| if (shouldLoadESM) { |
| return require('internal/modules/run_main').runEntryPointWithESMLoader(evalFunction); |
| } |
| evalFunction(); |
| } |
| |
| /** |
| * Wrapper of evalModuleEntryPoint |
| * |
| * This function wraps the compilation of the source code in a try-catch block. |
| * If the source code fails to be compiled, it will retry transpiling the source code |
| * with the TypeScript parser. |
| * @param {string} source The source code to evaluate |
| * @param {boolean} print If the result should be printed |
| * @returns {Promise} The module evaluation promise |
| */ |
| function evalTypeScriptModuleEntryPoint(source, print) { |
| if (print) { |
| throw new ERR_EVAL_ESM_CANNOT_PRINT(); |
| } |
| |
| RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs. |
| |
| return require('internal/modules/run_main').runEntryPointWithESMLoader( |
| async (loader) => { |
| const url = getEvalModuleUrl(); |
| let moduleWrap; |
| try { |
| // Compile the module to check for syntax errors. |
| moduleWrap = loader.createModuleWrap(source, url); |
| } catch (originalError) { |
| try { |
| const strippedSource = stripTypeScriptModuleTypes(source, url, false); |
| // If the moduleWrap was successfully created, execute the module job. |
| // outside the try-catch block to avoid catching runtime errors. |
| moduleWrap = loader.createModuleWrap(strippedSource, url); |
| // Emit the experimental warning after the code was successfully compiled. |
| emitExperimentalWarning('Type Stripping'); |
| } catch (tsError) { |
| // If it's invalid or unsupported TypeScript syntax, rethrow the original error |
| // with the TypeScript error message added to the stack. |
| if (tsError.code === 'ERR_INVALID_TYPESCRIPT_SYNTAX' || |
| tsError.code === 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') { |
| originalError.stack = `${tsError.message}\n\n${originalError.stack}`; |
| throw originalError; |
| } |
| |
| throw tsError; |
| } |
| } |
| // If the moduleWrap was successfully created either with by just compiling |
| // or after transpilation, execute the module job. |
| return loader.executeModuleJob(url, moduleWrap, true); |
| }, |
| ); |
| }; |
| |
| /** |
| * |
| * Function used to shortcut when `--input-type=module-typescript` is set. |
| * @param {string} source |
| * @param {boolean} print |
| */ |
| function parseAndEvalModuleTypeScript(source, print) { |
| // We know its a TypeScript module, we can safely emit the experimental warning. |
| const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl()); |
| evalModuleEntryPoint(strippedSource, print); |
| } |
| |
| /** |
| * Function used to shortcut when `--input-type=commonjs-typescript` is set |
| * @param {string} name The name of the file |
| * @param {string} source The source code to evaluate |
| * @param {boolean} breakFirstLine Whether to break on the first line |
| * @param {boolean} print If the result should be printed |
| * @param {boolean} shouldLoadESM If the code should be loaded as an ESM module |
| * @returns {void} |
| */ |
| function parseAndEvalCommonjsTypeScript(name, source, breakFirstLine, print, shouldLoadESM = false) { |
| // We know its a TypeScript module, we can safely emit the experimental warning. |
| const strippedSource = stripTypeScriptModuleTypes(source, getEvalModuleUrl()); |
| evalScript(name, strippedSource, breakFirstLine, print, shouldLoadESM); |
| } |
| |
| /** |
| * |
| * @param {string} name - The filename of the script. |
| * @param {string} body - The code of the script. |
| * @param {string} baseUrl Path of the parent importing the module. |
| * @returns {ContextifyScript} The created contextify script. |
| */ |
| function compileScript(name, body, baseUrl) { |
| const hostDefinedOptionId = Symbol(name); |
| async function importModuleDynamically(specifier, _, importAttributes) { |
| const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); |
| return cascadedLoader.import(specifier, baseUrl, importAttributes); |
| } |
| return makeContextifyScript( |
| body, // code |
| name, // filename, |
| 0, // lineOffset |
| 0, // columnOffset, |
| undefined, // cachedData |
| false, // produceCachedData |
| undefined, // parsingContext |
| hostDefinedOptionId, // hostDefinedOptionId |
| importModuleDynamically, // importModuleDynamically |
| ); |
| } |
| |
| /** |
| * @param {string} name - The filename of the script. |
| * @param {string} body - The code of the script. |
| * @returns {boolean} Whether the module entry point should be evaluated as a module. |
| */ |
| function shouldUseModuleEntryPoint(name, body) { |
| return getOptionValue('--experimental-detect-module') && |
| getOptionValue('--input-type') === '' && |
| containsModuleSyntax(body, name, null, 'no CJS variables'); |
| } |
| |
| /** |
| * |
| * @param {string} name - The filename of the script. |
| * @returns {import('internal/modules/esm/loader').CJSModule} The created module. |
| */ |
| function createModule(name) { |
| const CJSModule = require('internal/modules/cjs/loader').Module; |
| const cwd = tryGetCwd(); |
| const module = new CJSModule(name); |
| module.filename = path.join(cwd, name); |
| module.paths = CJSModule._nodeModulePaths(cwd); |
| return module; |
| } |
| |
| /** |
| * |
| * @param {string} name - The filename of the script. |
| * @param {string} body - The code of the script. |
| * @param {boolean} breakFirstLine Whether to break on the first line |
| * @param {boolean} print If the result should be printed |
| * @param {import('internal/modules/esm/loader').CJSModule} module The module |
| * @param {string} baseUrl Path of the parent importing the module. |
| * @param {object} compiledScript The compiled script. |
| * @param {any} origModule The original module. |
| * @returns {void} |
| */ |
| function runScriptInContext(name, body, breakFirstLine, print, module, baseUrl, compiledScript, origModule) { |
| // Create wrapper for cache entry |
| const script = ` |
| globalThis.module = module; |
| globalThis.exports = exports; |
| globalThis.__dirname = __dirname; |
| globalThis.require = require; |
| return (main) => main(); |
| `; |
| globalThis.__filename = name; |
| RegExpPrototypeExec(/^/, ''); // Necessary to reset RegExp statics before user code runs. |
| const result = module._compile(script, `${name}-wrapper`)(() => { |
| // If the script was already compiled, use it. |
| return runScriptInThisContext( |
| compiledScript ?? compileScript(name, body, baseUrl), |
| true, !!breakFirstLine); |
| }); |
| if (print) { |
| const { log } = require('internal/console/global'); |
| |
| process.on('exit', () => { |
| log(result); |
| }); |
| } |
| if (origModule !== undefined) |
| globalThis.module = origModule; |
| } |
| |
| module.exports = { |
| parseAndEvalCommonjsTypeScript, |
| parseAndEvalModuleTypeScript, |
| readStdin, |
| tryGetCwd, |
| evalModuleEntryPoint, |
| evalTypeScript, |
| evalScript, |
| onGlobalUncaughtException: createOnGlobalUncaughtException(), |
| setUncaughtExceptionCaptureCallback, |
| hasUncaughtExceptionCaptureCallback, |
| }; |