@gachlab/devup 0.1.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/LICENSE +21 -0
- package/README.md +332 -0
- package/dist/chunk-LG7UD5ZR.js +54 -0
- package/dist/chunk-LG7UD5ZR.js.map +1 -0
- package/dist/config/cli.d.ts +17 -0
- package/dist/config/cli.d.ts.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/types.d.ts +37 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/validator.d.ts +8 -0
- package/dist/config/validator.d.ts.map +1 -0
- package/dist/darwin-3KJ3IEXN.js +17 -0
- package/dist/darwin-3KJ3IEXN.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1336 -0
- package/dist/index.js.map +1 -0
- package/dist/lazy/classifier.d.ts +14 -0
- package/dist/lazy/classifier.d.ts.map +1 -0
- package/dist/lazy/proxy.d.ts +17 -0
- package/dist/lazy/proxy.d.ts.map +1 -0
- package/dist/linux-OQ3Q4Z2Z.js +8 -0
- package/dist/linux-OQ3Q4Z2Z.js.map +1 -0
- package/dist/platform/darwin.d.ts +6 -0
- package/dist/platform/darwin.d.ts.map +1 -0
- package/dist/platform/detect.d.ts +3 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/linux.d.ts +8 -0
- package/dist/platform/linux.d.ts.map +1 -0
- package/dist/platform/types.d.ts +11 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/win32.d.ts +8 -0
- package/dist/platform/win32.d.ts.map +1 -0
- package/dist/process/health.d.ts +8 -0
- package/dist/process/health.d.ts.map +1 -0
- package/dist/process/installer.d.ts +12 -0
- package/dist/process/installer.d.ts.map +1 -0
- package/dist/process/manager.d.ts +26 -0
- package/dist/process/manager.d.ts.map +1 -0
- package/dist/process/types.d.ts +21 -0
- package/dist/process/types.d.ts.map +1 -0
- package/dist/proxy-config/detect.d.ts +3 -0
- package/dist/proxy-config/detect.d.ts.map +1 -0
- package/dist/proxy-config/traefik.d.ts +8 -0
- package/dist/proxy-config/traefik.d.ts.map +1 -0
- package/dist/proxy-config/types.d.ts +21 -0
- package/dist/proxy-config/types.d.ts.map +1 -0
- package/dist/tui/App.d.ts +17 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/LogsPanel.d.ts +14 -0
- package/dist/tui/LogsPanel.d.ts.map +1 -0
- package/dist/tui/SearchInput.d.ts +7 -0
- package/dist/tui/SearchInput.d.ts.map +1 -0
- package/dist/tui/ServiceList.d.ts +11 -0
- package/dist/tui/ServiceList.d.ts.map +1 -0
- package/dist/tui/StatsPanel.d.ts +13 -0
- package/dist/tui/StatsPanel.d.ts.map +1 -0
- package/dist/tui/StatusBar.d.ts +2 -0
- package/dist/tui/StatusBar.d.ts.map +1 -0
- package/dist/tui/hooks/useKeyBindings.d.ts +31 -0
- package/dist/tui/hooks/useKeyBindings.d.ts.map +1 -0
- package/dist/tui/hooks/useProcessManager.d.ts +26 -0
- package/dist/tui/hooks/useProcessManager.d.ts.map +1 -0
- package/dist/tui/hooks/useProxySync.d.ts +4 -0
- package/dist/tui/hooks/useProxySync.d.ts.map +1 -0
- package/dist/utils.d.ts +22 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/win32-3X2OLSI6.js +49 -0
- package/dist/win32-3X2OLSI6.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gachlab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# devup
|
|
2
|
+
|
|
3
|
+
[](https://github.com/gachlab/devup/actions/workflows/ci.yml)
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
Built with TypeScript 6, Ink (React for terminals), and zero test dependencies (uses `node:test` natively).
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
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
|
|
13
|
+
- **TUI dashboard** — live logs and process stats (CPU, memory, health, errors, restarts) in a split-panel terminal UI
|
|
14
|
+
- **Cross-platform** — Linux, macOS, and Windows. Platform-specific process management, stats collection, and browser opening
|
|
15
|
+
- **Reverse proxy config** — generate Traefik (or other) dynamic config from running services. Health-aware: only routes to healthy services
|
|
16
|
+
- **Project-agnostic** — works with any Node.js monorepo. Your project defines a `devup.config.ts`, devup does the rest
|
|
17
|
+
- **npm install management** — automatic dependency installation with hash-based stamps to skip redundant installs
|
|
18
|
+
- **Auto-restart with backoff** — crashed services restart automatically with exponential backoff (2s → 4s → 8s), max 3 attempts
|
|
19
|
+
- **Port conflict detection** — checks if a port is already in use before starting a service
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
### 1. Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -D @gachlab/devup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Create config
|
|
30
|
+
|
|
31
|
+
Create `devup.config.ts` in your project root:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { defineConfig } from '@gachlab/devup';
|
|
35
|
+
|
|
36
|
+
export default defineConfig({
|
|
37
|
+
name: 'MyProject',
|
|
38
|
+
icon: '🚀',
|
|
39
|
+
envFile: '.env',
|
|
40
|
+
|
|
41
|
+
services: [
|
|
42
|
+
{
|
|
43
|
+
name: 'api',
|
|
44
|
+
cwd: 'packages/api',
|
|
45
|
+
cmd: 'node',
|
|
46
|
+
args: ['--watch-path', 'src', 'src/index.js'],
|
|
47
|
+
type: 'api',
|
|
48
|
+
port: 3000,
|
|
49
|
+
phase: 0,
|
|
50
|
+
maxMem: 256,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'web',
|
|
54
|
+
cwd: 'packages/web',
|
|
55
|
+
cmd: 'npx',
|
|
56
|
+
args: ['vite', '--port', '4200'],
|
|
57
|
+
type: 'web',
|
|
58
|
+
port: 4200,
|
|
59
|
+
phase: 1,
|
|
60
|
+
maxMem: 512,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Run
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx devup
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Config reference
|
|
73
|
+
|
|
74
|
+
### `DevStackConfig`
|
|
75
|
+
|
|
76
|
+
| Field | Type | Required | Description |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `name` | `string` | ✅ | Project name shown in the TUI header |
|
|
79
|
+
| `icon` | `string` | | Emoji shown before the project name. Default: `📦` |
|
|
80
|
+
| `envFile` | `string` | | Path to `.env` file relative to project root. Default: `.env` |
|
|
81
|
+
| `env` | `Record<string, string>` | | Extra environment variables. Won't overwrite existing ones |
|
|
82
|
+
| `services` | `ServiceConfig[]` | ✅ | List of services to manage |
|
|
83
|
+
| `lazy` | `LazyConfig` | | Lazy mode configuration |
|
|
84
|
+
| `proxy` | `ProxyConfig` | | Reverse proxy config generation |
|
|
85
|
+
|
|
86
|
+
### `ServiceConfig`
|
|
87
|
+
|
|
88
|
+
| Field | Type | Required | Description |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `name` | `string` | ✅ | Unique service name |
|
|
91
|
+
| `cwd` | `string` | ✅ | Working directory relative to project root |
|
|
92
|
+
| `cmd` | `string` | ✅ | Command to run (`node`, `npx`, etc.) |
|
|
93
|
+
| `args` | `string[]` | ✅ | Command arguments |
|
|
94
|
+
| `type` | `'api' \| 'web'` | ✅ | Service type. APIs get health-checked; webs are assumed ready after start |
|
|
95
|
+
| `port` | `number` | ✅ | Port the service listens on. Must be unique |
|
|
96
|
+
| `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 |
|
|
97
|
+
| `maxMem` | `number` | | Max memory in MB. Injects `--max-old-space-size` for `node` commands, or `NODE_OPTIONS` for `npx` |
|
|
98
|
+
| `preBuild` | `string` | | Command to run before starting (e.g., `npm run build`) |
|
|
99
|
+
| `watchBuild` | `string` | | Watch command to run alongside the service (e.g., `npx tsup --watch`) |
|
|
100
|
+
| `nodeArgs` | `string[]` | | Extra Node.js arguments |
|
|
101
|
+
| `extraEnv` | `Record<string, string>` | | Extra environment variables for this service |
|
|
102
|
+
|
|
103
|
+
### `LazyConfig`
|
|
104
|
+
|
|
105
|
+
| Field | Type | Required | Description |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| `alwaysOn` | `string[]` | ✅ | Service names that always start immediately |
|
|
108
|
+
| `timeout` | `number` | | Minutes of inactivity before stopping a lazy service. Default: `10` |
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
### `ProxyConfig`
|
|
113
|
+
|
|
114
|
+
| Field | Type | Required | Description |
|
|
115
|
+
|---|---|---|---|
|
|
116
|
+
| `provider` | `string` | ✅ | Proxy provider name. Currently: `'traefik'` |
|
|
117
|
+
| `routes` | `Record<string, string>` | ✅ | Map of service name → subdomain. Empty string = root domain |
|
|
118
|
+
| `confPath` | `string` | | Path to write the config file. Default: `~/.traefik/traefik_conf.yaml` |
|
|
119
|
+
| `host` | `string` | | Target host for proxy URLs. Default: auto-detected per platform |
|
|
120
|
+
| `tls` | `boolean` | | Enable TLS config. Default: `true` |
|
|
121
|
+
| `entrypoint` | `string` | | Proxy entrypoint name. Default: `'websecure'` |
|
|
122
|
+
|
|
123
|
+
The proxy config is only generated when `--proxy` is passed on the CLI. Only services with `health === 'up'` are included in the generated config.
|
|
124
|
+
|
|
125
|
+
## CLI flags
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
devup [options]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Service selection
|
|
132
|
+
|
|
133
|
+
| Flag | Description |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `--only apis` | Only start API services |
|
|
136
|
+
| `--only webs` | Only start web services |
|
|
137
|
+
| `--services api,web,auth` | Start only the named services |
|
|
138
|
+
| `--skip tasks-api,pickup-api` | Start everything except these |
|
|
139
|
+
| `--config path/to/config.ts` | Use a custom config file |
|
|
140
|
+
|
|
141
|
+
### Lazy mode
|
|
142
|
+
|
|
143
|
+
| Flag | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `--lazy` | Enable lazy mode (default) |
|
|
146
|
+
| `--no-lazy` | Disable lazy mode — start everything immediately |
|
|
147
|
+
| `--timeout 15` | Idle timeout in minutes (default: 10) |
|
|
148
|
+
|
|
149
|
+
### Reverse proxy
|
|
150
|
+
|
|
151
|
+
| Flag | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `--proxy` | Enable proxy config generation |
|
|
154
|
+
| `--proxy-host 127.0.0.1` | Override target host |
|
|
155
|
+
| `--proxy-conf /path/to/file` | Override config file path |
|
|
156
|
+
| `--proxy-tls` | Enable TLS (default) |
|
|
157
|
+
| `--no-proxy-tls` | Disable TLS |
|
|
158
|
+
| `--proxy-entrypoint web` | Override entrypoint name |
|
|
159
|
+
|
|
160
|
+
## TUI keybindings
|
|
161
|
+
|
|
162
|
+
| Key | Action |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `q` / `Ctrl+C` | Quit and stop all services |
|
|
165
|
+
| `Tab` | Switch focus between Logs and Stats panels |
|
|
166
|
+
| `f` | Filter logs by service |
|
|
167
|
+
| `a` | Show all logs (clear filter) |
|
|
168
|
+
| `/` | Search in logs |
|
|
169
|
+
| `p` | Pause/resume log output |
|
|
170
|
+
| `t` | Toggle timestamps |
|
|
171
|
+
| `c` | Clear logs |
|
|
172
|
+
| `s` | Cycle sort mode (name → memory → errors) |
|
|
173
|
+
| `r` | Restart a service |
|
|
174
|
+
| `o` | Open a web service in browser |
|
|
175
|
+
| `T` | Toggle reverse proxy config sync |
|
|
176
|
+
|
|
177
|
+
## Config file formats
|
|
178
|
+
|
|
179
|
+
devup looks for config files in this order:
|
|
180
|
+
|
|
181
|
+
1. `devup.config.ts` — TypeScript with full type checking and intellisense
|
|
182
|
+
2. `devup.config.js` — JavaScript (ESM or CJS)
|
|
183
|
+
3. `devup.config.json` — JSON (no functions or imports)
|
|
184
|
+
|
|
185
|
+
Or pass `--config path/to/file` to use a custom path.
|
|
186
|
+
|
|
187
|
+
## Phases
|
|
188
|
+
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Phase 0: Core infrastructure (config server, auth)
|
|
193
|
+
Phase 1: Base APIs (app, users, files, events)
|
|
194
|
+
Phase 2: Dependent APIs (communications, notifications)
|
|
195
|
+
Phase 3: Final APIs
|
|
196
|
+
Phase 4: Frontends (Angular, Svelte, React, Vite)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Phase numbers are arbitrary — use whatever makes sense for your dependency graph.
|
|
200
|
+
|
|
201
|
+
## Lazy mode
|
|
202
|
+
|
|
203
|
+
In lazy mode, devup creates a TCP proxy on each lazy service's original port. The real service runs on `port + 10000` when started.
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Client → :3000 (proxy) → :13000 (real service)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
When a connection arrives and the service is idle, devup:
|
|
210
|
+
1. Runs `npm install` if needed
|
|
211
|
+
2. Starts the service on the offset port
|
|
212
|
+
3. Waits for the port to be ready
|
|
213
|
+
4. Pipes the buffered connection through
|
|
214
|
+
|
|
215
|
+
After `timeout` minutes with no connections, the service stops and returns to idle.
|
|
216
|
+
|
|
217
|
+
Services listed in `lazy.alwaysOn` skip the proxy and start normally.
|
|
218
|
+
|
|
219
|
+
## Platform support
|
|
220
|
+
|
|
221
|
+
| Feature | Linux | macOS | Windows |
|
|
222
|
+
|---|---|---|---|
|
|
223
|
+
| Process stats (CPU, memory) | `ps` | `ps` | `wmic` |
|
|
224
|
+
| Kill process tree | `kill -pid` | `kill -pid` | `taskkill /T /F` |
|
|
225
|
+
| Open browser | `xdg-open` | `open` | `cmd /c start` |
|
|
226
|
+
| Default proxy host | `172.17.0.1` | `host.docker.internal` | `host.docker.internal` |
|
|
227
|
+
|
|
228
|
+
## Reverse proxy providers
|
|
229
|
+
|
|
230
|
+
devup generates dynamic config for reverse proxies. Currently supported:
|
|
231
|
+
|
|
232
|
+
### Traefik
|
|
233
|
+
|
|
234
|
+
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.
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
# docker-compose.yml
|
|
238
|
+
services:
|
|
239
|
+
traefik:
|
|
240
|
+
volumes:
|
|
241
|
+
- ~/.traefik:/etc/traefik/dynamic
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
devup --proxy --proxy-host 172.17.0.1
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Adding a new provider (Nginx, Caddy, etc.) requires implementing the `ProxyConfigProvider` interface:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
interface ProxyConfigProvider {
|
|
252
|
+
readonly name: string;
|
|
253
|
+
generate(services: Map<string, ServiceState>, opts: ProxyOpts): string;
|
|
254
|
+
write(content: string, opts: ProxyOpts): void;
|
|
255
|
+
clear(opts: ProxyOpts): void;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Example: full config
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { defineConfig } from '@gachlab/devup';
|
|
263
|
+
|
|
264
|
+
export default defineConfig({
|
|
265
|
+
name: 'MyApp',
|
|
266
|
+
icon: '⚡',
|
|
267
|
+
envFile: '.env',
|
|
268
|
+
env: {
|
|
269
|
+
DOMAIN: 'localhost',
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
services: [
|
|
273
|
+
// Phase 0 — Core
|
|
274
|
+
{ name: 'config-api', cwd: 'config/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 2999, phase: 0, maxMem: 192 },
|
|
275
|
+
|
|
276
|
+
// Phase 1 — APIs
|
|
277
|
+
{ name: 'auth-api', cwd: 'auth/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3002, phase: 1, maxMem: 192 },
|
|
278
|
+
{ name: 'app-api', cwd: 'app/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3000, phase: 1, maxMem: 256 },
|
|
279
|
+
{ name: 'files-api', cwd: 'files/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3013, phase: 1, maxMem: 192 },
|
|
280
|
+
|
|
281
|
+
// Phase 1 — TypeScript API with build step
|
|
282
|
+
{ name: 'orders-api', cwd: 'orders/api', cmd: 'node', args: ['dist/index.js'], type: 'api', port: 3031, phase: 1, maxMem: 256,
|
|
283
|
+
preBuild: 'npm run build', watchBuild: 'npx tsup --watch' },
|
|
284
|
+
|
|
285
|
+
// Phase 2 — Dependent APIs
|
|
286
|
+
{ name: 'notifications-api', cwd: 'notifications/api', cmd: 'node', args: ['src/index.js'], type: 'api', port: 3010, phase: 2, maxMem: 256 },
|
|
287
|
+
|
|
288
|
+
// Phase 4 — Frontends
|
|
289
|
+
{ name: 'app-web', cwd: 'app/web', cmd: 'npx', args: ['ng', 'serve', '--port', '4201'], type: 'web', port: 4201, phase: 4, maxMem: 512 },
|
|
290
|
+
{ name: 'admin-web', cwd: 'admin/web', cmd: 'npx', args: ['vite', '--port', '4204'], type: 'web', port: 4204, phase: 4, maxMem: 384 },
|
|
291
|
+
{ name: 'staff-web', cwd: 'staff/web', cmd: 'npx', args: ['vite', '--port', '4040'], type: 'web', port: 4040, phase: 4, maxMem: 384 },
|
|
292
|
+
],
|
|
293
|
+
|
|
294
|
+
lazy: {
|
|
295
|
+
alwaysOn: ['config-api', 'app-web'],
|
|
296
|
+
timeout: 10,
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
proxy: {
|
|
300
|
+
provider: 'traefik',
|
|
301
|
+
routes: {
|
|
302
|
+
'app-web': '',
|
|
303
|
+
'admin-web': 'admin',
|
|
304
|
+
'staff-web': 'staff',
|
|
305
|
+
'app-api': 'app-api',
|
|
306
|
+
'auth-api': 'auth-api',
|
|
307
|
+
'files-api': 'files-api',
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Requirements
|
|
314
|
+
|
|
315
|
+
- Node.js >= 22
|
|
316
|
+
- npm (for dependency installation)
|
|
317
|
+
- A terminal with TTY support (for the interactive TUI)
|
|
318
|
+
|
|
319
|
+
## Development
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
git clone https://github.com/gachlab/devup.git
|
|
323
|
+
cd devup
|
|
324
|
+
npm install
|
|
325
|
+
npm run build
|
|
326
|
+
npm test # 112 tests, node:test native
|
|
327
|
+
npm run test:coverage # coverage report
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## License
|
|
331
|
+
|
|
332
|
+
MIT © gachlab
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/platform/linux.ts
|
|
4
|
+
import { exec, spawn } from "child_process";
|
|
5
|
+
function parsePsTime(time) {
|
|
6
|
+
const parts = time.split(":").map(Number);
|
|
7
|
+
if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
8
|
+
if (parts.length === 2) return parts[0] * 60 + parts[1];
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
var LinuxPlatform = class {
|
|
12
|
+
defaultTraefikHost = "172.17.0.1";
|
|
13
|
+
getProcessStats(pids) {
|
|
14
|
+
if (!pids.length) return Promise.resolve(/* @__PURE__ */ new Map());
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
exec(
|
|
17
|
+
`ps -o pid=,rss=,time= -p ${pids.join(",")} 2>/dev/null || true`,
|
|
18
|
+
{ encoding: "utf8", timeout: 5e3 },
|
|
19
|
+
(err, stdout) => {
|
|
20
|
+
const map = /* @__PURE__ */ new Map();
|
|
21
|
+
if (err || !stdout) return resolve(map);
|
|
22
|
+
for (const line of stdout.trim().split("\n")) {
|
|
23
|
+
const parts = line.trim().split(/\s+/);
|
|
24
|
+
if (parts.length < 3) continue;
|
|
25
|
+
const pid = parseInt(parts[0], 10);
|
|
26
|
+
map.set(pid, {
|
|
27
|
+
rss: parseInt(parts[1], 10) || 0,
|
|
28
|
+
cpuSeconds: parsePsTime(parts[2])
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
resolve(map);
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
killTree(pid, signal = "SIGTERM") {
|
|
37
|
+
try {
|
|
38
|
+
process.kill(-pid, signal);
|
|
39
|
+
} catch {
|
|
40
|
+
try {
|
|
41
|
+
process.kill(pid, signal);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
openBrowser(url) {
|
|
47
|
+
spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
LinuxPlatform
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=chunk-LG7UD5ZR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/platform/linux.ts"],"sourcesContent":["import { exec, spawn } from 'node:child_process';\nimport type { Platform, ProcessStats } from './types.js';\n\nfunction parsePsTime(time: string): number {\n const parts = time.split(':').map(Number);\n if (parts.length === 3) return parts[0]! * 3600 + parts[1]! * 60 + parts[2]!;\n if (parts.length === 2) return parts[0]! * 60 + parts[1]!;\n return 0;\n}\n\nexport class LinuxPlatform implements Platform {\n readonly defaultTraefikHost: string = '172.17.0.1';\n\n getProcessStats(pids: number[]): Promise<Map<number, ProcessStats>> {\n if (!pids.length) return Promise.resolve(new Map());\n\n return new Promise(resolve => {\n exec(\n `ps -o pid=,rss=,time= -p ${pids.join(',')} 2>/dev/null || true`,\n { encoding: 'utf8', timeout: 5000 },\n (err, stdout) => {\n const map = new Map<number, ProcessStats>();\n if (err || !stdout) return resolve(map);\n\n for (const line of stdout.trim().split('\\n')) {\n const parts = line.trim().split(/\\s+/);\n if (parts.length < 3) continue;\n const pid = parseInt(parts[0]!, 10);\n map.set(pid, {\n rss: parseInt(parts[1]!, 10) || 0,\n cpuSeconds: parsePsTime(parts[2]!),\n });\n }\n resolve(map);\n },\n );\n });\n }\n\n killTree(pid: number, signal: NodeJS.Signals = 'SIGTERM'): void {\n try { process.kill(-pid, signal); } catch {\n try { process.kill(pid, signal); } catch { /* already dead */ }\n }\n }\n\n openBrowser(url: string): void {\n spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();\n }\n}\n"],"mappings":";;;AAAA,SAAS,MAAM,aAAa;AAG5B,SAAS,YAAY,MAAsB;AACzC,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM;AACxC,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,IAAK,OAAO,MAAM,CAAC,IAAK,KAAK,MAAM,CAAC;AAC1E,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,IAAK,KAAK,MAAM,CAAC;AACvD,SAAO;AACT;AAEO,IAAM,gBAAN,MAAwC;AAAA,EACpC,qBAA6B;AAAA,EAEtC,gBAAgB,MAAoD;AAClE,QAAI,CAAC,KAAK,OAAQ,QAAO,QAAQ,QAAQ,oBAAI,IAAI,CAAC;AAElD,WAAO,IAAI,QAAQ,aAAW;AAC5B;AAAA,QACE,4BAA4B,KAAK,KAAK,GAAG,CAAC;AAAA,QAC1C,EAAE,UAAU,QAAQ,SAAS,IAAK;AAAA,QAClC,CAAC,KAAK,WAAW;AACf,gBAAM,MAAM,oBAAI,IAA0B;AAC1C,cAAI,OAAO,CAAC,OAAQ,QAAO,QAAQ,GAAG;AAEtC,qBAAW,QAAQ,OAAO,KAAK,EAAE,MAAM,IAAI,GAAG;AAC5C,kBAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,KAAK;AACrC,gBAAI,MAAM,SAAS,EAAG;AACtB,kBAAM,MAAM,SAAS,MAAM,CAAC,GAAI,EAAE;AAClC,gBAAI,IAAI,KAAK;AAAA,cACX,KAAK,SAAS,MAAM,CAAC,GAAI,EAAE,KAAK;AAAA,cAChC,YAAY,YAAY,MAAM,CAAC,CAAE;AAAA,YACnC,CAAC;AAAA,UACH;AACA,kBAAQ,GAAG;AAAA,QACb;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,SAAS,KAAa,SAAyB,WAAiB;AAC9D,QAAI;AAAE,cAAQ,KAAK,CAAC,KAAK,MAAM;AAAA,IAAG,QAAQ;AACxC,UAAI;AAAE,gBAAQ,KAAK,KAAK,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAqB;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,YAAY,KAAmB;AAC7B,UAAM,YAAY,CAAC,GAAG,GAAG,EAAE,UAAU,MAAM,OAAO,SAAS,CAAC,EAAE,MAAM;AAAA,EACtE;AACF;","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ServiceConfig } from './types.js';
|
|
2
|
+
export interface CliArgs {
|
|
3
|
+
configPath?: string;
|
|
4
|
+
only?: string;
|
|
5
|
+
skip: string[];
|
|
6
|
+
services?: string[];
|
|
7
|
+
lazy: boolean;
|
|
8
|
+
lazyTimeout: number;
|
|
9
|
+
proxy: boolean;
|
|
10
|
+
proxyHost?: string;
|
|
11
|
+
proxyConf?: string;
|
|
12
|
+
proxyTls: boolean;
|
|
13
|
+
proxyEntrypoint: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseCliArgs(argv: string[]): CliArgs;
|
|
16
|
+
export declare function filterServices(services: ServiceConfig[], args: CliArgs): ServiceConfig[];
|
|
17
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/config/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,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,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;CACzB;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAgCpD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,aAAa,EAAE,CAoBxF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAQjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAcrE;AAED,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAgB5E"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ServiceConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
cwd: string;
|
|
4
|
+
cmd: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
type: 'api' | 'web';
|
|
7
|
+
port: number;
|
|
8
|
+
phase: number;
|
|
9
|
+
maxMem?: number;
|
|
10
|
+
preBuild?: string;
|
|
11
|
+
watchBuild?: string;
|
|
12
|
+
nodeArgs?: string[];
|
|
13
|
+
extraEnv?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export interface LazyConfig {
|
|
16
|
+
alwaysOn: string[];
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ProxyConfig {
|
|
20
|
+
provider: string;
|
|
21
|
+
routes: Record<string, string>;
|
|
22
|
+
confPath?: string;
|
|
23
|
+
host?: string;
|
|
24
|
+
tls?: boolean;
|
|
25
|
+
entrypoint?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DevStackConfig {
|
|
28
|
+
name: string;
|
|
29
|
+
icon?: string;
|
|
30
|
+
envFile?: string;
|
|
31
|
+
env?: Record<string, string>;
|
|
32
|
+
services: ServiceConfig[];
|
|
33
|
+
lazy?: LazyConfig;
|
|
34
|
+
proxy?: ProxyConfig;
|
|
35
|
+
}
|
|
36
|
+
export declare function defineConfig(config: DevStackConfig): DevStackConfig;
|
|
37
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAEnE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DevStackConfig } from './types.js';
|
|
2
|
+
export interface ValidationError {
|
|
3
|
+
field: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function validateConfig(config: DevStackConfig, cwd: string): ValidationError[];
|
|
7
|
+
export declare function formatValidationErrors(errors: ValidationError[]): string;
|
|
8
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/config/validator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAuErF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAExE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
LinuxPlatform
|
|
4
|
+
} from "./chunk-LG7UD5ZR.js";
|
|
5
|
+
|
|
6
|
+
// src/platform/darwin.ts
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
var DarwinPlatform = class extends LinuxPlatform {
|
|
9
|
+
defaultTraefikHost = "host.docker.internal";
|
|
10
|
+
openBrowser(url) {
|
|
11
|
+
spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
DarwinPlatform
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=darwin-3KJ3IEXN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/platform/darwin.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { LinuxPlatform } from './linux.js';\n\nexport class DarwinPlatform extends LinuxPlatform {\n override readonly defaultTraefikHost = 'host.docker.internal';\n\n override openBrowser(url: string): void {\n spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa;AAGf,IAAM,iBAAN,cAA6B,cAAc;AAAA,EAC9B,qBAAqB;AAAA,EAE9B,YAAY,KAAmB;AACtC,UAAM,QAAQ,CAAC,GAAG,GAAG,EAAE,UAAU,MAAM,OAAO,SAAS,CAAC,EAAE,MAAM;AAAA,EAClE;AACF;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { defineConfig } from './config/types.js';
|
|
2
|
+
export type { DevStackConfig, ServiceConfig, LazyConfig, ProxyConfig } from './config/types.js';
|
|
3
|
+
export type { Platform, ProcessStats } from './platform/types.js';
|
|
4
|
+
export type { ProxyConfigProvider, ProxyOpts } from './proxy-config/types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChG,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC"}
|