@devrouter/cli 0.0.23 → 0.0.24
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 +77 -77
- package/dist/{dev.js → devrouter.js} +275 -192
- package/package.json +5 -3
|
@@ -158,32 +158,32 @@ This folder is managed by the devrouter CLI.
|
|
|
158
158
|
|
|
159
159
|
## Commands
|
|
160
160
|
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
161
|
+
- devrouter init [--write-agents] [--write-skill] [--with-linear]
|
|
162
|
+
- devrouter -V [--repo <path>] (installed/local version + next upgrade)
|
|
163
|
+
- devrouter upgrade [version] [--repo <path>]
|
|
164
|
+
- devrouter setup --yes [--repo <path>] [--json]
|
|
165
|
+
- devrouter up
|
|
166
|
+
- devrouter down
|
|
167
|
+
- devrouter status
|
|
168
|
+
- devrouter doctor
|
|
169
|
+
- devrouter ls
|
|
170
|
+
- devrouter open <name>
|
|
171
|
+
- devrouter logs [-f] [--tail N]
|
|
172
|
+
- devrouter repo init
|
|
173
|
+
- devrouter repo inspect [--repo <path>] [--json]
|
|
174
|
+
- devrouter repo devcontainer write [--repo <path>] [--dry-run] [--yes] [--json]
|
|
175
|
+
- devrouter repo devcontainer verify [--repo <path>] [--live] [--yes] [--json]
|
|
176
|
+
- devrouter repo agents [--with-linear]
|
|
177
|
+
- devrouter app add --name <name> --host <host.localhost> --protocol <http|tcp> --runtime <host|docker>
|
|
178
|
+
- devrouter app run <name>
|
|
179
|
+
- devrouter app exec <name> [--shell] [--env <env>] -- <command>
|
|
180
|
+
- devrouter app ls
|
|
181
|
+
- devrouter app rm <name>
|
|
182
|
+
- devrouter tls install
|
|
183
183
|
|
|
184
184
|
## Troubleshooting
|
|
185
185
|
|
|
186
|
-
If
|
|
186
|
+
If devrouter up fails with port conflicts on 80/443 (or TCP protocol ports), run:
|
|
187
187
|
|
|
188
188
|
- lsof -nP -iTCP:80 -sTCP:LISTEN
|
|
189
189
|
- lsof -nP -iTCP:443 -sTCP:LISTEN
|
|
@@ -552,7 +552,14 @@ function listHostRouteState() {
|
|
|
552
552
|
return [];
|
|
553
553
|
}
|
|
554
554
|
return parsed.filter((item) => item && typeof item === "object").map((item) => item);
|
|
555
|
-
} catch {
|
|
555
|
+
} catch (err) {
|
|
556
|
+
const error = err;
|
|
557
|
+
if (error.code !== "ENOENT") {
|
|
558
|
+
process.stderr.write(
|
|
559
|
+
`Warning: devrouter host routes state file is corrupted or unreadable (${error.message}). Recreating route state.
|
|
560
|
+
`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
556
563
|
return [];
|
|
557
564
|
}
|
|
558
565
|
}
|
|
@@ -1536,7 +1543,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1536
1543
|
"",
|
|
1537
1544
|
"Top-level .devrouter.yml schema:",
|
|
1538
1545
|
"- version: 1 (required)",
|
|
1539
|
-
"- devrouter.version: semantic version string (recommended; required for `
|
|
1546
|
+
"- devrouter.version: semantic version string (recommended; required for `devrouter -V`/`devrouter upgrade`)",
|
|
1540
1547
|
"- project.name: string (optional)",
|
|
1541
1548
|
`- secretManager.command: string (optional; SM command including trailing \`--\` boundary; supports \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template placeholder)`,
|
|
1542
1549
|
`- secretManager.defaultEnv: string (optional; fallback env for \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template; required when command contains \`${SECRET_MANAGER_ENV_PLACEHOLDER}\`)`,
|
|
@@ -1603,20 +1610,20 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1603
1610
|
'- Example healthcheck for postgres: `test: ["CMD-SHELL", "pg_isready -U <user> -d <db>"]` with `interval: 5s`, `timeout: 3s`, `retries: 20`.',
|
|
1604
1611
|
"",
|
|
1605
1612
|
"Runtime behavior to account for:",
|
|
1606
|
-
"- Docker dependencies can be auto-started by
|
|
1607
|
-
"-
|
|
1608
|
-
"- Docker dependencies are automatically stopped when a host app exits (Ctrl+C or error); docker app services remain running until explicit cleanup (`docker compose down`, `
|
|
1613
|
+
"- Docker dependencies can be auto-started by devrouter app run.",
|
|
1614
|
+
"- devrouter app run waits for Docker dependencies to become healthy before starting the host or docker app.",
|
|
1615
|
+
"- Docker dependencies are automatically stopped when a host app exits (Ctrl+C or error); docker app services remain running until explicit cleanup (`docker compose down`, `devrouter down`, or equivalent).",
|
|
1609
1616
|
"- Recent dependency logs (last 20 lines) are printed after dependencies start.",
|
|
1610
1617
|
"- Host-runtime dependencies are NOT auto-started in v1 (must be started manually).",
|
|
1611
|
-
"- kind=dependency entries are dependency-only: they do not create routes and cannot be direct targets for `
|
|
1618
|
+
"- kind=dependency entries are dependency-only: they do not create routes and cannot be direct targets for `devrouter app run`, `devrouter app exec`, or `devrouter open`.",
|
|
1612
1619
|
"- kind=dependency services are started/stopped as declared in compose (no Traefik labels added, no env/port injection).",
|
|
1613
1620
|
`- For TCP dependencies of host apps, devrouter publishes a random host port and injects per-dep deterministic vars: \`{PREFIX}_HOST=localhost\`, \`{PREFIX}_PORT=<port>\`, \`{PREFIX}_URL\` (protocol-specific), \`{PREFIX}_SHADOW_URL\` (postgres only). \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`.`,
|
|
1614
1621
|
"- Config-level `envMap` on dependency references aliases per-dep vars to app-expected names. Example: `envMap: { DATABASE_URL: DB_URL }` maps per-dep `DB_URL` to `DATABASE_URL` in the app process.",
|
|
1615
1622
|
"- If the repo's Postgres docker-compose service uses different credentials than the injected defaults (`prisma:prisma`), flag this to the user and recommend aligning the compose env vars or using `envMap` aliasing.",
|
|
1616
1623
|
"- Postgres multiplexing on shared :5432 requires TLS/SNI (useful for psql 17+, pgAdmin, not standard app clients).",
|
|
1617
|
-
"- When TLS is enabled, `
|
|
1624
|
+
"- When TLS is enabled, `devrouter app run` and `devrouter app exec` auto-refresh cert SAN coverage for configured repo hosts before startup.",
|
|
1618
1625
|
"- For TCP/Postgres, standard app frameworks should use the injected port env vars; only direct-TLS-capable tools should use sslmode=require on :5432.",
|
|
1619
|
-
"- `
|
|
1626
|
+
"- `devrouter app exec <name> -- <command>` starts dependencies as needed, resolves env vars, and runs a one-shot command with the resolved env. Exec stops only dependencies started by that invocation (already-running dependencies stay running). If ownership detection fails, exec leaves selected dependencies running to avoid non-owned teardown. Exec preserves argv semantics by default (`shell: false`) and supports explicit shell mode via `--shell` when needed.",
|
|
1620
1627
|
"- `envMap` on dependency references (config-level) aliases per-dep vars after dependency env resolution. `envMap` fails fast when source var is missing.",
|
|
1621
1628
|
"",
|
|
1622
1629
|
"Workspace isolation (parallel git worktrees / agents):",
|
|
@@ -1624,11 +1631,11 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1624
1631
|
"- Token resolution precedence: `--workspace <slug>` flag > `DEVROUTER_WORKSPACE` env var > auto-derived from a linked git worktree branch (sanitized: lowercase, non-alphanumeric \u2192 `-`, capped at 32 chars) > none. The primary checkout resolves to no token and routes exactly as before (fully back-compatible).",
|
|
1625
1632
|
`- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
|
|
1626
1633
|
"- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
|
|
1627
|
-
"- Lifecycle: `
|
|
1634
|
+
"- Lifecycle: `devrouter workspace up <branch>` (create worktree + devpod + routes), `devrouter workspace ls` (list worktrees/tokens/route counts), `devrouter workspace down <workspace|branch>` (free routes + stop devpod + remove worktree). `devrouter doctor` reports orphaned workspace proxy routes whose worktree dir was removed without `devrouter workspace down`.",
|
|
1628
1635
|
`- devcontainer integration: the devcontainer compose service exposes a devnet alias \`${WORKSPACE_PLACEHOLDER}-app\` (default \`WORKSPACE=<project>\` in \`devcontainer.env\`), and the proxy app uses \`upstream: ${WORKSPACE_PLACEHOLDER}-app:<port>\`. Spinning up workspace \`feat-a\` \u2192 alias \`feat-a-app\`, host \`app.feat-a.localhost\`.`,
|
|
1629
1636
|
"",
|
|
1630
1637
|
"Secret Manager Integration (config-based):",
|
|
1631
|
-
"- Optional top-level `secretManager.command` in `.devrouter.yml` wraps `
|
|
1638
|
+
"- Optional top-level `secretManager.command` in `.devrouter.yml` wraps `devrouter app run` and `devrouter app exec` commands with the SM command and re-applies devrouter-injected dep env vars after the SM boundary via `env KEY=VAL` prefix.",
|
|
1632
1639
|
`- Example config: \`secretManager: { command: "infisical run --env ${SECRET_MANAGER_ENV_PLACEHOLDER} --", defaultEnv: "dev" }\`.`,
|
|
1633
1640
|
`- \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template placeholder in \`secretManager.command\` is resolved at runtime. \`defaultEnv\` provides the fallback; \`--env\` CLI flag overrides it.`,
|
|
1634
1641
|
"- When configured, the effective command becomes: `<secretManager.command> env {PREFIX}_URL=<val> ... <user-command>`.",
|
|
@@ -1641,40 +1648,40 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1641
1648
|
"- Secret managers may also define DB variables. Do not assume secret-manager precedence. Confirm the effective values in the command process before migrate/seed.",
|
|
1642
1649
|
"- For apps that require legacy names like `DATABASE_URL`, use config-level `envMap` on the dependency reference: `envMap: { DATABASE_URL: DB_URL }`.",
|
|
1643
1650
|
"- Avoid pre-wrapper DB assignments such as `DATABASE_URI=... <wrapper> run -- ...`; wrapper-managed env may override those values.",
|
|
1644
|
-
"- `
|
|
1645
|
-
"- With TLS enabled, `
|
|
1646
|
-
"- Robust one-shot migrate example (argv-safe, no nested shell quoting): `
|
|
1647
|
-
"- Robust one-shot seed example: `
|
|
1648
|
-
"- Environment probe/debug example (run before migrations): `
|
|
1651
|
+
"- `devrouter doctor --repo <REPO_PATH>` warns on risky pre-wrapper DB assignments before `run --` for host apps that depend on postgres.",
|
|
1652
|
+
"- With TLS enabled, `devrouter doctor --repo <REPO_PATH>` also warns on cert SAN mismatches for configured hosts (`repo.tls-host-coverage`).",
|
|
1653
|
+
"- Robust one-shot migrate example (argv-safe, no nested shell quoting): `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate`.",
|
|
1654
|
+
"- Robust one-shot seed example: `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload seed`.",
|
|
1655
|
+
"- Environment probe/debug example (run before migrations): `devrouter app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL`.",
|
|
1649
1656
|
"- Use `--shell` only when shell expansion is required; it must receive exactly one command string after `--`.",
|
|
1650
1657
|
"- Warning: Do not run migration/seed until env probe confirms expected DB variables and values.",
|
|
1651
1658
|
"",
|
|
1652
1659
|
"Required workflow:",
|
|
1653
|
-
"1) Run `
|
|
1654
|
-
"2) Run `
|
|
1655
|
-
"3) For the supported Node/pnpm/Postgres devcontainer shape, run `
|
|
1660
|
+
"1) Run `devrouter setup --yes --json` for devrouter-owned machine state; use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
|
|
1661
|
+
"2) Run `devrouter repo inspect --repo <REPO_PATH> --json` before editing files.",
|
|
1662
|
+
"3) For the supported Node/pnpm/Postgres devcontainer shape, run `devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json`, review the plan, then run `devrouter repo devcontainer write --repo <REPO_PATH> --yes`.",
|
|
1656
1663
|
"4) For unsupported shapes or custom existing files, make minimal manual edits and explain the assumptions.",
|
|
1657
|
-
"5) Verify static evidence with `
|
|
1664
|
+
"5) Verify static evidence with `devrouter repo devcontainer verify --repo <REPO_PATH> --json`; after the devcontainer is running, use `devrouter repo devcontainer verify --repo <REPO_PATH> --live --yes --json` for route probes.",
|
|
1658
1665
|
"6) Keep edits minimal, explicit, and idempotent. Do not modify unrelated services.",
|
|
1659
1666
|
"7) If required info is missing or ambiguous, stop and ask targeted questions.",
|
|
1660
1667
|
"",
|
|
1661
1668
|
"Validation commands to run/report for the devcontainer path:",
|
|
1662
|
-
"-
|
|
1663
|
-
"-
|
|
1664
|
-
"-
|
|
1665
|
-
"-
|
|
1666
|
-
"-
|
|
1667
|
-
"-
|
|
1668
|
-
"- After `devpod up <REPO_PATH>`:
|
|
1669
|
+
"- devrouter setup --yes --json",
|
|
1670
|
+
"- devrouter doctor --repo <REPO_PATH> --json",
|
|
1671
|
+
"- devrouter repo inspect --repo <REPO_PATH> --json",
|
|
1672
|
+
"- devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json",
|
|
1673
|
+
"- devrouter repo devcontainer write --repo <REPO_PATH> --yes",
|
|
1674
|
+
"- devrouter repo devcontainer verify --repo <REPO_PATH> --json",
|
|
1675
|
+
"- After `devpod up <REPO_PATH>`: devrouter repo devcontainer verify --repo <REPO_PATH> --live --yes --json",
|
|
1669
1676
|
"",
|
|
1670
1677
|
"Validation commands to run/report for host/docker runtime apps:",
|
|
1671
|
-
"-
|
|
1672
|
-
"-
|
|
1673
|
-
"-
|
|
1674
|
-
"- For each entry (when safe):
|
|
1675
|
-
"- Run one-shot commands with dep env:
|
|
1676
|
-
"- Probe effective env before migration/seed:
|
|
1677
|
-
"-
|
|
1678
|
+
"- devrouter setup --yes --json",
|
|
1679
|
+
"- devrouter doctor --repo <REPO_PATH> --json",
|
|
1680
|
+
"- devrouter app ls --repo <REPO_PATH>",
|
|
1681
|
+
"- For each entry (when safe): devrouter app run <name> --repo <REPO_PATH> --yes",
|
|
1682
|
+
"- Run one-shot commands with dep env: devrouter app exec <name> --repo <REPO_PATH> --yes -- <command>",
|
|
1683
|
+
"- Probe effective env before migration/seed: devrouter app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL",
|
|
1684
|
+
"- devrouter ls",
|
|
1678
1685
|
"- For HTTP entries: curl -I http://<host>",
|
|
1679
1686
|
'- For TCP postgres entries: provide connection hint (example: psql "... sslmode=require")',
|
|
1680
1687
|
"",
|
|
@@ -1687,8 +1694,8 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1687
1694
|
"6) Unresolved questions/risks (if any).",
|
|
1688
1695
|
"7) Definition-of-done checklist status:",
|
|
1689
1696
|
" - .devrouter.yml exists and validates",
|
|
1690
|
-
" -
|
|
1691
|
-
" -
|
|
1697
|
+
" - devrouter app ls matches expected entries",
|
|
1698
|
+
" - devrouter ls exposes expected endpoints",
|
|
1692
1699
|
" - HTTP routes reachable",
|
|
1693
1700
|
" - TCP Postgres route configured with TLS requirement noted",
|
|
1694
1701
|
...withLinear ? [
|
|
@@ -1705,9 +1712,9 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
1705
1712
|
"- While implementing Linear-tracked work, set issue status at session start and at each phase transition.",
|
|
1706
1713
|
"- Post progress comments at meaningful checkpoints during implementation (not only at the end).",
|
|
1707
1714
|
"- Before ending a session, post a final recap comment with completed work, remaining work, risks, and next step, then re-check status/comment freshness.",
|
|
1708
|
-
"- Optional bootstrap commands for repo artifacts: `
|
|
1709
|
-
"- If the repository uses devrouter, keep `.devrouter.yml` metadata `devrouter.version` updated and run `
|
|
1710
|
-
"- Resolve adaptation prompts with `
|
|
1715
|
+
"- Optional bootstrap commands for repo artifacts: `devrouter init --repo <REPO_PATH> --with-linear --write-agents --write-skill` or `devrouter repo agents --repo <REPO_PATH> --with-linear`.",
|
|
1716
|
+
"- If the repository uses devrouter, keep `.devrouter.yml` metadata `devrouter.version` updated and run `devrouter -V` to verify installed/local versions plus the next target.",
|
|
1717
|
+
"- Resolve adaptation prompts with `devrouter upgrade` (list targets) and `devrouter upgrade <version>` (target prompt), sourced from `upgrade-prompts/<version>.md` in the devrouter release."
|
|
1711
1718
|
] : [],
|
|
1712
1719
|
"",
|
|
1713
1720
|
renderCommandIntentSection()
|
|
@@ -1721,93 +1728,93 @@ var init_ai_prompt = __esm({
|
|
|
1721
1728
|
init_repo_config();
|
|
1722
1729
|
COMMAND_INTENTS = [
|
|
1723
1730
|
{
|
|
1724
|
-
command: "
|
|
1731
|
+
command: "devrouter init [--with-linear]",
|
|
1725
1732
|
purpose: "Print the AI onboarding prompt template for a repository (non-mutating by default)."
|
|
1726
1733
|
},
|
|
1727
1734
|
{
|
|
1728
|
-
command: "
|
|
1735
|
+
command: "devrouter -V",
|
|
1729
1736
|
purpose: "Show installed CLI version, local repo version, and next upgrade target."
|
|
1730
1737
|
},
|
|
1731
1738
|
{
|
|
1732
|
-
command: "
|
|
1739
|
+
command: "devrouter upgrade [version]",
|
|
1733
1740
|
purpose: "Show upgrade targets from .devrouter.yml devrouter.version and print target adaptation prompt."
|
|
1734
1741
|
},
|
|
1735
1742
|
{
|
|
1736
|
-
command: "
|
|
1743
|
+
command: "devrouter setup",
|
|
1737
1744
|
purpose: "Run first-time devrouter machine setup and report structured diagnostics."
|
|
1738
1745
|
},
|
|
1739
1746
|
{
|
|
1740
|
-
command: "
|
|
1747
|
+
command: "devrouter up",
|
|
1741
1748
|
purpose: "Start shared Traefik and ensure the shared devnet network."
|
|
1742
1749
|
},
|
|
1743
|
-
{ command: "
|
|
1750
|
+
{ command: "devrouter down", purpose: "Stop the shared Traefik router stack." },
|
|
1744
1751
|
{
|
|
1745
|
-
command: "
|
|
1752
|
+
command: "devrouter status",
|
|
1746
1753
|
purpose: "Show router/container/network/TLS health and bound ports."
|
|
1747
1754
|
},
|
|
1748
1755
|
{
|
|
1749
|
-
command: "
|
|
1756
|
+
command: "devrouter doctor",
|
|
1750
1757
|
purpose: "Run deep diagnostics across global router state and repo config."
|
|
1751
1758
|
},
|
|
1752
1759
|
{
|
|
1753
|
-
command: "
|
|
1760
|
+
command: "devrouter ls",
|
|
1754
1761
|
purpose: "List active HTTP and TCP routes resolved by devrouter."
|
|
1755
1762
|
},
|
|
1756
1763
|
{
|
|
1757
|
-
command: "
|
|
1764
|
+
command: "devrouter open <name>",
|
|
1758
1765
|
purpose: "Open HTTP routes or print connection hints for TCP routes (match app/service/host)."
|
|
1759
1766
|
},
|
|
1760
1767
|
{
|
|
1761
|
-
command: "
|
|
1768
|
+
command: "devrouter tls install",
|
|
1762
1769
|
purpose: "Install mkcert certs and enable TLS/HTTPS for local routing."
|
|
1763
1770
|
},
|
|
1764
1771
|
{
|
|
1765
|
-
command: "
|
|
1772
|
+
command: "devrouter repo init",
|
|
1766
1773
|
purpose: "Create `.devrouter.yml` in a target repository."
|
|
1767
1774
|
},
|
|
1768
1775
|
{
|
|
1769
|
-
command: "
|
|
1776
|
+
command: "devrouter repo inspect",
|
|
1770
1777
|
purpose: "Inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding."
|
|
1771
1778
|
},
|
|
1772
1779
|
{
|
|
1773
|
-
command: "
|
|
1780
|
+
command: "devrouter repo devcontainer write",
|
|
1774
1781
|
purpose: "Dry-run or write conservative managed Node/pnpm/Postgres devcontainer/devrouter scaffold files."
|
|
1775
1782
|
},
|
|
1776
1783
|
{
|
|
1777
|
-
command: "
|
|
1784
|
+
command: "devrouter repo devcontainer verify",
|
|
1778
1785
|
purpose: "Emit static onboarding evidence, or live route probes with --live --yes."
|
|
1779
1786
|
},
|
|
1780
1787
|
{
|
|
1781
|
-
command: "
|
|
1788
|
+
command: "devrouter app add",
|
|
1782
1789
|
purpose: "Add or update one app entry in `.devrouter.yml`."
|
|
1783
1790
|
},
|
|
1784
|
-
{ command: "
|
|
1791
|
+
{ command: "devrouter app ls", purpose: "List app entries from `.devrouter.yml`." },
|
|
1785
1792
|
{
|
|
1786
|
-
command: "
|
|
1793
|
+
command: "devrouter app run [--env <env>]",
|
|
1787
1794
|
purpose: "Run one configured app and reconcile its route at runtime (--env overrides SM defaultEnv)."
|
|
1788
1795
|
},
|
|
1789
1796
|
{
|
|
1790
|
-
command: "
|
|
1797
|
+
command: "devrouter app exec [--shell] [--env <env>]",
|
|
1791
1798
|
purpose: "Run a one-shot command with resolved dependency env vars (env aliasing via config-level envMap)."
|
|
1792
1799
|
},
|
|
1793
1800
|
{
|
|
1794
|
-
command: "
|
|
1801
|
+
command: "devrouter app rm [--keep-config]",
|
|
1795
1802
|
purpose: "Remove one app entry from `.devrouter.yml` and free its route. `--keep-config` frees only the live route/hostname (e.g. to release one claimed by another repo) and leaves the config file untouched."
|
|
1796
1803
|
},
|
|
1797
1804
|
{
|
|
1798
|
-
command: "
|
|
1805
|
+
command: "devrouter repo agents [--with-linear]",
|
|
1799
1806
|
purpose: "Write/update devrouter section in the repo's AGENTS.md and optionally add Linear workflow assets."
|
|
1800
1807
|
},
|
|
1801
1808
|
{
|
|
1802
|
-
command: "
|
|
1809
|
+
command: "devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]",
|
|
1803
1810
|
purpose: "Create a git worktree for <branch>, bring up its devpod (`devpod up --id <ws>`), and register workspace-namespaced routes."
|
|
1804
1811
|
},
|
|
1805
1812
|
{
|
|
1806
|
-
command: "
|
|
1813
|
+
command: "devrouter workspace ls [--json]",
|
|
1807
1814
|
purpose: "List git worktrees with their resolved workspace token and active route count."
|
|
1808
1815
|
},
|
|
1809
1816
|
{
|
|
1810
|
-
command: "
|
|
1817
|
+
command: "devrouter workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]",
|
|
1811
1818
|
purpose: "Free a workspace's routes, stop its devpod, and remove its worktree (routes are freed by state-file workspace tag, no config load)."
|
|
1812
1819
|
}
|
|
1813
1820
|
];
|
|
@@ -1827,11 +1834,11 @@ function buildDevrouterSection() {
|
|
|
1827
1834
|
`\`${DEVROUTER_SKILL_REL_PATH}\``,
|
|
1828
1835
|
"",
|
|
1829
1836
|
"Quick validation sequence:",
|
|
1830
|
-
"- `
|
|
1831
|
-
"- `
|
|
1832
|
-
"- `
|
|
1833
|
-
"- `
|
|
1834
|
-
"- `
|
|
1837
|
+
"- `devrouter up`",
|
|
1838
|
+
"- `devrouter tls install` (required when repo defines tcp/postgres apps)",
|
|
1839
|
+
"- `devrouter app ls --repo .`",
|
|
1840
|
+
"- `devrouter app run <host-app> --repo . --yes`",
|
|
1841
|
+
"- `devrouter ls`"
|
|
1835
1842
|
].join("\n");
|
|
1836
1843
|
}
|
|
1837
1844
|
function buildLinearWorkflowSection() {
|
|
@@ -1859,8 +1866,8 @@ function buildLinearWorkflowSection() {
|
|
|
1859
1866
|
"- Re-check status and comment freshness toward/at session end before stopping.",
|
|
1860
1867
|
"",
|
|
1861
1868
|
"Bootstrap commands:",
|
|
1862
|
-
"- `
|
|
1863
|
-
"- `
|
|
1869
|
+
"- `devrouter init --with-linear --write-agents --write-skill`",
|
|
1870
|
+
"- `devrouter repo agents --with-linear`"
|
|
1864
1871
|
].join("\n");
|
|
1865
1872
|
}
|
|
1866
1873
|
function yamlQuote(value) {
|
|
@@ -2034,7 +2041,7 @@ Local dev routing via a shared Traefik reverse proxy. Provides stable \`*.localh
|
|
|
2034
2041
|
\`\`\`yaml
|
|
2035
2042
|
version: 1
|
|
2036
2043
|
devrouter:
|
|
2037
|
-
version: <semver> # required for
|
|
2044
|
+
version: <semver> # required for devrouter -V / devrouter upgrade
|
|
2038
2045
|
project:
|
|
2039
2046
|
name: <string> # optional
|
|
2040
2047
|
apps:
|
|
@@ -2067,7 +2074,7 @@ apps:
|
|
|
2067
2074
|
# protocol: tcp
|
|
2068
2075
|
# tcpProtocol: postgres # selects shared entrypoint :5432
|
|
2069
2076
|
# upstream: <db-alias>:5432 # devnet alias of the DB container
|
|
2070
|
-
# Requires \`
|
|
2077
|
+
# Requires \`devrouter tls install\` (SNI is read from the TLS ClientHello). Connect
|
|
2071
2078
|
# with direct-SSL so the ClientHello carries SNI, e.g.:
|
|
2072
2079
|
# psql "host=db.<app>.localhost port=5432 sslmode=require sslnegotiation=direct ..."
|
|
2073
2080
|
|
|
@@ -2102,7 +2109,7 @@ Validation rules:
|
|
|
2102
2109
|
|
|
2103
2110
|
- \`kind=app\`: \`host\` must end with \`.localhost\`
|
|
2104
2111
|
- \`kind=app\`: \`runtime=host\` supports \`protocol=http\` only
|
|
2105
|
-
- \`kind=app\`: \`runtime=proxy\` supports \`protocol=http\` or \`protocol=tcp\`, requires \`upstream\` (\`host:port\`), and forbids \`hostRun\`/\`docker\`/\`dependencies\` (it only registers a route to an externally-managed upstream). \`protocol=tcp\` additionally requires \`tcpProtocol\` and TLS (\`
|
|
2112
|
+
- \`kind=app\`: \`runtime=proxy\` supports \`protocol=http\` or \`protocol=tcp\`, requires \`upstream\` (\`host:port\`), and forbids \`hostRun\`/\`docker\`/\`dependencies\` (it only registers a route to an externally-managed upstream). \`protocol=tcp\` additionally requires \`tcpProtocol\` and TLS (\`devrouter tls install\`)
|
|
2106
2113
|
- \`kind=app\`: \`protocol=tcp\` requires \`runtime=docker\` (devrouter-managed container) or \`runtime=proxy\` (externally-managed upstream), plus a supported \`tcpProtocol\` (postgres/redis/mariadb/mysql)
|
|
2107
2114
|
- \`kind=dependency\`: must use \`runtime=docker\` and does not allow routed fields (\`host\`/\`protocol\`/\`tcpProtocol\`/\`hostRun\`/\`docker.internalPort\`/\`docker.router\`)
|
|
2108
2115
|
- Unknown keys rejected (strict schema)
|
|
@@ -2126,7 +2133,7 @@ healthcheck:
|
|
|
2126
2133
|
|
|
2127
2134
|
## Env var injection
|
|
2128
2135
|
|
|
2129
|
-
When a host app depends on a TCP Docker service, \`
|
|
2136
|
+
When a host app depends on a TCP Docker service, \`devrouter app run\` and \`devrouter app exec\` inject per-dep deterministic vars (where \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`):
|
|
2130
2137
|
|
|
2131
2138
|
| Variable | Value |
|
|
2132
2139
|
| ----------------------- | ----------------------------------------------------------- |
|
|
@@ -2147,7 +2154,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
2147
2154
|
- **When active**: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`\${WORKSPACE}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.
|
|
2148
2155
|
- **TLS**: namespaced hosts (\`web.<ws>.localhost\`) are not covered by the \`*.localhost\` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.
|
|
2149
2156
|
- **devcontainer integration**: the devcontainer compose service exposes a devnet alias \`\${WORKSPACE}-app\` (default \`WORKSPACE=<project>\` in \`devcontainer.env\`); the proxy app uses \`upstream: \${WORKSPACE}-app:<port>\`. Workspace \`feat-a\` \u2192 alias \`feat-a-app\`, host \`app.feat-a.localhost\`.
|
|
2150
|
-
- **Lifecycle**: \`
|
|
2157
|
+
- **Lifecycle**: \`devrouter workspace up <branch>\` (create worktree + devpod + routes), \`devrouter workspace ls\` (list worktrees/tokens/route counts), \`devrouter workspace down <workspace|branch>\` (free routes by state-file workspace tag + stop devpod + remove worktree). \`devrouter doctor\` reports orphaned workspace proxy routes whose worktree dir was removed without \`devrouter workspace down\`.
|
|
2151
2158
|
|
|
2152
2159
|
## Secret manager interop (Infisical/Doppler)
|
|
2153
2160
|
|
|
@@ -2171,31 +2178,31 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
2171
2178
|
\`\`\`
|
|
2172
2179
|
- Prefer argv-safe command forms. Do not wrap \`infisical run\` or \`doppler run\` in \`sh -lc\` unless shell expansion is strictly required.
|
|
2173
2180
|
- Canonical Infisical migrate command:
|
|
2174
|
-
\`
|
|
2181
|
+
\`devrouter app exec <app> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate\`
|
|
2175
2182
|
- Canonical env probe command (run before migrate/seed):
|
|
2176
|
-
\`
|
|
2183
|
+
\`devrouter app exec <app> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL\`
|
|
2177
2184
|
- Canonical Doppler migrate command:
|
|
2178
|
-
\`
|
|
2185
|
+
\`devrouter app exec <app> --yes -- doppler run -- pnpm payload migrate\`
|
|
2179
2186
|
- Precedence best practice: avoid defining per-dep var names in Infisical/Doppler when you expect devrouter local DB injection.
|
|
2180
2187
|
- Precedence best practice: store remote/prod URLs under non-conflicting names (for example \`PROD_DATABASE_URL\`) and map intentionally via \`envMap\`.
|
|
2181
2188
|
- Precedence best practice: if secret manager must define DB vars, run the env probe and verify values before any migration/seed.
|
|
2182
|
-
- Use \`
|
|
2189
|
+
- Use \`devrouter app exec --shell -- "<single command string>"\` only when shell expansion is required.
|
|
2183
2190
|
- \`envMap\` fails fast when source var is missing so migrations do not run with partial mapping.
|
|
2184
2191
|
|
|
2185
2192
|
## Upgrade handling (required)
|
|
2186
2193
|
|
|
2187
2194
|
- Keep \`.devrouter.yml\` metadata \`devrouter.version\` aligned with the currently applied devrouter release.
|
|
2188
|
-
- Verify versions with \`
|
|
2189
|
-
- Use \`
|
|
2195
|
+
- Verify versions with \`devrouter -V\` (shows installed CLI version, local repo version, and next upgrade target).
|
|
2196
|
+
- Use \`devrouter upgrade\` to list available upgrade targets and \`devrouter upgrade <version>\` to print that target's Agent Adaptation Prompt from \`upgrade-prompts/<version>.md\`.
|
|
2190
2197
|
- Do not assume user-provided instructions include all required adaptation steps.
|
|
2191
|
-
- After upgrading the CLI in a dependent repo, refresh discoverability artifacts with \`
|
|
2192
|
-
- Re-run validation after upgrade: \`
|
|
2198
|
+
- After upgrading the CLI in a dependent repo, refresh discoverability artifacts with \`devrouter repo agents\` (or \`devrouter init --write-agents --write-skill\`).
|
|
2199
|
+
- Re-run validation after upgrade: \`devrouter doctor --repo .\`, \`devrouter app ls --repo .\`, one representative \`devrouter app exec\` flow, and \`devrouter ls\`.
|
|
2193
2200
|
|
|
2194
2201
|
## Optional Linear workflow bootstrap
|
|
2195
2202
|
|
|
2196
2203
|
- To add Linear task-management workflow assets to a repo, run:
|
|
2197
|
-
- \`
|
|
2198
|
-
- \`
|
|
2204
|
+
- \`devrouter init --with-linear --write-agents --write-skill\`, or
|
|
2205
|
+
- \`devrouter repo agents --with-linear\`
|
|
2199
2206
|
- This writes \`.agents/skills/linear-workflow/SKILL.md\` and reference templates, plus an idempotent AGENTS section.
|
|
2200
2207
|
- On AGENTS write flows, devrouter asks for minimal Linear mapping (workspace/team/project) and stores it in a managed AGENTS block:
|
|
2201
2208
|
- \`<!-- devrouter-linear-workflow-config:start -->\`
|
|
@@ -2204,65 +2211,65 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
2204
2211
|
|
|
2205
2212
|
## Commands
|
|
2206
2213
|
|
|
2207
|
-
- \`
|
|
2208
|
-
- \`
|
|
2209
|
-
- \`
|
|
2210
|
-
- \`
|
|
2211
|
-
- \`
|
|
2212
|
-
- \`
|
|
2213
|
-
- \`
|
|
2214
|
-
- \`
|
|
2215
|
-
- \`
|
|
2216
|
-
- \`
|
|
2217
|
-
- \`
|
|
2218
|
-
- \`
|
|
2219
|
-
- \`
|
|
2220
|
-
- \`
|
|
2221
|
-
- \`
|
|
2222
|
-
- \`
|
|
2223
|
-
- \`
|
|
2224
|
-
- \`
|
|
2225
|
-
- \`
|
|
2226
|
-
- \`
|
|
2227
|
-
- \`
|
|
2228
|
-
- \`
|
|
2229
|
-
- \`
|
|
2230
|
-
- \`
|
|
2231
|
-
- \`
|
|
2232
|
-
- \`
|
|
2214
|
+
- \`devrouter init [--write-agents] [--write-skill] [--with-linear]\`: print AI onboarding prompt (non-mutating by default)
|
|
2215
|
+
- \`devrouter -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
|
|
2216
|
+
- \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
|
|
2217
|
+
- \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
|
|
2218
|
+
- \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
|
|
2219
|
+
- \`devrouter status\`: router/container/network/TLS health
|
|
2220
|
+
- \`devrouter doctor [--repo .]\`: deep diagnostics (global + repo)
|
|
2221
|
+
- \`devrouter ls\`: list active HTTP + TCP routes
|
|
2222
|
+
- \`devrouter open <name>\`: open HTTP route or print TCP connection hint (matches app name, then service/container/host identities)
|
|
2223
|
+
- \`devrouter logs [-f]\`: Traefik access logs
|
|
2224
|
+
- \`devrouter tls install\`: install mkcert certs, enable HTTPS + TCP/SNI
|
|
2225
|
+
- \`devrouter repo init\`: create \`.devrouter.yml\`
|
|
2226
|
+
- \`devrouter repo inspect [--json]\`: inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding
|
|
2227
|
+
- \`devrouter repo devcontainer write --dry-run --json\`: plan conservative Node/pnpm/Postgres devcontainer/devrouter scaffold files without writing
|
|
2228
|
+
- \`devrouter repo devcontainer write --yes\`: write managed Node/pnpm/Postgres devcontainer/devrouter scaffold files when no custom-file conflicts exist
|
|
2229
|
+
- \`devrouter repo devcontainer verify --json\`: emit read-only onboarding evidence for PRs
|
|
2230
|
+
- \`devrouter repo devcontainer verify --live --yes --json\`: register proxy routes and probe HTTP routes after the devcontainer is running
|
|
2231
|
+
- \`devrouter repo agents [--with-linear]\`: write devrouter section in AGENTS.md + install this skill (and optional Linear workflow assets)
|
|
2232
|
+
- \`devrouter app add\`: add/update app entry in \`.devrouter.yml\`
|
|
2233
|
+
- \`devrouter app ls\`: list app entries
|
|
2234
|
+
- \`devrouter app run <name> [--env <env>] [--workspace <slug>]\`: run app with dependency lifecycle (--env overrides SM defaultEnv; --workspace overrides the per-workspace token)
|
|
2235
|
+
- \`devrouter app exec <name> [--shell] [--env <env>] [--workspace <slug>] -- <cmd>\`: one-shot command with resolved dep env
|
|
2236
|
+
- \`devrouter app rm <name> [--keep-config]\`: remove app entry (\`--keep-config\` frees only the live route/hostname, leaves \`.devrouter.yml\` untouched)
|
|
2237
|
+
- \`devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]\`: create a worktree + devpod + namespaced routes
|
|
2238
|
+
- \`devrouter workspace ls [--json]\`: list git worktrees with workspace token + route count
|
|
2239
|
+
- \`devrouter workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]\`: free routes + stop devpod + remove worktree
|
|
2233
2240
|
|
|
2234
2241
|
## Validation workflow
|
|
2235
2242
|
|
|
2236
2243
|
For devcontainer onboarding:
|
|
2237
2244
|
|
|
2238
|
-
1. \`
|
|
2239
|
-
2. \`
|
|
2240
|
-
3. \`
|
|
2241
|
-
4. \`
|
|
2242
|
-
5. \`
|
|
2243
|
-
6. \`
|
|
2245
|
+
1. \`devrouter setup --repo . --yes --json\`
|
|
2246
|
+
2. \`devrouter doctor --repo . --json\`
|
|
2247
|
+
3. \`devrouter repo inspect --repo . --json\`
|
|
2248
|
+
4. \`devrouter repo devcontainer write --repo . --dry-run --json\`
|
|
2249
|
+
5. \`devrouter repo devcontainer write --repo . --yes\`
|
|
2250
|
+
6. \`devrouter repo devcontainer verify --repo . --json\`
|
|
2244
2251
|
7. Start the devcontainer, for example \`devpod up .\`
|
|
2245
|
-
8. \`
|
|
2252
|
+
8. \`devrouter repo devcontainer verify --repo . --live --yes --json\`
|
|
2246
2253
|
|
|
2247
2254
|
For existing host/docker runtime apps:
|
|
2248
2255
|
|
|
2249
|
-
1. \`
|
|
2250
|
-
2. \`
|
|
2251
|
-
3. \`
|
|
2252
|
-
4. \`
|
|
2253
|
-
5. \`
|
|
2256
|
+
1. \`devrouter setup --repo . --yes\`
|
|
2257
|
+
2. \`devrouter doctor --repo .\`
|
|
2258
|
+
3. \`devrouter app ls --repo .\`
|
|
2259
|
+
4. \`devrouter app run <host-app> --repo . --yes\`
|
|
2260
|
+
5. \`devrouter ls\`
|
|
2254
2261
|
6. \`curl -I https://<host>.localhost\`
|
|
2255
|
-
7. For TCP/Postgres, use \`
|
|
2262
|
+
7. For TCP/Postgres, use \`devrouter open <name>\` for the connection hint.
|
|
2256
2263
|
|
|
2257
2264
|
## Runtime behavior notes
|
|
2258
2265
|
|
|
2259
|
-
- \`
|
|
2266
|
+
- \`devrouter app run\` auto-starts Docker dependencies and waits for health. Host app runs stop auto-started docker deps on exit; docker app runs leave target services running until explicit cleanup.
|
|
2260
2267
|
- Host-runtime dependencies are NOT auto-started (v1).
|
|
2261
|
-
- \`kind=dependency\` entries do not create routes and cannot be direct targets for \`
|
|
2268
|
+
- \`kind=dependency\` entries do not create routes and cannot be direct targets for \`devrouter app run\`, \`devrouter app exec\`, or \`devrouter open\`.
|
|
2262
2269
|
- \`kind=dependency\` services start as declared in compose (no Traefik label wiring, no random port publishing, no injected env vars).
|
|
2263
|
-
- Postgres on shared \`:5432\` requires TLS/SNI (\`
|
|
2264
|
-
- \`
|
|
2265
|
-
- \`
|
|
2270
|
+
- Postgres on shared \`:5432\` requires TLS/SNI (\`devrouter tls install\`). Standard app clients should use the injected random port instead.
|
|
2271
|
+
- \`devrouter app exec\` follows the same dep lifecycle for one-shot commands and preserves argv semantics by default (\`shell: false\`).
|
|
2272
|
+
- \`devrouter app exec --shell\` is explicit and requires exactly one command string after \`--\`.
|
|
2266
2273
|
- Secret-manager overlap caveat: if Infisical/Doppler defines DB vars too, probe effective env (\`printenv DB_URL DB_HOST DB_PORT\`) before migrate/seed.
|
|
2267
2274
|
`;
|
|
2268
2275
|
LINEAR_WORKFLOW_SKILL_CONTENT = `---
|
|
@@ -2314,7 +2321,7 @@ When working on Linear-tracked issues, this is required:
|
|
|
2314
2321
|
|
|
2315
2322
|
## Devrouter-specific note
|
|
2316
2323
|
|
|
2317
|
-
If the repository uses devrouter, use \`
|
|
2324
|
+
If the repository uses devrouter, use \`devrouter upgrade\` to resolve the required Agent Adaptation Prompt for the target version before major changes (prompt files are versioned under \`upgrade-prompts/<version>.md\`).
|
|
2318
2325
|
`;
|
|
2319
2326
|
LINEAR_ISSUE_TEMPLATE_CONTENT = `# Linear Issue Template
|
|
2320
2327
|
|
|
@@ -2976,7 +2983,7 @@ function printUpgradeTargets(currentVersion, availableTargets) {
|
|
|
2976
2983
|
process.stdout.write(`- ${release.version}${suffix}
|
|
2977
2984
|
`);
|
|
2978
2985
|
}
|
|
2979
|
-
process.stdout.write("\nRun `
|
|
2986
|
+
process.stdout.write("\nRun `devrouter upgrade <version>` to print the Agent Adaptation Prompt for a target version.\n");
|
|
2980
2987
|
}
|
|
2981
2988
|
async function runUpgradeCommand(options, deps = {}) {
|
|
2982
2989
|
const catalog = loadUpgradeCatalog({
|
|
@@ -3153,13 +3160,11 @@ function commandExists(command) {
|
|
|
3153
3160
|
return result.status === 0;
|
|
3154
3161
|
}
|
|
3155
3162
|
function ensureMkcert() {
|
|
3156
|
-
if (commandExists("mkcert")) {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
throw new Error("mkcert is missing and Homebrew is not available.");
|
|
3163
|
+
if (!commandExists("mkcert")) {
|
|
3164
|
+
throw new Error(
|
|
3165
|
+
"mkcert is not installed. Please install it to use TLS features (e.g., 'brew install mkcert' or via your package manager)."
|
|
3166
|
+
);
|
|
3161
3167
|
}
|
|
3162
|
-
runOrThrow("brew", ["install", "mkcert"]);
|
|
3163
3168
|
}
|
|
3164
3169
|
function normalizeHost(host) {
|
|
3165
3170
|
return host.trim().toLowerCase();
|
|
@@ -3885,7 +3890,7 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
3885
3890
|
level: compose.ok ? "ok" : "error",
|
|
3886
3891
|
summary: compose.ok ? "Docker Compose v2 is reachable." : "Docker Compose v2 is not reachable.",
|
|
3887
3892
|
details: firstLine(compose.output) ?? compose.error,
|
|
3888
|
-
suggestion: compose.ok ? void 0 : "Install/start Docker with Compose v2, then run:
|
|
3893
|
+
suggestion: compose.ok ? void 0 : "Install/start Docker with Compose v2, then run: devrouter setup --yes"
|
|
3889
3894
|
});
|
|
3890
3895
|
const mkcert = runTool("mkcert", ["-version"]);
|
|
3891
3896
|
const brew = runTool("brew", ["--version"]);
|
|
@@ -3894,7 +3899,7 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
3894
3899
|
level: mkcert.ok ? "ok" : "warn",
|
|
3895
3900
|
summary: mkcert.ok ? "mkcert is installed." : "mkcert is not installed.",
|
|
3896
3901
|
details: mkcert.ok ? firstLine(mkcert.output) : mkcert.error,
|
|
3897
|
-
suggestion: mkcert.ok ? void 0 : brew.ok ? "Install mkcert: brew install mkcert" : "Install mkcert for local HTTPS, then run:
|
|
3902
|
+
suggestion: mkcert.ok ? void 0 : brew.ok ? "Install mkcert: brew install mkcert" : "Install mkcert for local HTTPS, then run: devrouter setup --yes"
|
|
3898
3903
|
});
|
|
3899
3904
|
const devpod = runTool("devpod", ["version"]);
|
|
3900
3905
|
checks.push({
|
|
@@ -4670,7 +4675,7 @@ async function runSetup(options = {}) {
|
|
|
4670
4675
|
actions.push(action("failed", {
|
|
4671
4676
|
id: "setup.confirmation",
|
|
4672
4677
|
summary: "Setup requires --yes before mutating devrouter-owned machine state.",
|
|
4673
|
-
suggestion: "Run:
|
|
4678
|
+
suggestion: "Run: devrouter setup --yes"
|
|
4674
4679
|
}));
|
|
4675
4680
|
const doctor2 = await buildDoctorReport({ repo: options.repo });
|
|
4676
4681
|
const partialReport2 = { actions, checks: doctor2.checks };
|
|
@@ -4699,7 +4704,7 @@ async function runSetup(options = {}) {
|
|
|
4699
4704
|
id: "global.router-files",
|
|
4700
4705
|
summary: "Failed to ensure global router files.",
|
|
4701
4706
|
details: message,
|
|
4702
|
-
suggestion: "Check write access to ~/.config/devrouter, then run:
|
|
4707
|
+
suggestion: "Check write access to ~/.config/devrouter, then run: devrouter setup --yes"
|
|
4703
4708
|
}));
|
|
4704
4709
|
}
|
|
4705
4710
|
try {
|
|
@@ -4715,7 +4720,7 @@ async function runSetup(options = {}) {
|
|
|
4715
4720
|
id: "global.devnet",
|
|
4716
4721
|
summary: "Failed to ensure shared Docker network devnet.",
|
|
4717
4722
|
details: message,
|
|
4718
|
-
suggestion: "Start Docker and verify Docker context, then run:
|
|
4723
|
+
suggestion: "Start Docker and verify Docker context, then run: devrouter setup --yes"
|
|
4719
4724
|
}));
|
|
4720
4725
|
}
|
|
4721
4726
|
try {
|
|
@@ -4731,7 +4736,7 @@ async function runSetup(options = {}) {
|
|
|
4731
4736
|
id: "global.router-stack",
|
|
4732
4737
|
summary: "Failed to start shared Traefik router.",
|
|
4733
4738
|
details: message,
|
|
4734
|
-
suggestion: "Resolve Docker/port conflicts on 80, 443, or 5432, then run:
|
|
4739
|
+
suggestion: "Resolve Docker/port conflicts on 80, 443, or 5432, then run: devrouter setup --yes"
|
|
4735
4740
|
}));
|
|
4736
4741
|
}
|
|
4737
4742
|
const mkcert = runTool("mkcert", ["-version"]);
|
|
@@ -4740,7 +4745,7 @@ async function runSetup(options = {}) {
|
|
|
4740
4745
|
id: "global.tls",
|
|
4741
4746
|
summary: "Skipped TLS setup because mkcert is not installed.",
|
|
4742
4747
|
details: mkcert.error,
|
|
4743
|
-
suggestion: "Install mkcert, then run:
|
|
4748
|
+
suggestion: "Install mkcert, then run: devrouter setup --yes"
|
|
4744
4749
|
}));
|
|
4745
4750
|
} else {
|
|
4746
4751
|
try {
|
|
@@ -4756,7 +4761,7 @@ async function runSetup(options = {}) {
|
|
|
4756
4761
|
id: "global.tls",
|
|
4757
4762
|
summary: "Failed to install local TLS certificates.",
|
|
4758
4763
|
details: message,
|
|
4759
|
-
suggestion: "Run:
|
|
4764
|
+
suggestion: "Run: devrouter tls install"
|
|
4760
4765
|
}));
|
|
4761
4766
|
}
|
|
4762
4767
|
}
|
|
@@ -4811,10 +4816,46 @@ var init_setup2 = __esm({
|
|
|
4811
4816
|
});
|
|
4812
4817
|
|
|
4813
4818
|
// src/util/ports.ts
|
|
4819
|
+
function parseSsPortListeners(stdout, port) {
|
|
4820
|
+
const listeners = [];
|
|
4821
|
+
const lines = stdout.split(/\r?\n/);
|
|
4822
|
+
for (const line of lines) {
|
|
4823
|
+
if (!line.trim()) continue;
|
|
4824
|
+
const parts = line.trim().split(/\s+/);
|
|
4825
|
+
if (parts.length < 4) continue;
|
|
4826
|
+
const localAddr = parts[3];
|
|
4827
|
+
const colonIdx = localAddr.lastIndexOf(":");
|
|
4828
|
+
if (colonIdx === -1) continue;
|
|
4829
|
+
const linePort = Number(localAddr.slice(colonIdx + 1));
|
|
4830
|
+
if (linePort !== port) continue;
|
|
4831
|
+
const usersCol = parts.slice(5).join(" ");
|
|
4832
|
+
const pidMatch = /pid=(\d+)/.exec(usersCol);
|
|
4833
|
+
const cmdMatch = /"([^"]+)"/.exec(usersCol);
|
|
4834
|
+
const command = cmdMatch ? cmdMatch[1] : "?";
|
|
4835
|
+
const pid = pidMatch ? pidMatch[1] : "?";
|
|
4836
|
+
listeners.push({
|
|
4837
|
+
port,
|
|
4838
|
+
command,
|
|
4839
|
+
pid,
|
|
4840
|
+
user: "?",
|
|
4841
|
+
address: localAddr
|
|
4842
|
+
});
|
|
4843
|
+
}
|
|
4844
|
+
return listeners;
|
|
4845
|
+
}
|
|
4814
4846
|
function findPortListeners(port) {
|
|
4815
4847
|
const result = (0, import_node_child_process6.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
|
|
4816
4848
|
encoding: "utf-8"
|
|
4817
4849
|
});
|
|
4850
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
4851
|
+
if (process.platform === "linux") {
|
|
4852
|
+
const ssResult = (0, import_node_child_process6.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
4853
|
+
if (ssResult.status === 0 && ssResult.stdout) {
|
|
4854
|
+
return parseSsPortListeners(ssResult.stdout, port);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
return [];
|
|
4858
|
+
}
|
|
4818
4859
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
4819
4860
|
return [];
|
|
4820
4861
|
}
|
|
@@ -5784,10 +5825,10 @@ function renderReadme(projectName) {
|
|
|
5784
5825
|
Use this repo through the devcontainer, with devrouter providing stable local routes.
|
|
5785
5826
|
|
|
5786
5827
|
\`\`\`bash
|
|
5787
|
-
|
|
5828
|
+
devrouter setup --yes
|
|
5788
5829
|
devpod up .
|
|
5789
|
-
|
|
5790
|
-
|
|
5830
|
+
devrouter app run app --repo . --yes
|
|
5831
|
+
devrouter app run db --repo . --yes
|
|
5791
5832
|
\`\`\`
|
|
5792
5833
|
|
|
5793
5834
|
- App: https://${projectName}.localhost
|
|
@@ -5797,19 +5838,19 @@ dev app run db --repo . --yes
|
|
|
5797
5838
|
function postWriteNextSteps(repoPath) {
|
|
5798
5839
|
const quotedRepoPath = shellSingleQuote(repoPath);
|
|
5799
5840
|
return [
|
|
5800
|
-
`Run:
|
|
5841
|
+
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
5801
5842
|
`Run: cd ${quotedRepoPath} && devpod up .`,
|
|
5802
|
-
`Run:
|
|
5803
|
-
`Run:
|
|
5804
|
-
`Optional:
|
|
5843
|
+
`Run: devrouter app run app --repo ${quotedRepoPath} --yes`,
|
|
5844
|
+
`Run: devrouter app run db --repo ${quotedRepoPath} --yes`,
|
|
5845
|
+
`Optional: devrouter repo agents --repo ${quotedRepoPath}`
|
|
5805
5846
|
];
|
|
5806
5847
|
}
|
|
5807
5848
|
function issueNextSteps(issues) {
|
|
5808
5849
|
const steps = issues.filter((issue) => issue.level === "error").map((issue) => issue.suggestion).filter((suggestion) => Boolean(suggestion));
|
|
5809
5850
|
if (steps.length > 0) {
|
|
5810
|
-
return [...steps, "Re-run:
|
|
5851
|
+
return [...steps, "Re-run: devrouter repo devcontainer write --dry-run --json"];
|
|
5811
5852
|
}
|
|
5812
|
-
return ["Resolve reported errors, then re-run:
|
|
5853
|
+
return ["Resolve reported errors, then re-run: devrouter repo devcontainer write --dry-run --json"];
|
|
5813
5854
|
}
|
|
5814
5855
|
function packageManagerIssues(repo) {
|
|
5815
5856
|
if (!repo.packageManager) {
|
|
@@ -5919,7 +5960,7 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
5919
5960
|
filePlans.push({
|
|
5920
5961
|
path: "AGENTS.md",
|
|
5921
5962
|
action: "suggest",
|
|
5922
|
-
reason: "run
|
|
5963
|
+
reason: "run devrouter repo agents after reviewing the scaffold"
|
|
5923
5964
|
});
|
|
5924
5965
|
return {
|
|
5925
5966
|
files,
|
|
@@ -5930,7 +5971,7 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
5930
5971
|
dryRun,
|
|
5931
5972
|
files: filePlans,
|
|
5932
5973
|
issues,
|
|
5933
|
-
nextSteps: issues.some((issue) => issue.level === "error") ? issueNextSteps(issues) : dryRun ? [`Review this plan, then run:
|
|
5974
|
+
nextSteps: issues.some((issue) => issue.level === "error") ? issueNextSteps(issues) : dryRun ? [`Review this plan, then run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`] : postWriteNextSteps(repoPath)
|
|
5934
5975
|
}
|
|
5935
5976
|
};
|
|
5936
5977
|
}
|
|
@@ -5958,10 +5999,10 @@ function writeDevcontainer(options = {}) {
|
|
|
5958
5999
|
id: "repo.devcontainer.confirmation",
|
|
5959
6000
|
level: "error",
|
|
5960
6001
|
summary: "Writing devcontainer files requires --yes.",
|
|
5961
|
-
suggestion: `Run:
|
|
6002
|
+
suggestion: `Run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`
|
|
5962
6003
|
}
|
|
5963
6004
|
],
|
|
5964
|
-
nextSteps: [`Run:
|
|
6005
|
+
nextSteps: [`Run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`]
|
|
5965
6006
|
};
|
|
5966
6007
|
}
|
|
5967
6008
|
for (const file of files) {
|
|
@@ -6093,7 +6134,7 @@ function requiredFileChecks(repoPath) {
|
|
|
6093
6134
|
id: "repo.devcontainer.verify-files",
|
|
6094
6135
|
level: missing.length === 0 ? "ok" : "error",
|
|
6095
6136
|
summary: missing.length === 0 ? "Required devcontainer/devrouter files are present." : `Missing required devcontainer/devrouter file(s): ${missing.join(", ")}.`,
|
|
6096
|
-
suggestion: missing.length === 0 ? void 0 : "Run:
|
|
6137
|
+
suggestion: missing.length === 0 ? void 0 : "Run: devrouter repo devcontainer write --dry-run --json"
|
|
6097
6138
|
};
|
|
6098
6139
|
}
|
|
6099
6140
|
function proxyConfigCheck(apps) {
|
|
@@ -6101,7 +6142,7 @@ function proxyConfigCheck(apps) {
|
|
|
6101
6142
|
id: "repo.devcontainer.verify-proxy-apps",
|
|
6102
6143
|
level: apps.length > 0 ? "ok" : "error",
|
|
6103
6144
|
summary: apps.length > 0 ? `Found ${apps.length} proxy app(s) for devcontainer routing.` : "No proxy app entries found for devcontainer routing.",
|
|
6104
|
-
suggestion: apps.length > 0 ? void 0 : "Add runtime: proxy app entries to .devrouter.yml or run:
|
|
6145
|
+
suggestion: apps.length > 0 ? void 0 : "Add runtime: proxy app entries to .devrouter.yml or run: devrouter repo devcontainer write --dry-run --json"
|
|
6105
6146
|
};
|
|
6106
6147
|
}
|
|
6107
6148
|
function workspaceTemplateCheck(apps) {
|
|
@@ -6144,7 +6185,7 @@ function doctorGateCheck(doctor) {
|
|
|
6144
6185
|
level: blocking.length === 0 ? "ok" : "error",
|
|
6145
6186
|
summary: blocking.length === 0 ? "Doctor has no blocking devcontainer diagnostics." : `Doctor reported ${blocking.length} blocking devcontainer diagnostic(s).`,
|
|
6146
6187
|
details: blocking.length > 0 ? blocking.map((check) => check.id).join(", ") : void 0,
|
|
6147
|
-
suggestion: blocking.length > 0 ? "Run:
|
|
6188
|
+
suggestion: blocking.length > 0 ? "Run: devrouter doctor --repo <path> --json" : void 0
|
|
6148
6189
|
};
|
|
6149
6190
|
}
|
|
6150
6191
|
function routeUrl2(host) {
|
|
@@ -6167,7 +6208,7 @@ function registerProxyRoute(repoPath, app, workspace) {
|
|
|
6167
6208
|
const { port, upstreamHost: upstreamHost2 } = parseUpstream(app.upstream);
|
|
6168
6209
|
if (app.protocol === "tcp" && !isTLSEnabled()) {
|
|
6169
6210
|
throw new Error(
|
|
6170
|
-
`App "${app.name}" is a TCP proxy route, which requires TLS (SNI). Run \`
|
|
6211
|
+
`App "${app.name}" is a TCP proxy route, which requires TLS (SNI). Run \`devrouter tls install\` first.`
|
|
6171
6212
|
);
|
|
6172
6213
|
}
|
|
6173
6214
|
if (app.protocol === "tcp") {
|
|
@@ -6199,7 +6240,7 @@ async function liveChecks(repoPath, yes) {
|
|
|
6199
6240
|
id: "repo.devcontainer.verify-live-confirmation",
|
|
6200
6241
|
level: "error",
|
|
6201
6242
|
summary: "Live devcontainer verification requires --yes.",
|
|
6202
|
-
suggestion: "Run:
|
|
6243
|
+
suggestion: "Run: devrouter repo devcontainer verify --live --yes --json"
|
|
6203
6244
|
}
|
|
6204
6245
|
]
|
|
6205
6246
|
};
|
|
@@ -6216,7 +6257,7 @@ async function liveChecks(repoPath, yes) {
|
|
|
6216
6257
|
level: "error",
|
|
6217
6258
|
summary: "Could not load runtime config for live verification.",
|
|
6218
6259
|
details: error instanceof Error ? error.message : String(error),
|
|
6219
|
-
suggestion: "Fix .devrouter.yml and re-run:
|
|
6260
|
+
suggestion: "Fix .devrouter.yml and re-run: devrouter repo devcontainer verify --live --yes --json"
|
|
6220
6261
|
}
|
|
6221
6262
|
]
|
|
6222
6263
|
};
|
|
@@ -6268,7 +6309,7 @@ async function liveChecks(repoPath, yes) {
|
|
|
6268
6309
|
level: "error",
|
|
6269
6310
|
summary: `Could not register proxy route '${app.name}'.`,
|
|
6270
6311
|
details: message,
|
|
6271
|
-
suggestion: "Run:
|
|
6312
|
+
suggestion: "Run: devrouter setup --yes, start the devcontainer, then retry live verification."
|
|
6272
6313
|
});
|
|
6273
6314
|
}
|
|
6274
6315
|
}
|
|
@@ -6294,7 +6335,7 @@ async function verifyDevcontainer(options = {}) {
|
|
|
6294
6335
|
level: "error",
|
|
6295
6336
|
summary: "Could not load .devrouter.yml for devcontainer verification.",
|
|
6296
6337
|
details: error instanceof Error ? error.message : String(error),
|
|
6297
|
-
suggestion: "Fix .devrouter.yml and re-run:
|
|
6338
|
+
suggestion: "Fix .devrouter.yml and re-run: devrouter repo devcontainer verify --json"
|
|
6298
6339
|
});
|
|
6299
6340
|
}
|
|
6300
6341
|
let liveRoutes;
|
|
@@ -7025,6 +7066,35 @@ function parseListeningPorts(outputText) {
|
|
|
7025
7066
|
}
|
|
7026
7067
|
return Array.from(ports.values()).sort((a, b) => a - b);
|
|
7027
7068
|
}
|
|
7069
|
+
function parseSsListeningPorts(stdout, targetPids) {
|
|
7070
|
+
const ports = /* @__PURE__ */ new Set();
|
|
7071
|
+
const lines = stdout.split(/\r?\n/);
|
|
7072
|
+
for (const line of lines) {
|
|
7073
|
+
if (!line.trim()) continue;
|
|
7074
|
+
let matchedPid = false;
|
|
7075
|
+
for (const pid of targetPids) {
|
|
7076
|
+
if (line.includes(`pid=${pid}`)) {
|
|
7077
|
+
matchedPid = true;
|
|
7078
|
+
break;
|
|
7079
|
+
}
|
|
7080
|
+
}
|
|
7081
|
+
if (!matchedPid) {
|
|
7082
|
+
continue;
|
|
7083
|
+
}
|
|
7084
|
+
const parts = line.trim().split(/\s+/);
|
|
7085
|
+
if (parts.length < 4) continue;
|
|
7086
|
+
const localAddr = parts[3];
|
|
7087
|
+
const colonIdx = localAddr.lastIndexOf(":");
|
|
7088
|
+
if (colonIdx !== -1) {
|
|
7089
|
+
const portStr = localAddr.slice(colonIdx + 1);
|
|
7090
|
+
const port = Number(portStr);
|
|
7091
|
+
if (Number.isInteger(port) && port > 0) {
|
|
7092
|
+
ports.add(port);
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
return Array.from(ports.values()).sort((a, b) => a - b);
|
|
7097
|
+
}
|
|
7028
7098
|
function detectListeningPorts(pids) {
|
|
7029
7099
|
if (pids.length === 0) {
|
|
7030
7100
|
return [];
|
|
@@ -7034,6 +7104,15 @@ function detectListeningPorts(pids) {
|
|
|
7034
7104
|
["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")],
|
|
7035
7105
|
{ encoding: "utf-8" }
|
|
7036
7106
|
);
|
|
7107
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
7108
|
+
if (process.platform === "linux") {
|
|
7109
|
+
const ssResult = (0, import_node_child_process11.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
7110
|
+
if (ssResult.status === 0 && ssResult.stdout) {
|
|
7111
|
+
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
return [];
|
|
7115
|
+
}
|
|
7037
7116
|
if (result.status !== 0) {
|
|
7038
7117
|
return [];
|
|
7039
7118
|
}
|
|
@@ -7670,7 +7749,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
7670
7749
|
if (!opts.noDevpod && hasDevpod()) {
|
|
7671
7750
|
const dp = (0, import_node_child_process12.spawnSync)("devpod", ["up", worktreePath, "--id", ws, "--open-ide=false"], {
|
|
7672
7751
|
stdio: "inherit",
|
|
7673
|
-
env: {
|
|
7752
|
+
env: {
|
|
7753
|
+
...process.env,
|
|
7754
|
+
WORKSPACE: ws,
|
|
7755
|
+
DEVCONTAINER_COMPOSE_OVERLAY: "docker-compose.devrouter.yml"
|
|
7756
|
+
}
|
|
7674
7757
|
});
|
|
7675
7758
|
if (dp.status !== 0) {
|
|
7676
7759
|
process.stderr.write(`Warning: 'devpod up' failed; continuing with route registration.
|
|
@@ -7854,7 +7937,7 @@ async function runVersionCommand(options, deps = {}) {
|
|
|
7854
7937
|
`);
|
|
7855
7938
|
process.stdout.write(`All upgrade targets: ${availableTargets.map((entry) => entry.version).join(", ")}
|
|
7856
7939
|
`);
|
|
7857
|
-
process.stdout.write(`Run:
|
|
7940
|
+
process.stdout.write(`Run: devrouter upgrade ${next.version}
|
|
7858
7941
|
`);
|
|
7859
7942
|
}
|
|
7860
7943
|
var init_version = __esm({
|
|
@@ -7866,7 +7949,7 @@ var init_version = __esm({
|
|
|
7866
7949
|
|
|
7867
7950
|
// src/cli.ts
|
|
7868
7951
|
var import_commander = require("commander");
|
|
7869
|
-
var CLI_VERSION = true ? "0.0.
|
|
7952
|
+
var CLI_VERSION = true ? "0.0.24" : "0.0.0-dev";
|
|
7870
7953
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
7871
7954
|
function withErrorHandling(action2) {
|
|
7872
7955
|
return async (...args) => {
|
|
@@ -7881,7 +7964,7 @@ function withErrorHandling(action2) {
|
|
|
7881
7964
|
};
|
|
7882
7965
|
}
|
|
7883
7966
|
var program = new import_commander.Command();
|
|
7884
|
-
program.name("
|
|
7967
|
+
program.name("devrouter").description("Local dev router CLI for stable .localhost routing across repositories").showSuggestionAfterError(true).showHelpAfterError();
|
|
7885
7968
|
program.command("init").description("Print an AI onboarding prompt template for adapting a repository to devrouter").option("--repo <path>", "Repository path to embed in the prompt (defaults to current directory)").option("--entries-json <json>", "Optional JSON array of app entries to embed in the prompt").option("--json", "Output prompt and command intents as JSON").option("--write-agents", "Write/update devrouter section in AGENTS.md").option("--write-skill", "Write .agents/skills/devrouter/SKILL.md").option("--with-linear", "Include optional Linear workflow guidance/artifacts when writing").action(withErrorHandling(async (options) => {
|
|
7886
7969
|
const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
7887
7970
|
await runInitCommand2(options);
|