@devrouter/cli 0.0.40 → 0.0.41
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 +5 -3
- package/dist/devrouter.js +1003 -298
- package/package.json +1 -1
- package/upgrade-prompts/0.0.41.md +43 -0
package/dist/devrouter.js
CHANGED
|
@@ -317,10 +317,10 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
317
317
|
- **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. Managed \`ensure\` rejects every HTTP/TCP proxy upstream outside that exact alias namespace before it mutates DevPod or routes. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.
|
|
318
318
|
- **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.
|
|
319
319
|
- **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
|
|
320
|
-
- **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running DevPod. Never substitute raw \`devpod up\`, \`stop\`,
|
|
320
|
+
- **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects.
|
|
321
321
|
- **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
|
|
322
322
|
- **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
|
|
323
|
-
- **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`),
|
|
323
|
+
- **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
|
|
324
324
|
- **Sizing**: \`--measure-size\` adds per-workspace storage consumption to that report and stays read-only, but it walks each worktree and runs \`docker ps\` / \`docker inspect --size\`, so leave it off when you only need the evidence states. Each row reports reclaimable \`worktree\` and \`containerWritable\` bytes, non-reclaimable \`imageShared\` bytes, and a \`reclaimable\` total of the first two. \`imageShared\` covers image layers shared with other containers and overlaps across rows, so never sum it. Attribution is the workspace's own app container; sibling compose services such as a database are excluded. Any untrustworthy figure reports \`unknown\` with a reason rather than zero, and a workspace with no container reports a measured \`0\`.
|
|
325
325
|
- **Boundary**: workspace commands require Git. Normal config, app, status, and doctor flows remain usable from a \`.devrouter.yml\` folder without \`.git\`.
|
|
326
326
|
|
|
@@ -373,8 +373,8 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
373
373
|
- \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
|
|
374
374
|
- \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
|
|
375
375
|
- \`devrouter ensure [path] [--profile <name>] [--open] [--json]\`: canonical startup/reconciliation for primary and linked checkouts; a managed profile selects its independent resource dimensions
|
|
376
|
-
- \`devrouter stop [path] [--delete] [--json]\`: stop the exact
|
|
377
|
-
- \`devrouter exec [path] -- <command...>\`: literal one-shot command inside the exact running
|
|
376
|
+
- \`devrouter stop [path] [--delete] [--json]\`: stop the exact workspace runtime and remove exact routes; \`--delete\` explicitly deletes its ownership-proven data without removing the checkout
|
|
377
|
+
- \`devrouter exec [path] -- <command...>\`: literal one-shot command inside the exact running workspace runtime
|
|
378
378
|
- \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
|
|
379
379
|
- \`devrouter status\`: router/container/network/TLS health
|
|
380
380
|
- \`devrouter doctor [--repo .]\`: deep diagnostics (global + repo)
|
|
@@ -396,7 +396,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
396
396
|
- \`devrouter app rm <name> [--keep-config]\`: remove app entry (\`--keep-config\` frees only the live route/hostname, leaves \`.devrouter.yml\` untouched)
|
|
397
397
|
- \`devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]\`: create a worktree and start/prove it unless create-only mode is requested
|
|
398
398
|
- \`devrouter workspace ensure [path] [--profile <name>] [--open] [--json]\`: compatibility alias of \`devrouter ensure\`
|
|
399
|
-
- \`devrouter workspace ls [--json]\`: list ownership, Git,
|
|
399
|
+
- \`devrouter workspace ls [--json]\`: list ownership, Git, workspace runtime, route, path, and branch evidence
|
|
400
400
|
- \`devrouter workspace cleanup [--repo .] [--inactive-for 30d] [--check-merged] [--measure-size] [--json]\`: report-only cleanup evidence and exact guarded suggestions; no \`--yes\` or apply mode
|
|
401
401
|
- \`devrouter workspace stop <workspace|branch>\`: stop DevPod and routes; preserve checkout, owner record, and data
|
|
402
402
|
- \`devrouter workspace down <workspace|branch> [--keep-worktree]\`: delete runtime/routes and optionally remove the clean worktree and record
|
|
@@ -2504,7 +2504,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2504
2504
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2505
2505
|
const requiredVersion = config.devrouter?.version;
|
|
2506
2506
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2507
|
-
const cliVersion = true ? "0.0.
|
|
2507
|
+
const cliVersion = true ? "0.0.41" : "0.0.0-dev";
|
|
2508
2508
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2509
2509
|
hasWarnedVersionMismatch = true;
|
|
2510
2510
|
process.stderr.write(
|
|
@@ -2922,7 +2922,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
2922
2922
|
"- Shared Traefik router owns host ports 80 (HTTP), 443 (HTTPS), and 5432 (Postgres TCP).",
|
|
2923
2923
|
"- Per-repo source of truth is REPO_PATH/.devrouter.yml only.",
|
|
2924
2924
|
"- Global generated/runtime artifacts are managed under ~/.config/devrouter (do not edit these manually).",
|
|
2925
|
-
"- Managed
|
|
2925
|
+
"- Managed workspace runtime lifecycle uses devrouter ensure/stop/workspace commands; raw DevPod/Devsy mutations bypass exact checkout ownership checks. The active runtime is DevPod or Devsy, resolved per checkout: DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy forces one runtime, an exact-path registry owner wins next (mixed fleets stay separated), then the machine preference from 'devrouter setup --workspace-runtime', then installed-CLI auto-detection.",
|
|
2926
2926
|
"",
|
|
2927
2927
|
"Inputs:",
|
|
2928
2928
|
`- REPO_PATH=${repoPath}`,
|
|
@@ -3034,13 +3034,13 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3034
3034
|
"- The owner record survives linked-worktree removal and binds the exact path to its DevPod ID. First use reuses an exact-path DevPod or derives a sanitized branch/path identity. Later flags or `DEVROUTER_WORKSPACE` may repeat but cannot rename it. Ambiguous identities fail closed. The primary checkout stays non-namespaced.",
|
|
3035
3035
|
`- 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. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
|
|
3036
3036
|
"- 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.",
|
|
3037
|
-
"- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod mutations; they bypass the machine-global ownership lock. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
3037
|
+
"- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
3038
3038
|
"- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
|
|
3039
3039
|
"- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
|
|
3040
|
-
"- `devrouter workspace cleanup --repo . --inactive-for 30d --check-merged --json` is report-only and never mutates
|
|
3040
|
+
"- `devrouter workspace cleanup --repo . --inactive-for 30d --check-merged --json` is report-only and never mutates the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. It reports ownership, workspace runtime registration, runtime state (`running|stopped|busy|not-found|absent|unknown`), checkout, advisory activity, route, and integration evidence. Local DevPod/Devsy list/status checks always run; `--check-merged` alone enables read-only origin and matching GitHub/GitLab checks. Treat `not-found` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit `gc`/`down` can remove exact stale registration only after expected-ID `NotFound` proof and ownership revalidation. Runtime `lastUsed` remains advisory.",
|
|
3041
3041
|
'- `--measure-size` adds per-workspace storage consumption and stays read-only, but walks each worktree and runs `docker ps` / `docker inspect --size`, so leave it off for a quick report. Each row reports `worktree` and `containerWritable` bytes, which are reclaimable; `imageShared` bytes, which are not, because those image layers are shared with other containers and overlap across rows; and `reclaimable`, the sum of the reclaimable fields. Never add `imageShared` across rows. Any figure that cannot be trusted reports `{"status": "unknown", "reason": ...}` rather than zero, and a workspace with no container reports a measured `0`.',
|
|
3042
3042
|
"- Workspace commands require Git. Normal config, app, status, and doctor flows work from a `.devrouter.yml` folder without `.git`. Git has no worktree-removal hook; use `workspace ls`, doctor, or dry-run GC after out-of-band removal.",
|
|
3043
|
-
"- devcontainer integration: `devcontainer.json` lists the base compose file then `${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}`. The default overlay contains `services: {}`; `.devcontainer/docker-compose.devrouter.yml` passes `WORKSPACE` and `DEVROUTER_WORKSPACE` into the app and bind-mounts `${DEVROUTER_GIT_COMMON_DIR}` to the same absolute app-container path. Ensure proves exact
|
|
3043
|
+
"- devcontainer integration: `devcontainer.json` lists the base compose file then `${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}`. The default overlay contains `services: {}`; `.devcontainer/docker-compose.devrouter.yml` passes `WORKSPACE` and `DEVROUTER_WORKSPACE` into the app and bind-mounts `${DEVROUTER_GIT_COMMON_DIR}` to the same absolute app-container path. Ensure proves exact workspace runtime ownership, overlay/Git mounts, env, aliases, health, Git, HTTP route reachability, and unique running TCP upstream ownership before success.",
|
|
3044
3044
|
"",
|
|
3045
3045
|
"Secret Manager Integration (config-based):",
|
|
3046
3046
|
"- 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.",
|
|
@@ -3204,11 +3204,11 @@ var init_ai_prompt = __esm({
|
|
|
3204
3204
|
},
|
|
3205
3205
|
{
|
|
3206
3206
|
command: "devrouter stop [path] [--delete] [--json]",
|
|
3207
|
-
purpose: "Stop the exact checkout
|
|
3207
|
+
purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
|
|
3208
3208
|
},
|
|
3209
3209
|
{
|
|
3210
3210
|
command: "devrouter exec [path] -- <command...>",
|
|
3211
|
-
purpose: "Run literal argv inside the exact already-running
|
|
3211
|
+
purpose: "Run literal argv inside the exact already-running workspace runtime and proven checkout mount."
|
|
3212
3212
|
},
|
|
3213
3213
|
{
|
|
3214
3214
|
command: "devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]",
|
|
@@ -3220,7 +3220,7 @@ var init_ai_prompt = __esm({
|
|
|
3220
3220
|
},
|
|
3221
3221
|
{
|
|
3222
3222
|
command: "devrouter workspace ls [--json]",
|
|
3223
|
-
purpose: "List owner, Git,
|
|
3223
|
+
purpose: "List owner, Git, workspace runtime, route, path, and branch evidence for managed workspaces."
|
|
3224
3224
|
},
|
|
3225
3225
|
{
|
|
3226
3226
|
command: "devrouter workspace cleanup [--repo <path>] [--inactive-for 30d] [--check-merged] [--measure-size] [--json]",
|
|
@@ -3228,11 +3228,11 @@ var init_ai_prompt = __esm({
|
|
|
3228
3228
|
},
|
|
3229
3229
|
{
|
|
3230
3230
|
command: "devrouter workspace stop <workspace|branch>",
|
|
3231
|
-
purpose: "Stop the exact
|
|
3231
|
+
purpose: "Stop the exact workspace runtime and remove exact routes while preserving the worktree, owner record, and data."
|
|
3232
3232
|
},
|
|
3233
3233
|
{
|
|
3234
3234
|
command: "devrouter workspace down <workspace|branch> [--keep-worktree]",
|
|
3235
|
-
purpose: "Delete exact runtime/routes and remove only a clean, unlocked worktree and its owner record; --keep-worktree preserves checkout and record."
|
|
3235
|
+
purpose: "Delete exact workspace runtime/routes and remove only a clean, unlocked worktree and its owner record; --keep-worktree preserves checkout and record."
|
|
3236
3236
|
},
|
|
3237
3237
|
{
|
|
3238
3238
|
command: "devrouter workspace gc [--json] [--yes]",
|
|
@@ -6047,6 +6047,385 @@ var init_tls = __esm({
|
|
|
6047
6047
|
}
|
|
6048
6048
|
});
|
|
6049
6049
|
|
|
6050
|
+
// src/core/devpod-registry.ts
|
|
6051
|
+
function listDevpodWorkspacesRaw() {
|
|
6052
|
+
const result = (0, import_node_child_process9.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
|
|
6053
|
+
encoding: "utf-8"
|
|
6054
|
+
});
|
|
6055
|
+
if (result.status !== 0) {
|
|
6056
|
+
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6057
|
+
throw new Error(`devpod list failed: ${details || "devpod is not installed or unavailable"}`);
|
|
6058
|
+
}
|
|
6059
|
+
let parsed;
|
|
6060
|
+
try {
|
|
6061
|
+
parsed = JSON.parse(result.stdout);
|
|
6062
|
+
} catch {
|
|
6063
|
+
throw new Error("devpod list returned invalid JSON.");
|
|
6064
|
+
}
|
|
6065
|
+
if (!Array.isArray(parsed)) {
|
|
6066
|
+
throw new Error("devpod list returned an unexpected response.");
|
|
6067
|
+
}
|
|
6068
|
+
return parsed.map((entry) => {
|
|
6069
|
+
const candidate = entry;
|
|
6070
|
+
if (typeof candidate.id !== "string" || !candidate.source || typeof candidate.source.localFolder !== "string") {
|
|
6071
|
+
throw new Error("devpod list returned a workspace without id/source.localFolder.");
|
|
6072
|
+
}
|
|
6073
|
+
const workspace = {
|
|
6074
|
+
id: candidate.id,
|
|
6075
|
+
source: { localFolder: candidate.source.localFolder }
|
|
6076
|
+
};
|
|
6077
|
+
if ("lastUsed" in candidate) {
|
|
6078
|
+
if (typeof candidate.lastUsed === "string") {
|
|
6079
|
+
workspace.lastUsed = candidate.lastUsed;
|
|
6080
|
+
} else {
|
|
6081
|
+
workspace.lastUsedMalformed = true;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
return workspace;
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
var import_node_child_process9;
|
|
6088
|
+
var init_devpod_registry = __esm({
|
|
6089
|
+
"src/core/devpod-registry.ts"() {
|
|
6090
|
+
"use strict";
|
|
6091
|
+
import_node_child_process9 = require("child_process");
|
|
6092
|
+
}
|
|
6093
|
+
});
|
|
6094
|
+
|
|
6095
|
+
// src/core/devsy-workspaces.ts
|
|
6096
|
+
function listDevsyWorkspaces() {
|
|
6097
|
+
const result = (0, import_node_child_process10.spawnSync)(
|
|
6098
|
+
"devsy",
|
|
6099
|
+
["workspace", "list", "--result-format", "json", "--skip-pro"],
|
|
6100
|
+
{
|
|
6101
|
+
encoding: "utf-8"
|
|
6102
|
+
}
|
|
6103
|
+
);
|
|
6104
|
+
if (result.status !== 0) {
|
|
6105
|
+
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6106
|
+
throw new Error(
|
|
6107
|
+
`devsy workspace list failed: ${details || "devsy is not installed or unavailable"}`
|
|
6108
|
+
);
|
|
6109
|
+
}
|
|
6110
|
+
let parsed;
|
|
6111
|
+
try {
|
|
6112
|
+
parsed = JSON.parse(result.stdout);
|
|
6113
|
+
} catch {
|
|
6114
|
+
throw new Error("devsy workspace list returned invalid JSON.");
|
|
6115
|
+
}
|
|
6116
|
+
if (!Array.isArray(parsed)) {
|
|
6117
|
+
throw new Error("devsy workspace list returned an unexpected response.");
|
|
6118
|
+
}
|
|
6119
|
+
return parsed.map((entry) => {
|
|
6120
|
+
const candidate = entry;
|
|
6121
|
+
const source = candidate.source;
|
|
6122
|
+
if (typeof candidate.id !== "string" || !source || typeof source.localFolder !== "string") {
|
|
6123
|
+
throw new Error("devsy workspace list returned a workspace without id/source.localFolder.");
|
|
6124
|
+
}
|
|
6125
|
+
const workspace = {
|
|
6126
|
+
id: candidate.id,
|
|
6127
|
+
source: { localFolder: source.localFolder }
|
|
6128
|
+
};
|
|
6129
|
+
if ("lastUsed" in candidate) {
|
|
6130
|
+
if (typeof candidate.lastUsed === "string") {
|
|
6131
|
+
workspace.lastUsed = candidate.lastUsed;
|
|
6132
|
+
} else {
|
|
6133
|
+
workspace.lastUsedMalformed = true;
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
return workspace;
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
function parseDevsyRuntimeStatus(output2, expectedId) {
|
|
6140
|
+
let parsed;
|
|
6141
|
+
try {
|
|
6142
|
+
parsed = JSON.parse(output2);
|
|
6143
|
+
} catch {
|
|
6144
|
+
return "unknown";
|
|
6145
|
+
}
|
|
6146
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.id !== expectedId) {
|
|
6147
|
+
return "unknown";
|
|
6148
|
+
}
|
|
6149
|
+
switch (parsed.state) {
|
|
6150
|
+
case "Running":
|
|
6151
|
+
return "running";
|
|
6152
|
+
case "Stopped":
|
|
6153
|
+
return "stopped";
|
|
6154
|
+
case "Busy":
|
|
6155
|
+
return "busy";
|
|
6156
|
+
case "NotFound":
|
|
6157
|
+
return "not-found";
|
|
6158
|
+
default:
|
|
6159
|
+
return "unknown";
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
function inspectDevsyRuntimeStatus(devsyId) {
|
|
6163
|
+
const result = (0, import_node_child_process10.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
|
|
6164
|
+
encoding: "utf-8",
|
|
6165
|
+
timeout: 1e4
|
|
6166
|
+
});
|
|
6167
|
+
if (result.status !== 0 || result.error) return "unknown";
|
|
6168
|
+
return parseDevsyRuntimeStatus(result.stdout, devsyId);
|
|
6169
|
+
}
|
|
6170
|
+
function inspectDevsyWorkspaceOwnership(workspaces, devsyId, worktreePath) {
|
|
6171
|
+
const idOwners = workspaces.filter((workspace) => workspace.id === devsyId);
|
|
6172
|
+
const pathOwners = workspaces.filter(
|
|
6173
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, worktreePath)
|
|
6174
|
+
);
|
|
6175
|
+
const exact = idOwners.filter(
|
|
6176
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, worktreePath)
|
|
6177
|
+
);
|
|
6178
|
+
if (idOwners.length > 1 || pathOwners.length > 1 || idOwners[0] && exact.length === 0 || pathOwners[0] && pathOwners[0].id !== devsyId) {
|
|
6179
|
+
return {
|
|
6180
|
+
status: "conflict",
|
|
6181
|
+
reason: `Devsy workspace '${devsyId}' and worktree '${worktreePath}' do not have one exact owner.`
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
return exact[0] ? { status: "owned", workspace: exact[0] } : { status: "absent" };
|
|
6185
|
+
}
|
|
6186
|
+
function selectDevsyWorkspace(workspaces, repoPath) {
|
|
6187
|
+
const matches = workspaces.filter(
|
|
6188
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
6189
|
+
);
|
|
6190
|
+
if (matches.length > 1) {
|
|
6191
|
+
const ids = matches.map((match) => match.id).join(", ");
|
|
6192
|
+
throw new Error(`Multiple Devsy workspaces reference '${repoPath}': ${ids}`);
|
|
6193
|
+
}
|
|
6194
|
+
return matches[0];
|
|
6195
|
+
}
|
|
6196
|
+
var import_node_child_process10;
|
|
6197
|
+
var init_devsy_workspaces = __esm({
|
|
6198
|
+
"src/core/devsy-workspaces.ts"() {
|
|
6199
|
+
"use strict";
|
|
6200
|
+
import_node_child_process10 = require("child_process");
|
|
6201
|
+
init_workspace();
|
|
6202
|
+
}
|
|
6203
|
+
});
|
|
6204
|
+
|
|
6205
|
+
// src/core/workspace-runtime.ts
|
|
6206
|
+
function parseWorkspaceRuntime(value) {
|
|
6207
|
+
const requested = value.trim().toLowerCase();
|
|
6208
|
+
if (!SUPPORTED_RUNTIMES2.includes(requested)) {
|
|
6209
|
+
throw new UnsupportedWorkspaceRuntimeError(
|
|
6210
|
+
`Unsupported workspace runtime '${value}'. Supported: ${SUPPORTED_RUNTIMES2.join(", ")}.`
|
|
6211
|
+
);
|
|
6212
|
+
}
|
|
6213
|
+
return requested;
|
|
6214
|
+
}
|
|
6215
|
+
function isRuntimeInstalled(runtime) {
|
|
6216
|
+
const probeArgs = runtime === "devsy" ? ["--version"] : ["version"];
|
|
6217
|
+
const probe = (0, import_node_child_process11.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
|
|
6218
|
+
return probe.status === 0 && !probe.error;
|
|
6219
|
+
}
|
|
6220
|
+
function readWorkspaceRuntimeConfig() {
|
|
6221
|
+
let raw;
|
|
6222
|
+
try {
|
|
6223
|
+
raw = JSON.parse(import_node_fs16.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
|
|
6224
|
+
} catch {
|
|
6225
|
+
return {};
|
|
6226
|
+
}
|
|
6227
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
6228
|
+
const candidate = raw;
|
|
6229
|
+
const config = {};
|
|
6230
|
+
if (typeof candidate.runtime === "string") {
|
|
6231
|
+
try {
|
|
6232
|
+
config.runtime = parseWorkspaceRuntime(candidate.runtime);
|
|
6233
|
+
} catch {
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
if (typeof candidate.devsyInactivityTimeout === "string" && INACTIVITY_TIMEOUT_PATTERN.test(candidate.devsyInactivityTimeout)) {
|
|
6237
|
+
config.devsyInactivityTimeout = candidate.devsyInactivityTimeout;
|
|
6238
|
+
}
|
|
6239
|
+
return config;
|
|
6240
|
+
}
|
|
6241
|
+
function inspectWorkspaceRuntimeConfig() {
|
|
6242
|
+
let rawText;
|
|
6243
|
+
try {
|
|
6244
|
+
rawText = import_node_fs16.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
|
|
6245
|
+
} catch {
|
|
6246
|
+
return { exists: false, config: {}, problems: [] };
|
|
6247
|
+
}
|
|
6248
|
+
let raw;
|
|
6249
|
+
try {
|
|
6250
|
+
raw = JSON.parse(rawText);
|
|
6251
|
+
} catch {
|
|
6252
|
+
return { exists: true, config: {}, problems: ["workspace-runtime.json is not valid JSON."] };
|
|
6253
|
+
}
|
|
6254
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
6255
|
+
return {
|
|
6256
|
+
exists: true,
|
|
6257
|
+
config: {},
|
|
6258
|
+
problems: ["workspace-runtime.json must contain a JSON object."]
|
|
6259
|
+
};
|
|
6260
|
+
}
|
|
6261
|
+
const candidate = raw;
|
|
6262
|
+
const problems = [];
|
|
6263
|
+
if (candidate.runtime !== void 0) {
|
|
6264
|
+
try {
|
|
6265
|
+
parseWorkspaceRuntime(String(candidate.runtime));
|
|
6266
|
+
} catch {
|
|
6267
|
+
problems.push(`runtime='${String(candidate.runtime)}' is not a supported workspace runtime.`);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
if (candidate.devsyInactivityTimeout !== void 0) {
|
|
6271
|
+
const timeout = String(candidate.devsyInactivityTimeout);
|
|
6272
|
+
if (!isValidInactivityTimeout(timeout)) {
|
|
6273
|
+
problems.push(
|
|
6274
|
+
`devsyInactivityTimeout='${timeout}' is not a Go duration such as 30s, 10m, or 1h30m.`
|
|
6275
|
+
);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
return { exists: true, config: readWorkspaceRuntimeConfig(), problems };
|
|
6279
|
+
}
|
|
6280
|
+
function isValidInactivityTimeout(value) {
|
|
6281
|
+
return INACTIVITY_TIMEOUT_PATTERN.test(value.trim());
|
|
6282
|
+
}
|
|
6283
|
+
function writeWorkspaceRuntimeConfig(config) {
|
|
6284
|
+
const next = {};
|
|
6285
|
+
if (config.runtime !== void 0) next.runtime = parseWorkspaceRuntime(config.runtime);
|
|
6286
|
+
if (config.devsyInactivityTimeout !== void 0) {
|
|
6287
|
+
const timeout = config.devsyInactivityTimeout.trim();
|
|
6288
|
+
if (!isValidInactivityTimeout(timeout)) {
|
|
6289
|
+
throw new Error(
|
|
6290
|
+
`Invalid Devsy inactivity timeout '${config.devsyInactivityTimeout}': use a duration such as 30s, 10m, or 1h30m.`
|
|
6291
|
+
);
|
|
6292
|
+
}
|
|
6293
|
+
next.devsyInactivityTimeout = timeout;
|
|
6294
|
+
}
|
|
6295
|
+
import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6296
|
+
writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
|
|
6297
|
+
`);
|
|
6298
|
+
}
|
|
6299
|
+
function getWorkspaceRegistrySnapshots() {
|
|
6300
|
+
if (cachedSnapshots) return cachedSnapshots;
|
|
6301
|
+
const snapshots = { unavailable: [], errors: {} };
|
|
6302
|
+
if (isRuntimeInstalled("devsy")) {
|
|
6303
|
+
try {
|
|
6304
|
+
snapshots.devsy = listDevsyWorkspaces();
|
|
6305
|
+
} catch (error) {
|
|
6306
|
+
snapshots.unavailable.push("devsy");
|
|
6307
|
+
snapshots.errors.devsy = error instanceof Error ? error.message : String(error);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
if (isRuntimeInstalled("devpod")) {
|
|
6311
|
+
try {
|
|
6312
|
+
snapshots.devpod = listDevpodWorkspacesRaw();
|
|
6313
|
+
} catch (error) {
|
|
6314
|
+
snapshots.unavailable.push("devpod");
|
|
6315
|
+
snapshots.errors.devpod = error instanceof Error ? error.message : String(error);
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
cachedSnapshots = snapshots;
|
|
6319
|
+
return snapshots;
|
|
6320
|
+
}
|
|
6321
|
+
function pathOwnerRuntime(repoPath, snapshots) {
|
|
6322
|
+
if (snapshots.unavailable.length > 0) {
|
|
6323
|
+
return { status: "unavailable", runtimes: snapshots.unavailable };
|
|
6324
|
+
}
|
|
6325
|
+
const devsyOwners = snapshots.devsy?.filter(
|
|
6326
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
6327
|
+
);
|
|
6328
|
+
const devpodOwners = snapshots.devpod?.filter(
|
|
6329
|
+
(workspace) => sameWorkspacePath(workspace.source.localFolder, repoPath)
|
|
6330
|
+
);
|
|
6331
|
+
if (devsyOwners?.length && devpodOwners?.length) return { status: "conflict" };
|
|
6332
|
+
if (devsyOwners?.length) return { status: "owner", runtime: "devsy" };
|
|
6333
|
+
if (devpodOwners?.length) return { status: "owner", runtime: "devpod" };
|
|
6334
|
+
return { status: "none" };
|
|
6335
|
+
}
|
|
6336
|
+
function autoDetectedRuntime() {
|
|
6337
|
+
for (const runtime of SUPPORTED_RUNTIMES2) {
|
|
6338
|
+
if (isRuntimeInstalled(runtime)) return runtime;
|
|
6339
|
+
}
|
|
6340
|
+
return void 0;
|
|
6341
|
+
}
|
|
6342
|
+
function resolveWorkspaceRuntimeDetailed(repoPath, override) {
|
|
6343
|
+
if (override) {
|
|
6344
|
+
return { runtime: parseWorkspaceRuntime(override), source: "override" };
|
|
6345
|
+
}
|
|
6346
|
+
const envRaw = process.env.DEVROUTER_WORKSPACE_RUNTIME?.trim();
|
|
6347
|
+
if (envRaw) {
|
|
6348
|
+
return { runtime: parseWorkspaceRuntime(envRaw), source: "env" };
|
|
6349
|
+
}
|
|
6350
|
+
if (repoPath) {
|
|
6351
|
+
const snapshots = getWorkspaceRegistrySnapshots();
|
|
6352
|
+
const owner = pathOwnerRuntime(repoPath, snapshots);
|
|
6353
|
+
if (owner.status === "conflict") {
|
|
6354
|
+
throw new WorkspaceRuntimeOwnershipError(
|
|
6355
|
+
"Both DevPod and Devsy claim this checkout. Remove the stale registration before running a workspace lifecycle command."
|
|
6356
|
+
);
|
|
6357
|
+
}
|
|
6358
|
+
if (owner.status === "unavailable") {
|
|
6359
|
+
throw new WorkspaceRuntimeOwnershipError(
|
|
6360
|
+
`Cannot prove checkout ownership because the ${owner.runtimes.join(
|
|
6361
|
+
" and "
|
|
6362
|
+
)} workspace registry is unavailable. Restore registry access before running a workspace lifecycle command.`
|
|
6363
|
+
);
|
|
6364
|
+
}
|
|
6365
|
+
if (owner.status === "owner") {
|
|
6366
|
+
return { runtime: owner.runtime, source: "path-owner" };
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
if (cachedRuntime) return cachedRuntime;
|
|
6370
|
+
let resolution;
|
|
6371
|
+
const machine = readWorkspaceRuntimeConfig().runtime;
|
|
6372
|
+
if (machine) {
|
|
6373
|
+
resolution = { runtime: machine, source: "machine-config" };
|
|
6374
|
+
} else {
|
|
6375
|
+
const detected = autoDetectedRuntime();
|
|
6376
|
+
resolution = detected ? { runtime: detected, source: "auto-detect" } : { runtime: "devpod", source: "default" };
|
|
6377
|
+
}
|
|
6378
|
+
cachedRuntime = resolution;
|
|
6379
|
+
return resolution;
|
|
6380
|
+
}
|
|
6381
|
+
function resolveWorkspaceRuntimeOrDefault(repoPath) {
|
|
6382
|
+
return resolveWorkspaceRuntimeDetailed(repoPath).runtime;
|
|
6383
|
+
}
|
|
6384
|
+
function resolveWorkspaceRuntimeForReport(repoPath) {
|
|
6385
|
+
const snapshots = getWorkspaceRegistrySnapshots();
|
|
6386
|
+
const envRaw = process.env.DEVROUTER_WORKSPACE_RUNTIME?.trim();
|
|
6387
|
+
if (envRaw) {
|
|
6388
|
+
const runtime2 = parseWorkspaceRuntime(envRaw);
|
|
6389
|
+
return snapshots[runtime2] ? runtime2 : void 0;
|
|
6390
|
+
}
|
|
6391
|
+
if (repoPath) {
|
|
6392
|
+
const owner = pathOwnerRuntime(repoPath, snapshots);
|
|
6393
|
+
if (owner.status === "conflict" || owner.status === "unavailable") return void 0;
|
|
6394
|
+
if (owner.status === "owner") return owner.runtime;
|
|
6395
|
+
}
|
|
6396
|
+
const runtime = resolveWorkspaceRuntimeDetailed().runtime;
|
|
6397
|
+
return snapshots[runtime] ? runtime : void 0;
|
|
6398
|
+
}
|
|
6399
|
+
function resetWorkspaceRuntimeCaches() {
|
|
6400
|
+
cachedRuntime = void 0;
|
|
6401
|
+
cachedSnapshots = void 0;
|
|
6402
|
+
}
|
|
6403
|
+
var import_node_child_process11, import_node_fs16, import_node_path15, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
6404
|
+
var init_workspace_runtime = __esm({
|
|
6405
|
+
"src/core/workspace-runtime.ts"() {
|
|
6406
|
+
"use strict";
|
|
6407
|
+
import_node_child_process11 = require("child_process");
|
|
6408
|
+
import_node_fs16 = __toESM(require("fs"));
|
|
6409
|
+
import_node_path15 = __toESM(require("path"));
|
|
6410
|
+
init_atomic_file();
|
|
6411
|
+
init_devpod_registry();
|
|
6412
|
+
init_devsy_workspaces();
|
|
6413
|
+
init_router();
|
|
6414
|
+
init_workspace();
|
|
6415
|
+
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
6416
|
+
RUNTIME_CONFIG_FILE = import_node_path15.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
6417
|
+
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
6418
|
+
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
6419
|
+
};
|
|
6420
|
+
WorkspaceRuntimeOwnershipError = class extends Error {
|
|
6421
|
+
constructor(message) {
|
|
6422
|
+
super(message);
|
|
6423
|
+
this.name = "WorkspaceRuntimeOwnershipError";
|
|
6424
|
+
}
|
|
6425
|
+
};
|
|
6426
|
+
}
|
|
6427
|
+
});
|
|
6428
|
+
|
|
6050
6429
|
// src/core/tool-diagnostics.ts
|
|
6051
6430
|
function outputFromResult(result) {
|
|
6052
6431
|
const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
@@ -6055,7 +6434,7 @@ function outputFromResult(result) {
|
|
|
6055
6434
|
return output2.length > 0 ? output2 : void 0;
|
|
6056
6435
|
}
|
|
6057
6436
|
function runTool(command, args = []) {
|
|
6058
|
-
const result = (0,
|
|
6437
|
+
const result = (0, import_node_child_process12.spawnSync)(command, args, {
|
|
6059
6438
|
encoding: "utf-8"
|
|
6060
6439
|
});
|
|
6061
6440
|
if (result.error) {
|
|
@@ -6112,12 +6491,12 @@ function parseMinimumNodeMajor(value) {
|
|
|
6112
6491
|
return Number(match[1]);
|
|
6113
6492
|
}
|
|
6114
6493
|
function readPackageJson(repoPath) {
|
|
6115
|
-
const packagePath =
|
|
6116
|
-
if (!
|
|
6494
|
+
const packagePath = import_node_path16.default.join(repoPath, "package.json");
|
|
6495
|
+
if (!import_node_fs17.default.existsSync(packagePath)) {
|
|
6117
6496
|
return void 0;
|
|
6118
6497
|
}
|
|
6119
6498
|
try {
|
|
6120
|
-
return JSON.parse(
|
|
6499
|
+
return JSON.parse(import_node_fs17.default.readFileSync(packagePath, "utf-8"));
|
|
6121
6500
|
} catch {
|
|
6122
6501
|
return void 0;
|
|
6123
6502
|
}
|
|
@@ -6201,63 +6580,110 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6201
6580
|
details: mkcert.ok ? firstLine(mkcert.output) : mkcert.error,
|
|
6202
6581
|
suggestion: mkcert.ok ? void 0 : brew.ok ? "Install mkcert: brew install mkcert" : "Install mkcert for local HTTPS, then run: devrouter setup --yes"
|
|
6203
6582
|
});
|
|
6204
|
-
|
|
6583
|
+
let ownershipProblem;
|
|
6584
|
+
let runtimeResolution;
|
|
6585
|
+
try {
|
|
6586
|
+
runtimeResolution = resolveWorkspaceRuntimeDetailed(repoPath);
|
|
6587
|
+
} catch (error) {
|
|
6588
|
+
if (!(error instanceof WorkspaceRuntimeOwnershipError)) throw error;
|
|
6589
|
+
ownershipProblem = error.message;
|
|
6590
|
+
runtimeResolution = resolveWorkspaceRuntimeDetailed();
|
|
6591
|
+
}
|
|
6592
|
+
const workspaceRuntime = runtimeResolution.runtime;
|
|
6593
|
+
const machineConfig = readWorkspaceRuntimeConfig();
|
|
6594
|
+
const runtimeLabel = workspaceRuntime === "devsy" ? "Devsy" : "DevPod";
|
|
6595
|
+
const configInspection = inspectWorkspaceRuntimeConfig();
|
|
6596
|
+
const runtimeArgs = workspaceRuntime === "devsy" ? ["--version"] : ["version"];
|
|
6597
|
+
const runtimeTool = runTool(workspaceRuntime, runtimeArgs);
|
|
6598
|
+
if (ownershipProblem) {
|
|
6599
|
+
checks.push({
|
|
6600
|
+
id: "repo.workspace-runtime-ownership",
|
|
6601
|
+
level: "error",
|
|
6602
|
+
summary: "Workspace runtime ownership cannot be resolved safely.",
|
|
6603
|
+
details: ownershipProblem,
|
|
6604
|
+
suggestion: "Restore both registries and remove any duplicate checkout registration."
|
|
6605
|
+
});
|
|
6606
|
+
}
|
|
6205
6607
|
checks.push({
|
|
6206
6608
|
id: "global.devpod",
|
|
6207
|
-
level:
|
|
6208
|
-
summary:
|
|
6209
|
-
details:
|
|
6210
|
-
|
|
6609
|
+
level: runtimeTool.ok ? "ok" : "warn",
|
|
6610
|
+
summary: runtimeTool.ok ? ownershipProblem ? `${runtimeLabel} is configured, but checkout ownership is unresolved.` : `${runtimeLabel} is the active workspace runtime (source: ${runtimeResolution.source}).` : `${runtimeLabel} is the active workspace runtime but is not installed.`,
|
|
6611
|
+
details: [
|
|
6612
|
+
firstLine(runtimeTool.output) ?? runtimeTool.error,
|
|
6613
|
+
`source=${runtimeResolution.source}`,
|
|
6614
|
+
...machineConfig.devsyInactivityTimeout ? [`devsyInactivityTimeout=${machineConfig.devsyInactivityTimeout}`] : []
|
|
6615
|
+
].filter(Boolean).join(", "),
|
|
6616
|
+
suggestion: runtimeTool.ok ? void 0 : workspaceRuntime === "devsy" ? "Install Devsy for devcontainer workspace flows: brew install devsy-org/homebrew-tap/devsy" : "Install DevPod for devcontainer workspace flows: brew install devpod"
|
|
6617
|
+
});
|
|
6618
|
+
const configProblems = [...configInspection.problems];
|
|
6619
|
+
const machineResolvedDevpod = workspaceRuntime === "devpod" && (runtimeResolution.source === "auto-detect" || runtimeResolution.source === "default");
|
|
6620
|
+
if (machineConfig.devsyInactivityTimeout && machineResolvedDevpod) {
|
|
6621
|
+
configProblems.push(
|
|
6622
|
+
"devsyInactivityTimeout is configured but the active workspace runtime is DevPod."
|
|
6623
|
+
);
|
|
6624
|
+
}
|
|
6625
|
+
const configDetails = configInspection.exists ? [
|
|
6626
|
+
`runtime=${machineConfig.runtime ?? "unset"}`,
|
|
6627
|
+
...machineConfig.devsyInactivityTimeout ? [`devsyInactivityTimeout=${machineConfig.devsyInactivityTimeout}`] : []
|
|
6628
|
+
].join(", ") : "Runtime selection falls back to auto-detection.";
|
|
6629
|
+
checks.push({
|
|
6630
|
+
id: "global.workspace-runtime-config",
|
|
6631
|
+
level: configProblems.length > 0 ? "warn" : "ok",
|
|
6632
|
+
summary: !configInspection.exists ? "No machine workspace-runtime preference is configured." : configProblems.length > 0 ? "Machine workspace-runtime preference has problems." : "Machine workspace-runtime preference is valid.",
|
|
6633
|
+
details: configProblems.length > 0 ? [configDetails, ...configProblems].join(" ") : configDetails,
|
|
6634
|
+
suggestion: configProblems.length > 0 ? "Run: devrouter setup --yes --workspace-runtime <devpod|devsy>" : void 0
|
|
6211
6635
|
});
|
|
6212
6636
|
checks.push(nodeToolchainCheck(repoPath));
|
|
6213
6637
|
return checks;
|
|
6214
6638
|
}
|
|
6215
|
-
var
|
|
6639
|
+
var import_node_child_process12, import_node_fs17, import_node_path16;
|
|
6216
6640
|
var init_tool_diagnostics = __esm({
|
|
6217
6641
|
"src/core/tool-diagnostics.ts"() {
|
|
6218
6642
|
"use strict";
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6643
|
+
import_node_child_process12 = require("child_process");
|
|
6644
|
+
import_node_fs17 = __toESM(require("fs"));
|
|
6645
|
+
import_node_path16 = __toESM(require("path"));
|
|
6646
|
+
init_workspace_runtime();
|
|
6222
6647
|
}
|
|
6223
6648
|
});
|
|
6224
6649
|
|
|
6225
6650
|
// src/core/devpod-workspaces.ts
|
|
6226
|
-
function listDevpodWorkspaces() {
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
if (!
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
}
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6651
|
+
function listDevpodWorkspaces(repoPath) {
|
|
6652
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
6653
|
+
return listDevsyWorkspaces().map((workspace) => ({
|
|
6654
|
+
id: workspace.id,
|
|
6655
|
+
source: workspace.source,
|
|
6656
|
+
...workspace.lastUsed ? { lastUsed: workspace.lastUsed } : {},
|
|
6657
|
+
...workspace.lastUsedMalformed ? { lastUsedMalformed: true } : {}
|
|
6658
|
+
}));
|
|
6659
|
+
}
|
|
6660
|
+
return listDevpodWorkspacesRaw();
|
|
6661
|
+
}
|
|
6662
|
+
function listDevpodWorkspacesFromSnapshots(repoPath) {
|
|
6663
|
+
const snapshots = getWorkspaceRegistrySnapshots();
|
|
6664
|
+
const runtime = resolveWorkspaceRuntimeForReport(repoPath);
|
|
6665
|
+
if (!runtime) return void 0;
|
|
6666
|
+
if (runtime === "devsy") {
|
|
6667
|
+
return snapshots.devsy?.map((workspace) => ({
|
|
6668
|
+
id: workspace.id,
|
|
6669
|
+
source: workspace.source,
|
|
6670
|
+
...workspace.lastUsed ? { lastUsed: workspace.lastUsed } : {},
|
|
6671
|
+
...workspace.lastUsedMalformed ? { lastUsedMalformed: true } : {}
|
|
6672
|
+
}));
|
|
6673
|
+
}
|
|
6674
|
+
return snapshots.devpod;
|
|
6675
|
+
}
|
|
6676
|
+
function listMergedRuntimeWorkspaces() {
|
|
6677
|
+
const snapshots = getWorkspaceRegistrySnapshots();
|
|
6678
|
+
return [
|
|
6679
|
+
...snapshots.devpod ?? [],
|
|
6680
|
+
...snapshots.devsy?.map((workspace) => ({
|
|
6681
|
+
id: workspace.id,
|
|
6682
|
+
source: workspace.source,
|
|
6683
|
+
...workspace.lastUsed ? { lastUsed: workspace.lastUsed } : {},
|
|
6684
|
+
...workspace.lastUsedMalformed ? { lastUsedMalformed: true } : {}
|
|
6685
|
+
})) ?? []
|
|
6686
|
+
];
|
|
6261
6687
|
}
|
|
6262
6688
|
function parseDevpodRuntimeStatus(output2, expectedId) {
|
|
6263
6689
|
let parsed;
|
|
@@ -6282,8 +6708,11 @@ function parseDevpodRuntimeStatus(output2, expectedId) {
|
|
|
6282
6708
|
return "unknown";
|
|
6283
6709
|
}
|
|
6284
6710
|
}
|
|
6285
|
-
function inspectDevpodRuntimeStatus(devpodId) {
|
|
6286
|
-
|
|
6711
|
+
function inspectDevpodRuntimeStatus(devpodId, repoPath) {
|
|
6712
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
6713
|
+
return inspectDevsyRuntimeStatus(devpodId);
|
|
6714
|
+
}
|
|
6715
|
+
const result = (0, import_node_child_process13.spawnSync)("devpod", ["status", devpodId, "--output", "json", "--timeout", "5s"], {
|
|
6287
6716
|
encoding: "utf-8"
|
|
6288
6717
|
});
|
|
6289
6718
|
if (result.status !== 0 || result.error) return "unknown";
|
|
@@ -6316,61 +6745,228 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
6316
6745
|
}
|
|
6317
6746
|
return matches[0];
|
|
6318
6747
|
}
|
|
6319
|
-
var
|
|
6748
|
+
var import_node_child_process13;
|
|
6320
6749
|
var init_devpod_workspaces = __esm({
|
|
6321
6750
|
"src/core/devpod-workspaces.ts"() {
|
|
6322
6751
|
"use strict";
|
|
6323
|
-
|
|
6752
|
+
import_node_child_process13 = require("child_process");
|
|
6753
|
+
init_devpod_registry();
|
|
6754
|
+
init_devsy_workspaces();
|
|
6324
6755
|
init_workspace();
|
|
6756
|
+
init_workspace_runtime();
|
|
6325
6757
|
}
|
|
6326
6758
|
});
|
|
6327
6759
|
|
|
6328
|
-
// src/core/
|
|
6760
|
+
// src/core/devsy-mutation.ts
|
|
6329
6761
|
function withMutationLock(activity, target, operation) {
|
|
6330
|
-
|
|
6762
|
+
import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6763
|
+
return withFileLockSync(
|
|
6764
|
+
DEVSY_MUTATION_LOCK_FILE,
|
|
6765
|
+
{ activity, target: `'${target}'`, waitMs: DEVSY_MUTATION_WAIT_MS },
|
|
6766
|
+
operation
|
|
6767
|
+
);
|
|
6768
|
+
}
|
|
6769
|
+
function commandFailure2(result) {
|
|
6770
|
+
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6771
|
+
}
|
|
6772
|
+
function runDevsyAction(action2, devsyId, force = false) {
|
|
6773
|
+
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
6774
|
+
const result = (0, import_node_child_process14.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
6775
|
+
if (result.status !== 0) {
|
|
6776
|
+
throw new Error(
|
|
6777
|
+
`devsy workspace ${action2}${force ? " --force" : ""} failed for '${devsyId}': ${commandFailure2(result) || "unknown error"}`
|
|
6778
|
+
);
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
function inspectExactOwnership(devsyId, worktreePath) {
|
|
6782
|
+
const ownership = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, worktreePath);
|
|
6783
|
+
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6784
|
+
return ownership;
|
|
6785
|
+
}
|
|
6786
|
+
function mutateOwnedDevsyWorkspace(action2, devsyId, worktreePath) {
|
|
6787
|
+
return withMutationLock(`Devsy ${action2}`, worktreePath, () => {
|
|
6788
|
+
const before = inspectExactOwnership(devsyId, worktreePath);
|
|
6789
|
+
if (before.status === "absent") return { status: "absent" };
|
|
6790
|
+
runDevsyAction(action2, devsyId);
|
|
6791
|
+
let after = inspectExactOwnership(devsyId, worktreePath);
|
|
6792
|
+
if (action2 === "stop" && after.status !== "owned") {
|
|
6793
|
+
throw new Error(
|
|
6794
|
+
`Devsy workspace '${devsyId}' no longer owns '${worktreePath}' after provider stop.`
|
|
6795
|
+
);
|
|
6796
|
+
}
|
|
6797
|
+
if (action2 === "delete" && after.status === "owned") {
|
|
6798
|
+
const runtime = inspectDevsyRuntimeStatus(devsyId);
|
|
6799
|
+
if (runtime !== "not-found") {
|
|
6800
|
+
throw new Error(
|
|
6801
|
+
`Devsy workspace '${devsyId}' still owns '${worktreePath}' after provider delete (runtime=${runtime}).`
|
|
6802
|
+
);
|
|
6803
|
+
}
|
|
6804
|
+
after = inspectExactOwnership(devsyId, worktreePath);
|
|
6805
|
+
if (after.status === "owned") {
|
|
6806
|
+
runDevsyAction("delete", devsyId, true);
|
|
6807
|
+
after = inspectExactOwnership(devsyId, worktreePath);
|
|
6808
|
+
}
|
|
6809
|
+
if (after.status !== "absent") {
|
|
6810
|
+
throw new Error(
|
|
6811
|
+
`Devsy workspace '${devsyId}' still owns '${worktreePath}' after forced provider delete.`
|
|
6812
|
+
);
|
|
6813
|
+
}
|
|
6814
|
+
}
|
|
6815
|
+
return { status: "changed" };
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6818
|
+
function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
6819
|
+
return mutateOwnedDevsyWorkspace("stop", devsyId, worktreePath);
|
|
6820
|
+
}
|
|
6821
|
+
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
6822
|
+
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
6823
|
+
}
|
|
6824
|
+
function assertDevsyTarget(devsyId, repoPath) {
|
|
6825
|
+
const workspaces = listDevsyWorkspaces();
|
|
6826
|
+
const existing = selectDevsyWorkspace(workspaces, repoPath);
|
|
6827
|
+
const id = devsyId ?? existing?.id;
|
|
6828
|
+
if (id) {
|
|
6829
|
+
const before = inspectDevsyWorkspaceOwnership(workspaces, id, repoPath);
|
|
6830
|
+
if (before.status === "conflict") throw new Error(before.reason);
|
|
6831
|
+
}
|
|
6832
|
+
return id;
|
|
6833
|
+
}
|
|
6834
|
+
function startDevsyWorkspace(options) {
|
|
6835
|
+
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
6836
|
+
return withMutationLock(activity, options.repoPath, () => {
|
|
6837
|
+
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
6838
|
+
if (devsyId && options.recreate) {
|
|
6839
|
+
const attached = listDevsyWorkspaces();
|
|
6840
|
+
const ownership = inspectDevsyWorkspaceOwnership(attached, devsyId, options.repoPath);
|
|
6841
|
+
if (ownership.status !== "owned") {
|
|
6842
|
+
throw new Error(`Cannot recreate Devsy workspace '${devsyId}' without one exact owner.`);
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
if (!devsyId && options.recreate) {
|
|
6846
|
+
throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
|
|
6847
|
+
}
|
|
6848
|
+
const args = ["workspace", "up", options.repoPath];
|
|
6849
|
+
if (devsyId) args.push("--id", devsyId);
|
|
6850
|
+
if (options.devcontainerPath) args.push("--devcontainer", options.devcontainerPath);
|
|
6851
|
+
args.push("--ide-launch", "skip");
|
|
6852
|
+
if (options.workspace) {
|
|
6853
|
+
args.push(
|
|
6854
|
+
"--workspace-env",
|
|
6855
|
+
`WORKSPACE=${options.workspace.token}`,
|
|
6856
|
+
"--workspace-env",
|
|
6857
|
+
`DEVROUTER_WORKSPACE=${options.workspace.token}`
|
|
6858
|
+
);
|
|
6859
|
+
}
|
|
6860
|
+
if (options.inactivityTimeout) {
|
|
6861
|
+
args.push("--provider-option", `INACTIVITY_TIMEOUT=${options.inactivityTimeout}`);
|
|
6862
|
+
}
|
|
6863
|
+
if (options.recreate) args.push("--recreate");
|
|
6864
|
+
const env = { ...process.env };
|
|
6865
|
+
if (options.workspace) {
|
|
6866
|
+
env.WORKSPACE = options.workspace.token;
|
|
6867
|
+
env.DEVROUTER_WORKSPACE = options.workspace.token;
|
|
6868
|
+
env.DEVROUTER_GIT_COMMON_DIR = options.workspace.gitCommonDir;
|
|
6869
|
+
env.DEVCONTAINER_COMPOSE_OVERLAY = "docker-compose.devrouter.yml";
|
|
6870
|
+
} else {
|
|
6871
|
+
delete env.WORKSPACE;
|
|
6872
|
+
delete env.DEVROUTER_WORKSPACE;
|
|
6873
|
+
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
6874
|
+
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
6875
|
+
}
|
|
6876
|
+
const result = (0, import_node_child_process14.spawnSync)("devsy", args, {
|
|
6877
|
+
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
6878
|
+
env
|
|
6879
|
+
});
|
|
6880
|
+
if (result.status !== 0) {
|
|
6881
|
+
throw new Error(`devsy workspace up failed for '${devsyId ?? options.repoPath}'.`);
|
|
6882
|
+
}
|
|
6883
|
+
try {
|
|
6884
|
+
const attached = listDevsyWorkspaces();
|
|
6885
|
+
devsyId ??= selectDevsyWorkspace(attached, options.repoPath)?.id;
|
|
6886
|
+
if (!devsyId) {
|
|
6887
|
+
throw new Error(`Devsy did not attach '${options.repoPath}' after startup.`);
|
|
6888
|
+
}
|
|
6889
|
+
const ownership = inspectDevsyWorkspaceOwnership(attached, devsyId, options.repoPath);
|
|
6890
|
+
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6891
|
+
if (ownership.status !== "owned") {
|
|
6892
|
+
throw new Error(
|
|
6893
|
+
`Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
|
|
6894
|
+
);
|
|
6895
|
+
}
|
|
6896
|
+
return devsyId;
|
|
6897
|
+
} catch (error) {
|
|
6898
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6899
|
+
throw new DevsyStartPostconditionError(message);
|
|
6900
|
+
}
|
|
6901
|
+
});
|
|
6902
|
+
}
|
|
6903
|
+
var import_node_child_process14, import_node_fs18, import_node_path17, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError;
|
|
6904
|
+
var init_devsy_mutation = __esm({
|
|
6905
|
+
"src/core/devsy-mutation.ts"() {
|
|
6906
|
+
"use strict";
|
|
6907
|
+
import_node_child_process14 = require("child_process");
|
|
6908
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
6909
|
+
import_node_path17 = __toESM(require("path"));
|
|
6910
|
+
init_devsy_workspaces();
|
|
6911
|
+
init_file_lock();
|
|
6912
|
+
init_router();
|
|
6913
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
6914
|
+
DEVSY_MUTATION_WAIT_MS = 6e4;
|
|
6915
|
+
DevsyStartPostconditionError = class extends Error {
|
|
6916
|
+
};
|
|
6917
|
+
}
|
|
6918
|
+
});
|
|
6919
|
+
|
|
6920
|
+
// src/core/devpod-mutation.ts
|
|
6921
|
+
function withMutationLock2(activity, target, operation) {
|
|
6922
|
+
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6331
6923
|
return withFileLockSync(
|
|
6332
6924
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
6333
6925
|
{ activity, target: `'${target}'`, waitMs: DEVPOD_MUTATION_WAIT_MS },
|
|
6334
6926
|
operation
|
|
6335
6927
|
);
|
|
6336
6928
|
}
|
|
6337
|
-
function
|
|
6929
|
+
function commandFailure3(result) {
|
|
6338
6930
|
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6339
6931
|
}
|
|
6340
6932
|
function runDevpodAction(action2, devpodId, force = false) {
|
|
6341
6933
|
const args = action2 === "delete" ? [action2, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action2, devpodId];
|
|
6342
|
-
const result = (0,
|
|
6934
|
+
const result = (0, import_node_child_process15.spawnSync)("devpod", args, { encoding: "utf-8" });
|
|
6343
6935
|
if (result.status !== 0) {
|
|
6344
6936
|
throw new Error(
|
|
6345
|
-
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${
|
|
6937
|
+
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${commandFailure3(result) || "unknown error"}`
|
|
6346
6938
|
);
|
|
6347
6939
|
}
|
|
6348
6940
|
}
|
|
6349
|
-
function
|
|
6350
|
-
const ownership = inspectDevpodWorkspaceOwnership(
|
|
6941
|
+
function inspectExactOwnership2(devpodId, worktreePath) {
|
|
6942
|
+
const ownership = inspectDevpodWorkspaceOwnership(
|
|
6943
|
+
listDevpodWorkspaces(worktreePath),
|
|
6944
|
+
devpodId,
|
|
6945
|
+
worktreePath
|
|
6946
|
+
);
|
|
6351
6947
|
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6352
6948
|
return ownership;
|
|
6353
6949
|
}
|
|
6354
6950
|
function mutateOwnedDevpodWorkspace(action2, devpodId, worktreePath) {
|
|
6355
|
-
return
|
|
6356
|
-
const before =
|
|
6951
|
+
return withMutationLock2(`DevPod ${action2}`, worktreePath, () => {
|
|
6952
|
+
const before = inspectExactOwnership2(devpodId, worktreePath);
|
|
6357
6953
|
if (before.status === "absent") return { status: "absent" };
|
|
6358
6954
|
runDevpodAction(action2, devpodId);
|
|
6359
|
-
let after =
|
|
6955
|
+
let after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6360
6956
|
if (action2 === "stop" && after.status !== "owned") {
|
|
6361
6957
|
throw new Error(`DevPod '${devpodId}' no longer owns '${worktreePath}' after provider stop.`);
|
|
6362
6958
|
}
|
|
6363
6959
|
if (action2 === "delete" && after.status === "owned") {
|
|
6364
|
-
const runtime = inspectDevpodRuntimeStatus(devpodId);
|
|
6960
|
+
const runtime = inspectDevpodRuntimeStatus(devpodId, worktreePath);
|
|
6365
6961
|
if (runtime !== "not-found") {
|
|
6366
6962
|
throw new Error(
|
|
6367
6963
|
`DevPod '${devpodId}' still owns '${worktreePath}' after provider delete (runtime=${runtime}).`
|
|
6368
6964
|
);
|
|
6369
6965
|
}
|
|
6370
|
-
after =
|
|
6966
|
+
after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6371
6967
|
if (after.status === "owned") {
|
|
6372
6968
|
runDevpodAction("delete", devpodId, true);
|
|
6373
|
-
after =
|
|
6969
|
+
after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6374
6970
|
}
|
|
6375
6971
|
if (after.status !== "absent") {
|
|
6376
6972
|
throw new Error(
|
|
@@ -6382,15 +6978,42 @@ function mutateOwnedDevpodWorkspace(action2, devpodId, worktreePath) {
|
|
|
6382
6978
|
});
|
|
6383
6979
|
}
|
|
6384
6980
|
function stopOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
6385
|
-
|
|
6981
|
+
if (resolveWorkspaceRuntimeOrDefault(worktreePath) === "devsy") {
|
|
6982
|
+
const result2 = stopOwnedDevsyWorkspace(devpodId, worktreePath);
|
|
6983
|
+
resetWorkspaceRuntimeCaches();
|
|
6984
|
+
return result2;
|
|
6985
|
+
}
|
|
6986
|
+
const result = mutateOwnedDevpodWorkspace("stop", devpodId, worktreePath);
|
|
6987
|
+
resetWorkspaceRuntimeCaches();
|
|
6988
|
+
return result;
|
|
6386
6989
|
}
|
|
6387
6990
|
function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
6388
|
-
|
|
6991
|
+
if (resolveWorkspaceRuntimeOrDefault(worktreePath) === "devsy") {
|
|
6992
|
+
const result2 = deleteOwnedDevsyWorkspace(devpodId, worktreePath);
|
|
6993
|
+
resetWorkspaceRuntimeCaches();
|
|
6994
|
+
return result2;
|
|
6995
|
+
}
|
|
6996
|
+
const result = mutateOwnedDevpodWorkspace("delete", devpodId, worktreePath);
|
|
6997
|
+
resetWorkspaceRuntimeCaches();
|
|
6998
|
+
return result;
|
|
6389
6999
|
}
|
|
6390
7000
|
function startDevpodWorkspace(options) {
|
|
7001
|
+
if (resolveWorkspaceRuntimeOrDefault(options.repoPath) === "devsy") {
|
|
7002
|
+
const result = startDevsyWorkspace({
|
|
7003
|
+
repoPath: options.repoPath,
|
|
7004
|
+
devsyId: options.devpodId,
|
|
7005
|
+
devcontainerPath: options.devcontainerPath,
|
|
7006
|
+
recreate: options.recreate,
|
|
7007
|
+
quiet: options.quiet,
|
|
7008
|
+
workspace: options.workspace,
|
|
7009
|
+
inactivityTimeout: readWorkspaceRuntimeConfig().devsyInactivityTimeout
|
|
7010
|
+
});
|
|
7011
|
+
resetWorkspaceRuntimeCaches();
|
|
7012
|
+
return result;
|
|
7013
|
+
}
|
|
6391
7014
|
const activity = options.recreate ? "DevPod recreate" : "DevPod start";
|
|
6392
|
-
return
|
|
6393
|
-
const workspaces = listDevpodWorkspaces();
|
|
7015
|
+
return withMutationLock2(activity, options.repoPath, () => {
|
|
7016
|
+
const workspaces = listDevpodWorkspaces(options.repoPath);
|
|
6394
7017
|
let devpodId = options.devpodId ?? selectDevpodWorkspace(workspaces, options.repoPath)?.id;
|
|
6395
7018
|
if (devpodId) {
|
|
6396
7019
|
const before = inspectDevpodWorkspaceOwnership(workspaces, devpodId, options.repoPath);
|
|
@@ -6428,7 +7051,7 @@ function startDevpodWorkspace(options) {
|
|
|
6428
7051
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
6429
7052
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
6430
7053
|
}
|
|
6431
|
-
const result = (0,
|
|
7054
|
+
const result = (0, import_node_child_process15.spawnSync)("devpod", args, {
|
|
6432
7055
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
6433
7056
|
env
|
|
6434
7057
|
});
|
|
@@ -6436,7 +7059,7 @@ function startDevpodWorkspace(options) {
|
|
|
6436
7059
|
throw new Error(`devpod up failed for '${devpodId ?? options.repoPath}'.`);
|
|
6437
7060
|
}
|
|
6438
7061
|
try {
|
|
6439
|
-
const attached = listDevpodWorkspaces();
|
|
7062
|
+
const attached = listDevpodWorkspaces(options.repoPath);
|
|
6440
7063
|
devpodId ??= selectDevpodWorkspace(attached, options.repoPath)?.id;
|
|
6441
7064
|
if (!devpodId) {
|
|
6442
7065
|
throw new Error(`DevPod did not attach '${options.repoPath}' after startup.`);
|
|
@@ -6448,6 +7071,7 @@ function startDevpodWorkspace(options) {
|
|
|
6448
7071
|
`DevPod did not attach '${options.repoPath}' as '${devpodId}' after startup.`
|
|
6449
7072
|
);
|
|
6450
7073
|
}
|
|
7074
|
+
resetWorkspaceRuntimeCaches();
|
|
6451
7075
|
return devpodId;
|
|
6452
7076
|
} catch (error) {
|
|
6453
7077
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6455,17 +7079,19 @@ function startDevpodWorkspace(options) {
|
|
|
6455
7079
|
}
|
|
6456
7080
|
});
|
|
6457
7081
|
}
|
|
6458
|
-
var
|
|
7082
|
+
var import_node_child_process15, import_node_fs19, import_node_path18, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
|
|
6459
7083
|
var init_devpod_mutation = __esm({
|
|
6460
7084
|
"src/core/devpod-mutation.ts"() {
|
|
6461
7085
|
"use strict";
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
7086
|
+
import_node_child_process15 = require("child_process");
|
|
7087
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7088
|
+
import_node_path18 = __toESM(require("path"));
|
|
6465
7089
|
init_devpod_workspaces();
|
|
7090
|
+
init_devsy_mutation();
|
|
6466
7091
|
init_file_lock();
|
|
6467
7092
|
init_router();
|
|
6468
|
-
|
|
7093
|
+
init_workspace_runtime();
|
|
7094
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path18.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
6469
7095
|
DEVPOD_MUTATION_WAIT_MS = 6e4;
|
|
6470
7096
|
DevpodStartPostconditionError = class extends Error {
|
|
6471
7097
|
};
|
|
@@ -6479,7 +7105,7 @@ function commandError(command, repoPath, stderr) {
|
|
|
6479
7105
|
);
|
|
6480
7106
|
}
|
|
6481
7107
|
function resolveGitCommonDir(repoPath) {
|
|
6482
|
-
const result = (0,
|
|
7108
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
|
|
6483
7109
|
encoding: "utf-8",
|
|
6484
7110
|
env: READ_ONLY_GIT_ENV
|
|
6485
7111
|
});
|
|
@@ -6487,10 +7113,10 @@ function resolveGitCommonDir(repoPath) {
|
|
|
6487
7113
|
if (result.status !== 0 || !output2) {
|
|
6488
7114
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
6489
7115
|
}
|
|
6490
|
-
return comparableWorkspacePath(
|
|
7116
|
+
return comparableWorkspacePath(import_node_path19.default.isAbsolute(output2) ? output2 : import_node_path19.default.resolve(repoPath, output2));
|
|
6491
7117
|
}
|
|
6492
7118
|
function resolveGitTopLevel(repoPath) {
|
|
6493
|
-
const result = (0,
|
|
7119
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
6494
7120
|
encoding: "utf-8",
|
|
6495
7121
|
env: READ_ONLY_GIT_ENV
|
|
6496
7122
|
});
|
|
@@ -6501,7 +7127,7 @@ function resolveGitTopLevel(repoPath) {
|
|
|
6501
7127
|
return comparableWorkspacePath(output2);
|
|
6502
7128
|
}
|
|
6503
7129
|
function listGitWorktrees(repoPath) {
|
|
6504
|
-
const result = (0,
|
|
7130
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
|
|
6505
7131
|
encoding: "utf-8",
|
|
6506
7132
|
env: READ_ONLY_GIT_ENV
|
|
6507
7133
|
});
|
|
@@ -6538,7 +7164,7 @@ function listGitWorktrees(repoPath) {
|
|
|
6538
7164
|
return worktrees;
|
|
6539
7165
|
}
|
|
6540
7166
|
function ownershipDirectory(repoPath) {
|
|
6541
|
-
return
|
|
7167
|
+
return import_node_path19.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
6542
7168
|
}
|
|
6543
7169
|
function validateWorkspace(value, label) {
|
|
6544
7170
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -6566,7 +7192,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
6566
7192
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
6567
7193
|
);
|
|
6568
7194
|
}
|
|
6569
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
7195
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path19.default.isAbsolute(candidate.worktreePath)) {
|
|
6570
7196
|
throw new Error("invalid workspace ownership worktreePath");
|
|
6571
7197
|
}
|
|
6572
7198
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -6586,7 +7212,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
6586
7212
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
6587
7213
|
let parsed;
|
|
6588
7214
|
try {
|
|
6589
|
-
parsed = JSON.parse(
|
|
7215
|
+
parsed = JSON.parse(import_node_fs20.default.readFileSync(filePath, "utf-8"));
|
|
6590
7216
|
} catch (error) {
|
|
6591
7217
|
if (error instanceof SyntaxError) {
|
|
6592
7218
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -6602,7 +7228,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
6602
7228
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
6603
7229
|
let entries;
|
|
6604
7230
|
try {
|
|
6605
|
-
entries =
|
|
7231
|
+
entries = import_node_fs20.default.readdirSync(directory, { withFileTypes: true });
|
|
6606
7232
|
} catch (error) {
|
|
6607
7233
|
if (error.code === "ENOENT") return [];
|
|
6608
7234
|
throw error;
|
|
@@ -6610,14 +7236,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
6610
7236
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
6611
7237
|
const workspace = entry.name.slice(0, -".json".length);
|
|
6612
7238
|
validateWorkspace(workspace, "filename");
|
|
6613
|
-
return readRecordFile(
|
|
7239
|
+
return readRecordFile(import_node_path19.default.join(directory, entry.name), workspace);
|
|
6614
7240
|
});
|
|
6615
7241
|
}
|
|
6616
7242
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
6617
7243
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
6618
7244
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
6619
7245
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
6620
|
-
const filePath =
|
|
7246
|
+
const filePath = import_node_path19.default.join(directory, `${workspace}.json`);
|
|
6621
7247
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6622
7248
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
6623
7249
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -6653,9 +7279,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
6653
7279
|
return record;
|
|
6654
7280
|
}
|
|
6655
7281
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
6656
|
-
const filePath =
|
|
7282
|
+
const filePath = import_node_path19.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
6657
7283
|
try {
|
|
6658
|
-
|
|
7284
|
+
import_node_fs20.default.rmSync(filePath);
|
|
6659
7285
|
return true;
|
|
6660
7286
|
} catch (error) {
|
|
6661
7287
|
if (error.code === "ENOENT") return false;
|
|
@@ -6666,7 +7292,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
6666
7292
|
return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
|
|
6667
7293
|
}
|
|
6668
7294
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
6669
|
-
const filePath =
|
|
7295
|
+
const filePath = import_node_path19.default.join(
|
|
6670
7296
|
directory,
|
|
6671
7297
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
6672
7298
|
);
|
|
@@ -6678,14 +7304,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
6678
7304
|
throw error;
|
|
6679
7305
|
}
|
|
6680
7306
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
6681
|
-
|
|
7307
|
+
import_node_fs20.default.rmSync(filePath);
|
|
6682
7308
|
return "removed";
|
|
6683
7309
|
}
|
|
6684
7310
|
function withWorkspaceOwnershipTransaction(repoPath, operation) {
|
|
6685
7311
|
const directory = ownershipDirectory(repoPath);
|
|
6686
|
-
|
|
7312
|
+
import_node_fs20.default.mkdirSync(directory, { recursive: true });
|
|
6687
7313
|
return withFileLockSync(
|
|
6688
|
-
|
|
7314
|
+
import_node_path19.default.join(directory, ".lock"),
|
|
6689
7315
|
{ activity: "workspace ownership transaction", target: `'${repoPath}'`, waitMs: 5e3 },
|
|
6690
7316
|
() => operation({
|
|
6691
7317
|
list: () => listWorkspaceOwnershipInDirectory(directory),
|
|
@@ -6716,7 +7342,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
|
|
|
6716
7342
|
if (worktree?.locked) {
|
|
6717
7343
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
6718
7344
|
}
|
|
6719
|
-
if ((!worktree || worktree.prunable) &&
|
|
7345
|
+
if ((!worktree || worktree.prunable) && import_node_fs20.default.existsSync(record.worktreePath)) {
|
|
6720
7346
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
6721
7347
|
}
|
|
6722
7348
|
if (!worktree || worktree.prunable) {
|
|
@@ -6740,20 +7366,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
6740
7366
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
6741
7367
|
);
|
|
6742
7368
|
}
|
|
6743
|
-
var
|
|
7369
|
+
var import_node_child_process16, import_node_fs20, import_node_path19, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
6744
7370
|
var init_workspace_ownership = __esm({
|
|
6745
7371
|
"src/core/workspace-ownership.ts"() {
|
|
6746
7372
|
"use strict";
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7373
|
+
import_node_child_process16 = require("child_process");
|
|
7374
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
7375
|
+
import_node_path19 = __toESM(require("path"));
|
|
6750
7376
|
init_atomic_file();
|
|
6751
7377
|
init_devpod_workspaces();
|
|
6752
7378
|
init_file_lock();
|
|
6753
7379
|
init_workspace();
|
|
6754
7380
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
6755
7381
|
OWNERSHIP_VERSION = 1;
|
|
6756
|
-
OWNERSHIP_DIR =
|
|
7382
|
+
OWNERSHIP_DIR = import_node_path19.default.join("devrouter", "workspaces");
|
|
6757
7383
|
}
|
|
6758
7384
|
});
|
|
6759
7385
|
|
|
@@ -6767,10 +7393,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
6767
7393
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
6768
7394
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
6769
7395
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
6770
|
-
const localRoot =
|
|
7396
|
+
const localRoot = import_node_path20.default.join(comparableRepo, "trees") + import_node_path20.default.sep;
|
|
6771
7397
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
6772
|
-
const legacyPrefix = `${
|
|
6773
|
-
return
|
|
7398
|
+
const legacyPrefix = `${import_node_path20.default.basename(comparableRepo)}-`;
|
|
7399
|
+
return import_node_path20.default.dirname(comparableWorktree) === import_node_path20.default.dirname(comparableRepo) && import_node_path20.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
6774
7400
|
}
|
|
6775
7401
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
6776
7402
|
const actions = [
|
|
@@ -6886,7 +7512,7 @@ function revalidateCandidate(candidate, repoPath, transaction) {
|
|
|
6886
7512
|
const fresh = ownedCandidate(
|
|
6887
7513
|
record,
|
|
6888
7514
|
listGitWorktrees(repoPath),
|
|
6889
|
-
listDevpodWorkspaces(),
|
|
7515
|
+
listDevpodWorkspaces(record.worktreePath),
|
|
6890
7516
|
listHostRouteState()
|
|
6891
7517
|
);
|
|
6892
7518
|
if (fresh.eligible) return { status: "ready", candidate: fresh, record };
|
|
@@ -6987,11 +7613,20 @@ function inspectWorkspaceGc(repo) {
|
|
|
6987
7613
|
const repoPath = comparableWorkspacePath(resolveRepoPath(repo));
|
|
6988
7614
|
const worktrees = listGitWorktrees(repoPath);
|
|
6989
7615
|
const records = listWorkspaceOwnership(repoPath);
|
|
6990
|
-
const devpods = listDevpodWorkspaces();
|
|
6991
7616
|
const routes = listHostRouteState();
|
|
6992
7617
|
const candidates = [
|
|
6993
|
-
|
|
6994
|
-
|
|
7618
|
+
// Per-record resolution keeps mixed DevPod+Devsy fleets on the registry
|
|
7619
|
+
// that owns each record (snapshot reads: the apply path revalidates
|
|
7620
|
+
// live); legacy detection scans both registries.
|
|
7621
|
+
...records.map(
|
|
7622
|
+
(record) => ownedCandidate(
|
|
7623
|
+
record,
|
|
7624
|
+
worktrees,
|
|
7625
|
+
listDevpodWorkspacesFromSnapshots(record.worktreePath),
|
|
7626
|
+
routes
|
|
7627
|
+
)
|
|
7628
|
+
),
|
|
7629
|
+
...legacyCandidates(repoPath, records, worktrees, listMergedRuntimeWorkspaces(), routes)
|
|
6995
7630
|
];
|
|
6996
7631
|
return {
|
|
6997
7632
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7013,11 +7648,11 @@ function applyWorkspaceGc(plan) {
|
|
|
7013
7648
|
candidates
|
|
7014
7649
|
};
|
|
7015
7650
|
}
|
|
7016
|
-
var
|
|
7651
|
+
var import_node_path20;
|
|
7017
7652
|
var init_workspace_gc = __esm({
|
|
7018
7653
|
"src/core/workspace-gc.ts"() {
|
|
7019
7654
|
"use strict";
|
|
7020
|
-
|
|
7655
|
+
import_node_path20 = __toESM(require("path"));
|
|
7021
7656
|
init_devpod_mutation();
|
|
7022
7657
|
init_devpod_workspaces();
|
|
7023
7658
|
init_host_routes();
|
|
@@ -7087,11 +7722,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
7087
7722
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
7088
7723
|
continue;
|
|
7089
7724
|
}
|
|
7090
|
-
if (!
|
|
7725
|
+
if (!import_node_fs21.default.existsSync(absolutePath)) {
|
|
7091
7726
|
continue;
|
|
7092
7727
|
}
|
|
7093
7728
|
try {
|
|
7094
|
-
const raw =
|
|
7729
|
+
const raw = import_node_fs21.default.readFileSync(absolutePath, "utf-8");
|
|
7095
7730
|
const parsed = import_yaml5.default.parse(raw);
|
|
7096
7731
|
const root = asRecord2(parsed);
|
|
7097
7732
|
const services = asRecord2(root?.services);
|
|
@@ -7370,7 +8005,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
7370
8005
|
const config = runtimeConfig.config;
|
|
7371
8006
|
loadedConfig = config;
|
|
7372
8007
|
loadedWorkspace = runtimeConfig.workspace;
|
|
7373
|
-
const cliVersion = true ? "0.0.
|
|
8008
|
+
const cliVersion = true ? "0.0.41" : "0.0.0-dev";
|
|
7374
8009
|
const configVersion = config.devrouter?.version;
|
|
7375
8010
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
7376
8011
|
addCheck(checks, {
|
|
@@ -7400,9 +8035,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
7400
8035
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
7401
8036
|
app: app.name,
|
|
7402
8037
|
filePath,
|
|
7403
|
-
absolutePath:
|
|
8038
|
+
absolutePath: import_node_path21.default.resolve(repo.path, filePath)
|
|
7404
8039
|
}))
|
|
7405
|
-
).filter((entry) => !
|
|
8040
|
+
).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
|
|
7406
8041
|
addCheck(checks, {
|
|
7407
8042
|
id: "repo.compose-files",
|
|
7408
8043
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -7445,8 +8080,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
7445
8080
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
7446
8081
|
app: app.name,
|
|
7447
8082
|
cwd: app.hostRun.cwd,
|
|
7448
|
-
absolutePath:
|
|
7449
|
-
})).filter((entry) => !
|
|
8083
|
+
absolutePath: import_node_path21.default.resolve(repo.path, app.hostRun.cwd)
|
|
8084
|
+
})).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
|
|
7450
8085
|
addCheck(checks, {
|
|
7451
8086
|
id: "repo.host-cwd",
|
|
7452
8087
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -7590,12 +8225,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
7590
8225
|
nextSteps
|
|
7591
8226
|
};
|
|
7592
8227
|
}
|
|
7593
|
-
var
|
|
8228
|
+
var import_node_fs21, import_node_path21, import_yaml5, POSTGRES_DEFAULTS;
|
|
7594
8229
|
var init_doctor = __esm({
|
|
7595
8230
|
"src/core/doctor.ts"() {
|
|
7596
8231
|
"use strict";
|
|
7597
|
-
|
|
7598
|
-
|
|
8232
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
8233
|
+
import_node_path21 = __toESM(require("path"));
|
|
7599
8234
|
import_yaml5 = __toESM(require("yaml"));
|
|
7600
8235
|
init_docker();
|
|
7601
8236
|
init_host_routes();
|
|
@@ -7702,6 +8337,32 @@ async function runSetup(options = {}) {
|
|
|
7702
8337
|
})
|
|
7703
8338
|
);
|
|
7704
8339
|
}
|
|
8340
|
+
if (options.workspaceRuntime !== void 0 || options.devsyInactivityTimeout !== void 0) {
|
|
8341
|
+
try {
|
|
8342
|
+
const existing = readWorkspaceRuntimeConfig();
|
|
8343
|
+
const next = {
|
|
8344
|
+
runtime: options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : existing.runtime,
|
|
8345
|
+
devsyInactivityTimeout: options.devsyInactivityTimeout ?? existing.devsyInactivityTimeout
|
|
8346
|
+
};
|
|
8347
|
+
writeWorkspaceRuntimeConfig(next);
|
|
8348
|
+
actions.push(
|
|
8349
|
+
action("performed", {
|
|
8350
|
+
id: "global.workspace-runtime-config",
|
|
8351
|
+
summary: `Persisted workspace runtime preferences (runtime=${next.runtime ?? "unset"}, devsyInactivityTimeout=${next.devsyInactivityTimeout ?? "unset"}).`
|
|
8352
|
+
})
|
|
8353
|
+
);
|
|
8354
|
+
} catch (error) {
|
|
8355
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8356
|
+
actions.push(
|
|
8357
|
+
action("failed", {
|
|
8358
|
+
id: "global.workspace-runtime-config",
|
|
8359
|
+
summary: "Failed to persist workspace runtime preferences.",
|
|
8360
|
+
details: message,
|
|
8361
|
+
suggestion: "Use: devrouter setup --yes --workspace-runtime <devpod|devsy> [--devsy-inactivity-timeout <duration>]"
|
|
8362
|
+
})
|
|
8363
|
+
);
|
|
8364
|
+
}
|
|
8365
|
+
}
|
|
7705
8366
|
try {
|
|
7706
8367
|
const existed = await networkExists(DEVNET_NAME);
|
|
7707
8368
|
await ensureNetwork(DEVNET_NAME);
|
|
@@ -7797,6 +8458,7 @@ var init_setup = __esm({
|
|
|
7797
8458
|
init_router();
|
|
7798
8459
|
init_tls();
|
|
7799
8460
|
init_tool_diagnostics();
|
|
8461
|
+
init_workspace_runtime();
|
|
7800
8462
|
}
|
|
7801
8463
|
});
|
|
7802
8464
|
|
|
@@ -7806,7 +8468,12 @@ __export(setup_exports, {
|
|
|
7806
8468
|
runSetupCommand: () => runSetupCommand
|
|
7807
8469
|
});
|
|
7808
8470
|
async function runSetupCommand(options) {
|
|
7809
|
-
const report = await runSetup({
|
|
8471
|
+
const report = await runSetup({
|
|
8472
|
+
repo: options.repo,
|
|
8473
|
+
yes: Boolean(options.yes),
|
|
8474
|
+
workspaceRuntime: options.workspaceRuntime,
|
|
8475
|
+
devsyInactivityTimeout: options.devsyInactivityTimeout
|
|
8476
|
+
});
|
|
7810
8477
|
if (options.json) {
|
|
7811
8478
|
printJSON(report);
|
|
7812
8479
|
} else {
|
|
@@ -7853,12 +8520,12 @@ function parseSsPortListeners(stdout, port) {
|
|
|
7853
8520
|
return listeners;
|
|
7854
8521
|
}
|
|
7855
8522
|
function findPortListeners(port) {
|
|
7856
|
-
const result = (0,
|
|
8523
|
+
const result = (0, import_node_child_process17.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
|
|
7857
8524
|
encoding: "utf-8"
|
|
7858
8525
|
});
|
|
7859
8526
|
if (result.error && result.error.code === "ENOENT") {
|
|
7860
8527
|
if (process.platform === "linux") {
|
|
7861
|
-
const ssResult = (0,
|
|
8528
|
+
const ssResult = (0, import_node_child_process17.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
7862
8529
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
7863
8530
|
return parseSsPortListeners(ssResult.stdout, port);
|
|
7864
8531
|
}
|
|
@@ -7887,11 +8554,11 @@ function findPortListeners(port) {
|
|
|
7887
8554
|
};
|
|
7888
8555
|
});
|
|
7889
8556
|
}
|
|
7890
|
-
var
|
|
8557
|
+
var import_node_child_process17;
|
|
7891
8558
|
var init_ports = __esm({
|
|
7892
8559
|
"src/util/ports.ts"() {
|
|
7893
8560
|
"use strict";
|
|
7894
|
-
|
|
8561
|
+
import_node_child_process17 = require("child_process");
|
|
7895
8562
|
}
|
|
7896
8563
|
});
|
|
7897
8564
|
|
|
@@ -7968,18 +8635,18 @@ function probeHttpRoute(host, options = {}) {
|
|
|
7968
8635
|
args.push("--cacert", CERT_FILE);
|
|
7969
8636
|
}
|
|
7970
8637
|
args.push(url);
|
|
7971
|
-
const result = (0,
|
|
8638
|
+
const result = (0, import_node_child_process18.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
7972
8639
|
const stdout = result.stdout?.trim() ?? "";
|
|
7973
8640
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
7974
8641
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
7975
8642
|
const details = ok ? `HTTP ${status}` : [status === void 0 ? void 0 : `HTTP ${status}`, result.stderr?.trim()].filter(Boolean).join(": ") || `curl exited with status ${result.status ?? "unknown"}`;
|
|
7976
8643
|
return { ok, status, details };
|
|
7977
8644
|
}
|
|
7978
|
-
var
|
|
8645
|
+
var import_node_child_process18;
|
|
7979
8646
|
var init_http_route_probe = __esm({
|
|
7980
8647
|
"src/core/http-route-probe.ts"() {
|
|
7981
8648
|
"use strict";
|
|
7982
|
-
|
|
8649
|
+
import_node_child_process18 = require("child_process");
|
|
7983
8650
|
init_router();
|
|
7984
8651
|
init_tls();
|
|
7985
8652
|
}
|
|
@@ -8058,11 +8725,11 @@ var init_route_publication = __esm({
|
|
|
8058
8725
|
// src/core/workspace-ensure.ts
|
|
8059
8726
|
function assertOverlay(container, repoPath) {
|
|
8060
8727
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
8061
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
8728
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path22.default.join(repoPath, ".devcontainer"))) {
|
|
8062
8729
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
8063
8730
|
}
|
|
8064
8731
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
8065
|
-
const expectedOverlay =
|
|
8732
|
+
const expectedOverlay = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
8066
8733
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
8067
8734
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
8068
8735
|
}
|
|
@@ -8339,7 +9006,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8339
9006
|
target
|
|
8340
9007
|
});
|
|
8341
9008
|
if (target.kind === "linked") {
|
|
8342
|
-
const workspaceEnv = (0,
|
|
9009
|
+
const workspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8343
9010
|
"docker",
|
|
8344
9011
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
8345
9012
|
{ encoding: "utf-8" }
|
|
@@ -8349,7 +9016,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8349
9016
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
8350
9017
|
);
|
|
8351
9018
|
}
|
|
8352
|
-
const devrouterWorkspaceEnv = (0,
|
|
9019
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8353
9020
|
"docker",
|
|
8354
9021
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
8355
9022
|
{ encoding: "utf-8" }
|
|
@@ -8360,7 +9027,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8360
9027
|
);
|
|
8361
9028
|
}
|
|
8362
9029
|
}
|
|
8363
|
-
const gitCheck = (0,
|
|
9030
|
+
const gitCheck = (0, import_node_child_process19.spawnSync)(
|
|
8364
9031
|
"docker",
|
|
8365
9032
|
[
|
|
8366
9033
|
"exec",
|
|
@@ -8414,7 +9081,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
8414
9081
|
);
|
|
8415
9082
|
}
|
|
8416
9083
|
function resolveLinkedTarget(repoPath) {
|
|
8417
|
-
const devpods = listDevpodWorkspaces();
|
|
9084
|
+
const devpods = listDevpodWorkspaces(repoPath);
|
|
8418
9085
|
const existingDevpod = selectDevpodWorkspace(devpods, repoPath);
|
|
8419
9086
|
const persisted = readPersistedWorkspace(repoPath);
|
|
8420
9087
|
const candidate = existingDevpod?.id ?? persisted ?? resolveWorktreeWorkspace(repoPath);
|
|
@@ -8438,7 +9105,7 @@ function resolveLinkedTarget(repoPath) {
|
|
|
8438
9105
|
};
|
|
8439
9106
|
}
|
|
8440
9107
|
function resolvePrimaryTarget(repoPath) {
|
|
8441
|
-
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
9108
|
+
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
8442
9109
|
return {
|
|
8443
9110
|
kind: "primary",
|
|
8444
9111
|
devpodId: existingDevpod?.id,
|
|
@@ -8447,14 +9114,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
8447
9114
|
}
|
|
8448
9115
|
function isPrimaryCheckout(repoPath) {
|
|
8449
9116
|
try {
|
|
8450
|
-
return
|
|
9117
|
+
return import_node_fs22.default.statSync(import_node_path22.default.join(repoPath, ".git")).isDirectory();
|
|
8451
9118
|
} catch {
|
|
8452
9119
|
return false;
|
|
8453
9120
|
}
|
|
8454
9121
|
}
|
|
8455
9122
|
function openUrls(urls) {
|
|
8456
9123
|
for (const url of urls) {
|
|
8457
|
-
const opened = (0,
|
|
9124
|
+
const opened = (0, import_node_child_process19.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
8458
9125
|
if (opened.status !== 0) {
|
|
8459
9126
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
8460
9127
|
`);
|
|
@@ -8497,8 +9164,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8497
9164
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
8498
9165
|
let devpodId = target.devpodId;
|
|
8499
9166
|
if (target.kind === "linked") {
|
|
8500
|
-
const overlayPath =
|
|
8501
|
-
if (!
|
|
9167
|
+
const overlayPath = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9168
|
+
if (!import_node_fs22.default.existsSync(overlayPath)) {
|
|
8502
9169
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
8503
9170
|
}
|
|
8504
9171
|
}
|
|
@@ -8565,7 +9232,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8565
9232
|
}
|
|
8566
9233
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
8567
9234
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
8568
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
9235
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path22.default.basename(repoPath)) ?? "app";
|
|
8569
9236
|
for (const [index, app] of apps.entries()) {
|
|
8570
9237
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
8571
9238
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9009,13 +9676,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9009
9676
|
}
|
|
9010
9677
|
});
|
|
9011
9678
|
}
|
|
9012
|
-
var
|
|
9679
|
+
var import_node_child_process19, import_node_fs22, import_node_path22, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
9013
9680
|
var init_workspace_ensure = __esm({
|
|
9014
9681
|
"src/core/workspace-ensure.ts"() {
|
|
9015
9682
|
"use strict";
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9683
|
+
import_node_child_process19 = require("child_process");
|
|
9684
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
9685
|
+
import_node_path22 = __toESM(require("path"));
|
|
9019
9686
|
init_devcontainer_profile();
|
|
9020
9687
|
init_devpod_environment();
|
|
9021
9688
|
init_devpod_mutation();
|
|
@@ -9095,20 +9762,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
9095
9762
|
);
|
|
9096
9763
|
}
|
|
9097
9764
|
function defaultWorktreePath(mainRepo, ws) {
|
|
9098
|
-
return
|
|
9765
|
+
return import_node_path23.default.join(mainRepo, "trees", ws);
|
|
9099
9766
|
}
|
|
9100
9767
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
9101
|
-
const ignored = (0,
|
|
9768
|
+
const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
9102
9769
|
encoding: "utf-8"
|
|
9103
9770
|
});
|
|
9104
9771
|
if (ignored.status !== 0) {
|
|
9105
9772
|
throw new Error(
|
|
9106
|
-
`Default worktree root '${
|
|
9773
|
+
`Default worktree root '${import_node_path23.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path23.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
9107
9774
|
);
|
|
9108
9775
|
}
|
|
9109
9776
|
}
|
|
9110
9777
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
9111
|
-
return
|
|
9778
|
+
return import_node_path23.default.join(import_node_path23.default.dirname(mainRepo), `${import_node_path23.default.basename(mainRepo)}-${ws}`);
|
|
9112
9779
|
}
|
|
9113
9780
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
9114
9781
|
const candidates = [
|
|
@@ -9220,13 +9887,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
9220
9887
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
9221
9888
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
9222
9889
|
}
|
|
9223
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
9890
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs23.default.existsSync(target.worktreePath)) return;
|
|
9224
9891
|
if (target.worktree.locked) {
|
|
9225
9892
|
throw new Error(
|
|
9226
9893
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
9227
9894
|
);
|
|
9228
9895
|
}
|
|
9229
|
-
const status = (0,
|
|
9896
|
+
const status = (0, import_node_child_process20.spawnSync)(
|
|
9230
9897
|
"git",
|
|
9231
9898
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
9232
9899
|
{ encoding: "utf-8" }
|
|
@@ -9247,8 +9914,8 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9247
9914
|
if (!ws) {
|
|
9248
9915
|
throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
|
|
9249
9916
|
}
|
|
9250
|
-
const worktreePath = opts.path ?
|
|
9251
|
-
if (
|
|
9917
|
+
const worktreePath = opts.path ? import_node_path23.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
|
|
9918
|
+
if (import_node_fs23.default.existsSync(worktreePath)) {
|
|
9252
9919
|
const registered = listGitWorktrees(mainRepo).find(
|
|
9253
9920
|
(worktree) => sameWorkspacePath(worktree.path, worktreePath)
|
|
9254
9921
|
);
|
|
@@ -9266,11 +9933,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9266
9933
|
if (!opts.path) {
|
|
9267
9934
|
assertDefaultWorktreeRootIgnored(mainRepo);
|
|
9268
9935
|
}
|
|
9269
|
-
const add = (0,
|
|
9936
|
+
const add = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "add", worktreePath, branch], {
|
|
9270
9937
|
encoding: "utf-8"
|
|
9271
9938
|
});
|
|
9272
9939
|
if (add.status !== 0) {
|
|
9273
|
-
const addNew = (0,
|
|
9940
|
+
const addNew = (0, import_node_child_process20.spawnSync)(
|
|
9274
9941
|
"git",
|
|
9275
9942
|
["-C", mainRepo, "worktree", "add", "-b", branch, worktreePath],
|
|
9276
9943
|
{
|
|
@@ -9304,12 +9971,9 @@ function workspaceLs(repoPath) {
|
|
|
9304
9971
|
const mainRepo = resolveRepoPath(repoPath);
|
|
9305
9972
|
const worktrees = identifyGitWorktrees(mainRepo);
|
|
9306
9973
|
const records = listWorkspaceOwnership(mainRepo);
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
} catch {
|
|
9311
|
-
devpods = void 0;
|
|
9312
|
-
}
|
|
9974
|
+
const devpodsForPath = (worktreePath) => {
|
|
9975
|
+
return listDevpodWorkspacesFromSnapshots(worktreePath);
|
|
9976
|
+
};
|
|
9313
9977
|
const paths = Array.from(
|
|
9314
9978
|
/* @__PURE__ */ new Set([
|
|
9315
9979
|
...worktrees.map((worktree) => worktree.path),
|
|
@@ -9322,6 +9986,7 @@ function workspaceLs(repoPath) {
|
|
|
9322
9986
|
return paths.map((worktreePath) => {
|
|
9323
9987
|
const worktree = worktreesByPath.get(worktreePath);
|
|
9324
9988
|
const record = recordsByPath.get(worktreePath);
|
|
9989
|
+
const devpods = devpodsForPath(worktreePath);
|
|
9325
9990
|
const status = record ? inspectWorkspaceOwnership(record, worktrees, devpods) : void 0;
|
|
9326
9991
|
const matchingDevpods = devpods?.filter(
|
|
9327
9992
|
(devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
|
|
@@ -9340,7 +10005,7 @@ function workspaceLs(repoPath) {
|
|
|
9340
10005
|
});
|
|
9341
10006
|
}
|
|
9342
10007
|
function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = false) {
|
|
9343
|
-
const devpods = listDevpodWorkspaces();
|
|
10008
|
+
const devpods = listDevpodWorkspaces(resolved.worktreePath);
|
|
9344
10009
|
assertDevpodTargetSafe(resolved, worktrees, devpods);
|
|
9345
10010
|
const devpodId = resolved.record?.devpodId ?? resolved.workspace;
|
|
9346
10011
|
const mutation = action2 === "stop" ? stopOwnedDevpodWorkspace(devpodId, resolved.worktreePath) : deleteOwnedDevpodWorkspace(devpodId, resolved.worktreePath);
|
|
@@ -9375,8 +10040,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9375
10040
|
opts.quiet
|
|
9376
10041
|
);
|
|
9377
10042
|
if (removeWorktree) {
|
|
9378
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
9379
|
-
const rm = (0,
|
|
10043
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath)) {
|
|
10044
|
+
const rm = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
9380
10045
|
encoding: "utf-8"
|
|
9381
10046
|
});
|
|
9382
10047
|
if (rm.status !== 0) {
|
|
@@ -9394,7 +10059,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9394
10059
|
}
|
|
9395
10060
|
return result;
|
|
9396
10061
|
};
|
|
9397
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
10062
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
|
|
9398
10063
|
}
|
|
9399
10064
|
async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
|
|
9400
10065
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -9429,13 +10094,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
9429
10094
|
async function workspaceDown(target, opts = {}) {
|
|
9430
10095
|
return runWorkspaceLifecycle("down", target, opts);
|
|
9431
10096
|
}
|
|
9432
|
-
var
|
|
10097
|
+
var import_node_child_process20, import_node_fs23, import_node_path23;
|
|
9433
10098
|
var init_workspace_lifecycle = __esm({
|
|
9434
10099
|
"src/core/workspace-lifecycle.ts"() {
|
|
9435
10100
|
"use strict";
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
10101
|
+
import_node_child_process20 = require("child_process");
|
|
10102
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
10103
|
+
import_node_path23 = __toESM(require("path"));
|
|
9439
10104
|
init_devpod_mutation();
|
|
9440
10105
|
init_devpod_workspaces();
|
|
9441
10106
|
init_repo_config();
|
|
@@ -9485,7 +10150,7 @@ async function environmentStop(repoPath, options = {}) {
|
|
|
9485
10150
|
}
|
|
9486
10151
|
}
|
|
9487
10152
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9488
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10153
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9489
10154
|
const mutation = devpod ? options.delete ? deleteOwnedDevpodWorkspace(devpod.id, repoPath) : stopOwnedDevpodWorkspace(devpod.id, repoPath) : { status: "absent" };
|
|
9490
10155
|
const removedRoutes = removeHostRoutesWhere(
|
|
9491
10156
|
(route) => sameWorkspacePath(route.repoPath, repoPath)
|
|
@@ -9546,6 +10211,39 @@ var init_stop = __esm({
|
|
|
9546
10211
|
}
|
|
9547
10212
|
});
|
|
9548
10213
|
|
|
10214
|
+
// src/core/devsy-exec.ts
|
|
10215
|
+
function ensureGuidance(repoPath) {
|
|
10216
|
+
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
10217
|
+
}
|
|
10218
|
+
async function devsyExec(repoPath, command) {
|
|
10219
|
+
if (command.length === 0) {
|
|
10220
|
+
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
10221
|
+
}
|
|
10222
|
+
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
10223
|
+
const workspace = selectDevsyWorkspace(listDevsyWorkspaces(), repoPath);
|
|
10224
|
+
if (!workspace) {
|
|
10225
|
+
throw new Error(
|
|
10226
|
+
`No exact Devsy workspace exists for '${repoPath}'. ${ensureGuidance(repoPath)}`
|
|
10227
|
+
);
|
|
10228
|
+
}
|
|
10229
|
+
const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
|
|
10230
|
+
return new Promise((resolve, reject) => {
|
|
10231
|
+
const child = (0, import_node_child_process21.spawn)("devsy", args, { stdio: "inherit" });
|
|
10232
|
+
child.once("error", (error) => reject(new Error(`devsy exec failed: ${error.message}`)));
|
|
10233
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
10234
|
+
});
|
|
10235
|
+
});
|
|
10236
|
+
}
|
|
10237
|
+
var import_node_child_process21;
|
|
10238
|
+
var init_devsy_exec = __esm({
|
|
10239
|
+
"src/core/devsy-exec.ts"() {
|
|
10240
|
+
"use strict";
|
|
10241
|
+
import_node_child_process21 = require("child_process");
|
|
10242
|
+
init_devsy_workspaces();
|
|
10243
|
+
init_workspace();
|
|
10244
|
+
}
|
|
10245
|
+
});
|
|
10246
|
+
|
|
9549
10247
|
// src/core/devpod-exec.ts
|
|
9550
10248
|
function stripDevpodCompletionDiagnostic(value) {
|
|
9551
10249
|
const index = value.indexOf(DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC);
|
|
@@ -9558,27 +10256,27 @@ function stripDevpodCompletionDiagnostic(value) {
|
|
|
9558
10256
|
function quotePosixArg(value) {
|
|
9559
10257
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
9560
10258
|
}
|
|
9561
|
-
function
|
|
10259
|
+
function ensureGuidance2(repoPath) {
|
|
9562
10260
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
9563
10261
|
}
|
|
9564
10262
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
9565
|
-
const result = (0,
|
|
10263
|
+
const result = (0, import_node_child_process22.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
9566
10264
|
encoding: "utf-8"
|
|
9567
10265
|
});
|
|
9568
10266
|
if (result.status !== 0) {
|
|
9569
10267
|
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9570
10268
|
throw new Error(
|
|
9571
|
-
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${
|
|
10269
|
+
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${ensureGuidance2(repoPath)}`
|
|
9572
10270
|
);
|
|
9573
10271
|
}
|
|
9574
10272
|
let status;
|
|
9575
10273
|
try {
|
|
9576
10274
|
status = JSON.parse(result.stdout);
|
|
9577
10275
|
} catch {
|
|
9578
|
-
throw new Error(`DevPod '${devpodId}' returned invalid status. ${
|
|
10276
|
+
throw new Error(`DevPod '${devpodId}' returned invalid status. ${ensureGuidance2(repoPath)}`);
|
|
9579
10277
|
}
|
|
9580
10278
|
if (status.id !== devpodId || status.state !== "Running") {
|
|
9581
|
-
throw new Error(`DevPod '${devpodId}' is not running. ${
|
|
10279
|
+
throw new Error(`DevPod '${devpodId}' is not running. ${ensureGuidance2(repoPath)}`);
|
|
9582
10280
|
}
|
|
9583
10281
|
}
|
|
9584
10282
|
function resolveWorkspaceDirectory(repoPath) {
|
|
@@ -9586,17 +10284,20 @@ function resolveWorkspaceDirectory(repoPath) {
|
|
|
9586
10284
|
return resolveRunningWorkspaceContainer(repoPath).workspacePath;
|
|
9587
10285
|
} catch (error) {
|
|
9588
10286
|
const message = error instanceof Error ? error.message : String(error);
|
|
9589
|
-
throw new Error(`${message} ${
|
|
10287
|
+
throw new Error(`${message} ${ensureGuidance2(repoPath)}`);
|
|
9590
10288
|
}
|
|
9591
10289
|
}
|
|
9592
10290
|
async function devpodExec(repoPath, command) {
|
|
10291
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
10292
|
+
return devsyExec(repoPath, command);
|
|
10293
|
+
}
|
|
9593
10294
|
if (command.length === 0) {
|
|
9594
10295
|
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
9595
10296
|
}
|
|
9596
10297
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9597
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10298
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9598
10299
|
if (!devpod) {
|
|
9599
|
-
throw new Error(`No exact DevPod exists for '${repoPath}'. ${
|
|
10300
|
+
throw new Error(`No exact DevPod exists for '${repoPath}'. ${ensureGuidance2(repoPath)}`);
|
|
9600
10301
|
}
|
|
9601
10302
|
assertRunningDevpod(devpod.id, repoPath);
|
|
9602
10303
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
@@ -9618,7 +10319,7 @@ async function devpodExec(repoPath, command) {
|
|
|
9618
10319
|
wrappedCommand
|
|
9619
10320
|
];
|
|
9620
10321
|
return new Promise((resolve, reject) => {
|
|
9621
|
-
const child = (0,
|
|
10322
|
+
const child = (0, import_node_child_process22.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
9622
10323
|
let pending = Buffer.alloc(0);
|
|
9623
10324
|
let statusBytes = Buffer.alloc(0);
|
|
9624
10325
|
let readingStatus = false;
|
|
@@ -9677,15 +10378,17 @@ async function devpodExec(repoPath, command) {
|
|
|
9677
10378
|
});
|
|
9678
10379
|
});
|
|
9679
10380
|
}
|
|
9680
|
-
var
|
|
10381
|
+
var import_node_child_process22, import_node_crypto7, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
9681
10382
|
var init_devpod_exec = __esm({
|
|
9682
10383
|
"src/core/devpod-exec.ts"() {
|
|
9683
10384
|
"use strict";
|
|
9684
|
-
|
|
10385
|
+
import_node_child_process22 = require("child_process");
|
|
9685
10386
|
import_node_crypto7 = require("crypto");
|
|
9686
10387
|
init_devpod_environment();
|
|
9687
10388
|
init_devpod_workspaces();
|
|
10389
|
+
init_devsy_exec();
|
|
9688
10390
|
init_workspace();
|
|
10391
|
+
init_workspace_runtime();
|
|
9689
10392
|
DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC = Buffer.from(
|
|
9690
10393
|
"Error tunneling to container: wait: remote command exited without exit status or exit signal\n"
|
|
9691
10394
|
);
|
|
@@ -9866,7 +10569,7 @@ async function runOpenCommand(name) {
|
|
|
9866
10569
|
);
|
|
9867
10570
|
return;
|
|
9868
10571
|
}
|
|
9869
|
-
const result = (0,
|
|
10572
|
+
const result = (0, import_node_child_process23.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
9870
10573
|
if (result.status !== 0) {
|
|
9871
10574
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9872
10575
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -9874,11 +10577,11 @@ async function runOpenCommand(name) {
|
|
|
9874
10577
|
process.stdout.write(`Opened ${url}
|
|
9875
10578
|
`);
|
|
9876
10579
|
}
|
|
9877
|
-
var
|
|
10580
|
+
var import_node_child_process23;
|
|
9878
10581
|
var init_open = __esm({
|
|
9879
10582
|
"src/commands/open.ts"() {
|
|
9880
10583
|
"use strict";
|
|
9881
|
-
|
|
10584
|
+
import_node_child_process23 = require("child_process");
|
|
9882
10585
|
init_docker();
|
|
9883
10586
|
init_host_routes();
|
|
9884
10587
|
init_repo_config();
|
|
@@ -9901,7 +10604,7 @@ async function runLogsCommand(options) {
|
|
|
9901
10604
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
9902
10605
|
if (options.follow) {
|
|
9903
10606
|
args.splice(1, 0, "-f");
|
|
9904
|
-
const child = (0,
|
|
10607
|
+
const child = (0, import_node_child_process24.spawn)("docker", args, { stdio: "inherit" });
|
|
9905
10608
|
const onSignal = () => {
|
|
9906
10609
|
child.kill("SIGTERM");
|
|
9907
10610
|
};
|
|
@@ -9915,17 +10618,17 @@ async function runLogsCommand(options) {
|
|
|
9915
10618
|
});
|
|
9916
10619
|
});
|
|
9917
10620
|
} else {
|
|
9918
|
-
const result = (0,
|
|
10621
|
+
const result = (0, import_node_child_process24.spawnSync)("docker", args, { stdio: "inherit" });
|
|
9919
10622
|
if (result.status !== 0) {
|
|
9920
10623
|
throw new Error("Failed to retrieve router logs.");
|
|
9921
10624
|
}
|
|
9922
10625
|
}
|
|
9923
10626
|
}
|
|
9924
|
-
var
|
|
10627
|
+
var import_node_child_process24;
|
|
9925
10628
|
var init_logs = __esm({
|
|
9926
10629
|
"src/commands/logs.ts"() {
|
|
9927
10630
|
"use strict";
|
|
9928
|
-
|
|
10631
|
+
import_node_child_process24 = require("child_process");
|
|
9929
10632
|
init_docker();
|
|
9930
10633
|
init_router();
|
|
9931
10634
|
}
|
|
@@ -9965,17 +10668,17 @@ function asRecord3(value) {
|
|
|
9965
10668
|
return value;
|
|
9966
10669
|
}
|
|
9967
10670
|
function readJson(filePath) {
|
|
9968
|
-
if (!
|
|
10671
|
+
if (!import_node_fs24.default.existsSync(filePath)) {
|
|
9969
10672
|
return void 0;
|
|
9970
10673
|
}
|
|
9971
10674
|
try {
|
|
9972
|
-
return JSON.parse(
|
|
10675
|
+
return JSON.parse(import_node_fs24.default.readFileSync(filePath, "utf-8"));
|
|
9973
10676
|
} catch {
|
|
9974
10677
|
return void 0;
|
|
9975
10678
|
}
|
|
9976
10679
|
}
|
|
9977
10680
|
function relative(repoPath, filePath) {
|
|
9978
|
-
return
|
|
10681
|
+
return import_node_path24.default.relative(repoPath, filePath) || ".";
|
|
9979
10682
|
}
|
|
9980
10683
|
function redactEnvAssignments(value) {
|
|
9981
10684
|
return value.replace(
|
|
@@ -10016,7 +10719,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
10016
10719
|
["bun.lock", "bun"]
|
|
10017
10720
|
];
|
|
10018
10721
|
for (const [fileName, name] of lockfiles) {
|
|
10019
|
-
if (
|
|
10722
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10020
10723
|
return { name, source: fileName };
|
|
10021
10724
|
}
|
|
10022
10725
|
}
|
|
@@ -10031,9 +10734,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
10031
10734
|
if (typeof engines?.node === "string") {
|
|
10032
10735
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
10033
10736
|
}
|
|
10034
|
-
const nvmrc =
|
|
10035
|
-
if (
|
|
10036
|
-
const version =
|
|
10737
|
+
const nvmrc = import_node_path24.default.join(repoPath, ".nvmrc");
|
|
10738
|
+
if (import_node_fs24.default.existsSync(nvmrc)) {
|
|
10739
|
+
const version = import_node_fs24.default.readFileSync(nvmrc, "utf-8").trim();
|
|
10037
10740
|
return { version, source: ".nvmrc" };
|
|
10038
10741
|
}
|
|
10039
10742
|
return void 0;
|
|
@@ -10094,7 +10797,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
10094
10797
|
const files = config.apps.filter(
|
|
10095
10798
|
(app) => app.runtime === "docker"
|
|
10096
10799
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
10097
|
-
(fileName) => !
|
|
10800
|
+
(fileName) => !import_node_path24.default.isAbsolute(fileName) && !import_node_path24.default.normalize(fileName).startsWith("..")
|
|
10098
10801
|
);
|
|
10099
10802
|
return Array.from(new Set(files));
|
|
10100
10803
|
} catch {
|
|
@@ -10112,7 +10815,7 @@ function composeFiles(repoPath) {
|
|
|
10112
10815
|
...configuredComposeFiles(repoPath)
|
|
10113
10816
|
];
|
|
10114
10817
|
return Array.from(new Set(candidates)).filter(
|
|
10115
|
-
(fileName) =>
|
|
10818
|
+
(fileName) => import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))
|
|
10116
10819
|
);
|
|
10117
10820
|
}
|
|
10118
10821
|
function stringArray2(value) {
|
|
@@ -10150,7 +10853,7 @@ function inspectServices(repoPath) {
|
|
|
10150
10853
|
const services = [];
|
|
10151
10854
|
for (const fileName of composeFiles(repoPath)) {
|
|
10152
10855
|
try {
|
|
10153
|
-
const parsed = import_yaml6.default.parse(
|
|
10856
|
+
const parsed = import_yaml6.default.parse(import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8"));
|
|
10154
10857
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
10155
10858
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
10156
10859
|
const service = asRecord3(value);
|
|
@@ -10185,8 +10888,8 @@ function inspectServices(repoPath) {
|
|
|
10185
10888
|
return services;
|
|
10186
10889
|
}
|
|
10187
10890
|
function inspectEnvFiles(repoPath) {
|
|
10188
|
-
const files =
|
|
10189
|
-
const content =
|
|
10891
|
+
const files = import_node_fs24.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
10892
|
+
const content = import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8");
|
|
10190
10893
|
const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
|
|
10191
10894
|
return { path: fileName, names };
|
|
10192
10895
|
});
|
|
@@ -10200,18 +10903,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
10200
10903
|
};
|
|
10201
10904
|
}
|
|
10202
10905
|
function inspectDevcontainer(repoPath) {
|
|
10203
|
-
const dir =
|
|
10204
|
-
if (!
|
|
10906
|
+
const dir = import_node_path24.default.join(repoPath, ".devcontainer");
|
|
10907
|
+
if (!import_node_fs24.default.existsSync(dir)) {
|
|
10205
10908
|
return { exists: false, files: [] };
|
|
10206
10909
|
}
|
|
10207
10910
|
return {
|
|
10208
10911
|
exists: true,
|
|
10209
|
-
files:
|
|
10912
|
+
files: import_node_fs24.default.readdirSync(dir).filter((fileName) => import_node_fs24.default.statSync(import_node_path24.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
10210
10913
|
};
|
|
10211
10914
|
}
|
|
10212
10915
|
function inspectDevrouter(repoPath) {
|
|
10213
10916
|
const configPath = getRepoConfigPath(repoPath);
|
|
10214
|
-
if (!
|
|
10917
|
+
if (!import_node_fs24.default.existsSync(configPath)) {
|
|
10215
10918
|
return {
|
|
10216
10919
|
exists: false,
|
|
10217
10920
|
configPath,
|
|
@@ -10255,15 +10958,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
10255
10958
|
["AGENTS.md", "agents"],
|
|
10256
10959
|
["CLAUDE.md", "claude"]
|
|
10257
10960
|
]) {
|
|
10258
|
-
if (
|
|
10961
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10259
10962
|
results.push({ path: fileName, kind });
|
|
10260
10963
|
}
|
|
10261
10964
|
}
|
|
10262
|
-
const skillsDir =
|
|
10263
|
-
if (
|
|
10264
|
-
for (const name of
|
|
10265
|
-
const skillPath =
|
|
10266
|
-
if (
|
|
10965
|
+
const skillsDir = import_node_path24.default.join(repoPath, ".agents", "skills");
|
|
10966
|
+
if (import_node_fs24.default.existsSync(skillsDir)) {
|
|
10967
|
+
for (const name of import_node_fs24.default.readdirSync(skillsDir).sort()) {
|
|
10968
|
+
const skillPath = import_node_path24.default.join(skillsDir, name, "SKILL.md");
|
|
10969
|
+
if (import_node_fs24.default.existsSync(skillPath)) {
|
|
10267
10970
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
10268
10971
|
}
|
|
10269
10972
|
}
|
|
@@ -10306,7 +11009,7 @@ function buildIssues(report) {
|
|
|
10306
11009
|
}
|
|
10307
11010
|
function inspectRepo(options = {}) {
|
|
10308
11011
|
const repoPath = resolveRepoPath(options.repo);
|
|
10309
|
-
const pkg = readJson(
|
|
11012
|
+
const pkg = readJson(import_node_path24.default.join(repoPath, "package.json"));
|
|
10310
11013
|
const scripts = inspectScripts(pkg);
|
|
10311
11014
|
const reportWithoutIssues = {
|
|
10312
11015
|
repoPath,
|
|
@@ -10325,12 +11028,12 @@ function inspectRepo(options = {}) {
|
|
|
10325
11028
|
issues: buildIssues(reportWithoutIssues)
|
|
10326
11029
|
};
|
|
10327
11030
|
}
|
|
10328
|
-
var
|
|
11031
|
+
var import_node_fs24, import_node_path24, import_yaml6;
|
|
10329
11032
|
var init_repo_inspect = __esm({
|
|
10330
11033
|
"src/core/repo-inspect.ts"() {
|
|
10331
11034
|
"use strict";
|
|
10332
|
-
|
|
10333
|
-
|
|
11035
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
11036
|
+
import_node_path24 = __toESM(require("path"));
|
|
10334
11037
|
import_yaml6 = __toESM(require("yaml"));
|
|
10335
11038
|
init_repo_config();
|
|
10336
11039
|
}
|
|
@@ -10435,7 +11138,7 @@ function requiredFileChecks(repoPath) {
|
|
|
10435
11138
|
".devcontainer/docker-compose.yml",
|
|
10436
11139
|
".devrouter.yml"
|
|
10437
11140
|
];
|
|
10438
|
-
const missing = required.filter((fileName) => !
|
|
11141
|
+
const missing = required.filter((fileName) => !import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName)));
|
|
10439
11142
|
return {
|
|
10440
11143
|
id: "repo.devcontainer.verify-files",
|
|
10441
11144
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -10654,12 +11357,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
10654
11357
|
nextSteps: collectNextSteps3(checks)
|
|
10655
11358
|
};
|
|
10656
11359
|
}
|
|
10657
|
-
var
|
|
11360
|
+
var import_node_fs25, import_node_path25;
|
|
10658
11361
|
var init_devcontainer_verify = __esm({
|
|
10659
11362
|
"src/core/devcontainer-verify.ts"() {
|
|
10660
11363
|
"use strict";
|
|
10661
|
-
|
|
10662
|
-
|
|
11364
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
11365
|
+
import_node_path25 = __toESM(require("path"));
|
|
10663
11366
|
init_capabilities();
|
|
10664
11367
|
init_doctor();
|
|
10665
11368
|
init_host_routes();
|
|
@@ -10955,7 +11658,7 @@ function packageManagerIssues(repo) {
|
|
|
10955
11658
|
}
|
|
10956
11659
|
function plannedFiles(repoPath, version) {
|
|
10957
11660
|
const repo = inspectRepo({ repo: repoPath });
|
|
10958
|
-
const projectName = sanitizeProjectName(
|
|
11661
|
+
const projectName = sanitizeProjectName(import_node_path26.default.basename(repo.repoPath));
|
|
10959
11662
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
10960
11663
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
10961
11664
|
const port = inferPort2(repo);
|
|
@@ -11000,8 +11703,8 @@ function plannedFiles(repoPath, version) {
|
|
|
11000
11703
|
};
|
|
11001
11704
|
}
|
|
11002
11705
|
function classifyFile(repoPath, file) {
|
|
11003
|
-
const absolutePath =
|
|
11004
|
-
if (!
|
|
11706
|
+
const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
|
|
11707
|
+
if (!import_node_fs26.default.existsSync(absolutePath)) {
|
|
11005
11708
|
return {
|
|
11006
11709
|
path: file.relativePath,
|
|
11007
11710
|
action: "create",
|
|
@@ -11009,7 +11712,7 @@ function classifyFile(repoPath, file) {
|
|
|
11009
11712
|
bytes: Buffer.byteLength(file.content)
|
|
11010
11713
|
};
|
|
11011
11714
|
}
|
|
11012
|
-
const current =
|
|
11715
|
+
const current = import_node_fs26.default.readFileSync(absolutePath, "utf-8");
|
|
11013
11716
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
11014
11717
|
return {
|
|
11015
11718
|
path: file.relativePath,
|
|
@@ -11066,11 +11769,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
11066
11769
|
};
|
|
11067
11770
|
}
|
|
11068
11771
|
function writeFile(repoPath, file) {
|
|
11069
|
-
const absolutePath =
|
|
11070
|
-
|
|
11071
|
-
|
|
11772
|
+
const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
|
|
11773
|
+
import_node_fs26.default.mkdirSync(import_node_path26.default.dirname(absolutePath), { recursive: true });
|
|
11774
|
+
import_node_fs26.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
11072
11775
|
if (file.executable) {
|
|
11073
|
-
|
|
11776
|
+
import_node_fs26.default.chmodSync(absolutePath, 493);
|
|
11074
11777
|
}
|
|
11075
11778
|
}
|
|
11076
11779
|
function writeDevcontainer(options = {}) {
|
|
@@ -11108,12 +11811,12 @@ function writeDevcontainer(options = {}) {
|
|
|
11108
11811
|
nextSteps: postWriteNextSteps(repoPath)
|
|
11109
11812
|
};
|
|
11110
11813
|
}
|
|
11111
|
-
var
|
|
11814
|
+
var import_node_fs26, import_node_path26, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
11112
11815
|
var init_devcontainer_write = __esm({
|
|
11113
11816
|
"src/core/devcontainer-write.ts"() {
|
|
11114
11817
|
"use strict";
|
|
11115
|
-
|
|
11116
|
-
|
|
11818
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11819
|
+
import_node_path26 = __toESM(require("path"));
|
|
11117
11820
|
init_repo_config();
|
|
11118
11821
|
init_repo_inspect();
|
|
11119
11822
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -11504,7 +12207,7 @@ function sanitizeRouterId(value) {
|
|
|
11504
12207
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
11505
12208
|
}
|
|
11506
12209
|
function repoHash(repoPath) {
|
|
11507
|
-
return (0, import_node_crypto8.createHash)("sha1").update(
|
|
12210
|
+
return (0, import_node_crypto8.createHash)("sha1").update(import_node_path27.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
11508
12211
|
}
|
|
11509
12212
|
function asDockerApp(app) {
|
|
11510
12213
|
return app.runtime === "docker";
|
|
@@ -11583,11 +12286,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
11583
12286
|
if (dockerApps.length === 0) {
|
|
11584
12287
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
11585
12288
|
}
|
|
11586
|
-
const cachePath =
|
|
11587
|
-
|
|
11588
|
-
const overlayPath =
|
|
12289
|
+
const cachePath = import_node_path27.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
12290
|
+
import_node_fs27.default.mkdirSync(cachePath, { recursive: true });
|
|
12291
|
+
const overlayPath = import_node_path27.default.join(cachePath, "compose.devrouter.yml");
|
|
11589
12292
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
11590
|
-
|
|
12293
|
+
import_node_fs27.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
11591
12294
|
return {
|
|
11592
12295
|
overlayPath,
|
|
11593
12296
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -11601,7 +12304,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11601
12304
|
fileArgs.push("-f", resolved);
|
|
11602
12305
|
}
|
|
11603
12306
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
11604
|
-
const result = (0,
|
|
12307
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11605
12308
|
encoding: "utf-8",
|
|
11606
12309
|
cwd: repoPath
|
|
11607
12310
|
});
|
|
@@ -11629,7 +12332,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
11629
12332
|
"--services",
|
|
11630
12333
|
...services
|
|
11631
12334
|
];
|
|
11632
|
-
const result = (0,
|
|
12335
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11633
12336
|
encoding: "utf-8",
|
|
11634
12337
|
cwd: repoPath
|
|
11635
12338
|
});
|
|
@@ -11655,7 +12358,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11655
12358
|
fileArgs.push("-f", resolved);
|
|
11656
12359
|
}
|
|
11657
12360
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
11658
|
-
const result = (0,
|
|
12361
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11659
12362
|
encoding: "utf-8",
|
|
11660
12363
|
cwd: repoPath
|
|
11661
12364
|
});
|
|
@@ -11681,7 +12384,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
11681
12384
|
String(tail),
|
|
11682
12385
|
...services
|
|
11683
12386
|
];
|
|
11684
|
-
(0,
|
|
12387
|
+
(0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11685
12388
|
stdio: "inherit",
|
|
11686
12389
|
cwd: repoPath
|
|
11687
12390
|
});
|
|
@@ -11693,7 +12396,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11693
12396
|
fileArgs.push("-f", resolved);
|
|
11694
12397
|
}
|
|
11695
12398
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
11696
|
-
const result = (0,
|
|
12399
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11697
12400
|
encoding: "utf-8",
|
|
11698
12401
|
cwd: repoPath
|
|
11699
12402
|
});
|
|
@@ -11707,14 +12410,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11707
12410
|
const port = Number(match[1]);
|
|
11708
12411
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
11709
12412
|
}
|
|
11710
|
-
var
|
|
12413
|
+
var import_node_child_process25, import_node_crypto8, import_node_fs27, import_node_path27, import_yaml7;
|
|
11711
12414
|
var init_docker_run = __esm({
|
|
11712
12415
|
"src/core/docker-run.ts"() {
|
|
11713
12416
|
"use strict";
|
|
11714
|
-
|
|
12417
|
+
import_node_child_process25 = require("child_process");
|
|
11715
12418
|
import_node_crypto8 = require("crypto");
|
|
11716
|
-
|
|
11717
|
-
|
|
12419
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
12420
|
+
import_node_path27 = __toESM(require("path"));
|
|
11718
12421
|
import_yaml7 = __toESM(require("yaml"));
|
|
11719
12422
|
init_docker_error_guidance();
|
|
11720
12423
|
init_paths();
|
|
@@ -11804,7 +12507,7 @@ function isProcessRunning(pid) {
|
|
|
11804
12507
|
}
|
|
11805
12508
|
}
|
|
11806
12509
|
function readProcessTree(rootPid) {
|
|
11807
|
-
const result = (0,
|
|
12510
|
+
const result = (0, import_node_child_process26.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
11808
12511
|
if (result.status !== 0) {
|
|
11809
12512
|
return [rootPid];
|
|
11810
12513
|
}
|
|
@@ -11915,12 +12618,12 @@ function detectListeningPorts(pids) {
|
|
|
11915
12618
|
if (pids.length === 0) {
|
|
11916
12619
|
return [];
|
|
11917
12620
|
}
|
|
11918
|
-
const result = (0,
|
|
12621
|
+
const result = (0, import_node_child_process26.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
11919
12622
|
encoding: "utf-8"
|
|
11920
12623
|
});
|
|
11921
12624
|
if (result.error && result.error.code === "ENOENT") {
|
|
11922
12625
|
if (process.platform === "linux") {
|
|
11923
|
-
const ssResult = (0,
|
|
12626
|
+
const ssResult = (0, import_node_child_process26.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
11924
12627
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
11925
12628
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
11926
12629
|
}
|
|
@@ -11979,7 +12682,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
11979
12682
|
app.hostRun.command,
|
|
11980
12683
|
true
|
|
11981
12684
|
) : app.hostRun.command;
|
|
11982
|
-
const child = (0,
|
|
12685
|
+
const child = (0, import_node_child_process26.spawn)(spawnCommand, {
|
|
11983
12686
|
cwd: commandCwd,
|
|
11984
12687
|
stdio: "inherit",
|
|
11985
12688
|
shell: true,
|
|
@@ -12347,7 +13050,7 @@ async function execWithAppEnv(options) {
|
|
|
12347
13050
|
options.command[0],
|
|
12348
13051
|
true
|
|
12349
13052
|
);
|
|
12350
|
-
child = (0,
|
|
13053
|
+
child = (0, import_node_child_process26.spawn)(wrapped, {
|
|
12351
13054
|
cwd: deps.repoPath,
|
|
12352
13055
|
stdio: "inherit",
|
|
12353
13056
|
shell: true,
|
|
@@ -12361,7 +13064,7 @@ async function execWithAppEnv(options) {
|
|
|
12361
13064
|
false
|
|
12362
13065
|
);
|
|
12363
13066
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
12364
|
-
child = (0,
|
|
13067
|
+
child = (0, import_node_child_process26.spawn)(cmd, wrappedArgs, {
|
|
12365
13068
|
cwd: deps.repoPath,
|
|
12366
13069
|
stdio: "inherit",
|
|
12367
13070
|
shell: false,
|
|
@@ -12369,7 +13072,7 @@ async function execWithAppEnv(options) {
|
|
|
12369
13072
|
});
|
|
12370
13073
|
}
|
|
12371
13074
|
} else if (options.shell) {
|
|
12372
|
-
child = (0,
|
|
13075
|
+
child = (0, import_node_child_process26.spawn)(options.command[0], {
|
|
12373
13076
|
cwd: deps.repoPath,
|
|
12374
13077
|
stdio: "inherit",
|
|
12375
13078
|
shell: true,
|
|
@@ -12377,7 +13080,7 @@ async function execWithAppEnv(options) {
|
|
|
12377
13080
|
});
|
|
12378
13081
|
} else {
|
|
12379
13082
|
const [command, ...args] = options.command;
|
|
12380
|
-
child = (0,
|
|
13083
|
+
child = (0, import_node_child_process26.spawn)(command, args, {
|
|
12381
13084
|
cwd: deps.repoPath,
|
|
12382
13085
|
stdio: "inherit",
|
|
12383
13086
|
shell: false,
|
|
@@ -12398,11 +13101,11 @@ async function execWithAppEnv(options) {
|
|
|
12398
13101
|
deps.stopDeps();
|
|
12399
13102
|
}
|
|
12400
13103
|
}
|
|
12401
|
-
var
|
|
13104
|
+
var import_node_child_process26, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
|
|
12402
13105
|
var init_app_run = __esm({
|
|
12403
13106
|
"src/core/app-run.ts"() {
|
|
12404
13107
|
"use strict";
|
|
12405
|
-
|
|
13108
|
+
import_node_child_process26 = require("child_process");
|
|
12406
13109
|
import_node_net = __toESM(require("net"));
|
|
12407
13110
|
import_node_process = require("process");
|
|
12408
13111
|
import_promises = require("readline/promises");
|
|
@@ -12536,13 +13239,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12536
13239
|
const startedAt = Date.now();
|
|
12537
13240
|
let rootStat;
|
|
12538
13241
|
try {
|
|
12539
|
-
rootStat =
|
|
13242
|
+
rootStat = import_node_fs28.default.lstatSync(worktreePath);
|
|
12540
13243
|
} catch (error) {
|
|
12541
13244
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12542
13245
|
}
|
|
12543
13246
|
let rootEntries;
|
|
12544
13247
|
try {
|
|
12545
|
-
rootEntries =
|
|
13248
|
+
rootEntries = import_node_fs28.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
12546
13249
|
} catch (error) {
|
|
12547
13250
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12548
13251
|
}
|
|
@@ -12568,10 +13271,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12568
13271
|
timedOut = true;
|
|
12569
13272
|
break;
|
|
12570
13273
|
}
|
|
12571
|
-
const entryPath =
|
|
13274
|
+
const entryPath = import_node_path28.default.join(dirPath, entry.name);
|
|
12572
13275
|
let entryStat;
|
|
12573
13276
|
try {
|
|
12574
|
-
entryStat =
|
|
13277
|
+
entryStat = import_node_fs28.default.lstatSync(entryPath);
|
|
12575
13278
|
} catch (error) {
|
|
12576
13279
|
if (error?.code === "ENOENT") continue;
|
|
12577
13280
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -12581,7 +13284,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12581
13284
|
if (!entryStat.isDirectory()) continue;
|
|
12582
13285
|
let childEntries;
|
|
12583
13286
|
try {
|
|
12584
|
-
childEntries =
|
|
13287
|
+
childEntries = import_node_fs28.default.readdirSync(entryPath, { withFileTypes: true });
|
|
12585
13288
|
} catch (error) {
|
|
12586
13289
|
unreadableReason = describeIncompleteWalk(error);
|
|
12587
13290
|
break;
|
|
@@ -12645,12 +13348,12 @@ function describeError(error, worktreePath) {
|
|
|
12645
13348
|
}
|
|
12646
13349
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
12647
13350
|
}
|
|
12648
|
-
var
|
|
13351
|
+
var import_node_fs28, import_node_path28, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
12649
13352
|
var init_workspace_consumption = __esm({
|
|
12650
13353
|
"src/core/workspace-consumption.ts"() {
|
|
12651
13354
|
"use strict";
|
|
12652
|
-
|
|
12653
|
-
|
|
13355
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
13356
|
+
import_node_path28 = __toESM(require("path"));
|
|
12654
13357
|
init_devpod_environment();
|
|
12655
13358
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
12656
13359
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -12751,7 +13454,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
12751
13454
|
}
|
|
12752
13455
|
function readGitSnapshot(worktree, commandRunner) {
|
|
12753
13456
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
12754
|
-
if (worktree.prunable || !
|
|
13457
|
+
if (worktree.prunable || !import_node_fs29.default.existsSync(comparablePath)) {
|
|
12755
13458
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
12756
13459
|
}
|
|
12757
13460
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -13260,12 +13963,6 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13260
13963
|
const cutoff = new Date(now.getTime() - duration.seconds * 1e3).toISOString();
|
|
13261
13964
|
const worktrees = (dependencies.listWorktrees ?? listGitWorktrees)(repoPath);
|
|
13262
13965
|
const records = (dependencies.listOwnership ?? listWorkspaceOwnership)(repoPath);
|
|
13263
|
-
let devpods;
|
|
13264
|
-
try {
|
|
13265
|
-
devpods = (dependencies.listDevpods ?? listDevpodWorkspaces)();
|
|
13266
|
-
} catch {
|
|
13267
|
-
devpods = void 0;
|
|
13268
|
-
}
|
|
13269
13966
|
let routes;
|
|
13270
13967
|
try {
|
|
13271
13968
|
routes = (dependencies.readRoutes ?? readHostRouteStateReadOnly)();
|
|
@@ -13282,7 +13979,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13282
13979
|
containers = measureContainersFn(worktreePaths);
|
|
13283
13980
|
} catch (error) {
|
|
13284
13981
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
13285
|
-
containers = new Map(worktreePaths.map((
|
|
13982
|
+
containers = new Map(worktreePaths.map((path28) => [path28, unknownContainers(reason)]));
|
|
13286
13983
|
}
|
|
13287
13984
|
}
|
|
13288
13985
|
const rows = records.map((record) => {
|
|
@@ -13297,6 +13994,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13297
13994
|
prunable: true
|
|
13298
13995
|
}
|
|
13299
13996
|
);
|
|
13997
|
+
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record.worktreePath);
|
|
13300
13998
|
const row = buildRow(
|
|
13301
13999
|
repoPath,
|
|
13302
14000
|
record,
|
|
@@ -13312,7 +14010,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13312
14010
|
return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
|
|
13313
14011
|
}),
|
|
13314
14012
|
dependencies.inspectIntegration,
|
|
13315
|
-
dependencies.inspectDevpodRuntime ?? inspectDevpodRuntimeStatus
|
|
14013
|
+
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record.worktreePath))
|
|
13316
14014
|
);
|
|
13317
14015
|
return measureSize ? {
|
|
13318
14016
|
...row,
|
|
@@ -13330,12 +14028,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13330
14028
|
workspaces: rows
|
|
13331
14029
|
};
|
|
13332
14030
|
}
|
|
13333
|
-
var
|
|
14031
|
+
var import_node_child_process27, import_node_fs29, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
13334
14032
|
var init_workspace_cleanup = __esm({
|
|
13335
14033
|
"src/core/workspace-cleanup.ts"() {
|
|
13336
14034
|
"use strict";
|
|
13337
|
-
|
|
13338
|
-
|
|
14035
|
+
import_node_child_process27 = require("child_process");
|
|
14036
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
13339
14037
|
init_devpod_workspaces();
|
|
13340
14038
|
init_host_routes();
|
|
13341
14039
|
init_repo_config();
|
|
@@ -13352,7 +14050,7 @@ var init_workspace_cleanup = __esm({
|
|
|
13352
14050
|
];
|
|
13353
14051
|
defaultCommandRunner = (command, args, input2) => {
|
|
13354
14052
|
const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
|
|
13355
|
-
const result = (0,
|
|
14053
|
+
const result = (0, import_node_child_process27.spawnSync)(command, args, {
|
|
13356
14054
|
encoding: "utf-8",
|
|
13357
14055
|
env: environment,
|
|
13358
14056
|
...input2 === void 0 ? {} : { input: input2 }
|
|
@@ -13512,7 +14210,7 @@ var init_version = __esm({
|
|
|
13512
14210
|
|
|
13513
14211
|
// src/cli.ts
|
|
13514
14212
|
var import_commander = require("commander");
|
|
13515
|
-
var CLI_VERSION = true ? "0.0.
|
|
14213
|
+
var CLI_VERSION = true ? "0.0.41" : "0.0.0-dev";
|
|
13516
14214
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
13517
14215
|
function withErrorHandling(action2) {
|
|
13518
14216
|
return async (...args) => {
|
|
@@ -13550,11 +14248,16 @@ program.command("upgrade").description(
|
|
|
13550
14248
|
}
|
|
13551
14249
|
)
|
|
13552
14250
|
);
|
|
13553
|
-
program.command("setup").description("Run first-time devrouter machine setup and report diagnostics").option("--repo <path>", "Repository path for final diagnostics (defaults to current directory)").option("--yes", "Confirm non-interactive setup actions").option("--
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
14251
|
+
program.command("setup").description("Run first-time devrouter machine setup and report diagnostics").option("--repo <path>", "Repository path for final diagnostics (defaults to current directory)").option("--yes", "Confirm non-interactive setup actions").option("--workspace-runtime <runtime>", "Persist the machine workspace runtime (devpod|devsy)").option(
|
|
14252
|
+
"--devsy-inactivity-timeout <duration>",
|
|
14253
|
+
"Persist the Devsy inactivity shutdown duration (e.g. 30m)"
|
|
14254
|
+
).option("--json", "Output JSON").action(
|
|
14255
|
+
withErrorHandling(
|
|
14256
|
+
async (options) => {
|
|
14257
|
+
const { runSetupCommand: runSetupCommand2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports));
|
|
14258
|
+
await runSetupCommand2(options);
|
|
14259
|
+
}
|
|
14260
|
+
)
|
|
13558
14261
|
);
|
|
13559
14262
|
program.command("up").description("Ensure devnet and start shared Traefik (reserves 80/443/5432)").action(
|
|
13560
14263
|
withErrorHandling(async () => {
|
|
@@ -13574,7 +14277,7 @@ program.command("ensure").description("Start and prove a primary or linked check
|
|
|
13574
14277
|
});
|
|
13575
14278
|
})
|
|
13576
14279
|
);
|
|
13577
|
-
program.command("stop").description("Stop one checkout's exact DevPod and free its routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--json", "Output JSON").option("--delete", "Delete the exact
|
|
14280
|
+
program.command("stop").description("Stop one checkout's exact workspace runtime (DevPod/Devsy) and free its routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--json", "Output JSON").option("--delete", "Delete the exact workspace runtime instead of preserving its data").action(
|
|
13578
14281
|
withErrorHandling(async (repoPath, _options, command) => {
|
|
13579
14282
|
const options = command.opts();
|
|
13580
14283
|
const { runStopCommand: runStopCommand2 } = await Promise.resolve().then(() => (init_stop(), stop_exports));
|
|
@@ -13585,7 +14288,7 @@ program.command("stop").description("Stop one checkout's exact DevPod and free i
|
|
|
13585
14288
|
});
|
|
13586
14289
|
})
|
|
13587
14290
|
);
|
|
13588
|
-
program.command("exec").description("Run one literal command inside the exact checkout's running
|
|
14291
|
+
program.command("exec").description("Run one literal command inside the exact checkout's running workspace runtime").argument("[args...]", "[path] -- <command...>").allowUnknownOption(true).action(
|
|
13589
14292
|
withErrorHandling(async () => {
|
|
13590
14293
|
const commandIndex = process.argv.indexOf("exec", 2);
|
|
13591
14294
|
const { parseExecInvocation: parseExecInvocation2, runExecCommand: runExecCommand2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
|
|
@@ -13754,7 +14457,7 @@ tlsCommand.command("install").description("Install mkcert certs and enable HTTPS
|
|
|
13754
14457
|
);
|
|
13755
14458
|
var workspaceCommand2 = program.command("workspace").description("Spin up / list / tear down isolated worktree+devcontainer workspaces");
|
|
13756
14459
|
workspaceCommand2.command("up").description(
|
|
13757
|
-
"Create a worktree for <branch>, bring up its
|
|
14460
|
+
"Create a worktree for <branch>, bring up its workspace runtime, and register namespaced routes"
|
|
13758
14461
|
).argument("<branch>", "Git branch to base the workspace on").option("--path <dir>", "Worktree directory (default: <repo>/trees/<workspace>)").option("--no-devpod", "Only create the worktree; do not start the environment or change routes").option("--open", "Open the namespaced routes after registering").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13759
14462
|
withErrorHandling(async (branch, _options, command) => {
|
|
13760
14463
|
const options = command.opts();
|
|
@@ -13767,7 +14470,9 @@ workspaceCommand2.command("up").description(
|
|
|
13767
14470
|
});
|
|
13768
14471
|
})
|
|
13769
14472
|
);
|
|
13770
|
-
workspaceCommand2.command("ensure").description(
|
|
14473
|
+
workspaceCommand2.command("ensure").description(
|
|
14474
|
+
"Start and prove a primary or linked checkout's workspace runtime, upstreams, and routes"
|
|
14475
|
+
).argument("[path]", "Git checkout path (defaults to current directory)").option("--profile <name>", "Start only this profile's apps/dependencies from .devrouter.yml").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
13771
14476
|
withErrorHandling(
|
|
13772
14477
|
async (worktreePath, _options, command) => {
|
|
13773
14478
|
const options = command.opts();
|
|
@@ -13801,21 +14506,21 @@ workspaceCommand2.command("cleanup").description("Report managed workspace activ
|
|
|
13801
14506
|
});
|
|
13802
14507
|
})
|
|
13803
14508
|
);
|
|
13804
|
-
workspaceCommand2.command("gc").description("Report or clean missing ledger-owned workspace resources").option("--repo <path>", "Main repository path (defaults to current directory)").option("--json", "Output JSON").option("--yes", "Delete eligible
|
|
14509
|
+
workspaceCommand2.command("gc").description("Report or clean missing ledger-owned workspace resources").option("--repo <path>", "Main repository path (defaults to current directory)").option("--json", "Output JSON").option("--yes", "Delete eligible workspace runtime, route, and ownership resources").action(
|
|
13805
14510
|
withErrorHandling(async (_options, command) => {
|
|
13806
14511
|
const options = command.opts();
|
|
13807
14512
|
const { runWorkspaceGcCommand: runWorkspaceGcCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13808
14513
|
runWorkspaceGcCommand2(options);
|
|
13809
14514
|
})
|
|
13810
14515
|
);
|
|
13811
|
-
workspaceCommand2.command("stop").description("Stop a workspace's
|
|
14516
|
+
workspaceCommand2.command("stop").description("Stop a workspace's runtime and free routes while preserving its worktree and data").argument("<workspace>", "Workspace token or live branch name").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13812
14517
|
withErrorHandling(async (target, _options, command) => {
|
|
13813
14518
|
const options = command.opts();
|
|
13814
14519
|
const { runWorkspaceStopCommand: runWorkspaceStopCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13815
14520
|
await runWorkspaceStopCommand2(target, options);
|
|
13816
14521
|
})
|
|
13817
14522
|
);
|
|
13818
|
-
workspaceCommand2.command("down").description("Delete a workspace's
|
|
14523
|
+
workspaceCommand2.command("down").description("Delete a workspace's runtime and routes, then remove its clean Git worktree").argument("<workspace>", "Workspace token or live branch name").option("--keep-worktree", "Delete runtime resources but preserve the Git worktree and record").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13819
14524
|
withErrorHandling(async (target, _options, command) => {
|
|
13820
14525
|
const options = command.opts();
|
|
13821
14526
|
const { runWorkspaceDownCommand: runWorkspaceDownCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|