Compare commits

..

15 Commits
v3.1.0 ... main

Author SHA1 Message Date
11ada650e1 v3.3.2
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-24 19:02:50 +00:00
f7443fabf1 fix(config): migrate project metadata and documentation to .smartconfig.json 2026-03-24 19:02:50 +00:00
5128802ecf v3.3.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-24 15:09:42 +00:00
37da233ddf fix(config): switch configuration loading and saving from npmextra.json to smartconfig.json 2026-03-24 15:09:42 +00:00
6a3de7dba8 v3.3.0
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 4m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 15:54:55 +00:00
07cdee6bff feat(server): use UtilityWebsiteServer for dev server, add domain option, update docs, and bump dependencies 2026-03-10 15:54:55 +00:00
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
17 changed files with 3272 additions and 3447 deletions

View File

@@ -1,7 +1,7 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"fileMatch": ["/.smartconfig.json"],
"schema": {
"type": "object",
"properties": {

View File

@@ -1,5 +1,43 @@
# Changelog
## 2026-03-24 - 3.3.2 - fix(config)
migrate project metadata and documentation to .smartconfig.json
- replace npmextra.json with .smartconfig.json in package files and documentation
- update dependency versions to align with the smartconfig-based setup
- allow watcher executions to be undefined until a process starts
## 2026-03-24 - 3.3.1 - fix(config)
switch configuration loading and saving from npmextra.json to smartconfig.json
- replace the @push.rocks/npmextra dependency with @push.rocks/smartconfig
- update config handling, CLI messaging, and init flow to use smartconfig.json consistently
## 2026-03-10 - 3.3.0 - feat(server)
use UtilityWebsiteServer for dev server, add domain option, update docs, and bump dependencies
- Replace TypedServer with UtilityWebsiteServer in TsWatch (start/stop/reload adapted)
- Add server.domain config option and update interfaces
- Update readme to document UtilityWebsiteServer features (PWA, brotli+gzip, service worker, live reload via WebSocket) and add bundle config hints (outputMode, bundler, production)
- Bump dependencies and devDependencies (notable: @api.global/typedserver ^8.4.2, @git.zone/tsbundle ^2.9.1, @push.rocks/* updates)
- Remove unused taskbuffer export from tswatch.plugins.ts
## 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

View File

@@ -1,4 +1,4 @@
Copyright (c) 2018 Lossless GmbH (hello@lossless.com)
Copyright (c) 2018 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
{
"name": "@git.zone/tswatch",
"version": "3.1.0",
"version": "3.3.2",
"private": false,
"description": "A development tool for automatically watching and re-compiling TypeScript projects upon detecting file changes, enhancing developer workflows.",
"exports": {
@@ -18,26 +18,26 @@
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.1.2",
"@git.zone/tstest": "^3.1.8",
"@types/node": "^25.2.1"
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tstest": "^3.5.1",
"@types/node": "^25.5.0"
},
"dependencies": {
"@api.global/typedserver": "^8.3.0",
"@git.zone/tsbundle": "^2.8.3",
"@api.global/typedserver": "^8.4.6",
"@git.zone/tsbundle": "^2.10.0",
"@git.zone/tsrun": "^2.0.1",
"@push.rocks/early": "^4.0.4",
"@push.rocks/lik": "^6.2.2",
"@push.rocks/npmextra": "^5.3.3",
"@push.rocks/lik": "^6.4.0",
"@push.rocks/smartcli": "^4.0.20",
"@push.rocks/smartconfig": "^6.0.1",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfs": "^1.3.1",
"@push.rocks/smartexit": "^2.0.3",
"@push.rocks/smartfs": "^1.5.0",
"@push.rocks/smartinteract": "^2.0.16",
"@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartlog": "^3.2.1",
"@push.rocks/smartlog-destination-local": "^9.0.2",
"@push.rocks/smartshell": "^3.3.0",
"@push.rocks/smartwatch": "^6.3.0",
"@push.rocks/taskbuffer": "^4.2.0"
"@push.rocks/smartshell": "^3.3.8",
"@push.rocks/smartwatch": "^6.4.0"
},
"files": [
"ts/**/*",
@@ -48,7 +48,7 @@
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
".smartconfig.json",
"readme.md"
],
"browserslist": [

6364
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,13 @@
## Core Architecture (v3.x - Config-Driven)
tswatch is now a config-driven TypeScript file watcher. Configuration is read from `npmextra.json` under the key `@git.zone/tswatch`.
tswatch is a config-driven TypeScript file watcher. Configuration is read from `.smartconfig.json` under the key `@git.zone/tswatch`.
### Key Classes
- **TsWatch**: Main orchestrator class, accepts `ITswatchConfig`
- **Watcher**: Handles individual file watching with debouncing and restart modes
- **ConfigHandler**: Loads and manages configuration from npmextra.json
- **ConfigHandler**: Loads and manages configuration from .smartconfig.json
- **TswatchInit**: Interactive wizard for creating configuration
### Configuration Structure
@@ -63,10 +63,10 @@ tswatch is now a config-driven TypeScript file watcher. Configuration is read fr
- Uses `@push.rocks/smartwatch` (v6.x) for file watching - class is `Smartwatch`
- Uses `@push.rocks/smartfs` (v1.x) for filesystem operations
- Uses `@push.rocks/npmextra` for reading npmextra.json config
- Uses `@push.rocks/smartconfig` for reading .smartconfig.json config
- Uses `@push.rocks/smartinteract` for the init wizard
- Uses `@git.zone/tsbundle` for bundling with esbuild
- Uses `@api.global/typedserver` for development server
- Uses `@api.global/typedserver` `UtilityWebsiteServer` for development server (wraps TypedServer with service worker, PWA manifest, and live reload)
### Watcher Features
@@ -77,12 +77,16 @@ tswatch is now a config-driven TypeScript file watcher. Configuration is read fr
### Server Features
- Uses `UtilityWebsiteServer` from `@api.global/typedserver` (not raw `TypedServer`)
- Port configurable (default: 3002)
- CORS enabled
- Gzip compression
- Live reload injection (configurable)
- Compression (brotli + gzip)
- Live reload via WebSocket (service worker + devtools injection)
- SPA fallback support
- No-cache headers (prevents browser caching during development)
- PWA manifest auto-generated
- Service worker version info handler
- Domain configurable (default: 'localhost')
## Project Structure

132
readme.md
View File

@@ -1,6 +1,6 @@
# @git.zone/tswatch
A powerful, config-driven TypeScript file watcher that automatically recompiles and executes your project when files change. Built for modern TypeScript development with zero-config presets and deep customization options.
A powerful, config-driven TypeScript file watcher that automatically recompiles and executes your project when files change. Built for modern TypeScript development with zero-config presets, smart bundling, a built-in dev server with live reload, and deep customization options.
## Issue Reporting and Security
@@ -8,14 +8,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
## ✨ Features
- 🔄 **Config-driven architecture** - Define watchers, bundles, and dev server in `npmextra.json`
-**Zero-config presets** - Get started instantly with `npm`, `element`, `service`, `website`, and `test` presets
- 🧙 **Interactive wizard** - Run `tswatch init` to generate configuration interactively
- 🌐 **Built-in dev server** - Live reload, CORS, compression, SPA fallback out of the box
- 📦 **Smart bundling** - TypeScript, HTML, and assets with esbuild integration
- 🔁 **Debounced execution** - Configurable debounce prevents command spam
- 🛑 **Process management** - Automatic restart or queue mode for long-running commands
- 🎯 **Glob patterns** - Watch any files with flexible pattern matching
- 🔄 **Config-driven architecture** Define watchers, bundles, and dev server in `.smartconfig.json`
-**Zero-config presets** Get started instantly with `npm`, `element`, `service`, `website`, and `test` presets
- 🧙 **Interactive wizard** Run `tswatch init` to generate configuration interactively
- 🌐 **Built-in dev server** Live reload, CORS, compression, SPA fallback out of the box
- 📦 **Smart bundling** TypeScript, HTML, and assets with esbuild/rolldown/rspack integration
- 🔁 **Debounced execution** Configurable debounce prevents command spam during rapid file saves
- 🛑 **Process management** Automatic restart or queue mode for long-running commands
- 🎯 **Glob patterns** Watch any files with flexible pattern matching
- 🧹 **Graceful shutdown** — Signal-aware process lifecycle with tree-kill for clean teardowns
## 📦 Installation
@@ -36,7 +37,7 @@ pnpm install --save-dev @git.zone/tswatch
tswatch init
```
The wizard will guide you through creating a `npmextra.json` configuration with your chosen preset or custom watchers.
The wizard guides you through creating a `.smartconfig.json` configuration with your chosen preset or custom watchers.
### Using Presets
@@ -46,11 +47,11 @@ If you already have a configuration, just run:
tswatch
```
This reads your config from `npmextra.json` under the `@git.zone/tswatch` key and starts watching.
This reads your config from `.smartconfig.json` under the `@git.zone/tswatch` key and starts watching.
## ⚙️ Configuration
tswatch uses `npmextra.json` for configuration. Add your config under the `@git.zone/tswatch` key:
tswatch uses `.smartconfig.json` for configuration. Add your config under the `@git.zone/tswatch` key:
```json
{
@@ -68,7 +69,7 @@ tswatch uses `npmextra.json` for configuration. Add your config under the `@git.
| `test` | Watch `ts/` and `test/`, run `npm run test2` on changes |
| `service` | Watch `ts/`, restart `npm run startTs` (ideal for backend services) |
| `element` | Dev server on port 3002 + bundling for web components |
| `website` | Full-stack: backend + frontend bundling + asset processing |
| `website` | Full-stack: backend restart + frontend bundling + asset processing |
### Full Configuration Schema
@@ -81,7 +82,8 @@ tswatch uses `npmextra.json` for configuration. Add your config under the `@git.
"enabled": true,
"port": 3002,
"serveDir": "./dist_watch/",
"liveReload": true
"liveReload": true,
"domain": "localhost"
},
"bundles": [
@@ -90,7 +92,9 @@ tswatch uses `npmextra.json` for configuration. Add your config under the `@git.
"from": "./ts_web/index.ts",
"to": "./dist_watch/bundle.js",
"watchPatterns": ["./ts_web/**/*"],
"triggerReload": true
"triggerReload": true,
"bundler": "esbuild",
"production": false
},
{
"name": "html",
@@ -134,13 +138,15 @@ tswatch uses `npmextra.json` for configuration. Add your config under the `@git.
| `server` | `IServerConfig` | Development server configuration |
| `bundles` | `IBundleConfig[]` | Bundle configurations |
> **Tip:** When a preset is specified alongside explicit `watchers`, `bundles`, or `server`, your explicit values take precedence over the preset defaults.
#### `IWatcherConfig`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `name` | `string` | *required* | Name for logging purposes |
| `watch` | `string \| string[]` | *required* | Glob pattern(s) to watch |
| `command` | `string` | - | Shell command to execute on changes |
| `command` | `string` | | Shell command to execute on changes |
| `restart` | `boolean` | `true` | Kill previous process before restarting |
| `debounce` | `number` | `300` | Debounce delay in milliseconds |
| `runOnStart` | `boolean` | `true` | Run the command immediately on start |
@@ -153,22 +159,28 @@ tswatch uses `npmextra.json` for configuration. Add your config under the `@git.
| `port` | `number` | `3002` | Server port |
| `serveDir` | `string` | `./dist_watch/` | Directory to serve |
| `liveReload` | `boolean` | `true` | Inject live reload script |
| `domain` | `string` | `localhost` | Domain name for the dev server |
#### `IBundleConfig`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `name` | `string` | - | Name for logging purposes |
| `name` | `string` | | Name for logging purposes |
| `from` | `string` | *required* | Entry point file |
| `to` | `string` | *required* | Output file |
| `watchPatterns` | `string[]` | - | Additional patterns to watch |
| `watchPatterns` | `string[]` | | Additional patterns to watch |
| `triggerReload` | `boolean` | `true` | Trigger server reload after bundling |
| `outputMode` | `'bundle' \| 'base64ts'` | `'bundle'` | Output mode for the bundle |
| `bundler` | `'esbuild' \| 'rolldown' \| 'rspack'` | `'esbuild'` | Bundler engine to use |
| `production` | `boolean` | `false` | Enable minification for production builds |
| `includeFiles` | `(string \| { from, to })[]` | — | Additional files to include alongside the bundle |
| `maxLineLength` | `number` | — | Max chars per line for `base64ts` output mode |
## 🛠️ CLI Commands
### `tswatch`
Runs with configuration from `npmextra.json`. If no config exists, launches the interactive wizard.
Runs with configuration from `.smartconfig.json`. If no config exists, launches the interactive wizard automatically.
```bash
tswatch
@@ -184,12 +196,11 @@ tswatch init
## 💻 Programmatic API
### Basic Usage with Config
### Basic Usage with Inline Config
```typescript
import { TsWatch } from '@git.zone/tswatch';
// Create TsWatch with inline configuration
const watcher = new TsWatch({
watchers: [
{
@@ -214,7 +225,7 @@ await watcher.stop();
```typescript
import { TsWatch } from '@git.zone/tswatch';
// Load configuration from npmextra.json
// Load configuration from .smartconfig.json
const watcher = TsWatch.fromConfig();
if (watcher) {
@@ -237,7 +248,7 @@ if (configHandler.hasConfig()) {
// Get available presets
const presets = configHandler.getPresetNames();
console.log(presets); // ['npm', 'test', 'service', 'element', 'website']
// => ['npm', 'test', 'service', 'element', 'website']
// Get a specific preset
const npmPreset = configHandler.getPreset('npm');
@@ -280,19 +291,18 @@ const watcher = new Watcher({
await watcher.start();
```
## 📁 Project Structures
## 📁 Project Structure Examples
### NPM Package / Node.js Library
```
project/
├── ts/ # TypeScript source files
├── test/ # Test files
├── package.json # With "test" script
└── npmextra.json # tswatch config
├── ts/ # TypeScript source files
├── test/ # Test files
├── package.json # With "test" script
└── .smartconfig.json # tswatch config
```
Config:
```json
{
"@git.zone/tswatch": {
@@ -305,12 +315,11 @@ Config:
```
project/
├── ts/ # TypeScript source files
├── package.json # With "startTs" script
└── npmextra.json
├── ts/ # TypeScript source files
├── package.json # With "startTs" script
└── .smartconfig.json
```
Config:
```json
{
"@git.zone/tswatch": {
@@ -323,16 +332,15 @@ Config:
```
project/
├── ts/ # Backend TypeScript (optional)
├── ts_web/ # Frontend TypeScript
├── ts/ # Backend TypeScript (optional)
├── ts_web/ # Frontend TypeScript
├── html/
│ ├── index.ts # Web entry point
│ ├── index.ts # Web entry point
│ └── index.html
├── dist_watch/ # Output (auto-created)
└── npmextra.json
├── dist_watch/ # Output (auto-created)
└── .smartconfig.json
```
Config:
```json
{
"@git.zone/tswatch": {
@@ -347,17 +355,16 @@ Access your project at `http://localhost:3002`
```
project/
├── ts/ # Backend TypeScript
├── ts_web/ # Frontend TypeScript
├── ts/ # Backend TypeScript
├── ts_web/ # Frontend TypeScript
│ └── index.ts
├── html/
│ └── index.html
├── assets/ # Static assets
├── dist_serve/ # Output
└── npmextra.json
├── assets/ # Static assets
├── dist_serve/ # Output
└── .smartconfig.json
```
Config:
```json
{
"@git.zone/tswatch": {
@@ -368,30 +375,37 @@ Config:
## 🌐 Development Server
The built-in development server (enabled in `element` and `website` presets) features:
The built-in development server (powered by `@api.global/typedserver`'s `UtilityWebsiteServer`) is enabled in `element` and `website` presets:
- **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
- **Compression** - Gzip compression for faster loading
- **SPA Fallback** - Single-page application routing support
- **Security Headers** - Cross-origin isolation headers
- 🔄 **Live Reload** — WebSocket-based instant browser refresh on changes (via service worker + devtools injection)
- 🚫 **No Caching** Prevents browser caching during development (`Cache-Control: no-store, no-cache` headers)
- 🌍 **CORS** Cross-origin requests enabled
- 🗜️ **Compression** — Brotli + gzip compression for faster loading
- 📱 **SPA Fallback** Single-page application routing support
- 🔒 **Security Headers** Cross-origin isolation (`COOP`, `COEP`)
- 📦 **PWA Manifest** — Auto-generated Progressive Web App manifest
-**Service Worker** — Built-in service worker version info for cache busting
Default configuration:
- **Port**: 3002
- **Serve Directory**: `./dist_watch/`
- **Live Reload**: Enabled
| Setting | Default |
|---------|---------|
| Port | `3002` |
| Serve Directory | `./dist_watch/` |
| Live Reload | Enabled |
| Domain | `localhost` |
## 🔧 Configuration Tips
1. **Use presets for common workflows** - They're battle-tested and cover most use cases
2. **Customize with explicit config** - Override preset defaults by adding explicit `watchers`, `bundles`, or `server` config
3. **Debounce wisely** - Default 300ms works well; increase for slower builds
1. **Use presets for common workflows** They're battle-tested and cover most use cases
2. **Customize with explicit config** Override preset defaults by adding explicit `watchers`, `bundles`, or `server` config
3. **Debounce wisely** Default 300ms works well; increase for slower builds
4. **Use `restart: false`** for one-shot commands (like builds) and `restart: true` for long-running processes (like servers)
5. **Multiple bundlers** — Choose between `esbuild` (fastest), `rolldown` (smallest output), or `rspack` (webpack-compatible) per bundle
## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

View File

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

View File

@@ -28,6 +28,8 @@ export interface IServerConfig {
serveDir?: string;
/** Whether to inject live reload script (default: true) */
liveReload?: boolean;
/** Domain name for the dev server (default: 'localhost') */
domain?: string;
}
/**
@@ -44,6 +46,16 @@ export interface IBundleConfig {
watchPatterns?: string[];
/** If true, trigger server reload after bundling (default: true) */
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

@@ -119,28 +119,28 @@ const presets: Record<string, interfaces.ITswatchConfig> = {
* Handles loading and managing tswatch configuration
*/
export class ConfigHandler {
private npmextra: plugins.npmextra.Npmextra;
private smartconfig: plugins.smartconfig.Smartconfig;
private cwd: string;
constructor(cwdArg?: string) {
this.cwd = cwdArg || paths.cwd;
this.npmextra = new plugins.npmextra.Npmextra(this.cwd);
this.smartconfig = new plugins.smartconfig.Smartconfig(this.cwd);
}
/**
* Check if a tswatch configuration exists
*/
public hasConfig(): boolean {
const config = this.npmextra.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
const config = this.smartconfig.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
return config !== null;
}
/**
* Load configuration from npmextra.json
* Load configuration from smartconfig.json
* If a preset is specified, merge preset defaults with user overrides
*/
public loadConfig(): interfaces.ITswatchConfig | null {
const config = this.npmextra.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
const config = this.smartconfig.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
if (!config) {
return null;
@@ -177,7 +177,7 @@ export class ConfigHandler {
}
/**
* Get the config key for npmextra.json
* Get the config key for smartconfig.json
*/
public getConfigKey(): string {
return CONFIG_KEY;

View File

@@ -9,15 +9,16 @@ import { logger } from './tswatch.logging.js';
/**
* TsWatch - Config-driven file watcher
*
* Reads configuration from npmextra.json under the key '@git.zone/tswatch'
* Reads configuration from smartconfig.json under the key '@git.zone/tswatch'
* and sets up watchers, bundles, and dev server accordingly.
*/
export class TsWatch {
public config: interfaces.ITswatchConfig;
public watcherMap = new plugins.lik.ObjectMap<Watcher>();
public typedserver: plugins.typedserver.TypedServer | null = null;
public utilityWebsiteServer: plugins.typedserver.utilityservers.UtilityWebsiteServer | null = null;
private tsbundle = new plugins.tsbundle.TsBundle();
private customBundleHandler = new plugins.tsbundle.CustomBundleHandler();
private htmlHandler = new plugins.tsbundle.HtmlHandler();
private assetsHandler = new plugins.tsbundle.AssetsHandler();
@@ -26,7 +27,7 @@ export class TsWatch {
}
/**
* Create TsWatch from npmextra.json configuration
* Create TsWatch from smartconfig.json configuration
*/
public static fromConfig(cwdArg?: string): TsWatch | null {
const configHandler = new ConfigHandler(cwdArg);
@@ -45,6 +46,14 @@ export class TsWatch {
public async start() {
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
if (this.config.server?.enabled) {
await this.startServer();
@@ -66,8 +75,8 @@ export class TsWatch {
});
// Start server after watchers are ready
if (this.typedserver) {
await this.typedserver.start();
if (this.utilityWebsiteServer) {
await this.utilityWebsiteServer.start(this.config.server?.port || 3002);
logger.log('ok', `Dev server started on port ${this.config.server?.port || 3002}`);
}
}
@@ -82,14 +91,14 @@ export class TsWatch {
logger.log('info', `Setting up dev server on port ${port}, serving ${serveDir}`);
this.typedserver = new plugins.typedserver.TypedServer({
cors: true,
injectReload: serverConfig.liveReload !== false,
this.utilityWebsiteServer = new plugins.typedserver.utilityservers.UtilityWebsiteServer({
domain: serverConfig.domain || 'localhost',
serveDir: plugins.path.join(paths.cwd, serveDir),
port: port,
compression: true,
cors: true,
spaFallback: true,
noCache: true,
injectReload: serverConfig.liveReload !== false,
securityHeaders: {
crossOriginOpenerPolicy: 'same-origin',
crossOriginEmbedderPolicy: 'require-corp',
@@ -128,18 +137,30 @@ export class TsWatch {
} else if (fromPath.endsWith('/') || !fromPath.includes('.')) {
// Assets directory copy
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 {
// TypeScript bundling
// Standard TypeScript bundling (default)
await this.tsbundle.build(paths.cwd, fromPath, toPath, {
bundler: 'esbuild',
bundler: bundleConfig.bundler || 'esbuild',
production: bundleConfig.production || false,
});
}
logger.log('ok', `[${name}] bundle complete`);
// Trigger reload if configured and server is running
if (bundleConfig.triggerReload !== false && this.typedserver) {
await this.typedserver.reload();
if (bundleConfig.triggerReload !== false && this.utilityWebsiteServer?.typedserver) {
await this.utilityWebsiteServer.typedserver.reload();
}
};
@@ -190,8 +211,8 @@ export class TsWatch {
* stops the execution of any active Watchers
*/
public async stop() {
if (this.typedserver) {
await this.typedserver.stop();
if (this.utilityWebsiteServer) {
await this.utilityWebsiteServer.stop();
}
await this.watcherMap.forEach(async (watcher) => {
await watcher.stop();

View File

@@ -32,7 +32,7 @@ export class Watcher {
executor: 'bash',
});
private currentExecution: plugins.smartshell.IExecResultStreaming;
private currentExecution: plugins.smartshell.IExecResultStreaming | undefined;
private smartwatchInstance = new plugins.smartwatch.Smartwatch([]);
private options: IWatcherConstructorOptions;
private debounceTimer: NodeJS.Timeout | null = null;
@@ -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}`);
}
@@ -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() {
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) {
plugins.smartdelay.delayFor(this.options.timeout).then(() => {
console.log(`timed out afer ${this.options.timeout} milliseconds! exiting!`);
this.stop();
plugins.smartdelay.delayFor(this.options.timeout).then(async () => {
console.log(`timed out after ${this.options.timeout} milliseconds! exiting!`);
await this.stop();
process.exit(0);
});
}
@@ -215,8 +202,10 @@ export class Watcher {
clearTimeout(this.debounceTimer);
}
await this.smartwatchInstance.stop();
if (this.currentExecution && !this.currentExecution.childProcess.killed) {
this.currentExecution.kill();
if (this.currentExecution) {
// 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

@@ -18,7 +18,7 @@ tswatchCli.standardCommand().subscribe(async (argvArg) => {
// Config exists - run with it
const tsWatch = TsWatch.fromConfig();
if (tsWatch) {
logger.log('info', 'Starting tswatch with configuration from npmextra.json');
logger.log('info', 'Starting tswatch with configuration from smartconfig.json');
await tsWatch.start();
} else {
logger.log('error', 'Failed to load configuration');
@@ -26,7 +26,7 @@ tswatchCli.standardCommand().subscribe(async (argvArg) => {
}
} else {
// No config - launch wizard
logger.log('info', 'No tswatch configuration found in npmextra.json');
logger.log('info', 'No tswatch configuration found in smartconfig.json');
const config = await runInit();
if (config) {
// Run with the newly created config

View File

@@ -56,10 +56,10 @@ export class TswatchInit {
config = { ...preset, preset: template as interfaces.ITswatchConfig['preset'] };
}
// Save to npmextra.json
// Save to smartconfig.json
await this.saveConfig(config);
console.log('\nConfiguration saved to npmextra.json');
console.log('\nConfiguration saved to smartconfig.json');
console.log('Run "tswatch" to start watching.\n');
return config;
@@ -166,16 +166,16 @@ export class TswatchInit {
}
/**
* Save configuration to npmextra.json
* Save configuration to smartconfig.json
*/
private async saveConfig(config: interfaces.ITswatchConfig): Promise<void> {
const npmextraPath = plugins.path.join(paths.cwd, 'npmextra.json');
const smartconfigPath = plugins.path.join(paths.cwd, 'smartconfig.json');
// Read existing npmextra.json if it exists
// Read existing smartconfig.json if it exists
let existingConfig: Record<string, any> = {};
try {
const smartfsInstance = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
const content = await smartfsInstance.file(npmextraPath).encoding('utf8').read() as string;
const content = await smartfsInstance.file(smartconfigPath).encoding('utf8').read() as string;
existingConfig = JSON.parse(content);
} catch {
// File doesn't exist or is invalid, start fresh
@@ -186,7 +186,7 @@ export class TswatchInit {
// Write back
const smartfsInstance = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
await smartfsInstance.file(npmextraPath).encoding('utf8').write(JSON.stringify(existingConfig, null, 2));
await smartfsInstance.file(smartconfigPath).encoding('utf8').write(JSON.stringify(existingConfig, null, 2));
}
}

View File

@@ -13,27 +13,27 @@ export { typedserver };
// @push.rocks scope
import * as lik from '@push.rocks/lik';
import * as npmextra from '@push.rocks/npmextra';
import * as smartconfig from '@push.rocks/smartconfig';
import * as smartcli from '@push.rocks/smartcli';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartexit from '@push.rocks/smartexit';
import * as smartfs from '@push.rocks/smartfs';
import * as smartinteract from '@push.rocks/smartinteract';
import * as smartlog from '@push.rocks/smartlog';
import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
import * as smartshell from '@push.rocks/smartshell';
import * as smartwatch from '@push.rocks/smartwatch';
import * as taskbuffer from '@push.rocks/taskbuffer';
export {
lik,
npmextra,
smartconfig,
smartcli,
smartdelay,
smartexit,
smartfs,
smartinteract,
smartlog,
smartlogDestinationLocal,
smartshell,
smartwatch,
taskbuffer,
};

View File

@@ -7,7 +7,6 @@
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": {}
},
"exclude": [