@gachlab/devup 0.5.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/CHANGELOG.md +30 -0
- package/README.md +74 -454
- package/dist/config/cli.d.ts +2 -1
- package/dist/config/cli.d.ts.map +1 -1
- package/dist/config/diff.d.ts +19 -0
- package/dist/config/diff.d.ts.map +1 -0
- package/dist/config/validator.d.ts +9 -0
- package/dist/config/validator.d.ts.map +1 -1
- package/dist/control-plane/socket-server.d.ts +31 -0
- package/dist/control-plane/socket-server.d.ts.map +1 -0
- package/dist/index.js +347 -19
- package/dist/index.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/LogsPanel.d.ts +9 -1
- package/dist/tui/LogsPanel.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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.0] — 2026-05-21
|
|
9
|
+
|
|
10
|
+
Polish release. Two small quality-of-life items that closed out the low-value tail of the roadmap.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **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.
|
|
14
|
+
- **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.
|
|
15
|
+
|
|
16
|
+
### Internals
|
|
17
|
+
- New pure helper `resolveBorder()` exported from `tui/LogsPanel.tsx` for testability.
|
|
18
|
+
- Test suite grown to ~331 (+10).
|
|
19
|
+
|
|
20
|
+
## [0.6.0] — 2026-05-21
|
|
21
|
+
|
|
22
|
+
Control plane release. Two features that unlock external integrations and editor workflows.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **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.
|
|
26
|
+
- **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.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
31
|
+
### Internals
|
|
32
|
+
- New module `src/control-plane/socket-server.ts` exposing `startSocketServer()` / `defaultSocketPath()` (pure helpers + `RpcContext` interface).
|
|
33
|
+
- New module `src/config/diff.ts` exposing `diffServices()` and `summariseDiff()` (pure functions, no side effects).
|
|
34
|
+
- Test suite grown to ~321 (+22). New suites: `socket-server` (9), `diff` (11).
|
|
35
|
+
|
|
8
36
|
## [0.5.0] — 2026-05-21
|
|
9
37
|
|
|
10
38
|
Config power release — six features that sharpen day-to-day debugging in a long-running stack.
|
|
@@ -156,6 +184,8 @@ Initial release.
|
|
|
156
184
|
- Config file resolution order: `devup.config.ts` → `.js` → `.json`, with `--config <path>` override. TypeScript loaded via the `tsx` import hook.
|
|
157
185
|
- CLI flags: `--only`, `--services`, `--skip`, `--lazy`/`--no-lazy`, `--timeout`, `--proxy`, `--proxy-host`, `--proxy-conf`, `--proxy-tls`/`--no-proxy-tls`, `--proxy-entrypoint`, `--config`.
|
|
158
186
|
|
|
187
|
+
[0.7.0]: https://github.com/gachlab/devup/releases/tag/0.7.0
|
|
188
|
+
[0.6.0]: https://github.com/gachlab/devup/releases/tag/0.6.0
|
|
159
189
|
[0.5.0]: https://github.com/gachlab/devup/releases/tag/0.5.0
|
|
160
190
|
[0.4.0]: https://github.com/gachlab/devup/releases/tag/0.4.0
|
|
161
191
|
[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
|
[](https://github.com/gachlab/devup/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@gachlab/devup)
|
|
4
5
|
|
|
5
|
-
A terminal UI dev
|
|
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
|
-
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- **
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
readyPattern: 'ready in',
|
|
66
82
|
},
|
|
67
83
|
],
|
|
68
84
|
});
|
|
69
85
|
```
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
Run:
|
|
72
88
|
|
|
73
89
|
```bash
|
|
74
90
|
npx devup
|
|
75
91
|
```
|
|
76
92
|
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
The comprehensive guide lives in [docs/](./docs/README.md):
|
|
312
98
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
508
|
-
- npm
|
|
509
|
-
- A terminal with TTY support (for the
|
|
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 #
|
|
519
|
-
npm run test:coverage
|
|
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
|