@gachlab/devup 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,47 +1,63 @@
1
1
  # devup
2
2
 
3
3
  [![CI](https://github.com/gachlab/devup/actions/workflows/ci.yml/badge.svg)](https://github.com/gachlab/devup/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@gachlab/devup.svg)](https://www.npmjs.com/package/@gachlab/devup)
4
5
 
5
- A terminal UI dev stack runner for Node.js monorepos. Define your services in a config file, and devup handles the rest: phased startup, health checks, lazy on-demand proxies, process stats, and reverse proxy config generation — all in a single TUI dashboard.
6
+ A terminal UI dev-stack runner for Node.js monorepos. Define your services in a config file; devup handles **phased startup, health checks, lazy on-demand proxies, build hooks, persistent logs, reverse-proxy config generation, and a JSON-RPC control plane** — all from a single TUI dashboard.
6
7
 
7
8
  Built with TypeScript 6, Ink (React for terminals), and zero test dependencies (uses `node:test` natively).
8
9
 
9
10
  ## Features
10
11
 
11
- - **Phased startup** — boot services in dependency order with automatic port readiness detection
12
- - **Lazy mode** — only start services when they receive traffic. Idle services shut down after a configurable timeout (respects active connections, no killing mid-WebSocket)
13
- - **TUI dashboard** — live logs and process stats (CPU, memory, health, errors, restarts) in a split-panel terminal UI with scrolling, search, filter, and auto-pause when you scroll up
14
- - **Cross-platform** — Linux, macOS, and Windows. Platform-specific process management, stats collection, and browser opening
15
- - **HTTP or TCP health checks** — per-service `healthCheck` config: TCP probe (default) or HTTP GET with configurable path and status codes
16
- - **Reverse proxy config** — generate Traefik, Nginx, or Caddy config from running services. Health-aware: only routes to healthy services
17
- - **Persistent logs** — every line streamed to `~/.devup/logs/<project>/<svc>.log` with rotation on each launch
18
- - **CI-ready** — `--dry-run` prints the boot plan; `--once` boots, waits for readiness, exits `0/1` without a TUI
19
- - **Project-agnostic** — works with any Node.js monorepo. Your project defines a `devup.config.ts`, devup does the rest
20
- - **npm install management** — automatic dependency installation with hash-based stamps to skip redundant installs
21
- - **Auto-restart with backoff** — crashed services restart automatically with exponential backoff (2s 4s → 8s), max 3 attempts; manual restart resets the counter
22
- - **Port conflict detection** — checks if a port is already in use before starting a service; also validates lazy `port + 10000` collisions at config-load time
23
- - **Pre-flight validation** — `--watch-path` arguments are checked against disk before spawn so a stale config after a rebase fails loudly instead of silently
24
- - **Subcommands** — `devup logs <svc>`, `devup install`, `devup status` work without launching the TUI
12
+ ### Orchestration
13
+ - **Phased startup** — boot services in dependency order with automatic port readiness detection.
14
+ - **Lazy mode** — only start services when they receive traffic; idle services shut down after a configurable timeout (respects active connections, no killing mid-WebSocket).
15
+ - **Profiles** — name common service subsets in config; boot with `devup --profile check-in`.
16
+ - **External hooks** — start `docker compose` (DBs, queues) before phase 0, with health gating and `stopCmd` on shutdown.
17
+ - **Build hooks** — `preBuild` (must succeed before spawn) and `watchBuild` (runs alongside the service), both managed by devup with kill-tree cleanup.
18
+ - **Hot reload** — `--watch-config` diffs `devup.config.*` on save and applies add/remove/restart without killing the TUI.
19
+ - **Auto-restart with backoff** — crashed services restart automatically with exponential backoff (2s 4s → 8s), max 3 attempts; manual restart resets the counter; crash-loop badge surfaces services that need attention.
20
+ - **Pre-flight validation** — `--watch-path` arguments are checked against disk before spawn so a stale config after a rebase fails loudly instead of silently.
21
+ - **Port-conflict detection** — refuses to boot when ports collide, including lazy-mode `port + 10000` clashes.
22
+ - **npm install management** — automatic dependency installation with hash-based stamps to skip redundant installs.
23
+
24
+ ### Readiness
25
+ - **TCP or HTTP health checks** — per-service `healthCheck` with configurable path, status codes, timeout, and `startPeriod` grace window.
26
+ - **`readyPattern`** — regex matched against stdout/stderr; the first match flips the service to `up`, short-circuiting the next health poll.
27
+ - **`errorPattern`** — only matching stderr lines bump the error counter (filters info-on-stderr noise).
28
+
29
+ ### TUI
30
+ - **Live logs and process stats** — CPU, memory, health, errors, restarts in a split-panel terminal UI.
31
+ - **Scrolling, search, filter** — ↑/↓/PgUp/PgDn/Home/End; auto-pause when you scroll up; regex search with `/pattern/flags`.
32
+ - **Level filter** — `L` cycles `all → error → warn+error`.
33
+ - **Verbose stats** — `v` expands rows to show resolved `cmd`/args/env (secrets redacted).
34
+ - **Fuzzy filter** — service-picker modals (`f`/`r`/`o`) filter as you type.
35
+ - **Contextual tips** — one-liner nudges at teachable moments (high log volume, crash loop), once per session.
36
+ - **RAM watchdog** — banner surfaces when system RAM crosses 80% with top consumers (hysteresis: clears below 75%).
37
+ - **TLS-aware open** — `o` opens `https://<sub>.<domain>` when `--proxy` is active and TLS is on.
38
+
39
+ ### Operations
40
+ - **Persistent logs** — every line streamed to `~/.devup/logs/<project>/<svc>.log` with rotation on each launch.
41
+ - **Subcommands** — `devup logs <svc> [--follow]`, `devup install`, `devup status`, `devup help` work without launching the TUI.
42
+ - **CI-ready** — `--dry-run` prints the resolved boot plan; `--once` boots, waits for readiness, exits `0/1` without a TUI.
43
+ - **Reverse-proxy config** — generate Traefik, Nginx, or Caddy config from running services; health-aware.
44
+ - **Unix-socket control plane** — local JSON-RPC at `~/.devup/sock-<project>.sock` (chmod 0600); `status`, `restart`, `stop`, `logs.tail`, `ping`.
45
+ - **Cross-platform** — Linux, macOS, and Windows. Platform-specific process management, stats collection, and browser opening.
25
46
 
26
47
  ## Quick start
27
48
 
28
- ### 1. Install
29
-
30
49
  ```bash
31
50
  npm install -D @gachlab/devup
32
51
  ```
33
52
 
34
- ### 2. Create config
35
-
36
- Create `devup.config.ts` in your project root:
53
+ Create `devup.config.ts`:
37
54
 
38
55
  ```typescript
39
56
  import { defineConfig } from '@gachlab/devup';
40
57
 
41
58
  export default defineConfig({
42
- name: 'MyProject',
59
+ name: 'MyApp',
43
60
  icon: '🚀',
44
- envFile: '.env',
45
61
 
46
62
  services: [
47
63
  {
@@ -52,7 +68,7 @@ export default defineConfig({
52
68
  type: 'api',
53
69
  port: 3000,
54
70
  phase: 0,
55
- maxMem: 256,
71
+ readyPattern: 'listening on',
56
72
  },
57
73
  {
58
74
  name: 'web',
@@ -62,447 +78,45 @@ export default defineConfig({
62
78
  type: 'web',
63
79
  port: 4200,
64
80
  phase: 1,
65
- maxMem: 512,
81
+ readyPattern: 'ready in',
66
82
  },
67
83
  ],
68
84
  });
69
85
  ```
70
86
 
71
- ### 3. Run
87
+ Run:
72
88
 
73
89
  ```bash
74
90
  npx devup
75
91
  ```
76
92
 
77
- ## Config reference
78
-
79
- ### `DevStackConfig`
80
-
81
- | Field | Type | Required | Description |
82
- |---|---|---|---|
83
- | `name` | `string` | ✅ | Project name shown in the TUI header |
84
- | `icon` | `string` | | Emoji shown before the project name. Default: `📦` |
85
- | `envFile` | `string` | | Path to `.env` file relative to project root. Default: `.env` |
86
- | `env` | `Record<string, string>` | | Extra environment variables. Won't overwrite existing ones |
87
- | `services` | `ServiceConfig[]` | ✅ | List of services to manage |
88
- | `lazy` | `LazyConfig` | | Lazy mode configuration |
89
- | `proxy` | `ProxyConfig` | | Reverse proxy config generation |
90
- | `profiles` | `Record<string, string[]>` | | Named lists of services to boot. Select with `--profile <name>` |
91
- | `external` | `ExternalService[]` | | Dependencies started **before** phase 0 (databases, queues, etc.) |
92
-
93
- #### Profiles
94
-
95
- A profile is a named subset of `services`. Instead of memorising service names for `--services`, you give your common workflows a name:
96
-
97
- ```typescript
98
- export default defineConfig({
99
- // ...
100
- profiles: {
101
- 'check-in': ['configurations-api', 'authorization-api', 'app-api', 'check-in-api', 'app-web'],
102
- 'pickup': ['configurations-api', 'pickup-api', 'pickup-drivers-web'],
103
- 'frontends': ['app-web', 'admin-web', 'staff-web'],
104
- },
105
- });
106
- ```
107
-
108
- Then `devup --profile check-in` boots that subset. Composable with `--skip`. The validator catches typos at config-load time. Unknown profile names produce a friendly error listing what's available.
109
-
110
- ### `ServiceConfig`
111
-
112
- | Field | Type | Required | Description |
113
- |---|---|---|---|
114
- | `name` | `string` | ✅ | Unique service name |
115
- | `cwd` | `string` | ✅ | Working directory relative to project root |
116
- | `cmd` | `string` | ✅ | Command to run (`node`, `npx`, etc.) |
117
- | `args` | `string[]` | ✅ | Command arguments |
118
- | `type` | `'api' \| 'web'` | ✅ | Service type. APIs get health-checked; webs are assumed ready after start |
119
- | `port` | `number` | ✅ | Port the service listens on. Must be unique |
120
- | `phase` | `number` | ✅ | Startup phase (0 = first). Services in the same phase start together; devup waits for all APIs in a phase to be ready before starting the next phase |
121
- | `maxMem` | `number` | | Max memory in MB. Injects `--max-old-space-size` for `node` commands, or `NODE_OPTIONS` for `npx` |
122
- | `preBuild` | `string` | | Shell command run **before** the service starts. If it exits non-zero the service is marked `crashed` and skipped. Output is tagged `[build]` in the logs panel |
123
- | `watchBuild` | `string` | | Shell command spawned **alongside** the service (e.g. `npx tsup --watch`). Killed automatically when the service stops/restarts. Output is tagged `[watch]` in the logs panel |
124
- | `nodeArgs` | `string[]` | | Extra Node.js arguments |
125
- | `extraEnv` | `Record<string, string>` | | Extra environment variables for this service |
126
- | `healthCheck` | `HealthCheckConfig` | | Override the readiness check for this service. Default: TCP probe on `port` |
127
- | `readyPattern` | `string` | | Regex matched against stdout/stderr lines. On match, service is marked `up` immediately, short-circuiting the next health-check poll. Plain string or vim-style `/pattern/flags`. Case-insensitive by default |
128
-
129
- ### `HealthCheckConfig`
130
-
131
- | Field | Type | Required | Description |
132
- |---|---|---|---|
133
- | `type` | `'tcp' \| 'http'` | ✅ | `tcp` (default) just opens a socket; `http` issues an HTTP GET and inspects the status code |
134
- | `path` | `string` | | HTTP-only request path. Default: `/`. Must start with `/` |
135
- | `expect` | `number \| number[]` | | HTTP-only acceptable status code(s). Default: any 2xx (200-299) |
136
- | `host` | `string` | | Override target host for the HTTP check. Default: `127.0.0.1` |
137
- | `timeoutMs` | `number` | | Per-check socket/request timeout in ms. Default: `2000` |
138
-
139
- ```typescript
140
- // Wait for /healthz to return 200 before considering the service up
141
- { name: 'api', /* ... */, healthCheck: { type: 'http', path: '/healthz' } }
142
-
143
- // Accept 200 or 204
144
- { name: 'api', /* ... */, healthCheck: { type: 'http', path: '/health', expect: [200, 204] } }
145
- ```
146
-
147
- #### readyPattern
148
-
149
- A regex matched against each line of the service's stdout/stderr. The first matching line flips the service to `up` immediately, without waiting for the next 3-second health-check poll. The periodic health-check still runs as a fallback. Useful for tools that print recognisable boot lines:
150
-
151
- ```typescript
152
- // Vite: "ready in 423 ms"
153
- { name: 'web', cmd: 'npx', args: ['vite'], readyPattern: 'ready in' }
154
-
155
- // Angular: "Compiled successfully"
156
- { name: 'app', cmd: 'npx', args: ['ng', 'serve'], readyPattern: '/compiled successfully/i' }
157
-
158
- // Fastify: "Server listening at"
159
- { name: 'api', cmd: 'node', args: ['index.js'], readyPattern: 'server listening' }
160
- ```
161
-
162
- Both plain strings and vim-style `/pattern/flags` are accepted. Strings are case-insensitive by default.
163
-
164
- #### Build hooks: `preBuild` and `watchBuild`
165
-
166
- For TypeScript services or anything that needs a compile step, two hooks remove the usual `sh -c 'npm run build && (npx tsup --watch &) && node ...'` workaround:
167
-
168
- ```typescript
169
- {
170
- name: 'orders-api',
171
- cwd: 'orders/api',
172
- cmd: 'node', args: ['dist/index.js'],
173
- type: 'api', port: 3031, phase: 1,
174
- preBuild: 'npm run build', // runs once, must succeed before the service starts
175
- watchBuild: 'npx tsup --watch', // runs in parallel; killed when the service stops
176
- }
177
- ```
178
-
179
- - **`preBuild`** runs synchronously before the spawn. Non-zero exit marks the service as `crashed` (the spawn is skipped). Output is tagged `[build]` in the logs panel.
180
- - **`watchBuild`** runs as a sibling process. devup kills it when the service stops, restarts, or the TUI exits. Output is tagged `[watch]`.
181
-
182
- Both are passed through the platform shell (`sh -c` on Unix, `cmd /c` on Windows), so pipes and `&&` work.
183
-
184
- ### `ExternalService`
185
-
186
- External dependencies (databases, message queues, etc.) launched **before** phase 0. devup waits for each `healthCheck` (when set) to pass before starting any service. Typical use: `docker compose up -d`.
187
-
188
- | Field | Type | Required | Description |
189
- |---|---|---|---|
190
- | `name` | `string` | ✅ | Friendly name shown in logs (logs use the prefix `ext:<name>`) |
191
- | `cmd` | `string` | ✅ | Shell command (passed through `sh -c` / `cmd /c`). Pipes and `&&` work |
192
- | `cwd` | `string` | | Working directory relative to the project root |
193
- | `extraEnv` | `Record<string, string>` | | Extra env vars merged on top of the project env |
194
- | `healthCheck` | `HealthCheckConfig` | | Readiness probe. devup waits for `up` before starting phase 0 |
195
- | `port` | `number` | when `healthCheck` is set | Port to probe |
196
- | `startTimeout` | `number` | | Max seconds to wait for healthCheck. Default: `60` |
197
- | `stopCmd` | `string` | | Shell command run on shutdown (e.g. `docker compose down`) |
198
-
199
- ```typescript
200
- export default defineConfig({
201
- // ...
202
- external: [
203
- {
204
- name: 'mongo',
205
- cmd: 'docker compose -f docker-compose.dev.yml up -d mongo',
206
- port: 27017,
207
- healthCheck: { type: 'tcp' },
208
- stopCmd: 'docker compose -f docker-compose.dev.yml stop mongo',
209
- },
210
- {
211
- name: 'redis',
212
- cmd: 'docker compose -f docker-compose.dev.yml up -d redis',
213
- port: 6379,
214
- healthCheck: { type: 'tcp' },
215
- },
216
- ],
217
- });
218
- ```
219
-
220
- If any external fails its healthCheck within `startTimeout` seconds, devup aborts the boot, runs each external's `stopCmd` (best-effort), and exits.
221
-
222
- ### `LazyConfig`
223
-
224
- | Field | Type | Required | Description |
225
- |---|---|---|---|
226
- | `alwaysOn` | `string[]` | ✅ | Service names that always start immediately |
227
- | `timeout` | `number` | | Minutes of inactivity before stopping a lazy service. Default: `10` |
228
-
229
- When lazy mode is active (default), services not in `alwaysOn` start a TCP proxy on their port. The real service only boots when something connects to that port. After `timeout` minutes of no connections, the service shuts down and returns to idle.
230
-
231
- ### `ProxyConfig`
232
-
233
- | Field | Type | Required | Description |
234
- |---|---|---|---|
235
- | `provider` | `string` | ✅ | Proxy provider name. One of: `'traefik'`, `'nginx'`, `'caddy'` |
236
- | `routes` | `Record<string, string>` | ✅ | Map of service name → subdomain. Empty string = root domain |
237
- | `confPath` | `string` | | Path to write the config file. Default: `~/.traefik/traefik_conf.yaml` |
238
- | `host` | `string` | | Target host for proxy URLs. Default: auto-detected per platform |
239
- | `tls` | `boolean` | | Enable TLS config. Default: `true` |
240
- | `entrypoint` | `string` | | Proxy entrypoint name. Default: `'websecure'` |
241
-
242
- The proxy config is only generated when `--proxy` is passed on the CLI. Only services with `health === 'up'` are included in the generated config.
243
-
244
- ## CLI subcommands
245
-
246
- ```
247
- devup # launch the interactive TUI (default)
248
- devup logs <service> [--follow] # print the persisted log file
249
- devup install # parallel npm install across services
250
- devup status # health-check every service in config
251
- devup help [<subcommand>] # show usage
252
- devup --version # print version
253
- devup --help # print flag summary
254
- ```
255
-
256
- All subcommands need the project's `devup.config.ts` to be resolvable (use `--config <path>` to override).
257
-
258
- ## CLI flags
259
-
260
- ```
261
- devup [options]
262
- ```
263
-
264
- ### Service selection
265
-
266
- | Flag | Description |
267
- |---|---|
268
- | `--only apis` | Only start API services |
269
- | `--only webs` | Only start web services |
270
- | `--services api,web,auth` | Start only the named services |
271
- | `--profile <name>` | Start the services in the named profile (see `profiles` in the config) |
272
- | `--skip tasks-api,pickup-api` | Start everything except these |
273
- | `--config path/to/config.ts` | Use a custom config file |
274
-
275
- ### Lazy mode
276
-
277
- | Flag | Description |
278
- |---|---|
279
- | `--lazy` | Enable lazy mode (default) |
280
- | `--no-lazy` | Disable lazy mode — start everything immediately |
281
- | `--timeout 15` | Idle timeout in minutes (default: 10) |
282
-
283
- ### Reverse proxy
284
-
285
- | Flag | Description |
286
- |---|---|
287
- | `--proxy` | Enable proxy config generation |
288
- | `--proxy-host 127.0.0.1` | Override target host |
289
- | `--proxy-conf /path/to/file` | Override config file path |
290
- | `--proxy-tls` | Enable TLS (default) |
291
- | `--no-proxy-tls` | Disable TLS |
292
- | `--proxy-entrypoint web` | Override entrypoint name (Traefik-only) |
293
-
294
- ### CI / scripting
295
-
296
- | Flag | Description |
297
- |---|---|
298
- | `--dry-run` | Print the resolved boot plan (phases, commands, proxy YAML) and exit. Doesn't start any process |
299
- | `--once` | Boot services, wait until every API is healthy, then exit `0` (or `1` on timeout). Skips the TUI — meant for CI smoke tests |
300
- | `--once-timeout 60` | Max seconds to wait in `--once` mode. Default: `90` |
301
-
302
- ### Log files
93
+ See [docs/getting-started.md](./docs/getting-started.md) for a full walkthrough.
303
94
 
304
- | Flag | Description |
305
- |---|---|
306
- | `--no-log-file` | Disable persistent log files |
307
- | `--log-dir /path` | Override log root. Default: `~/.devup/logs/<project>/<service>.log` |
95
+ ## Documentation
308
96
 
309
- devup writes a separate `.log` file per service to disk. Lines are prefixed with an ISO timestamp. On each fresh launch the previous file is rotated to `<service>.log.prev`, so you always have at most two runs of history per service.
97
+ The comprehensive guide lives in [docs/](./docs/README.md):
310
98
 
311
- ## TUI keybindings
312
-
313
- | Key | Action |
314
- |---|---|
315
- | `q` / `Ctrl+C` | Quit and stop all services |
316
- | `Tab` | Switch focus between Logs and Stats panels |
317
- | `↑` / `↓` | Scroll the focused panel by 1 line/row |
318
- | `[` / `]` (or `Ctrl+B` / `Ctrl+F`) | Page up / page down |
319
- | `Ctrl+A` / `Ctrl+E` | Jump to top / bottom of the focused panel |
320
- | `f` | Filter logs by service |
321
- | `a` | Show all logs (clear filter) |
322
- | `/` | Search in logs |
323
- | `p` | Pause/resume log output (auto-engaged when you scroll up) |
324
- | `t` | Toggle timestamps |
325
- | `c` | Clear logs |
326
- | `s` | Cycle sort mode (name → memory → errors) |
327
- | `r` | Restart a service |
328
- | `o` | Open a web service in browser |
329
- | `T` | Toggle reverse proxy config sync |
330
-
331
- When you scroll the Logs panel up, devup auto-pauses the log stream so new lines don't push your reading position. New lines are buffered and replay when you return to the bottom (`Ctrl+E` or scroll all the way down).
332
-
333
- ## Config file formats
334
-
335
- devup looks for config files in this order:
336
-
337
- 1. `devup.config.ts` — TypeScript with full type checking and intellisense
338
- 2. `devup.config.js` — JavaScript (ESM or CJS)
339
- 3. `devup.config.json` — JSON (no functions or imports)
340
-
341
- Or pass `--config path/to/file` to use a custom path.
342
-
343
- ## Phases
344
-
345
- Services boot in phase order. Within a phase, all services start simultaneously. devup waits for all API services in a phase to respond on their port before moving to the next phase.
346
-
347
- ```
348
- Phase 0: Core infrastructure (config server, auth)
349
- Phase 1: Base APIs (app, users, files, events)
350
- Phase 2: Dependent APIs (communications, notifications)
351
- Phase 3: Final APIs
352
- Phase 4: Frontends (Angular, Svelte, React, Vite)
353
- ```
354
-
355
- Phase numbers are arbitrary — use whatever makes sense for your dependency graph.
356
-
357
- ## Lazy mode
358
-
359
- In lazy mode, devup creates a TCP proxy on each lazy service's original port. The real service runs on `port + 10000` when started.
360
-
361
- ```
362
- Client → :3000 (proxy) → :13000 (real service)
363
- ```
364
-
365
- When a connection arrives and the service is idle, devup:
366
- 1. Runs `npm install` if needed
367
- 2. Starts the service on the offset port
368
- 3. Waits for the port to be ready
369
- 4. Pipes the buffered connection through
370
-
371
- After `timeout` minutes with no connections, the service stops and returns to idle.
372
-
373
- Services listed in `lazy.alwaysOn` skip the proxy and start normally.
374
-
375
- ## Platform support
376
-
377
- | Feature | Linux | macOS | Windows |
378
- |---|---|---|---|
379
- | Process stats (CPU, memory) | `ps` | `ps` | `wmic` |
380
- | Kill process tree | `kill -pid` | `kill -pid` | `taskkill /T /F` |
381
- | Open browser | `xdg-open` | `open` | `cmd /c start` |
382
- | Default proxy host | `172.17.0.1` | `host.docker.internal` | `host.docker.internal` |
383
-
384
- ## Reverse proxy providers
385
-
386
- devup generates dynamic config for reverse proxies. Three providers are built in: **Traefik**, **Nginx**, and **Caddy**. Only services with `health === 'up'` are included — flapping services are silently dropped from the generated config and re-added when they recover.
387
-
388
- ### Traefik
389
-
390
- Generates a YAML file for Traefik's [file provider](https://doc.traefik.io/traefik/providers/file/). Mount the config file as a volume in your Traefik container.
391
-
392
- ```yaml
393
- # docker-compose.yml
394
- services:
395
- traefik:
396
- volumes:
397
- - ~/.traefik:/etc/traefik/dynamic
398
- ```
399
-
400
- ```bash
401
- devup --proxy --proxy-host 172.17.0.1
402
- ```
403
-
404
- ### Nginx
405
-
406
- Generates an Nginx file with one `server { }` block per healthy service. Drop it into `/etc/nginx/conf.d/` (or `include` it from your main config) and reload Nginx — devup rewrites the file in place every 3 seconds.
407
-
408
- ```typescript
409
- proxy: {
410
- provider: 'nginx',
411
- confPath: '/etc/nginx/conf.d/devup.conf',
412
- routes: { 'app-web': '', 'api': 'api' },
413
- }
414
- ```
415
-
416
- With `tls: true` (default) each block listens on `:443 ssl` and points to `/etc/nginx/certs/<server_name>.crt` and `.key`. With `tls: false` it listens on `:80`. WebSocket / HTTP-upgrade headers are forwarded by default.
417
-
418
- > **Note:** Nginx doesn't watch files automatically — you'll need `nginx -s reload` (or `nginx-reload` sidecar) to pick up devup's updates. For a watch-and-reload workflow, prefer Traefik or Caddy.
419
-
420
- ### Caddy
421
-
422
- Generates a Caddyfile with one `reverse_proxy` directive per healthy service. Caddy auto-reloads its file on change (`caddy run --watch`), so devup's updates take effect without intervention.
423
-
424
- ```typescript
425
- proxy: {
426
- provider: 'caddy',
427
- confPath: '/etc/caddy/devup.Caddyfile',
428
- routes: { 'app-web': '', 'api': 'api' },
429
- }
430
- ```
431
-
432
- With `tls: true` (default) Caddy provisions TLS automatically (Let's Encrypt or local CA). With `tls: false` each site is prefixed with `http://`.
433
-
434
- ### Adding a custom provider
435
-
436
- Implement the `ProxyConfigProvider` interface and register it manually before calling `render(<App />)`:
437
-
438
- ```typescript
439
- interface ProxyConfigProvider {
440
- readonly name: string;
441
- generate(services: Map<string, ServiceState>, opts: ProxyOpts): string;
442
- write(content: string, opts: ProxyOpts): void;
443
- clear(opts: ProxyOpts): void;
444
- }
445
- ```
446
-
447
- ## Example: full config
448
-
449
- ```typescript
450
- import { defineConfig } from '@gachlab/devup';
451
-
452
- export default defineConfig({
453
- name: 'MyApp',
454
- icon: '⚡',
455
- envFile: '.env',
456
- env: {
457
- DOMAIN: 'localhost',
458
- },
459
-
460
- services: [
461
- // Phase 0 — Core
462
- { name: 'config-api', cwd: 'config/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 2999, phase: 0, maxMem: 192 },
463
-
464
- // Phase 1 — APIs
465
- { name: 'auth-api', cwd: 'auth/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3002, phase: 1, maxMem: 192 },
466
- { name: 'app-api', cwd: 'app/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3000, phase: 1, maxMem: 256 },
467
- { name: 'files-api', cwd: 'files/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3013, phase: 1, maxMem: 192 },
468
-
469
- // Phase 1 — TypeScript API with build step
470
- { name: 'orders-api', cwd: 'orders/api', cmd: 'node', args: ['dist/index.js'], type: 'api', port: 3031, phase: 1, maxMem: 256,
471
- preBuild: 'npm run build', watchBuild: 'npx tsup --watch' },
472
-
473
- // Phase 2 — Dependent APIs
474
- { name: 'notifications-api', cwd: 'notifications/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3010, phase: 2, maxMem: 256 },
475
-
476
- // Phase 4 — Frontends
477
- { name: 'app-web', cwd: 'app/web', cmd: 'npx', args: ['ng', 'serve', '--port', '4201'], type: 'web', port: 4201, phase: 4, maxMem: 512 },
478
- { name: 'admin-web', cwd: 'admin/web', cmd: 'npx', args: ['vite', '--port', '4204'], type: 'web', port: 4204, phase: 4, maxMem: 384 },
479
- { name: 'staff-web', cwd: 'staff/web', cmd: 'npx', args: ['vite', '--port', '4040'], type: 'web', port: 4040, phase: 4, maxMem: 384 },
480
- ],
481
-
482
- lazy: {
483
- alwaysOn: ['config-api', 'app-web'],
484
- timeout: 10,
485
- },
486
-
487
- proxy: {
488
- provider: 'traefik',
489
- routes: {
490
- 'app-web': '',
491
- 'admin-web': 'admin',
492
- 'staff-web': 'staff',
493
- 'app-api': 'app-api',
494
- 'auth-api': 'auth-api',
495
- 'files-api': 'files-api',
496
- },
497
- },
498
- });
499
- ```
99
+ - **[Getting started](./docs/getting-started.md)** — 5-minute tutorial
100
+ - **[Configuration reference](./docs/configuration.md)** — every field of `devup.config.ts`
101
+ - **[Health checks](./docs/health-checks.md)** TCP / HTTP / `readyPattern` / `startPeriod` / `errorPattern`
102
+ - **[Lazy mode](./docs/lazy-mode.md)** — on-demand spawning, idle timeouts, troubleshooting
103
+ - **[Build hooks](./docs/build-hooks.md)** `preBuild` and `watchBuild` for TypeScript services
104
+ - **[External services](./docs/external-services.md)** wire docker-compose into the boot sequence
105
+ - **[Profiles](./docs/profiles.md)** save service subsets under a name
106
+ - **[Reverse proxy](./docs/proxy.md)** Traefik / Nginx / Caddy generators
107
+ - **[TUI tour](./docs/tui.md)** every keybinding
108
+ - **[CLI reference](./docs/cli.md)** flags + subcommands
109
+ - **[Control plane](./docs/control-plane.md)** Unix-socket JSON-RPC
110
+ - **[Hot reload](./docs/hot-reload.md)** `--watch-config`
111
+ - **[Recipes](./docs/recipes.md)** patterns for Vite, Angular, NestJS, TypeScript, Docker
112
+ - **[Troubleshooting](./docs/troubleshooting.md)**
113
+ - **[Architecture](./docs/architecture.md)** for contributors
500
114
 
501
115
  ## Requirements
502
116
 
503
- - Node.js >= 22
504
- - npm (for dependency installation)
505
- - A terminal with TTY support (for the interactive TUI)
117
+ - Node.js 22
118
+ - npm
119
+ - A terminal with TTY support (for the TUI; subcommands don't need it)
506
120
 
507
121
  ## Development
508
122
 
@@ -511,10 +125,20 @@ git clone https://github.com/gachlab/devup.git
511
125
  cd devup
512
126
  npm install
513
127
  npm run build
514
- npm test # 200 tests, node:test native
515
- npm run test:coverage # coverage report
128
+ npm test # 331 tests, node:test native
129
+ npm run test:coverage
516
130
  ```
517
131
 
132
+ See [docs/architecture.md](./docs/architecture.md) for the codebase tour.
133
+
134
+ ## Changelog
135
+
136
+ [CHANGELOG.md](./CHANGELOG.md) — every release, every notable change.
137
+
138
+ ## Roadmap
139
+
140
+ [ROADMAP.md](./ROADMAP.md) — what's next, open questions, scope discussions.
141
+
518
142
  ## License
519
143
 
520
144
  MIT © gachlab
@@ -17,8 +17,9 @@ export interface CliArgs {
17
17
  onceTimeout: number;
18
18
  logFile: boolean;
19
19
  logDir?: string;
20
+ watchConfig: boolean;
20
21
  }
21
- export declare const USAGE = "devup \u2014 terminal UI dev stack runner\n\nUsage: devup [options]\n\nService selection:\n --only apis | webs Start only APIs or only webs\n --services a,b,c Start only the named services\n --profile <name> Start the services in a named profile (see ROADMAP)\n --skip a,b,c Start everything except these\n --config <path> Use a custom config file\n\nLazy mode:\n --lazy Enable lazy mode (default)\n --no-lazy Start every service immediately\n --timeout <minutes> Idle timeout for lazy services. Default: 10\n\nReverse proxy:\n --proxy Enable proxy config generation\n --proxy-host <host> Override the target host (Docker/local)\n --proxy-conf <path> Override the generated config file path\n --proxy-tls Enable TLS in the generated config (default)\n --no-proxy-tls Disable TLS\n --proxy-entrypoint <n> Override entrypoint name (Traefik only)\n\nCI / scripting:\n --dry-run Print the resolved boot plan and exit\n --once Boot, wait for readiness, exit 0/1 (no TUI)\n --once-timeout <s> Max seconds to wait in --once mode. Default: 90\n\nLog files:\n --no-log-file Disable persistent log files\n --log-dir <path> Override log root (default: ~/.devup/logs)\n\nOther:\n -h, --help Show this help and exit\n -v, --version Show version and exit\n\nSee https://github.com/gachlab/devup for the full documentation.";
22
+ export declare const USAGE = "devup \u2014 terminal UI dev stack runner\n\nUsage: devup [options]\n\nService selection:\n --only apis | webs Start only APIs or only webs\n --services a,b,c Start only the named services\n --profile <name> Start the services in a named profile (see ROADMAP)\n --skip a,b,c Start everything except these\n --config <path> Use a custom config file\n\nLazy mode:\n --lazy Enable lazy mode (default)\n --no-lazy Start every service immediately\n --timeout <minutes> Idle timeout for lazy services. Default: 10\n\nReverse proxy:\n --proxy Enable proxy config generation\n --proxy-host <host> Override the target host (Docker/local)\n --proxy-conf <path> Override the generated config file path\n --proxy-tls Enable TLS in the generated config (default)\n --no-proxy-tls Disable TLS\n --proxy-entrypoint <n> Override entrypoint name (Traefik only)\n\nCI / scripting:\n --dry-run Print the resolved boot plan and exit\n --once Boot, wait for readiness, exit 0/1 (no TUI)\n --once-timeout <s> Max seconds to wait in --once mode. Default: 90\n\nLog files:\n --no-log-file Disable persistent log files\n --log-dir <path> Override log root (default: ~/.devup/logs)\n\nHot reload:\n --watch-config Watch devup.config.* and apply add/remove/restart\n service changes without exiting the TUI\n\nOther:\n -h, --help Show this help and exit\n -v, --version Show version and exit\n\nSee https://github.com/gachlab/devup for the full documentation.";
22
23
  export declare function parseCliArgs(argv: string[]): CliArgs;
23
24
  export declare function filterServices(services: ServiceConfig[], args: CliArgs, config?: Pick<DevStackConfig, 'profiles'>): ServiceConfig[];
24
25
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/config/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,eAAO,MAAM,KAAK,8gDAqC+C,CAAC;AAElE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CA0CpD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GACxC,aAAa,EAAE,CA6BjB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/config/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAKD,eAAO,MAAM,KAAK,+qDAyC+C,CAAC;AAElE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CA4CpD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,EACzB,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GACxC,aAAa,EAAE,CA6BjB"}
@@ -0,0 +1,19 @@
1
+ import type { ServiceConfig } from './types.js';
2
+ export interface ServiceDiff {
3
+ added: ServiceConfig[];
4
+ removed: string[];
5
+ changed: Array<{
6
+ next: ServiceConfig;
7
+ prev: ServiceConfig;
8
+ }>;
9
+ unchanged: string[];
10
+ }
11
+ /** Computes the set-difference between two service lists by name.
12
+ * - added: in `next` but not in `prev`
13
+ * - removed: in `prev` but not in `next`
14
+ * - changed: in both but with a spawn-relevant field change
15
+ * - unchanged: in both with identical spawn-relevant fields */
16
+ export declare function diffServices(prev: ServiceConfig[], next: ServiceConfig[]): ServiceDiff;
17
+ /** Short human-readable summary for the TUI banner. */
18
+ export declare function summariseDiff(d: ServiceDiff): string;
19
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/config/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC7D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAeD;;;;gEAIgE;AAChE,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,WAAW,CAmBtF;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAOpD"}