@devrouter/cli 0.0.34 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -449
- package/bin/devrouter-process +52 -5
- package/dist/devrouter.js +945 -546
- package/package.json +2 -1
- package/upgrade-prompts/0.0.35.md +29 -0
package/README.md
CHANGED
|
@@ -1,487 +1,101 @@
|
|
|
1
1
|
# devrouter
|
|
2
2
|
|
|
3
|
-
Local-first routing for macOS development
|
|
3
|
+
Local-first routing for macOS development through one shared Traefik router.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Devrouter gives repositories stable `*.localhost` routes without manual port
|
|
6
|
+
juggling. HTTP applications share `:443`; PostgreSQL, Redis, MariaDB, and MySQL
|
|
7
|
+
can share their standard ports through TLS/SNI hostname routing.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## What devrouter owns
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
pnpm dlx @devrouter/cli init --repo .
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
This prints a canonical prompt containing the full config schemas, best-practice workflows, docker dependency configurations, and verification commands required to configure the repo's devrouter setup.
|
|
18
|
-
|
|
19
|
-
## What it solves
|
|
20
|
-
|
|
21
|
-
Run multiple repos concurrently without manual port juggling:
|
|
22
|
-
|
|
23
|
-
- HTTP apps by hostname: `web.localhost`, `api.localhost`
|
|
24
|
-
- Databases (Postgres, Redis, MariaDB, MySQL) by hostname on their standard shared ports via TLS/SNI: `db.localhost`, `redis.localhost`
|
|
25
|
-
|
|
26
|
-
Traefik owns:
|
|
11
|
+
- One strict per-repository configuration file: `.devrouter.yml`.
|
|
12
|
+
- Machine setup, the shared `devnet` network, Traefik, local TLS, and route state.
|
|
13
|
+
- Host, Docker, proxy, and dependency-only application definitions.
|
|
14
|
+
- Exact-checkout DevPod lifecycle for primary and linked Git worktrees.
|
|
15
|
+
- Check-only diagnostics and JSON evidence for local and agent workflows.
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
- Shared protocol ports for activated databases (e.g. `:5432`, `:6379`, `:3306`)
|
|
17
|
+
Devrouter does not own application source, consumer toolchains, database data,
|
|
18
|
+
or repository-specific startup commands.
|
|
31
19
|
|
|
32
|
-
##
|
|
20
|
+
## Choose a runtime model
|
|
33
21
|
|
|
34
|
-
|
|
22
|
+
| Model | Use it when | Lifecycle owner |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `runtime: proxy` with a self-contained devcontainer | Preferred for reproducible repositories and parallel worktrees. | The devcontainer owns the environment; `devrouter ensure` owns exact-checkout reconciliation and routes. |
|
|
25
|
+
| `runtime: host` | The application should run directly on the host. | Devrouter starts the configured command and detects its port. |
|
|
26
|
+
| `runtime: docker` | Devrouter should start a Compose service or routed datastore. | Devrouter starts the selected service and publishes its route. |
|
|
35
27
|
|
|
36
|
-
|
|
28
|
+
The models can coexist in one repository. See the [managed devcontainer
|
|
29
|
+
contract](./docs/DEVCONTAINER.md) or the [repository onboarding
|
|
30
|
+
guide](./docs/REPO_ONBOARDING.md) before adapting an existing project.
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
## Five-minute first route
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Both are configured the same way (`.devrouter.yml`) and can be mixed in one repo.
|
|
43
|
-
|
|
44
|
-
### 1. Front a devcontainer / existing process: `runtime: proxy` (preferred)
|
|
45
|
-
|
|
46
|
-
The recommended setup is devcontainer first. The devcontainer owns the toolchain,
|
|
47
|
-
databases, auth mocks, app process, and seed data. devrouter owns only the local
|
|
48
|
-
routes. In the best case the container joins `devnet` and exposes stable network
|
|
49
|
-
aliases, so the app and database need no published host ports.
|
|
50
|
-
|
|
51
|
-
```yaml
|
|
52
|
-
apps:
|
|
53
|
-
- name: app
|
|
54
|
-
host: myapp.localhost
|
|
55
|
-
protocol: http
|
|
56
|
-
runtime: proxy
|
|
57
|
-
upstream: myapp-app:3000 # devnet alias inside the devcontainer compose
|
|
58
|
-
```
|
|
34
|
+
Install the published CLI and prepare the machine:
|
|
59
35
|
|
|
60
36
|
```bash
|
|
37
|
+
npm install -g @devrouter/cli
|
|
61
38
|
devrouter setup --yes
|
|
62
|
-
devrouter ensure .
|
|
63
|
-
devrouter exec . -- pnpm seed
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Why prefer it: the environment is reproducible and runs anywhere the devcontainer
|
|
67
|
-
spec runs, while devrouter gives it stable local HTTPS and database hostnames.
|
|
68
|
-
|
|
69
|
-
We recommend **DevPod** to orchestrate the devcontainer lifecycle locally because:
|
|
70
|
-
- **IDE/Editor Independence**: DevPod manages the container lifecycle and code synchronization in the background, allowing developers to use any local editor (VS Code, JetBrains, vim) with the containerized toolchain.
|
|
71
|
-
- **No Vendor Lock-in**: It is a client-only, open-source runner that runs entirely locally on Docker without requiring Microsoft's proprietary VS Code extensions or cloud-based runners.
|
|
72
|
-
- **devrouter Integration**: DevPod spins up the devcontainer compose stack on the `devnet` network, and devrouter handles dynamic HTTPS domain routing (`*.localhost`), making manual port-forwarding management obsolete.
|
|
73
|
-
|
|
74
|
-
Agents can add the scaffold with `devrouter repo inspect`, `devrouter repo devcontainer write`,
|
|
75
|
-
and `devrouter repo devcontainer verify`, then include the JSON evidence in a PR. See
|
|
76
|
-
[`docs/DEVCONTAINER.md`](./docs/DEVCONTAINER.md) for the full reference.
|
|
77
|
-
|
|
78
|
-
### 2. devrouter runs everything — `runtime: host` / `runtime: docker`
|
|
79
|
-
|
|
80
|
-
The original mode: devrouter starts your app (`runtime: host`, via `hostRun`) and
|
|
81
|
-
manages its Docker datastores/dependencies (`runtime: docker`), injecting DB env
|
|
82
|
-
vars. Use it when you are not (yet) on a devcontainer. Fully supported.
|
|
83
|
-
|
|
84
|
-
## Core commands
|
|
85
|
-
|
|
86
|
-
- `devrouter init [--repo <path>] [--entries-json <json>] [--json] [--write-agents] [--write-skill]`
|
|
87
|
-
- `devrouter -V [--repo <path>]` (installed CLI version, local repo version, next upgrade target)
|
|
88
|
-
- `devrouter upgrade [version] [--repo <path>]`
|
|
89
|
-
- `devrouter setup --yes [--repo <path>] [--json]`
|
|
90
|
-
- `devrouter ensure [path] [--open] [--json]`
|
|
91
|
-
- `devrouter stop [path] [--json]`
|
|
92
|
-
- `devrouter exec [path] -- <command...>`
|
|
93
|
-
- `devrouter up`
|
|
94
|
-
- `devrouter down`
|
|
95
|
-
- `devrouter status [--repo <path>] [--json]`
|
|
96
|
-
- `devrouter doctor|verify [--repo <path>] [--json]`
|
|
97
|
-
- `devrouter ls [--json]`
|
|
98
|
-
- `devrouter open <name>` (matches app name, then service/container/host)
|
|
99
|
-
- `devrouter tls install`
|
|
100
|
-
- `devrouter repo init [--repo <path>]`
|
|
101
|
-
- `devrouter repo inspect [--repo <path>] [--json]`
|
|
102
|
-
- `devrouter repo devcontainer write [--repo <path>] [--dry-run] [--yes] [--json]`
|
|
103
|
-
- `devrouter repo devcontainer verify [--repo <path>] [--live] [--yes] [--json]`
|
|
104
|
-
- `devrouter repo agents [--repo <path>]`
|
|
105
|
-
- `devrouter app add ...` (`--kind app|dependency`, default `app`)
|
|
106
|
-
- `devrouter app ls [--repo <path>] [--json]`
|
|
107
|
-
- `devrouter app run <name> [--repo <path>] [--yes] [--workspace <slug>]`
|
|
108
|
-
- `devrouter app exec <name> [--repo <path>] [--yes] [--shell] [--env <env>] [--workspace <slug>] -- <command>`
|
|
109
|
-
- `devrouter app rm <name> [--repo <path>]`
|
|
110
|
-
- `devrouter logs [-f]`
|
|
111
|
-
- `devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open] [--repo <path>]`
|
|
112
|
-
- `devrouter workspace ensure [path] [--open] [--json]` (compatible alias of `ensure`)
|
|
113
|
-
- `devrouter workspace ls [--repo <path>] [--json]`
|
|
114
|
-
- `devrouter workspace stop <workspace|branch> [--repo <path>]`
|
|
115
|
-
- `devrouter workspace down <workspace|branch> [--keep-worktree] [--repo <path>]`
|
|
116
|
-
- `devrouter workspace gc [--repo <path>] [--json] [--yes]`
|
|
117
|
-
|
|
118
|
-
The current `devrouter repo devcontainer write` scaffold is intentionally narrow:
|
|
119
|
-
Node + pnpm + Postgres. Other package managers stop with a JSON diagnostic
|
|
120
|
-
instead of writing files that would need manual repair.
|
|
121
|
-
The generated image contains no Devrouter package or helper. `devrouter ensure`
|
|
122
|
-
delivers its matching `devrouter-process` helper to the exact running container,
|
|
123
|
-
then invokes the repository-owned `post-start.sh` for locked, owned, idempotent
|
|
124
|
-
background startup. Application commands and environment setup remain
|
|
125
|
-
repository-owned; route readiness remains part of `devrouter ensure`.
|
|
126
|
-
Use `devrouter repo devcontainer verify --json` for read-only PR evidence; add
|
|
127
|
-
`--live --yes` only for compatibility checks. Normal startup uses `ensure`.
|
|
128
|
-
|
|
129
|
-
## Workspace isolation (parallel worktrees)
|
|
130
|
-
|
|
131
|
-
A **workspace token** lets several git worktrees of the same repo run side-by-side without host or route collisions. The token is a single identity spanning three layers: the devpod workspace id, the routes devrouter registers, and the `${WORKSPACE}` placeholder in `.devrouter.yml` proxy upstreams and devcontainer compose network aliases.
|
|
132
|
-
|
|
133
|
-
Each managed linked worktree keeps its local token in Git metadata and a durable
|
|
134
|
-
owner record under the repository's Git common directory. The record survives
|
|
135
|
-
out-of-band linked-worktree removal and ties the exact worktree path to its DevPod
|
|
136
|
-
ID. On first use, devrouter reuses an exact-path DevPod or derives a sanitized
|
|
137
|
-
identity from the linked branch/path. Ambiguous identities and DevPods owned by
|
|
138
|
-
another worktree fail closed. The primary checkout keeps its plain,
|
|
139
|
-
non-namespaced routes.
|
|
140
|
-
|
|
141
|
-
**When a workspace token is active:**
|
|
142
|
-
|
|
143
|
-
- Hosts are auto-namespaced: `web.localhost` → `web.<ws>.localhost`
|
|
144
|
-
- `${WORKSPACE}` in a proxy app's `upstream` (e.g. `upstream: ${WORKSPACE}-app:3000`) is substituted with the token at runtime
|
|
145
|
-
- The committed `.devrouter.yml` is never rewritten — all namespacing is computed in memory
|
|
146
|
-
- TLS: namespaced hosts are not covered by `*.localhost`; devrouter auto-extends mkcert cert SANs for active workspace hosts
|
|
147
|
-
|
|
148
|
-
`${WORKSPACE}` is valid in `upstream` only. Using it in `host` is rejected (hosts are namespaced automatically).
|
|
149
|
-
|
|
150
|
-
**Typical workflow:**
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
# Bring up a feature branch as an isolated workspace
|
|
154
|
-
devrouter workspace up feat/my-feature
|
|
155
|
-
|
|
156
|
-
# Reconcile either a primary or linked checkout
|
|
157
|
-
devrouter ensure .
|
|
158
|
-
|
|
159
|
-
# List ownership, Git, DevPod, and route state
|
|
160
|
-
devrouter workspace ls
|
|
161
|
-
|
|
162
|
-
# Pause runtime while preserving the checkout, owner record, and data
|
|
163
|
-
devrouter workspace stop feat/my-feature
|
|
164
|
-
|
|
165
|
-
# Delete runtime and routes, then remove the clean worktree and owner record
|
|
166
|
-
devrouter workspace down feat/my-feature
|
|
167
|
-
|
|
168
|
-
# Review missing owners; add --yes only after reviewing the dry run
|
|
169
|
-
devrouter workspace gc
|
|
170
|
-
devrouter workspace gc --yes
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
New worktrees default to the repository's ignored `trees/<workspace>` directory; use `--path` only when a repository intentionally follows another layout.
|
|
174
|
-
|
|
175
|
-
| Command | DevPod | Routes | Worktree and owner record |
|
|
176
|
-
| --- | --- | --- | --- |
|
|
177
|
-
| `workspace stop` | stop | remove | keep |
|
|
178
|
-
| `workspace down` | delete | remove | remove only when clean and unlocked |
|
|
179
|
-
| `workspace down --keep-worktree` | delete | remove | keep |
|
|
180
|
-
| `workspace gc` | report only | report only | never removes Git worktrees |
|
|
181
|
-
| `workspace gc --yes` | delete eligible missing owners | remove exact owned routes | remove only the owner record |
|
|
182
|
-
|
|
183
|
-
`workspace ls` reports owner status as `present`, `missing`, `locked`, or
|
|
184
|
-
`conflict`. Full `workspace down` checks ownership, the Git registration, lock,
|
|
185
|
-
and cleanliness before changing runtime or routes; dirty and locked worktrees fail
|
|
186
|
-
with zero teardown side effects.
|
|
187
|
-
|
|
188
|
-
Workspace lifecycle commands require a Git repository. Normal config, app,
|
|
189
|
-
status, and diagnostic commands remain usable from a `.devrouter.yml` directory
|
|
190
|
-
without Git. Git has no worktree-removal hook, so devrouter does not install one:
|
|
191
|
-
after manual removal, use `workspace ls`, `doctor`, or the dry-run
|
|
192
|
-
`workspace gc` report before applying cleanup with `--yes`.
|
|
193
|
-
|
|
194
|
-
**devcontainer integration:** the devcontainer compose service exposes a devnet network alias `${WORKSPACE}-app` (defaulting to the project name in `devcontainer.env`); the proxy app uses `upstream: ${WORKSPACE}-app:<port>`. `.devcontainer/docker-compose.devrouter.yml` passes `WORKSPACE` and `DEVROUTER_WORKSPACE` into the app and bind-mounts `${DEVROUTER_GIT_COMMON_DIR}` to the same absolute container path, so linked-worktree `.git` pointers remain valid. Workspace `feat-a` → alias `feat-a-app`, host `app.feat-a.localhost`.
|
|
195
|
-
|
|
196
|
-
`ensure` is intentionally proof-driven: it verifies exact checkout ownership,
|
|
197
|
-
the required compose/Git mount contract, workspace env when linked, devnet aliases,
|
|
198
|
-
container health, Git access, HTTP route reachability, and unique running TCP
|
|
199
|
-
upstream ownership (plus health when configured) before reporting ready. It
|
|
200
|
-
retries one stale DevPod with `--recreate`; failed proof does not leave new routes
|
|
201
|
-
behind.
|
|
202
|
-
|
|
203
|
-
Use `devrouter stop .` to pause the exact environment without deleting data. Use
|
|
204
|
-
`devrouter exec . -- <command...>` for seeds, migrations, and other one-shot
|
|
205
|
-
container commands; it does not start a missing or stopped environment.
|
|
206
|
-
|
|
207
|
-
**Try it:** [`examples/workspace/`](examples/workspace/) is a runnable showcase — `./run.sh` brings up one app in two parallel worktrees (`wsdemo.localhost` and `wsdemo.feat-a.localhost`) served at once, then `./run.sh down` tears it down.
|
|
208
|
-
|
|
209
|
-
**DevPod example:** [`examples/devcontainer/`](examples/devcontainer/) is the
|
|
210
|
-
agent-native devcontainer path end to end — `./run.sh` brings up a DevPod
|
|
211
|
-
workspace, runs static verification and `ensure`, proves exact-container `exec`,
|
|
212
|
-
and checks trusted app/Postgres routes. `./run.sh down` tears it down.
|
|
213
|
-
|
|
214
|
-
**Missing-owner detection:** `devrouter doctor` reports ledger-owned workspaces
|
|
215
|
-
whose checkout disappeared or conflicts with live Git/DevPod evidence. It does
|
|
216
|
-
not mutate workspace state and prints the exact dry-run remediation command:
|
|
217
|
-
`devrouter workspace gc --repo <repo>`.
|
|
218
|
-
|
|
219
|
-
## Upgrade metadata and prompts
|
|
220
|
-
|
|
221
|
-
`devrouter upgrade` and `devrouter -V` read local upgrade metadata from `.devrouter.yml` in the target repo (`devrouter.version`):
|
|
222
|
-
|
|
223
|
-
```yaml
|
|
224
|
-
version: 1
|
|
225
|
-
devrouter:
|
|
226
|
-
version: <semver>
|
|
227
|
-
apps: []
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Quick checks:
|
|
231
|
-
|
|
232
|
-
- `devrouter -V` shows installed CLI version, local repo version, and next available upgrade target.
|
|
233
|
-
- `devrouter upgrade` lists all upgrade targets newer than the local repo version and marks the next one.
|
|
234
|
-
- `devrouter upgrade <version>` prints that target release's Agent Adaptation Prompt and then shows if a further version is available.
|
|
235
|
-
- Upgrade prompts are sourced from `upgrade-prompts/<version>.md`.
|
|
236
|
-
- `devrouter repo init` initializes `devrouter.version` to the installed CLI version.
|
|
237
|
-
|
|
238
|
-
## AI-native onboarding prompt
|
|
239
|
-
|
|
240
|
-
Generate a ready-to-copy onboarding prompt for an AI agent:
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
devrouter init --repo /absolute/path/to/repo
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
By default, this command is non-mutating (it prints prompt text only).
|
|
247
|
-
|
|
248
|
-
Optional: embed target app entries as JSON:
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
devrouter init --repo /absolute/path/to/repo --entries-json '[{"name":"web","host":"web.localhost","protocol":"http","runtime":"host"}]'
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
JSON mode for machine consumption:
|
|
255
|
-
|
|
256
|
-
```bash
|
|
257
|
-
devrouter init --repo /absolute/path/to/repo --json
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Optional repo artifact writes are explicit:
|
|
261
|
-
|
|
262
|
-
```bash
|
|
263
|
-
devrouter init --repo /absolute/path/to/repo --write-agents --write-skill
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Health diagnostics
|
|
267
|
-
|
|
268
|
-
Run check-only diagnostics for global router state, machine prerequisites, route state, and repo configuration:
|
|
269
|
-
|
|
270
|
-
```bash
|
|
271
|
-
devrouter doctor --repo /absolute/path/to/repo
|
|
272
39
|
```
|
|
273
40
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
devrouter doctor --repo /absolute/path/to/repo --json
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
`devrouter status` now includes readiness hints and next-step commands.
|
|
281
|
-
For host apps that depend on postgres, `devrouter doctor` also checks host command wrapper precedence and warns with `repo.host-command-env-precedence` when `DATABASE_URI`/`DATABASE_URL` is assigned before a `run --` wrapper boundary.
|
|
282
|
-
When TLS is enabled, `devrouter doctor` also checks TLS host coverage and warns with `repo.tls-host-coverage` if configured `.localhost` hosts are not covered by the current cert SANs.
|
|
283
|
-
When `.devcontainer/` exists, `devrouter doctor` checks devnet aliases, published host ports, and proxy upstream alias matches.
|
|
284
|
-
`devrouter doctor` reports stale host routes and ownership cleanup candidates without mutating route state; workspace findings point to dry-run `devrouter workspace gc --repo <repo>`.
|
|
285
|
-
|
|
286
|
-
## `.devrouter.yml` example
|
|
287
|
-
|
|
288
|
-
```yaml
|
|
289
|
-
version: 1
|
|
290
|
-
devrouter:
|
|
291
|
-
version: 0.0.14
|
|
292
|
-
project:
|
|
293
|
-
name: my-repo
|
|
294
|
-
apps:
|
|
295
|
-
- name: web
|
|
296
|
-
host: web.localhost
|
|
297
|
-
protocol: http
|
|
298
|
-
runtime: host
|
|
299
|
-
hostRun:
|
|
300
|
-
command: pnpm dev
|
|
301
|
-
cwd: .
|
|
302
|
-
strategy:
|
|
303
|
-
type: auto
|
|
304
|
-
denyPorts: [80, 443, 5432]
|
|
305
|
-
allowPortRange: "1024-65535"
|
|
306
|
-
dependencies:
|
|
307
|
-
- app: db
|
|
308
|
-
- app: redis
|
|
309
|
-
|
|
310
|
-
- name: db
|
|
311
|
-
host: db.localhost
|
|
312
|
-
protocol: tcp
|
|
313
|
-
tcpProtocol: postgres
|
|
314
|
-
runtime: docker
|
|
315
|
-
docker:
|
|
316
|
-
service: db
|
|
317
|
-
internalPort: 5432
|
|
318
|
-
composeFiles:
|
|
319
|
-
- docker-compose.yml
|
|
320
|
-
|
|
321
|
-
- name: redis
|
|
322
|
-
kind: dependency
|
|
323
|
-
runtime: docker
|
|
324
|
-
docker:
|
|
325
|
-
service: redis
|
|
326
|
-
composeFiles:
|
|
327
|
-
- docker-compose.yml
|
|
328
|
-
|
|
329
|
-
# Route to an already-running port (e.g. a devcontainer's published app).
|
|
330
|
-
# No lifecycle, env injection, or dependencies — devrouter only registers the route.
|
|
331
|
-
# Use ${WORKSPACE} in upstream for parallel-worktree isolation (see "Workspace isolation").
|
|
332
|
-
- name: app
|
|
333
|
-
host: app.localhost
|
|
334
|
-
protocol: http
|
|
335
|
-
runtime: proxy
|
|
336
|
-
upstream: 127.0.0.1:3000
|
|
337
|
-
# upstream: ${WORKSPACE}-app:3000 # workspace-aware variant
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
Notes:
|
|
341
|
-
|
|
342
|
-
- `kind` defaults to routed app behavior. Use `kind: dependency` for non-routed Docker dependencies.
|
|
343
|
-
- `runtime: proxy` registers an HTTP route to an externally-managed `upstream` (`host:port`) and does nothing else — use it to put a stable `*.localhost` HTTPS host in front of a devcontainer or any process you start yourself. Loopback upstreams (`localhost`/`127.0.0.1`/`0.0.0.0`) are rewritten to `host.docker.internal` so Traefik (in Docker) can reach the host. The route persists until `devrouter app rm`.
|
|
344
|
-
- TCP routing supports `tcpProtocol: postgres`, `redis`, `mariadb`, and `mysql` on shared protocol ports with TLS/SNI.
|
|
345
|
-
- Plaintext TCP is not supported for multiplexed hostname routing.
|
|
346
|
-
- Multi-segment `.localhost` hosts are supported (for example `elearning.klicker.localhost`).
|
|
347
|
-
|
|
348
|
-
## Runtime behavior
|
|
349
|
-
|
|
350
|
-
`devrouter app run <name>`:
|
|
351
|
-
|
|
352
|
-
- reads `.devrouter.yml`
|
|
353
|
-
- prompts to start declared dependencies (or use `--yes`)
|
|
354
|
-
- starts docker target services for `runtime: docker` apps, plus declared docker dependencies
|
|
355
|
-
- for `runtime: proxy` apps: registers the route to `upstream` and returns immediately (no process started, no dependencies); re-running is an idempotent upsert and the route persists until `devrouter app rm`
|
|
356
|
-
- fails fast if host-runtime dependencies are configured (start those manually)
|
|
357
|
-
- waits for Docker dependencies to become healthy (`--wait`) before proceeding
|
|
358
|
-
- automatically stops Docker dependencies when a host app exits; docker app services remain running until explicit cleanup (`docker compose down`, `devrouter down`, or equivalent)
|
|
359
|
-
- prints recent dependency logs (last 20 lines) after deps start
|
|
360
|
-
- `kind=dependency` apps are dependency-only: they do not create routes and cannot be direct targets for `devrouter app run`, `devrouter app exec`, or `devrouter open`
|
|
361
|
-
- `kind=dependency` services start as declared in compose (no Traefik labels, no random published ports, no injected env vars)
|
|
362
|
-
- for TCP deps of host apps: publishes a random host port and injects per-dependency `<NAME>_HOST`/`<NAME>_PORT`/`<NAME>_URL` env vars into the host process; for postgres deps also injects `<NAME>_SHADOW_URL` (fixed credentials `prisma:prisma`, databases `prisma`/`shadow`)
|
|
363
|
-
- for one-shot commands, `devrouter app exec` starts declared docker deps as needed and only stops deps it started in that invocation (already-running deps stay running)
|
|
364
|
-
- if `devrouter app exec` cannot determine pre-existing running services, it leaves selected deps running to avoid stopping non-owned services
|
|
365
|
-
- when TLS is enabled, `devrouter app run` / `devrouter app exec` auto-refresh cert SAN coverage for configured repo hosts before startup (fails fast with `Run: devrouter tls install` guidance if refresh fails)
|
|
366
|
-
- for one-shot commands, `devrouter app exec` preserves argv semantics by default (`shell: false`) to avoid nested quoting issues
|
|
367
|
-
- `devrouter app exec --shell` is explicit and requires one command string after `--`
|
|
368
|
-
- config-level `envMap` on dependency references maps aliases after dependency env resolution (for example `DATABASE_URL: DB_URL`)
|
|
369
|
-
- starts host app command for host runtime apps
|
|
370
|
-
- generates docker overlay in `~/.config/devrouter/cache/...` for docker runtime apps
|
|
371
|
-
|
|
372
|
-
Secret manager interop (Infisical/Doppler):
|
|
373
|
-
|
|
374
|
-
- dependency env injection from devrouter includes `<NAME>_HOST`, `<NAME>_PORT`, `<NAME>_URL`, and postgres-only `<NAME>_SHADOW_URL`
|
|
375
|
-
- do not assume secret-manager precedence when DB vars overlap; validate effective env before migrate/seed
|
|
376
|
-
- avoid pre-wrapper DB assignments such as `DATABASE_URI=... <wrapper> run -- ...`; wrapper-managed env may override those values
|
|
377
|
-
- map app-specific names in `.devrouter.yml`:
|
|
378
|
-
```yaml
|
|
379
|
-
dependencies:
|
|
380
|
-
- app: db
|
|
381
|
-
envMap:
|
|
382
|
-
DATABASE_URL: DB_URL
|
|
383
|
-
DIRECT_URL: DB_URL
|
|
384
|
-
SHADOW_DATABASE_URL: DB_SHADOW_URL
|
|
385
|
-
```
|
|
386
|
-
- safe host-run override pattern when wrapper also defines `DATABASE_URI`: `infisical run --projectId <id> --env=<env> -- env DATABASE_URI=${DB_URL:?missing DB_URL} pnpm dev`
|
|
387
|
-
- non-Prisma mapping example: `devrouter app exec web --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate`
|
|
388
|
-
- env probe example: `devrouter app exec web --yes -- printenv DB_URL DATABASE_URL DB_HOST DB_PORT DB_SHADOW_URL SHADOW_DATABASE_URL`
|
|
389
|
-
- run `devrouter doctor --repo <path>` to surface risky wrapper precedence (`repo.host-command-env-precedence`) before migrations or app startup
|
|
390
|
-
|
|
391
|
-
`devrouter ls` output includes both configured app identity (`APP`) and runtime service identity (`SERVICE`).
|
|
392
|
-
|
|
393
|
-
## First onboarding quick path
|
|
394
|
-
|
|
395
|
-
In a repo that has a host app and a Docker Postgres service:
|
|
41
|
+
In a repository with a development command, initialize the config and add a
|
|
42
|
+
host application. Replace `pnpm dev` with the repository's real command.
|
|
396
43
|
|
|
397
44
|
```bash
|
|
45
|
+
cd /absolute/path/to/repository
|
|
398
46
|
devrouter repo init
|
|
399
|
-
devrouter app add
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
47
|
+
devrouter app add \
|
|
48
|
+
--name web \
|
|
49
|
+
--host web.localhost \
|
|
50
|
+
--protocol http \
|
|
51
|
+
--runtime host \
|
|
52
|
+
--command "pnpm dev" \
|
|
53
|
+
--cwd .
|
|
404
54
|
devrouter app run web --yes
|
|
405
|
-
devrouter ls
|
|
406
55
|
```
|
|
407
56
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
- `https://web.localhost`
|
|
411
|
-
- `postgres://db.localhost:5432 (tls required)`
|
|
412
|
-
|
|
413
|
-
## Routing example (without devcontainers)
|
|
414
|
-
|
|
415
|
-
A complete no-devcontainer sample repository is included at:
|
|
416
|
-
|
|
417
|
-
- [`./examples/routing`](./examples/routing)
|
|
418
|
-
|
|
419
|
-
It contains:
|
|
420
|
-
|
|
421
|
-
- one app running on host (`web-host`)
|
|
422
|
-
- the same app running in Docker (`web-docker`)
|
|
423
|
-
- Postgres in Docker (`db`)
|
|
424
|
-
- ready-to-use `.devrouter.yml`
|
|
425
|
-
|
|
426
|
-
Run the bundled routing smoke:
|
|
57
|
+
Open `https://web.localhost`. In another terminal, confirm the route and run
|
|
58
|
+
check-only diagnostics:
|
|
427
59
|
|
|
428
60
|
```bash
|
|
429
|
-
|
|
61
|
+
devrouter ls
|
|
62
|
+
devrouter doctor --repo .
|
|
430
63
|
```
|
|
431
64
|
|
|
432
|
-
|
|
433
|
-
|
|
65
|
+
For a repository that already has the managed devcontainer contract, the normal
|
|
66
|
+
startup path is simply:
|
|
434
67
|
|
|
435
68
|
```bash
|
|
436
|
-
|
|
437
|
-
pnpm
|
|
69
|
+
devrouter ensure .
|
|
70
|
+
devrouter exec . -- pnpm seed
|
|
438
71
|
```
|
|
439
72
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
- [`./examples/routing/README.md`](./examples/routing/README.md)
|
|
443
|
-
- [`./examples/devcontainer/README.md`](./examples/devcontainer/README.md)
|
|
444
|
-
|
|
445
|
-
## AI agent discoverability
|
|
73
|
+
Use devrouter lifecycle commands for managed environments. Raw `devpod up`,
|
|
74
|
+
`stop`, or `delete` bypass ownership locks and exact checkout validation.
|
|
446
75
|
|
|
447
|
-
|
|
76
|
+
## Agent onboarding
|
|
448
77
|
|
|
449
|
-
|
|
78
|
+
Generate the canonical, non-mutating onboarding prompt from a target repository:
|
|
450
79
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
Pre-commit hooks run on every commit once installed:
|
|
454
|
-
|
|
455
|
-
```sh
|
|
456
|
-
pre-commit install
|
|
80
|
+
```bash
|
|
81
|
+
npx --yes @devrouter/cli init --repo .
|
|
457
82
|
```
|
|
458
83
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
- Host-runtime dependencies are not auto-started; only Docker dependencies are auto-started.
|
|
464
|
-
- `kind=dependency` apps are not direct run/exec/open targets (must be started via a routed app dependency graph).
|
|
465
|
-
- TCP routing supports `tcpProtocol: postgres`, `redis`, `mariadb`, and `mysql`.
|
|
466
|
-
- Shared TCP hostname multiplexing requires TLS/SNI (`sslmode=require` or protocol-equivalent client SNI).
|
|
467
|
-
|
|
468
|
-
## Router state
|
|
469
|
-
|
|
470
|
-
Global managed artifacts remain under:
|
|
84
|
+
`devrouter repo agents` writes the matching Devrouter section and bundled skill
|
|
85
|
+
into the consumer repository. Artifact writes from `devrouter init` require the
|
|
86
|
+
explicit `--write-agents` or `--write-skill` flags.
|
|
471
87
|
|
|
472
|
-
|
|
473
|
-
- `~/.config/devrouter/traefik/traefik.yml`
|
|
474
|
-
- `~/.config/devrouter/traefik/dynamic/base.yml`
|
|
475
|
-
- `~/.config/devrouter/traefik/dynamic/host-routes.yml`
|
|
476
|
-
- `~/.config/devrouter/host-routes-state.json`
|
|
477
|
-
- `~/.config/devrouter/cache/...`
|
|
478
|
-
- `~/.config/devrouter/certs/*`
|
|
88
|
+
## Documentation
|
|
479
89
|
|
|
480
|
-
|
|
90
|
+
- [Documentation map](./docs/README.md) — choose the authoritative current manual or record.
|
|
91
|
+
- [Getting started](./docs/GETTING_STARTED.md) — install Devrouter and prove the first route.
|
|
92
|
+
- [Repository onboarding](./docs/REPO_ONBOARDING.md) — adapt a consumer repository and verify it.
|
|
93
|
+
- [Managed devcontainers](./docs/DEVCONTAINER.md) — canonical devnet, proxy, startup, and teardown contract.
|
|
94
|
+
- [Repository knowledge](./docs/knowledge/index.md) — architecture, ownership, lifecycle, and change guidance.
|
|
95
|
+
- [Domain context](./CONTEXT.md) — shared vocabulary consumed by Matt Pocock planning skills.
|
|
96
|
+
- [Contributor guide](./AGENTS.md) — source map, invariants, validation, and release checklist.
|
|
97
|
+
- [Examples](./examples/routing/README.md) — routing without a devcontainer; see also the [managed DevPod example](./examples/devcontainer/README.md) and [parallel workspace example](./examples/workspace/README.md).
|
|
98
|
+
- [Project records](./docs/project/index.md) — dated plans and roadmap state.
|
|
99
|
+
- [Release history](./CHANGELOG.md) and [adaptation prompts](./upgrade-prompts/) — versioned change guidance.
|
|
481
100
|
|
|
482
|
-
|
|
483
|
-
- Onboarding repositories and AI prompt: [`docs/REPO_ONBOARDING.md`](./docs/REPO_ONBOARDING.md)
|
|
484
|
-
- Agent contributor guide: [`AGENTS.md`](./AGENTS.md)
|
|
485
|
-
- Routing example: [`./examples/routing/README.md`](./examples/routing/README.md)
|
|
486
|
-
- Roadmap: [`docs/PLAN.md`](./docs/PLAN.md)
|
|
487
|
-
- Release and adaptation history: [`CHANGELOG.md`](./CHANGELOG.md) and [`upgrade-prompts/`](./upgrade-prompts/)
|
|
101
|
+
Run `devrouter --help` for the complete current command and option reference.
|
package/bin/devrouter-process
CHANGED
|
@@ -7,8 +7,12 @@ Usage:
|
|
|
7
7
|
devrouter-process ensure --name <name> --match <regex> [options] -- <command> [args...]
|
|
8
8
|
|
|
9
9
|
Options:
|
|
10
|
-
--fingerprint <value> Runtime identity. Defaults to command
|
|
10
|
+
--fingerprint <value> Runtime identity. Defaults to command, workspace, adapter, and safe allowlisted environment identity.
|
|
11
11
|
--log <path> Log file. Defaults to /tmp/devrouter-process-<name>.log.
|
|
12
|
+
|
|
13
|
+
Environment:
|
|
14
|
+
DEVROUTER_PROCESS_ADAPTER_SHA256 Exact managed-adapter SHA-256 supplied by devrouter.
|
|
15
|
+
DEVROUTER_PROCESS_FINGERPRINT_ENV Comma-separated non-secret environment names to fingerprint.
|
|
12
16
|
EOF
|
|
13
17
|
}
|
|
14
18
|
|
|
@@ -73,7 +77,7 @@ done
|
|
|
73
77
|
[ "$#" -gt 0 ] || die "A command is required after --."
|
|
74
78
|
[ -r "/proc/$$/environ" ] || die "Linux /proc process metadata is required."
|
|
75
79
|
|
|
76
|
-
for tool in awk
|
|
80
|
+
for tool in awk flock grep pgrep ps setsid sha256sum sort tr; do
|
|
77
81
|
command -v "$tool" >/dev/null 2>&1 || die "Required command is unavailable: $tool"
|
|
78
82
|
done
|
|
79
83
|
|
|
@@ -81,11 +85,54 @@ match_status=0
|
|
|
81
85
|
pgrep -f -- "$process_match" >/dev/null 2>&1 || match_status=$?
|
|
82
86
|
[ "$match_status" -le 1 ] || die "--match is not a valid process regular expression."
|
|
83
87
|
|
|
88
|
+
fingerprint_env_names=()
|
|
89
|
+
fingerprint_env_list="${DEVROUTER_PROCESS_FINGERPRINT_ENV:-}"
|
|
90
|
+
if [ -n "$fingerprint_env_list" ]; then
|
|
91
|
+
[[ "$fingerprint_env_list" =~ ^[a-zA-Z_][a-zA-Z0-9_]*(,[a-zA-Z_][a-zA-Z0-9_]*)*$ ]] ||
|
|
92
|
+
die "DEVROUTER_PROCESS_FINGERPRINT_ENV must be an exact comma-separated list of environment names."
|
|
93
|
+
IFS=',' read -r -a fingerprint_env_names <<<"$fingerprint_env_list"
|
|
94
|
+
for env_name in "${fingerprint_env_names[@]}"; do
|
|
95
|
+
upper_name="${env_name^^}"
|
|
96
|
+
case "$upper_name" in
|
|
97
|
+
*SECRET* | *TOKEN* | *PASSWORD* | *PASSWD* | *API_KEY* | *PRIVATE_KEY* | *CREDENTIAL* | *COOKIE_VALUE* | *COOKIE)
|
|
98
|
+
die "DEVROUTER_PROCESS_FINGERPRINT_ENV rejects secret-like name '$env_name'."
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
mapfile -t fingerprint_env_names < <(printf '%s\n' "${fingerprint_env_names[@]}" | LC_ALL=C sort -u)
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if [[ -v DEVROUTER_PROCESS_ADAPTER_SHA256 ]] &&
|
|
106
|
+
! [[ "$DEVROUTER_PROCESS_ADAPTER_SHA256" =~ ^[a-fA-F0-9]{64}$ ]]; then
|
|
107
|
+
die "DEVROUTER_PROCESS_ADAPTER_SHA256 must be a SHA-256 digest."
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
emit_environment_field() {
|
|
111
|
+
local field="$1"
|
|
112
|
+
local variable="$2"
|
|
113
|
+
|
|
114
|
+
if [[ -v "$variable" ]]; then
|
|
115
|
+
printf '%s\0set\0%s\0' "$field" "${!variable}"
|
|
116
|
+
else
|
|
117
|
+
printf '%s\0unset\0' "$field"
|
|
118
|
+
fi
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
if [ -z "$fingerprint" ]; then
|
|
85
122
|
fingerprint="$({
|
|
86
|
-
printf '
|
|
87
|
-
printf '%s\0' "
|
|
88
|
-
|
|
123
|
+
printf 'format\0devrouter-process-v2\0'
|
|
124
|
+
printf 'name\0%s\0' "$name"
|
|
125
|
+
emit_environment_field workspace WORKSPACE
|
|
126
|
+
emit_environment_field devrouter-workspace DEVROUTER_WORKSPACE
|
|
127
|
+
emit_environment_field adapter-sha256 DEVROUTER_PROCESS_ADAPTER_SHA256
|
|
128
|
+
for argument in "$@"; do
|
|
129
|
+
printf 'argv\0%s\0' "$argument"
|
|
130
|
+
done
|
|
131
|
+
for env_name in "${fingerprint_env_names[@]}"; do
|
|
132
|
+
printf 'environment-name\0%s\0' "$env_name"
|
|
133
|
+
emit_environment_field environment-value "$env_name"
|
|
134
|
+
done
|
|
135
|
+
} | LC_ALL=C sha256sum | awk '{print $1}')"
|
|
89
136
|
fi
|
|
90
137
|
[[ "$fingerprint" =~ ^[a-zA-Z0-9][a-zA-Z0-9._:-]*$ ]] || die "--fingerprint must be a safe identifier."
|
|
91
138
|
|