Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11ada650e1 | |||
| f7443fabf1 | |||
| 5128802ecf | |||
| 37da233ddf | |||
| 6a3de7dba8 | |||
| 07cdee6bff | |||
| b62e380750 | |||
| 0eef560f1d | |||
| f7f42ff36c | |||
| 77d2e6ee57 | |||
| e8bd8da3c7 | |||
| 91b3e273de | |||
| e6a7b352f3 | |||
| 8c1b306313 | |||
| a893e7a771 |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"fileMatch": ["/.smartconfig.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
38
changelog.md
38
changelog.md
@@ -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
|
||||
|
||||
|
||||
2
license
2
license
@@ -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
|
||||
|
||||
28
package.json
28
package.json
@@ -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
6364
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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
132
readme.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user