@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/CHANGELOG.md +52 -0
- package/README.md +74 -450
- 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/types.d.ts +8 -0
- package/dist/config/types.d.ts.map +1 -1
- 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 +512 -63
- package/dist/index.js.map +1 -1
- package/dist/process/manager.d.ts.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/LogsPanel.d.ts +10 -1
- package/dist/tui/LogsPanel.d.ts.map +1 -1
- package/dist/tui/StatsPanel.d.ts +2 -1
- package/dist/tui/StatsPanel.d.ts.map +1 -1
- package/dist/tui/hooks/useKeyBindings.d.ts +5 -0
- package/dist/tui/hooks/useKeyBindings.d.ts.map +1 -1
- package/dist/tui/hooks/useProcessManager.d.ts +2 -0
- package/dist/tui/hooks/useProcessManager.d.ts.map +1 -1
- package/dist/utils.d.ts +25 -0
- package/dist/utils.d.ts.map +1 -1
- package/package.json +1 -1
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,447 +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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
The comprehensive guide lives in [docs/](./docs/README.md):
|
|
310
98
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
504
|
-
- npm
|
|
505
|
-
- 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)
|
|
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 #
|
|
515
|
-
npm run test:coverage
|
|
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
|
package/dist/config/cli.d.ts
CHANGED
|
@@ -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
|
package/dist/config/cli.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|