Compare commits

...

4 Commits

Author SHA1 Message Date
91b3e273de v3.2.1
Some checks failed
Default (tags) / security (push) Successful in 45s
Default (tags) / test (push) Failing after 4m3s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-03 22:38:31 +00:00
e6a7b352f3 fix(watcher): ensure child processes are killed and awaited during shutdown; improve cleanup handlers; bump smartshell dependency to ^3.3.2 2026-03-03 22:38:31 +00:00
8c1b306313 v3.2.0
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 4m5s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-24 19:02:39 +00:00
a893e7a771 feat(bundle): add configurable bundle output modes and bundler options (support base64ts, production builds, includeFiles, maxLineLength) and route non-default outputs to a CustomBundleHandler 2026-02-24 19:02:39 +00:00
7 changed files with 84 additions and 37 deletions

View File

@@ -1,5 +1,21 @@
# Changelog # 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
- Added new ITswatchConfig fields: outputMode, bundler, production, includeFiles, maxLineLength
- tswatch now creates a CustomBundleHandler and uses it when outputMode is not 'bundle' (e.g. base64ts)
- Default bundling path now reads bundler and production from bundleConfig (defaults to 'esbuild' and false)
- Bumped dependency @git.zone/tsbundle to ^2.9.0
## 2026-02-05 - 3.1.0 - feat(dev-server) ## 2026-02-05 - 3.1.0 - feat(dev-server)
add no-cache headers to built-in development server; update docs and bump dependencies add no-cache headers to built-in development server; update docs and bump dependencies

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tswatch", "name": "@git.zone/tswatch",
"version": "3.1.0", "version": "3.2.1",
"private": false, "private": false,
"description": "A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.", "description": "A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.",
"exports": { "exports": {
@@ -24,7 +24,7 @@
}, },
"dependencies": { "dependencies": {
"@api.global/typedserver": "^8.3.0", "@api.global/typedserver": "^8.3.0",
"@git.zone/tsbundle": "^2.8.3", "@git.zone/tsbundle": "^2.9.0",
"@git.zone/tsrun": "^2.0.1", "@git.zone/tsrun": "^2.0.1",
"@push.rocks/early": "^4.0.4", "@push.rocks/early": "^4.0.4",
"@push.rocks/lik": "^6.2.2", "@push.rocks/lik": "^6.2.2",
@@ -35,7 +35,7 @@
"@push.rocks/smartinteract": "^2.0.16", "@push.rocks/smartinteract": "^2.0.16",
"@push.rocks/smartlog": "^3.1.10", "@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartlog-destination-local": "^9.0.2", "@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/smartwatch": "^6.3.0",
"@push.rocks/taskbuffer": "^4.2.0" "@push.rocks/taskbuffer": "^4.2.0"
}, },

42
pnpm-lock.yaml generated
View File

@@ -12,8 +12,8 @@ importers:
specifier: ^8.3.0 specifier: ^8.3.0
version: 8.3.0(@tiptap/pm@2.27.2) version: 8.3.0(@tiptap/pm@2.27.2)
'@git.zone/tsbundle': '@git.zone/tsbundle':
specifier: ^2.8.3 specifier: ^2.9.0
version: 2.8.3 version: 2.9.0
'@git.zone/tsrun': '@git.zone/tsrun':
specifier: ^2.0.1 specifier: ^2.0.1
version: 2.0.1 version: 2.0.1
@@ -45,8 +45,8 @@ importers:
specifier: ^9.0.2 specifier: ^9.0.2
version: 9.0.2 version: 9.0.2
'@push.rocks/smartshell': '@push.rocks/smartshell':
specifier: ^3.3.0 specifier: ^3.3.2
version: 3.3.0 version: 3.3.2
'@push.rocks/smartwatch': '@push.rocks/smartwatch':
specifier: ^6.3.0 specifier: ^6.3.0
version: 6.3.0 version: 6.3.0
@@ -630,8 +630,8 @@ packages:
resolution: {integrity: sha512-S518ulKveO76pS6jrAELrnFaCw5nDAIZD9j6QzVmLYDiZuJmlRwPK3/2E8ugQ+b7ffpkwJ9MT685ooEGDcWQ4Q==} resolution: {integrity: sha512-S518ulKveO76pS6jrAELrnFaCw5nDAIZD9j6QzVmLYDiZuJmlRwPK3/2E8ugQ+b7ffpkwJ9MT685ooEGDcWQ4Q==}
hasBin: true hasBin: true
'@git.zone/tsbundle@2.8.3': '@git.zone/tsbundle@2.9.0':
resolution: {integrity: sha512-9q+KbVGKUTDNND+jDiJuk4bPH/mtiA2B0EWtV+/NyvgZfIbpe/ItHemyIvXB4RAqncMdBhzXquCFCvGjAhwVIQ==} resolution: {integrity: sha512-itXX/oiJjrRHUlIGTHUEqSwPuGwsG4Cq8kh7aqFOm8mYzJwtXYE1gBqLJTWZma6gI5n+xAk5qTxTyfikuPgWQA==}
hasBin: true hasBin: true
'@git.zone/tspublish@1.11.0': '@git.zone/tspublish@1.11.0':
@@ -972,12 +972,12 @@ packages:
'@push.rocks/smarterror@2.0.1': '@push.rocks/smarterror@2.0.1':
resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==} resolution: {integrity: sha512-iCcH1D8tlDJgMFsaJ6lhdOTKhbU0KoprNv9MRP9o7691QOx4JEDXiHtr/lNtxVo8BUtdb9CF6kazaknO9KuORA==}
'@push.rocks/smartexit@1.0.23':
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
'@push.rocks/smartexit@1.1.0': '@push.rocks/smartexit@1.1.0':
resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==} resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==}
'@push.rocks/smartexit@1.1.1':
resolution: {integrity: sha512-UwcVJbp7vzzDM9RQmnfTaVOJ+DK127lAC5gwyfKU2GfPAv0Jng62Sv601otP+jnly9nRt5fUuttNHDl34Mjn3g==}
'@push.rocks/smartexpect@2.5.0': '@push.rocks/smartexpect@2.5.0':
resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==} resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==}
@@ -1098,8 +1098,8 @@ packages:
'@push.rocks/smartserve@2.0.1': '@push.rocks/smartserve@2.0.1':
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==} resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
'@push.rocks/smartshell@3.3.0': '@push.rocks/smartshell@3.3.2':
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} resolution: {integrity: sha512-xDakRUYBO/WDXlBvS2IbreAvXke/oUul2hcna953a1Bv5gMPOSVBVFsFIaUEqTzAQ5/1YjjEhbnjPeXq87jgkA==}
'@push.rocks/smartsitemap@2.0.4': '@push.rocks/smartsitemap@2.0.4':
resolution: {integrity: sha512-76dYWG/o/EjV4vYCK7ZKM35T9xgrI+oHEiiIE6E2MDaFIU6QnSfciTfbscH5nc0vxx8Ah+I0HPEJO94BM2S39w==} resolution: {integrity: sha512-76dYWG/o/EjV4vYCK7ZKM35T9xgrI+oHEiiIE6E2MDaFIU6QnSfciTfbscH5nc0vxx8Ah+I0HPEJO94BM2S39w==}
@@ -5589,7 +5589,7 @@ snapshots:
- supports-color - supports-color
- vue - vue
'@git.zone/tsbundle@2.8.3': '@git.zone/tsbundle@2.9.0':
dependencies: dependencies:
'@push.rocks/early': 4.0.4 '@push.rocks/early': 4.0.4
'@push.rocks/npmextra': 5.3.3 '@push.rocks/npmextra': 5.3.3
@@ -5627,7 +5627,7 @@ snapshots:
'@push.rocks/smartnpm': 2.0.6 '@push.rocks/smartnpm': 2.0.6
'@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpath': 6.0.0
'@push.rocks/smartrequest': 5.0.1 '@push.rocks/smartrequest': 5.0.1
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.2
transitivePeerDependencies: transitivePeerDependencies:
- '@nuxt/kit' - '@nuxt/kit'
- aws-crt - aws-crt
@@ -5640,13 +5640,13 @@ snapshots:
'@git.zone/tsrun@2.0.1': '@git.zone/tsrun@2.0.1':
dependencies: dependencies:
'@push.rocks/smartfile': 13.1.2 '@push.rocks/smartfile': 13.1.2
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.2
tsx: 4.21.0 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)': '@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)':
dependencies: dependencies:
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@2.0.1) '@api.global/typedserver': 3.0.80(@push.rocks/smartserve@2.0.1)
'@git.zone/tsbundle': 2.8.3 '@git.zone/tsbundle': 2.9.0
'@git.zone/tsrun': 2.0.1 '@git.zone/tsrun': 2.0.1
'@push.rocks/consolecolor': 2.0.3 '@push.rocks/consolecolor': 2.0.3
'@push.rocks/qenv': 6.1.3 '@push.rocks/qenv': 6.1.3
@@ -5665,7 +5665,7 @@ snapshots:
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 5.0.1 '@push.rocks/smartrequest': 5.0.1
'@push.rocks/smarts3': 3.0.3 '@push.rocks/smarts3': 3.0.3
'@push.rocks/smartshell': 3.3.0 '@push.rocks/smartshell': 3.3.2
'@push.rocks/smarttime': 4.1.1 '@push.rocks/smarttime': 4.1.1
'@types/ws': 8.18.1 '@types/ws': 8.18.1
figures: 6.1.0 figures: 6.1.0
@@ -6307,14 +6307,14 @@ snapshots:
clean-stack: 1.3.0 clean-stack: 1.3.0
make-error-cause: 2.3.0 make-error-cause: 2.3.0
'@push.rocks/smartexit@1.0.23': '@push.rocks/smartexit@1.1.0':
dependencies: dependencies:
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
tree-kill: 1.2.2 tree-kill: 1.2.2
'@push.rocks/smartexit@1.1.0': '@push.rocks/smartexit@1.1.1':
dependencies: dependencies:
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
@@ -6579,7 +6579,7 @@ snapshots:
'@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)': '@push.rocks/smartpuppeteer@2.0.5(typescript@5.9.3)':
dependencies: dependencies:
'@push.rocks/smartdelay': 3.0.5 '@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) puppeteer: 24.36.0(typescript@5.9.3)
tree-kill: 1.2.2 tree-kill: 1.2.2
transitivePeerDependencies: transitivePeerDependencies:
@@ -6650,10 +6650,10 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
'@push.rocks/smartshell@3.3.0': '@push.rocks/smartshell@3.3.2':
dependencies: dependencies:
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartexit': 1.0.23 '@push.rocks/smartexit': 1.1.1
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
'@types/which': 3.0.4 '@types/which': 3.0.4
tree-kill: 1.2.2 tree-kill: 1.2.2

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tswatch', name: '@git.zone/tswatch',
version: '3.1.0', version: '3.2.1',
description: 'A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.' description: 'A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.'
} }

View File

@@ -44,6 +44,16 @@ export interface IBundleConfig {
watchPatterns?: string[]; watchPatterns?: string[];
/** If true, trigger server reload after bundling (default: true) */ /** If true, trigger server reload after bundling (default: true) */
triggerReload?: boolean; triggerReload?: boolean;
/** Output mode: 'bundle' writes JS, 'base64ts' writes base64-encoded TS (default: 'bundle') */
outputMode?: 'bundle' | 'base64ts';
/** Bundler to use (default: 'esbuild') */
bundler?: 'esbuild' | 'rolldown' | 'rspack';
/** Whether to produce a production build (default: false) */
production?: boolean;
/** Files to include alongside the bundle */
includeFiles?: (string | { from: string; to: string })[];
/** Max chars per line for base64ts output. 0 or undefined = unlimited */
maxLineLength?: number;
} }
/** /**

View File

@@ -18,6 +18,7 @@ export class TsWatch {
public typedserver: plugins.typedserver.TypedServer | null = null; public typedserver: plugins.typedserver.TypedServer | null = null;
private tsbundle = new plugins.tsbundle.TsBundle(); private tsbundle = new plugins.tsbundle.TsBundle();
private customBundleHandler = new plugins.tsbundle.CustomBundleHandler();
private htmlHandler = new plugins.tsbundle.HtmlHandler(); private htmlHandler = new plugins.tsbundle.HtmlHandler();
private assetsHandler = new plugins.tsbundle.AssetsHandler(); private assetsHandler = new plugins.tsbundle.AssetsHandler();
@@ -128,10 +129,22 @@ export class TsWatch {
} else if (fromPath.endsWith('/') || !fromPath.includes('.')) { } else if (fromPath.endsWith('/') || !fromPath.includes('.')) {
// Assets directory copy // Assets directory copy
await this.assetsHandler.processAssets(); await this.assetsHandler.processAssets();
} else if (bundleConfig.outputMode && bundleConfig.outputMode !== 'bundle') {
// Non-default outputMode (e.g. base64ts) — use CustomBundleHandler
await this.customBundleHandler.processSingleBundle({
from: bundleConfig.from,
to: bundleConfig.to,
outputMode: bundleConfig.outputMode,
bundler: bundleConfig.bundler || 'esbuild',
production: bundleConfig.production || false,
includeFiles: bundleConfig.includeFiles,
maxLineLength: bundleConfig.maxLineLength,
});
} else { } else {
// TypeScript bundling // Standard TypeScript bundling (default)
await this.tsbundle.build(paths.cwd, fromPath, toPath, { await this.tsbundle.build(paths.cwd, fromPath, toPath, {
bundler: 'esbuild', bundler: bundleConfig.bundler || 'esbuild',
production: bundleConfig.production || false,
}); });
} }

View File

@@ -154,7 +154,7 @@ export class Watcher {
if (this.options.commandToExecute) { if (this.options.commandToExecute) {
if (this.currentExecution && this.options.restart) { if (this.currentExecution && this.options.restart) {
logger.log('ok', `[${name}] restarting: ${this.options.commandToExecute}`); logger.log('ok', `[${name}] restarting: ${this.options.commandToExecute}`);
this.currentExecution.kill(); await this.currentExecution.kill();
} else if (!this.currentExecution) { } else if (!this.currentExecution) {
logger.log('ok', `[${name}] executing: ${this.options.commandToExecute}`); logger.log('ok', `[${name}] executing: ${this.options.commandToExecute}`);
} }
@@ -184,24 +184,32 @@ export class Watcher {
* this method sets up a clean exit strategy * this method sets up a clean exit strategy
*/ */
private async setupCleanup() { 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', () => { process.on('exit', () => {
console.log(''); if (this.currentExecution && !this.currentExecution.childProcess.killed) {
console.log('now exiting!'); const pid = this.currentExecution.childProcess.pid;
this.stop(); if (pid) {
process.exit(0); try {
process.kill(pid, 'SIGKILL');
} catch {
// Process may already be dead
}
}
}
}); });
process.on('SIGINT', () => { process.on('SIGINT', async () => {
console.log(''); console.log('');
console.log('ok! got SIGINT We are exiting! Just cleaning up to exit neatly :)'); console.log('ok! got SIGINT We are exiting! Just cleaning up to exit neatly :)');
this.stop(); await this.stop();
process.exit(0); process.exit(0);
}); });
// handle timeout // handle timeout
if (this.options.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!`); console.log(`timed out afer ${this.options.timeout} milliseconds! exiting!`);
this.stop(); await this.stop();
process.exit(0); process.exit(0);
}); });
} }
@@ -216,7 +224,7 @@ export class Watcher {
} }
await this.smartwatchInstance.stop(); await this.smartwatchInstance.stop();
if (this.currentExecution && !this.currentExecution.childProcess.killed) { if (this.currentExecution && !this.currentExecution.childProcess.killed) {
this.currentExecution.kill(); await this.currentExecution.kill();
} }
} }
} }