Compare commits

...

11 Commits

Author SHA1 Message Date
b62e380750 fix(deps): bump smartshell ^3.3.5 (detached:true) and smartexit ^2.0.2
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 39s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-04 00:50:14 +00:00
0eef560f1d fix(deps): pin smartexit to ^2.0.1 and smartshell to ^3.3.4 for PID tracking
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 39s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-04 00:17:35 +00:00
f7f42ff36c fix(watcher): always tree-kill on stop regardless of childProcess.killed flag
Some checks failed
Default (tags) / security (push) Successful in 33s
Default (tags) / test (push) Failing after 39s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
The direct bash child may already be dead from terminal SIGINT while
grandchildren (tsrun, devserver) are still running. Removing the .killed
guard ensures tree-kill always runs to clean up the entire process tree.
2026-03-04 00:09:21 +00:00
77d2e6ee57 v3.2.2
Some checks failed
Default (tags) / security (push) Successful in 44s
Default (tags) / test (push) Failing after 4m3s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-03 23:43:39 +00:00
e8bd8da3c7 fix(lifecycle): use ProcessLifecycle for coordinated shutdown
Replace per-Watcher SIGINT handlers with a single ProcessLifecycle.install()
in TsWatch.start(). This eliminates competing signal handler races that left
orphaned child processes. Add @push.rocks/smartexit as direct dependency.
2026-03-03 23:43:26 +00:00
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
c330420eea v3.1.0
Some checks failed
Default (tags) / security (push) Successful in 41s
Default (tags) / test (push) Failing after 4m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-05 14:58:27 +00:00
df8b164434 feat(dev-server): add no-cache headers to built-in development server; update docs and bump dependencies 2026-02-05 14:58:27 +00:00
10 changed files with 1806 additions and 2720 deletions

View File

@@ -1,5 +1,28 @@
# 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)
add no-cache headers to built-in development server; update docs and bump dependencies
- Introduce noCache: true in ts/tswatch.classes.tswatch.ts to send Cache-Control: no-store, no-cache during development (prevents browser caching).
- Update documentation to describe no-caching behavior (readme.md and readme.hints.md).
- Bump dependencies: @git.zone/tstest ^3.1.8, @types/node ^25.2.1, @push.rocks/npmextra ^5.3.3, @push.rocks/taskbuffer ^4.2.0.
## 2026-01-24 - 3.0.1 - fix(deps) ## 2026-01-24 - 3.0.1 - fix(deps)
downgrade @push.rocks/smartinteract to ^2.0.16 downgrade @push.rocks/smartinteract to ^2.0.16

View File

@@ -1,6 +1,6 @@
{ {
"name": "@git.zone/tswatch", "name": "@git.zone/tswatch",
"version": "3.0.1", "version": "3.2.5",
"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": {
@@ -19,25 +19,26 @@
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^4.1.2", "@git.zone/tsbuild": "^4.1.2",
"@git.zone/tstest": "^3.1.6", "@git.zone/tstest": "^3.1.8",
"@types/node": "^25.0.10" "@types/node": "^25.2.1"
}, },
"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",
"@push.rocks/npmextra": "^5.1.2", "@push.rocks/npmextra": "^5.3.3",
"@push.rocks/smartcli": "^4.0.20", "@push.rocks/smartcli": "^4.0.20",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartexit": "^2.0.2",
"@push.rocks/smartfs": "^1.3.1", "@push.rocks/smartfs": "^1.3.1",
"@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.5",
"@push.rocks/smartwatch": "^6.3.0", "@push.rocks/smartwatch": "^6.3.0",
"@push.rocks/taskbuffer": "^3.5.0" "@push.rocks/taskbuffer": "^4.2.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

4415
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -82,6 +82,7 @@ tswatch is now a config-driven TypeScript file watcher. Configuration is read fr
- Gzip compression - Gzip compression
- Live reload injection (configurable) - Live reload injection (configurable)
- SPA fallback support - SPA fallback support
- No-cache headers (prevents browser caching during development)
## Project Structure ## Project Structure

View File

@@ -371,6 +371,7 @@ Config:
The built-in development server (enabled in `element` and `website` presets) features: The built-in development server (enabled in `element` and `website` presets) features:
- **Live Reload** - Automatically refreshes browser on changes - **Live Reload** - Automatically refreshes browser on changes
- **No Caching** - Prevents browser caching during development (sends `Cache-Control: no-store, no-cache` headers)
- **CORS** - Cross-origin requests enabled - **CORS** - Cross-origin requests enabled
- **Compression** - Gzip compression for faster loading - **Compression** - Gzip compression for faster loading
- **SPA Fallback** - Single-page application routing support - **SPA Fallback** - Single-page application routing support

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tswatch', name: '@git.zone/tswatch',
version: '3.0.1', 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();
@@ -45,6 +46,14 @@ export class TsWatch {
public async start() { public async start() {
logger.log('info', 'Starting tswatch with config-driven mode'); logger.log('info', 'Starting tswatch with config-driven mode');
// Install global process lifecycle handlers (SIGINT, SIGTERM, etc.)
// This is the single authority for signal handling — no per-watcher handlers.
plugins.smartexit.ProcessLifecycle.install();
const exitInstance = new plugins.smartexit.SmartExit({ silent: true });
exitInstance.addCleanupFunction(async () => {
await this.stop();
});
// Start server if configured // Start server if configured
if (this.config.server?.enabled) { if (this.config.server?.enabled) {
await this.startServer(); await this.startServer();
@@ -89,6 +98,7 @@ export class TsWatch {
port: port, port: port,
compression: true, compression: true,
spaFallback: true, spaFallback: true,
noCache: true,
securityHeaders: { securityHeaders: {
crossOriginOpenerPolicy: 'same-origin', crossOriginOpenerPolicy: 'same-origin',
crossOriginEmbedderPolicy: 'require-corp', crossOriginEmbedderPolicy: 'require-corp',
@@ -127,10 +137,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}`);
} }
@@ -181,27 +181,14 @@ export class Watcher {
} }
/** /**
* this method sets up a clean exit strategy * Sets up timeout-based cleanup if configured.
* Signal handling (SIGINT/SIGTERM) is managed globally by ProcessLifecycle in TsWatch.
*/ */
private async setupCleanup() { private async setupCleanup() {
process.on('exit', () => {
console.log('');
console.log('now exiting!');
this.stop();
process.exit(0);
});
process.on('SIGINT', () => {
console.log('');
console.log('ok! got SIGINT We are exiting! Just cleaning up to exit neatly :)');
this.stop();
process.exit(0);
});
// 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 after ${this.options.timeout} milliseconds! exiting!`);
this.stop(); await this.stop();
process.exit(0); process.exit(0);
}); });
} }
@@ -215,8 +202,10 @@ export class Watcher {
clearTimeout(this.debounceTimer); clearTimeout(this.debounceTimer);
} }
await this.smartwatchInstance.stop(); await this.smartwatchInstance.stop();
if (this.currentExecution && !this.currentExecution.childProcess.killed) { if (this.currentExecution) {
this.currentExecution.kill(); // Always tree-kill — even if the direct child is dead (.killed === true),
// grandchildren (e.g. tsrun, devserver) may still be running.
await this.currentExecution.kill();
} }
} }
} }

View File

@@ -16,6 +16,7 @@ import * as lik from '@push.rocks/lik';
import * as npmextra from '@push.rocks/npmextra'; import * as npmextra from '@push.rocks/npmextra';
import * as smartcli from '@push.rocks/smartcli'; import * as smartcli from '@push.rocks/smartcli';
import * as smartdelay from '@push.rocks/smartdelay'; import * as smartdelay from '@push.rocks/smartdelay';
import * as smartexit from '@push.rocks/smartexit';
import * as smartfs from '@push.rocks/smartfs'; import * as smartfs from '@push.rocks/smartfs';
import * as smartinteract from '@push.rocks/smartinteract'; import * as smartinteract from '@push.rocks/smartinteract';
import * as smartlog from '@push.rocks/smartlog'; import * as smartlog from '@push.rocks/smartlog';
@@ -29,6 +30,7 @@ export {
npmextra, npmextra,
smartcli, smartcli,
smartdelay, smartdelay,
smartexit,
smartfs, smartfs,
smartinteract, smartinteract,
smartlog, smartlog,