@gachlab/devup 0.5.0 → 0.7.1

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +74 -454
  3. package/dist/config/cli.d.ts +2 -1
  4. package/dist/config/cli.d.ts.map +1 -1
  5. package/dist/config/diff.d.ts +19 -0
  6. package/dist/config/diff.d.ts.map +1 -0
  7. package/dist/config/validator.d.ts +9 -0
  8. package/dist/config/validator.d.ts.map +1 -1
  9. package/dist/control-plane/socket-server.d.ts +31 -0
  10. package/dist/control-plane/socket-server.d.ts.map +1 -0
  11. package/dist/index.js +1047 -525
  12. package/dist/index.js.map +1 -1
  13. package/dist/process/health-poller.d.ts +15 -0
  14. package/dist/process/health-poller.d.ts.map +1 -0
  15. package/dist/process/internals.d.ts +14 -0
  16. package/dist/process/internals.d.ts.map +1 -0
  17. package/dist/process/lifecycle.d.ts +31 -0
  18. package/dist/process/lifecycle.d.ts.map +1 -0
  19. package/dist/process/manager.d.ts +11 -13
  20. package/dist/process/manager.d.ts.map +1 -1
  21. package/dist/process/restarter.d.ts +26 -0
  22. package/dist/process/restarter.d.ts.map +1 -0
  23. package/dist/process/spawner.d.ts +38 -0
  24. package/dist/process/spawner.d.ts.map +1 -0
  25. package/dist/tui/App.d.ts.map +1 -1
  26. package/dist/tui/LogsPanel.d.ts +9 -1
  27. package/dist/tui/LogsPanel.d.ts.map +1 -1
  28. package/dist/tui/hooks/useBootSequence.d.ts +20 -0
  29. package/dist/tui/hooks/useBootSequence.d.ts.map +1 -0
  30. package/dist/tui/hooks/useContextualTips.d.ts +6 -0
  31. package/dist/tui/hooks/useContextualTips.d.ts.map +1 -0
  32. package/dist/tui/hooks/useControlPlane.d.ts +10 -0
  33. package/dist/tui/hooks/useControlPlane.d.ts.map +1 -0
  34. package/dist/tui/hooks/useHotReload.d.ts +7 -0
  35. package/dist/tui/hooks/useHotReload.d.ts.map +1 -0
  36. package/dist/tui/hooks/useLogsPause.d.ts +4 -0
  37. package/dist/tui/hooks/useLogsPause.d.ts.map +1 -0
  38. package/dist/tui/hooks/useTerminalSize.d.ts +4 -0
  39. package/dist/tui/hooks/useTerminalSize.d.ts.map +1 -0
  40. package/dist/utils/colors.d.ts +4 -0
  41. package/dist/utils/colors.d.ts.map +1 -0
  42. package/dist/utils/env.d.ts +5 -0
  43. package/dist/utils/env.d.ts.map +1 -0
  44. package/dist/utils/format.d.ts +3 -0
  45. package/dist/utils/format.d.ts.map +1 -0
  46. package/dist/utils/install-stamp.d.ts +6 -0
  47. package/dist/utils/install-stamp.d.ts.map +1 -0
  48. package/dist/utils/phases.d.ts +4 -0
  49. package/dist/utils/phases.d.ts.map +1 -0
  50. package/dist/utils/process-args.d.ts +8 -0
  51. package/dist/utils/process-args.d.ts.map +1 -0
  52. package/dist/utils/redact.d.ts +4 -0
  53. package/dist/utils/redact.d.ts.map +1 -0
  54. package/dist/utils/search.d.ts +17 -0
  55. package/dist/utils/search.d.ts.map +1 -0
  56. package/dist/utils/stats.d.ts +19 -0
  57. package/dist/utils/stats.d.ts.map +1 -0
  58. package/dist/utils.d.ts +10 -41
  59. package/dist/utils.d.ts.map +1 -1
  60. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,45 @@ All notable changes to `@gachlab/devup` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.1] — 2026-05-22
9
+
10
+ Internals cleanup. No user-facing changes; safe drop-in upgrade from 0.7.0.
11
+
12
+ ### Internals
13
+ - **Split `utils.ts` into focused modules** (#52). The old junk-drawer became `src/utils/*` with one file per concern: `env`, `format`, `search`, `redact`, `install-stamp`, `process-args`, `stats`, `phases`, `colors`. `src/utils.ts` survives as a re-export façade so existing imports keep working.
14
+ - **Extract `App.tsx` useEffects into focused hooks** (#51). Six effects (terminal size, control plane, hot reload, log pause, contextual tips, boot sequence) moved into colocated hooks in `src/tui/hooks/`. `App.tsx` shrank from 397 to 150 lines (-62%); each hook ≤ 144 lines.
15
+ - **Split `ProcessManager` into Spawner / Restarter / HealthPoller / Lifecycle** (#50). All four share the same `state` Map and `procs` Set via constructor injection. Public API unchanged. `manager.ts` shrank from 361 to 91 lines (-75%); spawn pipeline isolated in `Spawner`; auto-restart backoff isolated in `Restarter`; cleanup + kill-tree in `Lifecycle`; health polling + grace window in `HealthPoller`.
16
+
17
+ Test count: 331 → 338. Build clean. Every public call site (TUI, control plane, runOnce, subcommands) keeps working unchanged.
18
+
19
+ ## [0.7.0] — 2026-05-21
20
+
21
+ Polish release. Two small quality-of-life items that closed out the low-value tail of the roadmap.
22
+
23
+ ### Added
24
+ - **Non-blocking config warnings** (#9). Devup now emits warnings — separate from errors — at config-load time. The first warning: `extraEnv.PORT` set to a value different from `svc.port`. That's a common footgun (the service binds to the value in `extraEnv`, devup health-checks `port`, nothing connects). Warnings are advisory: they print and the boot continues. Errors still abort as before. New helpers `collectWarnings()` / `formatValidationWarnings()` parallel to the existing `validateConfig` flow.
25
+ - **Active-service color on the LogsPanel border** (#20). When a service filter is active and the Logs panel is not focused, the border takes the filtered service's tag color. Subtle reinforcement of "you're seeing only this service", especially helpful after `Tab`'ing between panels. Focus (cyan) still wins so the active-pane affordance is never lost.
26
+
27
+ ### Internals
28
+ - New pure helper `resolveBorder()` exported from `tui/LogsPanel.tsx` for testability.
29
+ - Test suite grown to ~331 (+10).
30
+
31
+ ## [0.6.0] — 2026-05-21
32
+
33
+ Control plane release. Two features that unlock external integrations and editor workflows.
34
+
35
+ ### Added
36
+ - **Unix-socket control plane** (#26). devup now binds a JSON-RPC server to `~/.devup/sock-<project>.sock` with `chmod 0600`. Speaks newline-delimited JSON. Methods: `ping` (liveness), `status` (full snapshot of every service), `restart { svc }`, `stop { svc }`, `logs.tail { svc, lines? }` (capped at 10 000). Auth is filesystem-perms-only — strictly local; TCP exposure intentionally out of scope. Designed as the foundation for `devup logs --follow` against a running instance, IDE plugins, and future hot-reload coordination. If `listen()` fails (perms, dir missing) devup keeps running without the control plane and logs a single notice.
37
+ - **Hot reload of `devup.config.*`** (#23). Opt-in via `--watch-config`. devup watches the resolved config file and applies add/remove/restart at the service level when it changes — no need to kill the TUI. Validation runs first; a failed config leaves the running set untouched. The diff classifies each service as added / removed / changed / unchanged (changed = any spawn-relevant field differs). Banner via the logs panel summarises each reload: `🔁 config reloaded: +1 added, -2 removed, ~1 changed`. 250 ms debounce because editors emit several change events per save; in-flight guard coalesces back-to-back saves.
38
+
39
+ ### Changed
40
+ - README "Features" section reorganised into Orchestration / Readiness / TUI / Operations and brought up to date with everything added since 0.2.0 — every feature now has a one-line entry in the header.
41
+
42
+ ### Internals
43
+ - New module `src/control-plane/socket-server.ts` exposing `startSocketServer()` / `defaultSocketPath()` (pure helpers + `RpcContext` interface).
44
+ - New module `src/config/diff.ts` exposing `diffServices()` and `summariseDiff()` (pure functions, no side effects).
45
+ - Test suite grown to ~321 (+22). New suites: `socket-server` (9), `diff` (11).
46
+
8
47
  ## [0.5.0] — 2026-05-21
9
48
 
10
49
  Config power release — six features that sharpen day-to-day debugging in a long-running stack.
@@ -156,6 +195,9 @@ Initial release.
156
195
  - Config file resolution order: `devup.config.ts` → `.js` → `.json`, with `--config <path>` override. TypeScript loaded via the `tsx` import hook.
157
196
  - CLI flags: `--only`, `--services`, `--skip`, `--lazy`/`--no-lazy`, `--timeout`, `--proxy`, `--proxy-host`, `--proxy-conf`, `--proxy-tls`/`--no-proxy-tls`, `--proxy-entrypoint`, `--config`.
158
197
 
198
+ [0.7.1]: https://github.com/gachlab/devup/releases/tag/0.7.1
199
+ [0.7.0]: https://github.com/gachlab/devup/releases/tag/0.7.0
200
+ [0.6.0]: https://github.com/gachlab/devup/releases/tag/0.6.0
159
201
  [0.5.0]: https://github.com/gachlab/devup/releases/tag/0.5.0
160
202
  [0.4.0]: https://github.com/gachlab/devup/releases/tag/0.4.0
161
203
  [0.3.0]: https://github.com/gachlab/devup/releases/tag/0.3.0
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,451 +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
- | `errorPattern` | `string` | | Only stderr lines matching this regex bump `state.errors`. Without it every non-empty stderr line counts. Same `/pattern/flags` grammar as `readyPattern` |
129
-
130
- ### `HealthCheckConfig`
131
-
132
- | Field | Type | Required | Description |
133
- |---|---|---|---|
134
- | `type` | `'tcp' \| 'http'` | ✅ | `tcp` (default) just opens a socket; `http` issues an HTTP GET and inspects the status code |
135
- | `path` | `string` | | HTTP-only request path. Default: `/`. Must start with `/` |
136
- | `expect` | `number \| number[]` | | HTTP-only acceptable status code(s). Default: any 2xx (200-299) |
137
- | `host` | `string` | | Override target host for the HTTP check. Default: `127.0.0.1` |
138
- | `timeoutMs` | `number` | | Per-check socket/request timeout in ms. Default: `2000` |
139
- | `startPeriod` | `number` | | Grace period in seconds before the first probe runs. Useful for slow boots (Angular cold-start, etc.) so failed probes during boot don't pollute `state.errors`. Default: `0` |
140
-
141
- ```typescript
142
- // Wait for /healthz to return 200 before considering the service up
143
- { name: 'api', /* ... */, healthCheck: { type: 'http', path: '/healthz' } }
144
-
145
- // Accept 200 or 204
146
- { name: 'api', /* ... */, healthCheck: { type: 'http', path: '/health', expect: [200, 204] } }
147
- ```
148
-
149
- #### readyPattern
150
-
151
- 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:
152
-
153
- ```typescript
154
- // Vite: "ready in 423 ms"
155
- { name: 'web', cmd: 'npx', args: ['vite'], readyPattern: 'ready in' }
156
-
157
- // Angular: "Compiled successfully"
158
- { name: 'app', cmd: 'npx', args: ['ng', 'serve'], readyPattern: '/compiled successfully/i' }
159
-
160
- // Fastify: "Server listening at"
161
- { name: 'api', cmd: 'node', args: ['index.js'], readyPattern: 'server listening' }
162
- ```
163
-
164
- Both plain strings and vim-style `/pattern/flags` are accepted. Strings are case-insensitive by default.
165
-
166
- #### Build hooks: `preBuild` and `watchBuild`
167
-
168
- 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:
169
-
170
- ```typescript
171
- {
172
- name: 'orders-api',
173
- cwd: 'orders/api',
174
- cmd: 'node', args: ['dist/index.js'],
175
- type: 'api', port: 3031, phase: 1,
176
- preBuild: 'npm run build', // runs once, must succeed before the service starts
177
- watchBuild: 'npx tsup --watch', // runs in parallel; killed when the service stops
178
- }
179
- ```
180
-
181
- - **`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.
182
- - **`watchBuild`** runs as a sibling process. devup kills it when the service stops, restarts, or the TUI exits. Output is tagged `[watch]`.
183
-
184
- Both are passed through the platform shell (`sh -c` on Unix, `cmd /c` on Windows), so pipes and `&&` work.
185
-
186
- ### `ExternalService`
187
-
188
- 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`.
189
-
190
- | Field | Type | Required | Description |
191
- |---|---|---|---|
192
- | `name` | `string` | ✅ | Friendly name shown in logs (logs use the prefix `ext:<name>`) |
193
- | `cmd` | `string` | ✅ | Shell command (passed through `sh -c` / `cmd /c`). Pipes and `&&` work |
194
- | `cwd` | `string` | | Working directory relative to the project root |
195
- | `extraEnv` | `Record<string, string>` | | Extra env vars merged on top of the project env |
196
- | `healthCheck` | `HealthCheckConfig` | | Readiness probe. devup waits for `up` before starting phase 0 |
197
- | `port` | `number` | when `healthCheck` is set | Port to probe |
198
- | `startTimeout` | `number` | | Max seconds to wait for healthCheck. Default: `60` |
199
- | `stopCmd` | `string` | | Shell command run on shutdown (e.g. `docker compose down`) |
200
-
201
- ```typescript
202
- export default defineConfig({
203
- // ...
204
- external: [
205
- {
206
- name: 'mongo',
207
- cmd: 'docker compose -f docker-compose.dev.yml up -d mongo',
208
- port: 27017,
209
- healthCheck: { type: 'tcp' },
210
- stopCmd: 'docker compose -f docker-compose.dev.yml stop mongo',
211
- },
212
- {
213
- name: 'redis',
214
- cmd: 'docker compose -f docker-compose.dev.yml up -d redis',
215
- port: 6379,
216
- healthCheck: { type: 'tcp' },
217
- },
218
- ],
219
- });
220
- ```
221
-
222
- If any external fails its healthCheck within `startTimeout` seconds, devup aborts the boot, runs each external's `stopCmd` (best-effort), and exits.
223
-
224
- ### `LazyConfig`
225
-
226
- | Field | Type | Required | Description |
227
- |---|---|---|---|
228
- | `alwaysOn` | `string[]` | ✅ | Service names that always start immediately |
229
- | `timeout` | `number` | | Minutes of inactivity before stopping a lazy service. Default: `10` |
230
-
231
- 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.
232
-
233
- ### `ProxyConfig`
234
-
235
- | Field | Type | Required | Description |
236
- |---|---|---|---|
237
- | `provider` | `string` | ✅ | Proxy provider name. One of: `'traefik'`, `'nginx'`, `'caddy'` |
238
- | `routes` | `Record<string, string>` | ✅ | Map of service name → subdomain. Empty string = root domain |
239
- | `confPath` | `string` | | Path to write the config file. Default: `~/.traefik/traefik_conf.yaml` |
240
- | `host` | `string` | | Target host for proxy URLs. Default: auto-detected per platform |
241
- | `tls` | `boolean` | | Enable TLS config. Default: `true` |
242
- | `entrypoint` | `string` | | Proxy entrypoint name. Default: `'websecure'` |
243
-
244
- The proxy config is only generated when `--proxy` is passed on the CLI. Only services with `health === 'up'` are included in the generated config.
245
-
246
- ## CLI subcommands
247
-
248
- ```
249
- devup # launch the interactive TUI (default)
250
- devup logs <service> [--follow] # print the persisted log file
251
- devup install # parallel npm install across services
252
- devup status # health-check every service in config
253
- devup help [<subcommand>] # show usage
254
- devup --version # print version
255
- devup --help # print flag summary
256
- ```
257
-
258
- All subcommands need the project's `devup.config.ts` to be resolvable (use `--config <path>` to override).
259
-
260
- ## CLI flags
261
-
262
- ```
263
- devup [options]
264
- ```
265
-
266
- ### Service selection
267
-
268
- | Flag | Description |
269
- |---|---|
270
- | `--only apis` | Only start API services |
271
- | `--only webs` | Only start web services |
272
- | `--services api,web,auth` | Start only the named services |
273
- | `--profile <name>` | Start the services in the named profile (see `profiles` in the config) |
274
- | `--skip tasks-api,pickup-api` | Start everything except these |
275
- | `--config path/to/config.ts` | Use a custom config file |
276
-
277
- ### Lazy mode
278
-
279
- | Flag | Description |
280
- |---|---|
281
- | `--lazy` | Enable lazy mode (default) |
282
- | `--no-lazy` | Disable lazy mode — start everything immediately |
283
- | `--timeout 15` | Idle timeout in minutes (default: 10) |
284
-
285
- ### Reverse proxy
286
-
287
- | Flag | Description |
288
- |---|---|
289
- | `--proxy` | Enable proxy config generation |
290
- | `--proxy-host 127.0.0.1` | Override target host |
291
- | `--proxy-conf /path/to/file` | Override config file path |
292
- | `--proxy-tls` | Enable TLS (default) |
293
- | `--no-proxy-tls` | Disable TLS |
294
- | `--proxy-entrypoint web` | Override entrypoint name (Traefik-only) |
295
-
296
- ### CI / scripting
297
-
298
- | Flag | Description |
299
- |---|---|
300
- | `--dry-run` | Print the resolved boot plan (phases, commands, proxy YAML) and exit. Doesn't start any process |
301
- | `--once` | Boot services, wait until every API is healthy, then exit `0` (or `1` on timeout). Skips the TUI — meant for CI smoke tests |
302
- | `--once-timeout 60` | Max seconds to wait in `--once` mode. Default: `90` |
303
-
304
- ### Log files
93
+ See [docs/getting-started.md](./docs/getting-started.md) for a full walkthrough.
305
94
 
306
- | Flag | Description |
307
- |---|---|
308
- | `--no-log-file` | Disable persistent log files |
309
- | `--log-dir /path` | Override log root. Default: `~/.devup/logs/<project>/<service>.log` |
95
+ ## Documentation
310
96
 
311
- 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):
312
98
 
313
- ## TUI keybindings
314
-
315
- | Key | Action |
316
- |---|---|
317
- | `q` / `Ctrl+C` | Quit and stop all services |
318
- | `Tab` | Switch focus between Logs and Stats panels |
319
- | `↑` / `↓` | Scroll the focused panel by 1 line/row |
320
- | `[` / `]` (or `Ctrl+B` / `Ctrl+F`) | Page up / page down |
321
- | `Ctrl+A` / `Ctrl+E` | Jump to top / bottom of the focused panel |
322
- | `f` | Filter logs by service |
323
- | `L` | Cycle log level filter (all error → warn+error → all) |
324
- | `a` | Show all logs (clear service / search / level filters) |
325
- | `/` | Search in logs (accepts `/pattern/flags` regex) |
326
- | `p` | Pause/resume log output (auto-engaged when you scroll up) |
327
- | `t` | Toggle timestamps |
328
- | `c` | Clear logs |
329
- | `s` | Cycle sort mode (name → memory → errors) |
330
- | `r` | Restart a service |
331
- | `o` | Open a web service in browser (TLS-aware when `--proxy`) |
332
- | `v` | Verbose stats: show resolved `cmd`/args/env per service (env secrets redacted) |
333
- | `T` | Toggle reverse proxy config sync |
334
-
335
- 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).
336
-
337
- ## Config file formats
338
-
339
- devup looks for config files in this order:
340
-
341
- 1. `devup.config.ts` — TypeScript with full type checking and intellisense
342
- 2. `devup.config.js` — JavaScript (ESM or CJS)
343
- 3. `devup.config.json` — JSON (no functions or imports)
344
-
345
- Or pass `--config path/to/file` to use a custom path.
346
-
347
- ## Phases
348
-
349
- 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.
350
-
351
- ```
352
- Phase 0: Core infrastructure (config server, auth)
353
- Phase 1: Base APIs (app, users, files, events)
354
- Phase 2: Dependent APIs (communications, notifications)
355
- Phase 3: Final APIs
356
- Phase 4: Frontends (Angular, Svelte, React, Vite)
357
- ```
358
-
359
- Phase numbers are arbitrary — use whatever makes sense for your dependency graph.
360
-
361
- ## Lazy mode
362
-
363
- In lazy mode, devup creates a TCP proxy on each lazy service's original port. The real service runs on `port + 10000` when started.
364
-
365
- ```
366
- Client → :3000 (proxy) → :13000 (real service)
367
- ```
368
-
369
- When a connection arrives and the service is idle, devup:
370
- 1. Runs `npm install` if needed
371
- 2. Starts the service on the offset port
372
- 3. Waits for the port to be ready
373
- 4. Pipes the buffered connection through
374
-
375
- After `timeout` minutes with no connections, the service stops and returns to idle.
376
-
377
- Services listed in `lazy.alwaysOn` skip the proxy and start normally.
378
-
379
- ## Platform support
380
-
381
- | Feature | Linux | macOS | Windows |
382
- |---|---|---|---|
383
- | Process stats (CPU, memory) | `ps` | `ps` | `wmic` |
384
- | Kill process tree | `kill -pid` | `kill -pid` | `taskkill /T /F` |
385
- | Open browser | `xdg-open` | `open` | `cmd /c start` |
386
- | Default proxy host | `172.17.0.1` | `host.docker.internal` | `host.docker.internal` |
387
-
388
- ## Reverse proxy providers
389
-
390
- 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.
391
-
392
- ### Traefik
393
-
394
- 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.
395
-
396
- ```yaml
397
- # docker-compose.yml
398
- services:
399
- traefik:
400
- volumes:
401
- - ~/.traefik:/etc/traefik/dynamic
402
- ```
403
-
404
- ```bash
405
- devup --proxy --proxy-host 172.17.0.1
406
- ```
407
-
408
- ### Nginx
409
-
410
- 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.
411
-
412
- ```typescript
413
- proxy: {
414
- provider: 'nginx',
415
- confPath: '/etc/nginx/conf.d/devup.conf',
416
- routes: { 'app-web': '', 'api': 'api' },
417
- }
418
- ```
419
-
420
- 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.
421
-
422
- > **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.
423
-
424
- ### Caddy
425
-
426
- 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.
427
-
428
- ```typescript
429
- proxy: {
430
- provider: 'caddy',
431
- confPath: '/etc/caddy/devup.Caddyfile',
432
- routes: { 'app-web': '', 'api': 'api' },
433
- }
434
- ```
435
-
436
- With `tls: true` (default) Caddy provisions TLS automatically (Let's Encrypt or local CA). With `tls: false` each site is prefixed with `http://`.
437
-
438
- ### Adding a custom provider
439
-
440
- Implement the `ProxyConfigProvider` interface and register it manually before calling `render(<App />)`:
441
-
442
- ```typescript
443
- interface ProxyConfigProvider {
444
- readonly name: string;
445
- generate(services: Map<string, ServiceState>, opts: ProxyOpts): string;
446
- write(content: string, opts: ProxyOpts): void;
447
- clear(opts: ProxyOpts): void;
448
- }
449
- ```
450
-
451
- ## Example: full config
452
-
453
- ```typescript
454
- import { defineConfig } from '@gachlab/devup';
455
-
456
- export default defineConfig({
457
- name: 'MyApp',
458
- icon: '⚡',
459
- envFile: '.env',
460
- env: {
461
- DOMAIN: 'localhost',
462
- },
463
-
464
- services: [
465
- // Phase 0 — Core
466
- { name: 'config-api', cwd: 'config/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 2999, phase: 0, maxMem: 192 },
467
-
468
- // Phase 1 — APIs
469
- { name: 'auth-api', cwd: 'auth/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3002, phase: 1, maxMem: 192 },
470
- { name: 'app-api', cwd: 'app/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3000, phase: 1, maxMem: 256 },
471
- { name: 'files-api', cwd: 'files/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3013, phase: 1, maxMem: 192 },
472
-
473
- // Phase 1 — TypeScript API with build step
474
- { name: 'orders-api', cwd: 'orders/api', cmd: 'node', args: ['dist/index.js'], type: 'api', port: 3031, phase: 1, maxMem: 256,
475
- preBuild: 'npm run build', watchBuild: 'npx tsup --watch' },
476
-
477
- // Phase 2 — Dependent APIs
478
- { name: 'notifications-api', cwd: 'notifications/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3010, phase: 2, maxMem: 256 },
479
-
480
- // Phase 4 — Frontends
481
- { name: 'app-web', cwd: 'app/web', cmd: 'npx', args: ['ng', 'serve', '--port', '4201'], type: 'web', port: 4201, phase: 4, maxMem: 512 },
482
- { name: 'admin-web', cwd: 'admin/web', cmd: 'npx', args: ['vite', '--port', '4204'], type: 'web', port: 4204, phase: 4, maxMem: 384 },
483
- { name: 'staff-web', cwd: 'staff/web', cmd: 'npx', args: ['vite', '--port', '4040'], type: 'web', port: 4040, phase: 4, maxMem: 384 },
484
- ],
485
-
486
- lazy: {
487
- alwaysOn: ['config-api', 'app-web'],
488
- timeout: 10,
489
- },
490
-
491
- proxy: {
492
- provider: 'traefik',
493
- routes: {
494
- 'app-web': '',
495
- 'admin-web': 'admin',
496
- 'staff-web': 'staff',
497
- 'app-api': 'app-api',
498
- 'auth-api': 'auth-api',
499
- 'files-api': 'files-api',
500
- },
501
- },
502
- });
503
- ```
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
504
114
 
505
115
  ## Requirements
506
116
 
507
- - Node.js >= 22
508
- - npm (for dependency installation)
509
- - 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)
510
120
 
511
121
  ## Development
512
122
 
@@ -515,10 +125,20 @@ git clone https://github.com/gachlab/devup.git
515
125
  cd devup
516
126
  npm install
517
127
  npm run build
518
- npm test # 200 tests, node:test native
519
- npm run test:coverage # coverage report
128
+ npm test # 331 tests, node:test native
129
+ npm run test:coverage
520
130
  ```
521
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
+
522
142
  ## License
523
143
 
524
144
  MIT © gachlab