From e6a7b352f393f977b94210c99bad84fa16685806 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 3 Mar 2026 22:38:31 +0000 Subject: [PATCH] fix(watcher): ensure child processes are killed and awaited during shutdown; improve cleanup handlers; bump smartshell dependency to ^3.3.2 --- changelog.md | 8 ++++++++ package.json | 2 +- pnpm-lock.yaml | 30 +++++++++++++++--------------- ts/00_commitinfo_data.ts | 2 +- ts/tswatch.classes.watcher.ts | 28 ++++++++++++++++++---------- 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/changelog.md b/changelog.md index b43c1c0..ee6f7a4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-03-03 - 3.2.1 - fix(watcher) +ensure child processes are killed and awaited during shutdown; improve cleanup handlers; bump smartshell dependency to ^3.3.2 + +- Await child process kill() calls in restart and stop to avoid race conditions and ensure proper termination. +- Add last-resort synchronous SIGKILL in process 'exit' handler to terminate orphaned child processes. +- Make SIGINT and timeout handlers async and await stop() to perform a clean shutdown before exiting. +- Bump @push.rocks/smartshell from ^3.3.0 to ^3.3.2 in package.json. + ## 2026-02-24 - 3.2.0 - feat(bundle) add configurable bundle output modes and bundler options (support base64ts, production builds, includeFiles, maxLineLength) and route non-default outputs to a CustomBundleHandler diff --git a/package.json b/package.json index c7e6b19..c09872b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@push.rocks/smartinteract": "^2.0.16", "@push.rocks/smartlog": "^3.1.10", "@push.rocks/smartlog-destination-local": "^9.0.2", - "@push.rocks/smartshell": "^3.3.0", + "@push.rocks/smartshell": "^3.3.2", "@push.rocks/smartwatch": "^6.3.0", "@push.rocks/taskbuffer": "^4.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e87ab1..2f452ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^9.0.2 version: 9.0.2 '@push.rocks/smartshell': - specifier: ^3.3.0 - version: 3.3.0 + specifier: ^3.3.2 + version: 3.3.2 '@push.rocks/smartwatch': specifier: ^6.3.0 version: 6.3.0 @@ -972,12 +972,12 @@ packages: '@push.rocks/smarterror@2.0.1': resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==} - '@push.rocks/smartexit@1.0.23': - resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==} - '@push.rocks/smartexit@1.1.0': resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==} + '@push.rocks/smartexit@1.1.1': + resolution: {integrity: sha512-UwcVJbp7vzzDM9RQmnfTaVOJ+DK127lAC5gwyfKU2GfPAv0Jng62Sv601otP+jnly9nRt5fUuttNHDl34Mjn3g==} + '@push.rocks/smartexpect@2.5.0': resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==} @@ -1098,8 +1098,8 @@ packages: '@push.rocks/smartserve@2.0.1': resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==} - '@push.rocks/smartshell@3.3.0': - resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} + '@push.rocks/smartshell@3.3.2': + resolution: {integrity: sha512-xDakRUYBO/WDXlBvS2IbreAvXke/oUul2hcna953a1Bv5gMPOSVBVFsFIaUEqTzAQ5/1YjjEhbnjPeXq87jgkA==} '@push.rocks/smartsitemap@2.0.4': resolution: {integrity: sha512-76dYWG/o/EjV4vYCK7ZKM35T9xgrI+oHEiiIE6E2MDaFIU6QnSfciTfbscH5nc0vxx8Ah+I0HPEJO94BM2S39w==} @@ -5627,7 +5627,7 @@ snapshots: '@push.rocks/smartnpm': 2.0.6 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartrequest': 5.0.1 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.2 transitivePeerDependencies: - '@nuxt/kit' - aws-crt @@ -5640,7 +5640,7 @@ snapshots: '@git.zone/tsrun@2.0.1': dependencies: '@push.rocks/smartfile': 13.1.2 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.2 tsx: 4.21.0 '@git.zone/tstest@3.1.8(@aws-sdk/credential-providers@3.855.0)(@push.rocks/smartserve@2.0.1)(socks@2.8.7)(typescript@5.9.3)': @@ -5665,7 +5665,7 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartrequest': 5.0.1 '@push.rocks/smarts3': 3.0.3 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.2 '@push.rocks/smarttime': 4.1.1 '@types/ws': 8.18.1 figures: 6.1.0 @@ -6307,14 +6307,14 @@ snapshots: clean-stack: 1.3.0 make-error-cause: 2.3.0 - '@push.rocks/smartexit@1.0.23': + '@push.rocks/smartexit@1.1.0': dependencies: '@push.rocks/lik': 6.2.2 '@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartpromise': 4.2.3 tree-kill: 1.2.2 - '@push.rocks/smartexit@1.1.0': + '@push.rocks/smartexit@1.1.1': dependencies: '@push.rocks/lik': 6.2.2 '@push.rocks/smartdelay': 3.0.5 @@ -6579,7 +6579,7 @@ snapshots: '@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartshell': 3.3.0 + '@push.rocks/smartshell': 3.3.2 puppeteer: 24.36.0(typescript@5.9.3) tree-kill: 1.2.2 transitivePeerDependencies: @@ -6650,10 +6650,10 @@ snapshots: - bufferutil - utf-8-validate - '@push.rocks/smartshell@3.3.0': + '@push.rocks/smartshell@3.3.2': dependencies: '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartexit': 1.0.23 + '@push.rocks/smartexit': 1.1.1 '@push.rocks/smartpromise': 4.2.3 '@types/which': 3.0.4 tree-kill: 1.2.2 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 73bdc7f..4365c01 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tswatch', - version: '3.2.0', + version: '3.2.1', description: 'A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.' } diff --git a/ts/tswatch.classes.watcher.ts b/ts/tswatch.classes.watcher.ts index 7846572..c254080 100644 --- a/ts/tswatch.classes.watcher.ts +++ b/ts/tswatch.classes.watcher.ts @@ -154,7 +154,7 @@ export class Watcher { if (this.options.commandToExecute) { if (this.currentExecution && this.options.restart) { logger.log('ok', `[${name}] restarting: ${this.options.commandToExecute}`); - this.currentExecution.kill(); + await this.currentExecution.kill(); } else if (!this.currentExecution) { logger.log('ok', `[${name}] executing: ${this.options.commandToExecute}`); } @@ -184,24 +184,32 @@ export class Watcher { * this method sets up a clean exit strategy */ private async setupCleanup() { + // Last-resort synchronous cleanup — 'exit' event cannot await async work. + // By this point, SIGINT handler should have already called stop(). process.on('exit', () => { - console.log(''); - console.log('now exiting!'); - this.stop(); - process.exit(0); + if (this.currentExecution && !this.currentExecution.childProcess.killed) { + const pid = this.currentExecution.childProcess.pid; + if (pid) { + try { + process.kill(pid, 'SIGKILL'); + } catch { + // Process may already be dead + } + } + } }); - process.on('SIGINT', () => { + process.on('SIGINT', async () => { console.log(''); console.log('ok! got SIGINT We are exiting! Just cleaning up to exit neatly :)'); - this.stop(); + await this.stop(); process.exit(0); }); // handle timeout if (this.options.timeout) { - plugins.smartdelay.delayFor(this.options.timeout).then(() => { + plugins.smartdelay.delayFor(this.options.timeout).then(async () => { console.log(`timed out afer ${this.options.timeout} milliseconds! exiting!`); - this.stop(); + await this.stop(); process.exit(0); }); } @@ -216,7 +224,7 @@ export class Watcher { } await this.smartwatchInstance.stop(); if (this.currentExecution && !this.currentExecution.childProcess.killed) { - this.currentExecution.kill(); + await this.currentExecution.kill(); } } }