From 2cb9f0dad0907b683107e13c949a75acd0abbfe0 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Wed, 6 Dec 2023 09:51:41 -0500 Subject: [PATCH] fix(core): run-commands should handle signals correctly --- e2e/nx-misc/src/extras.test.ts | 32 ++++++++++++++++++- .../run-commands/run-commands.impl.ts | 14 +++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/e2e/nx-misc/src/extras.test.ts b/e2e/nx-misc/src/extras.test.ts index 9a845cca20e6bd..3aad885fc843d6 100644 --- a/e2e/nx-misc/src/extras.test.ts +++ b/e2e/nx-misc/src/extras.test.ts @@ -4,12 +4,13 @@ import { cleanupProject, isNotWindows, newProject, + readFile, readJson, runCLI, + runCommandUntil, setMaxWorkers, uniq, updateFile, - readFile, updateJson, } from '@nx/e2e/utils'; import { join } from 'path'; @@ -265,6 +266,35 @@ describe('Extra Nx Misc Tests', () => { runCLI(`build ${mylib}`); checkFilesExist(`${folder}/dummy.txt`); }, 120000); + + it('should handle SIGTERM signal gracefully', async () => { + updateFile( + 'main.js', + ` + const fs = require('fs'); + console.log('Started'); + const t = setInterval(() => { + // Keep alive + }, 1000); + process.on('SIGTERM', () => { + fs.writeFileSync('exited.txt', ''); + clearInterval(t); + }); + ` + ); + runCLI( + `generate @nx/workspace:run-commands serve --command="node main.js" --project=${mylib}` + ); + + const p = await runCommandUntil(`serve ${mylib}`, (output) => + output.includes('Started') + ); + const exited = new Promise((res) => p.on('exit', () => res())); + + p.kill('SIGTERM'); + await exited; + checkFilesExist('exited.txt'); + }); }); describe('generate --quiet', () => { diff --git a/packages/nx/src/executors/run-commands/run-commands.impl.ts b/packages/nx/src/executors/run-commands/run-commands.impl.ts index ba6cf37c0cba83..355001ac116598 100644 --- a/packages/nx/src/executors/run-commands/run-commands.impl.ts +++ b/packages/nx/src/executors/run-commands/run-commands.impl.ts @@ -20,7 +20,9 @@ async function loadEnvVars(path?: string) { } } -export type Json = { [k: string]: any }; +export type Json = { + [k: string]: any; +}; export interface RunCommandsOptions extends Json { command?: string; @@ -65,13 +67,17 @@ export interface NormalizedRunCommandsOptions extends RunCommandsOptions { command: string; forwardAllArgs?: boolean; }[]; - parsedArgs: { [k: string]: any }; + parsedArgs: { + [k: string]: any; + }; } export default async function ( options: RunCommandsOptions, context: ExecutorContext -): Promise<{ success: boolean }> { +): Promise<{ + success: boolean; +}> { await loadEnvVars(options.envFile); const normalized = normalizeOptions(options); @@ -215,7 +221,7 @@ function createProcess( /** * Ensure the child process is killed when the parent exits */ - const processExitListener = (signal?: number | NodeJS.Signals) => () => + const processExitListener = (signal?: number | NodeJS.Signals) => childProcess.kill(signal); process.on('exit', processExitListener);