feat(core): add shutdown lifecycle hook to node executor (#27354)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> When the application are received a shutdown signal, the application doesn't execute before shutdown functions and directly shutdown whole application. The situation cannot execute before shutdown functions like [NestJS Lifecycle Events](https://docs.nestjs.com/fundamentals/lifecycle-events) and custom shutdown hooks. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> The application can run shutdown hooks like below output: NX Successfully ran target build for project nest-test (5s) Debugger listening on ws://localhost:9229/e4bd44c0-9a6a-468a-8b46-b6fef1cef1c7 For help, see: https://nodejs.org/en/docs/inspector ``` NX Successfully ran target build for project nest-test (4s) Debugger listening on ws://localhost:9229/75c8449b-43a4-4d8b-88c0-231761d7248c For help, see: https://nodejs.org/en/docs/inspector To exit the process with SIGINT, press Ctrl+C [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG [NestFactory] Starting Nest application... [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG [InstanceLoader] AppModule dependencies initialized +10ms [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG [RoutesResolver] AppController {/api}: +7ms [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG [RouterExplorer] Mapped {/api, GET} route +3ms [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG [NestApplication] Nest application successfully started +2ms [Nest] 393107 - 08/09/2024, 6:31:26 PM LOG 🚀 Application is running on: http://localhost:3000/api [Nest] 393107 - 08/09/2024, 6:31:29 PM LOG onModuleDestroy onModuleDestroy: 5.001s [Nest] 393107 - 08/09/2024, 6:31:34 PM LOG beforeApplicationShutdown SIGINT beforeApplicationShutdown: 5.004s [Nest] 393107 - 08/09/2024, 6:31:39 PM LOG onApplicationShutdown SIGINT onApplicationShutdown: 5.005s NX Process exited with code 130, waiting for changes to restart... ``` ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #9237 and #18037 --------- Co-authored-by: Jack Hsu <jack.hsu@gmail.com>
This commit is contained in:
parent
958f188bd0
commit
b5a93364c5
@ -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));
|
||||
|
||||
@ -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();
|
||||
|
||||
23
packages/js/src/executors/node/patch-require.ts
Normal file
23
packages/js/src/executors/node/patch-require.ts
Normal file
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
21
packages/js/src/executors/node/patch-sigint.ts
Normal file
21
packages/js/src/executors/node/patch-sigint.ts
Normal file
@ -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');
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user