diff --git a/packages/js/src/executors/node/node-with-require-overrides.ts b/packages/js/src/executors/node/node-with-require-overrides.ts index a67202a792..704a484d47 100644 --- a/packages/js/src/executors/node/node-with-require-overrides.ts +++ b/packages/js/src/executors/node/node-with-require-overrides.ts @@ -1,23 +1,9 @@ -const Module = require('module'); const url = require('node:url'); -const originalLoader = Module._load; +const { patchSigint } = require('./patch-sigint'); +const { patchRequire } = require('./patch-require'); + +patchSigint(); +patchRequire(); const dynamicImport = new Function('specifier', 'return import(specifier)'); - -const mappings = JSON.parse(process.env.NX_MAPPINGS); -const keys = Object.keys(mappings); -const fileToRun = url.pathToFileURL(process.env.NX_FILE_TO_RUN); - -Module._load = function (request, parent) { - if (!parent) return originalLoader.apply(this, arguments); - const match = keys.find((k) => request === k); - if (match) { - const newArguments = [...arguments]; - newArguments[0] = mappings[match]; - return originalLoader.apply(this, newArguments); - } else { - return originalLoader.apply(this, arguments); - } -}; - -dynamicImport(fileToRun); +dynamicImport(url.pathToFileURL(process.env.NX_FILE_TO_RUN)); diff --git a/packages/js/src/executors/node/node.impl.ts b/packages/js/src/executors/node/node.impl.ts index dcbfb85699..fe46a906c3 100644 --- a/packages/js/src/executors/node/node.impl.ts +++ b/packages/js/src/executors/node/node.impl.ts @@ -172,13 +172,18 @@ export async function* nodeExecutor( task.childProcess.stderr.on('data', handleStdErr); task.childProcess.once('exit', (code) => { task.childProcess.off('data', handleStdErr); - if (options.watch && !task.killed) { + if ( + options.watch && + !task.killed && + // SIGINT should exist the process rather than watch for changes. + code !== 130 + ) { logger.info( `NX Process exited with code ${code}, waiting for changes to restart...` ); } - if (!options.watch) { - if (code !== 0) { + if (!options.watch || code === 130) { + if (code !== 0 && code !== 130) { error(new Error(`Process exited with code ${code}`)); } else { done(); diff --git a/packages/js/src/executors/node/patch-require.ts b/packages/js/src/executors/node/patch-require.ts new file mode 100644 index 0000000000..8bec535651 --- /dev/null +++ b/packages/js/src/executors/node/patch-require.ts @@ -0,0 +1,23 @@ +const Module = require('node:module'); +const originalLoader = Module._load; + +/** + * Overrides require calls to map buildable workspace libs to their output location. + * This is useful for running programs compiled via TSC/SWC that aren't bundled. + */ +export function patchRequire() { + const mappings = JSON.parse(process.env.NX_MAPPINGS); + const keys = Object.keys(mappings); + + Module._load = function (request, parent) { + if (!parent) return originalLoader.apply(this, arguments); + const match = keys.find((k) => request === k); + if (match) { + const newArguments = [...arguments]; + newArguments[0] = mappings[match]; + return originalLoader.apply(this, newArguments); + } else { + return originalLoader.apply(this, arguments); + } + }; +} diff --git a/packages/js/src/executors/node/patch-sigint.ts b/packages/js/src/executors/node/patch-sigint.ts new file mode 100644 index 0000000000..286887524d --- /dev/null +++ b/packages/js/src/executors/node/patch-sigint.ts @@ -0,0 +1,21 @@ +const readline = require('node:readline'); + +/** + * Patches the current process so that Ctrl+C is properly handled. + * Without this patch, SIGINT or Ctrl+C does not wait for graceful shutdown and exits immediately. + */ +export function patchSigint() { + readline.emitKeypressEvents(process.stdin); + if (process.stdin.isTTY) process.stdin.setRawMode(true); + process.stdin.on('keypress', async (chunk, key) => { + if (key && key.ctrl && key.name === 'c') { + process.stdin.setRawMode(false); // To ensure nx terminal is not stuck in raw mode + const listeners = process.listeners('SIGINT'); + for (const listener of listeners) { + await listener('SIGINT'); + } + process.exit(130); + } + }); + console.log('To exit the process, press Ctrl+C'); +}