@devrouter/cli 0.0.39 → 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 +1023 -301
- package/package.json +1 -1
- package/upgrade-prompts/0.0.40.md +34 -0
- 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
|
}
|
|
@@ -8217,6 +8884,18 @@ function restorePreviousManagedConfig(options) {
|
|
|
8217
8884
|
}
|
|
8218
8885
|
writeManagedDevcontainerConfig(restoredPlan);
|
|
8219
8886
|
}
|
|
8887
|
+
function restoreFirstTransitionManagedConfig(options) {
|
|
8888
|
+
const restoredPlan = inspectManagedDevcontainerConfig({
|
|
8889
|
+
repoPath: options.repoPath,
|
|
8890
|
+
config: options.config,
|
|
8891
|
+
profile: {
|
|
8892
|
+
apps: [],
|
|
8893
|
+
devcontainerServices: [...options.services]
|
|
8894
|
+
},
|
|
8895
|
+
linked: options.linked
|
|
8896
|
+
});
|
|
8897
|
+
writeManagedDevcontainerConfig(restoredPlan);
|
|
8898
|
+
}
|
|
8220
8899
|
function isWarmWorkspaceActive(repoPath) {
|
|
8221
8900
|
try {
|
|
8222
8901
|
return workspaceAppContainers(inspectWorkspaceContainers(), repoPath).some(
|
|
@@ -8327,7 +9006,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8327
9006
|
target
|
|
8328
9007
|
});
|
|
8329
9008
|
if (target.kind === "linked") {
|
|
8330
|
-
const workspaceEnv = (0,
|
|
9009
|
+
const workspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8331
9010
|
"docker",
|
|
8332
9011
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
8333
9012
|
{ encoding: "utf-8" }
|
|
@@ -8337,7 +9016,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8337
9016
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
8338
9017
|
);
|
|
8339
9018
|
}
|
|
8340
|
-
const devrouterWorkspaceEnv = (0,
|
|
9019
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8341
9020
|
"docker",
|
|
8342
9021
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
8343
9022
|
{ encoding: "utf-8" }
|
|
@@ -8348,7 +9027,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8348
9027
|
);
|
|
8349
9028
|
}
|
|
8350
9029
|
}
|
|
8351
|
-
const gitCheck = (0,
|
|
9030
|
+
const gitCheck = (0, import_node_child_process19.spawnSync)(
|
|
8352
9031
|
"docker",
|
|
8353
9032
|
[
|
|
8354
9033
|
"exec",
|
|
@@ -8402,7 +9081,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
8402
9081
|
);
|
|
8403
9082
|
}
|
|
8404
9083
|
function resolveLinkedTarget(repoPath) {
|
|
8405
|
-
const devpods = listDevpodWorkspaces();
|
|
9084
|
+
const devpods = listDevpodWorkspaces(repoPath);
|
|
8406
9085
|
const existingDevpod = selectDevpodWorkspace(devpods, repoPath);
|
|
8407
9086
|
const persisted = readPersistedWorkspace(repoPath);
|
|
8408
9087
|
const candidate = existingDevpod?.id ?? persisted ?? resolveWorktreeWorkspace(repoPath);
|
|
@@ -8426,7 +9105,7 @@ function resolveLinkedTarget(repoPath) {
|
|
|
8426
9105
|
};
|
|
8427
9106
|
}
|
|
8428
9107
|
function resolvePrimaryTarget(repoPath) {
|
|
8429
|
-
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
9108
|
+
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
8430
9109
|
return {
|
|
8431
9110
|
kind: "primary",
|
|
8432
9111
|
devpodId: existingDevpod?.id,
|
|
@@ -8435,14 +9114,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
8435
9114
|
}
|
|
8436
9115
|
function isPrimaryCheckout(repoPath) {
|
|
8437
9116
|
try {
|
|
8438
|
-
return
|
|
9117
|
+
return import_node_fs22.default.statSync(import_node_path22.default.join(repoPath, ".git")).isDirectory();
|
|
8439
9118
|
} catch {
|
|
8440
9119
|
return false;
|
|
8441
9120
|
}
|
|
8442
9121
|
}
|
|
8443
9122
|
function openUrls(urls) {
|
|
8444
9123
|
for (const url of urls) {
|
|
8445
|
-
const opened = (0,
|
|
9124
|
+
const opened = (0, import_node_child_process19.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
8446
9125
|
if (opened.status !== 0) {
|
|
8447
9126
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
8448
9127
|
`);
|
|
@@ -8485,8 +9164,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8485
9164
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
8486
9165
|
let devpodId = target.devpodId;
|
|
8487
9166
|
if (target.kind === "linked") {
|
|
8488
|
-
const overlayPath =
|
|
8489
|
-
if (!
|
|
9167
|
+
const overlayPath = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9168
|
+
if (!import_node_fs22.default.existsSync(overlayPath)) {
|
|
8490
9169
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
8491
9170
|
}
|
|
8492
9171
|
}
|
|
@@ -8553,7 +9232,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8553
9232
|
}
|
|
8554
9233
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
8555
9234
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
8556
|
-
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";
|
|
8557
9236
|
for (const [index, app] of apps.entries()) {
|
|
8558
9237
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
8559
9238
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -8824,8 +9503,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8824
9503
|
linked,
|
|
8825
9504
|
previousState: previousManagedState
|
|
8826
9505
|
});
|
|
8827
|
-
} else {
|
|
8828
|
-
|
|
9506
|
+
} else if (managedRuntimeConfig) {
|
|
9507
|
+
restoreFirstTransitionManagedConfig({
|
|
9508
|
+
repoPath,
|
|
9509
|
+
config: managedRuntimeConfig,
|
|
9510
|
+
linked,
|
|
9511
|
+
services: previousServices
|
|
9512
|
+
});
|
|
8829
9513
|
}
|
|
8830
9514
|
}
|
|
8831
9515
|
} catch (rollbackError) {
|
|
@@ -8952,7 +9636,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8952
9636
|
linked,
|
|
8953
9637
|
previousState: previousManagedState
|
|
8954
9638
|
});
|
|
8955
|
-
} else {
|
|
9639
|
+
} else if (!environmentStarted) {
|
|
8956
9640
|
removeManagedDevcontainerConfig(managedPlan);
|
|
8957
9641
|
}
|
|
8958
9642
|
} catch (rollbackError) {
|
|
@@ -8992,13 +9676,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8992
9676
|
}
|
|
8993
9677
|
});
|
|
8994
9678
|
}
|
|
8995
|
-
var
|
|
9679
|
+
var import_node_child_process19, import_node_fs22, import_node_path22, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
8996
9680
|
var init_workspace_ensure = __esm({
|
|
8997
9681
|
"src/core/workspace-ensure.ts"() {
|
|
8998
9682
|
"use strict";
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9683
|
+
import_node_child_process19 = require("child_process");
|
|
9684
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
9685
|
+
import_node_path22 = __toESM(require("path"));
|
|
9002
9686
|
init_devcontainer_profile();
|
|
9003
9687
|
init_devpod_environment();
|
|
9004
9688
|
init_devpod_mutation();
|
|
@@ -9078,20 +9762,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
9078
9762
|
);
|
|
9079
9763
|
}
|
|
9080
9764
|
function defaultWorktreePath(mainRepo, ws) {
|
|
9081
|
-
return
|
|
9765
|
+
return import_node_path23.default.join(mainRepo, "trees", ws);
|
|
9082
9766
|
}
|
|
9083
9767
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
9084
|
-
const ignored = (0,
|
|
9768
|
+
const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
9085
9769
|
encoding: "utf-8"
|
|
9086
9770
|
});
|
|
9087
9771
|
if (ignored.status !== 0) {
|
|
9088
9772
|
throw new Error(
|
|
9089
|
-
`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.`
|
|
9090
9774
|
);
|
|
9091
9775
|
}
|
|
9092
9776
|
}
|
|
9093
9777
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
9094
|
-
return
|
|
9778
|
+
return import_node_path23.default.join(import_node_path23.default.dirname(mainRepo), `${import_node_path23.default.basename(mainRepo)}-${ws}`);
|
|
9095
9779
|
}
|
|
9096
9780
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
9097
9781
|
const candidates = [
|
|
@@ -9203,13 +9887,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
9203
9887
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
9204
9888
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
9205
9889
|
}
|
|
9206
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
9890
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs23.default.existsSync(target.worktreePath)) return;
|
|
9207
9891
|
if (target.worktree.locked) {
|
|
9208
9892
|
throw new Error(
|
|
9209
9893
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
9210
9894
|
);
|
|
9211
9895
|
}
|
|
9212
|
-
const status = (0,
|
|
9896
|
+
const status = (0, import_node_child_process20.spawnSync)(
|
|
9213
9897
|
"git",
|
|
9214
9898
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
9215
9899
|
{ encoding: "utf-8" }
|
|
@@ -9230,8 +9914,8 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9230
9914
|
if (!ws) {
|
|
9231
9915
|
throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
|
|
9232
9916
|
}
|
|
9233
|
-
const worktreePath = opts.path ?
|
|
9234
|
-
if (
|
|
9917
|
+
const worktreePath = opts.path ? import_node_path23.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
|
|
9918
|
+
if (import_node_fs23.default.existsSync(worktreePath)) {
|
|
9235
9919
|
const registered = listGitWorktrees(mainRepo).find(
|
|
9236
9920
|
(worktree) => sameWorkspacePath(worktree.path, worktreePath)
|
|
9237
9921
|
);
|
|
@@ -9249,11 +9933,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9249
9933
|
if (!opts.path) {
|
|
9250
9934
|
assertDefaultWorktreeRootIgnored(mainRepo);
|
|
9251
9935
|
}
|
|
9252
|
-
const add = (0,
|
|
9936
|
+
const add = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "add", worktreePath, branch], {
|
|
9253
9937
|
encoding: "utf-8"
|
|
9254
9938
|
});
|
|
9255
9939
|
if (add.status !== 0) {
|
|
9256
|
-
const addNew = (0,
|
|
9940
|
+
const addNew = (0, import_node_child_process20.spawnSync)(
|
|
9257
9941
|
"git",
|
|
9258
9942
|
["-C", mainRepo, "worktree", "add", "-b", branch, worktreePath],
|
|
9259
9943
|
{
|
|
@@ -9287,12 +9971,9 @@ function workspaceLs(repoPath) {
|
|
|
9287
9971
|
const mainRepo = resolveRepoPath(repoPath);
|
|
9288
9972
|
const worktrees = identifyGitWorktrees(mainRepo);
|
|
9289
9973
|
const records = listWorkspaceOwnership(mainRepo);
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
} catch {
|
|
9294
|
-
devpods = void 0;
|
|
9295
|
-
}
|
|
9974
|
+
const devpodsForPath = (worktreePath) => {
|
|
9975
|
+
return listDevpodWorkspacesFromSnapshots(worktreePath);
|
|
9976
|
+
};
|
|
9296
9977
|
const paths = Array.from(
|
|
9297
9978
|
/* @__PURE__ */ new Set([
|
|
9298
9979
|
...worktrees.map((worktree) => worktree.path),
|
|
@@ -9305,6 +9986,7 @@ function workspaceLs(repoPath) {
|
|
|
9305
9986
|
return paths.map((worktreePath) => {
|
|
9306
9987
|
const worktree = worktreesByPath.get(worktreePath);
|
|
9307
9988
|
const record = recordsByPath.get(worktreePath);
|
|
9989
|
+
const devpods = devpodsForPath(worktreePath);
|
|
9308
9990
|
const status = record ? inspectWorkspaceOwnership(record, worktrees, devpods) : void 0;
|
|
9309
9991
|
const matchingDevpods = devpods?.filter(
|
|
9310
9992
|
(devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
|
|
@@ -9323,7 +10005,7 @@ function workspaceLs(repoPath) {
|
|
|
9323
10005
|
});
|
|
9324
10006
|
}
|
|
9325
10007
|
function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = false) {
|
|
9326
|
-
const devpods = listDevpodWorkspaces();
|
|
10008
|
+
const devpods = listDevpodWorkspaces(resolved.worktreePath);
|
|
9327
10009
|
assertDevpodTargetSafe(resolved, worktrees, devpods);
|
|
9328
10010
|
const devpodId = resolved.record?.devpodId ?? resolved.workspace;
|
|
9329
10011
|
const mutation = action2 === "stop" ? stopOwnedDevpodWorkspace(devpodId, resolved.worktreePath) : deleteOwnedDevpodWorkspace(devpodId, resolved.worktreePath);
|
|
@@ -9358,8 +10040,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9358
10040
|
opts.quiet
|
|
9359
10041
|
);
|
|
9360
10042
|
if (removeWorktree) {
|
|
9361
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
9362
|
-
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], {
|
|
9363
10045
|
encoding: "utf-8"
|
|
9364
10046
|
});
|
|
9365
10047
|
if (rm.status !== 0) {
|
|
@@ -9377,7 +10059,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9377
10059
|
}
|
|
9378
10060
|
return result;
|
|
9379
10061
|
};
|
|
9380
|
-
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();
|
|
9381
10063
|
}
|
|
9382
10064
|
async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
|
|
9383
10065
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -9412,13 +10094,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
9412
10094
|
async function workspaceDown(target, opts = {}) {
|
|
9413
10095
|
return runWorkspaceLifecycle("down", target, opts);
|
|
9414
10096
|
}
|
|
9415
|
-
var
|
|
10097
|
+
var import_node_child_process20, import_node_fs23, import_node_path23;
|
|
9416
10098
|
var init_workspace_lifecycle = __esm({
|
|
9417
10099
|
"src/core/workspace-lifecycle.ts"() {
|
|
9418
10100
|
"use strict";
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
10101
|
+
import_node_child_process20 = require("child_process");
|
|
10102
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
10103
|
+
import_node_path23 = __toESM(require("path"));
|
|
9422
10104
|
init_devpod_mutation();
|
|
9423
10105
|
init_devpod_workspaces();
|
|
9424
10106
|
init_repo_config();
|
|
@@ -9468,7 +10150,7 @@ async function environmentStop(repoPath, options = {}) {
|
|
|
9468
10150
|
}
|
|
9469
10151
|
}
|
|
9470
10152
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9471
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10153
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9472
10154
|
const mutation = devpod ? options.delete ? deleteOwnedDevpodWorkspace(devpod.id, repoPath) : stopOwnedDevpodWorkspace(devpod.id, repoPath) : { status: "absent" };
|
|
9473
10155
|
const removedRoutes = removeHostRoutesWhere(
|
|
9474
10156
|
(route) => sameWorkspacePath(route.repoPath, repoPath)
|
|
@@ -9529,6 +10211,39 @@ var init_stop = __esm({
|
|
|
9529
10211
|
}
|
|
9530
10212
|
});
|
|
9531
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
|
+
|
|
9532
10247
|
// src/core/devpod-exec.ts
|
|
9533
10248
|
function stripDevpodCompletionDiagnostic(value) {
|
|
9534
10249
|
const index = value.indexOf(DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC);
|
|
@@ -9541,27 +10256,27 @@ function stripDevpodCompletionDiagnostic(value) {
|
|
|
9541
10256
|
function quotePosixArg(value) {
|
|
9542
10257
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
9543
10258
|
}
|
|
9544
|
-
function
|
|
10259
|
+
function ensureGuidance2(repoPath) {
|
|
9545
10260
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
9546
10261
|
}
|
|
9547
10262
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
9548
|
-
const result = (0,
|
|
10263
|
+
const result = (0, import_node_child_process22.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
9549
10264
|
encoding: "utf-8"
|
|
9550
10265
|
});
|
|
9551
10266
|
if (result.status !== 0) {
|
|
9552
10267
|
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9553
10268
|
throw new Error(
|
|
9554
|
-
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${
|
|
10269
|
+
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${ensureGuidance2(repoPath)}`
|
|
9555
10270
|
);
|
|
9556
10271
|
}
|
|
9557
10272
|
let status;
|
|
9558
10273
|
try {
|
|
9559
10274
|
status = JSON.parse(result.stdout);
|
|
9560
10275
|
} catch {
|
|
9561
|
-
throw new Error(`DevPod '${devpodId}' returned invalid status. ${
|
|
10276
|
+
throw new Error(`DevPod '${devpodId}' returned invalid status. ${ensureGuidance2(repoPath)}`);
|
|
9562
10277
|
}
|
|
9563
10278
|
if (status.id !== devpodId || status.state !== "Running") {
|
|
9564
|
-
throw new Error(`DevPod '${devpodId}' is not running. ${
|
|
10279
|
+
throw new Error(`DevPod '${devpodId}' is not running. ${ensureGuidance2(repoPath)}`);
|
|
9565
10280
|
}
|
|
9566
10281
|
}
|
|
9567
10282
|
function resolveWorkspaceDirectory(repoPath) {
|
|
@@ -9569,17 +10284,20 @@ function resolveWorkspaceDirectory(repoPath) {
|
|
|
9569
10284
|
return resolveRunningWorkspaceContainer(repoPath).workspacePath;
|
|
9570
10285
|
} catch (error) {
|
|
9571
10286
|
const message = error instanceof Error ? error.message : String(error);
|
|
9572
|
-
throw new Error(`${message} ${
|
|
10287
|
+
throw new Error(`${message} ${ensureGuidance2(repoPath)}`);
|
|
9573
10288
|
}
|
|
9574
10289
|
}
|
|
9575
10290
|
async function devpodExec(repoPath, command) {
|
|
10291
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
10292
|
+
return devsyExec(repoPath, command);
|
|
10293
|
+
}
|
|
9576
10294
|
if (command.length === 0) {
|
|
9577
10295
|
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
9578
10296
|
}
|
|
9579
10297
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9580
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10298
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9581
10299
|
if (!devpod) {
|
|
9582
|
-
throw new Error(`No exact DevPod exists for '${repoPath}'. ${
|
|
10300
|
+
throw new Error(`No exact DevPod exists for '${repoPath}'. ${ensureGuidance2(repoPath)}`);
|
|
9583
10301
|
}
|
|
9584
10302
|
assertRunningDevpod(devpod.id, repoPath);
|
|
9585
10303
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
@@ -9601,7 +10319,7 @@ async function devpodExec(repoPath, command) {
|
|
|
9601
10319
|
wrappedCommand
|
|
9602
10320
|
];
|
|
9603
10321
|
return new Promise((resolve, reject) => {
|
|
9604
|
-
const child = (0,
|
|
10322
|
+
const child = (0, import_node_child_process22.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
9605
10323
|
let pending = Buffer.alloc(0);
|
|
9606
10324
|
let statusBytes = Buffer.alloc(0);
|
|
9607
10325
|
let readingStatus = false;
|
|
@@ -9660,15 +10378,17 @@ async function devpodExec(repoPath, command) {
|
|
|
9660
10378
|
});
|
|
9661
10379
|
});
|
|
9662
10380
|
}
|
|
9663
|
-
var
|
|
10381
|
+
var import_node_child_process22, import_node_crypto7, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
9664
10382
|
var init_devpod_exec = __esm({
|
|
9665
10383
|
"src/core/devpod-exec.ts"() {
|
|
9666
10384
|
"use strict";
|
|
9667
|
-
|
|
10385
|
+
import_node_child_process22 = require("child_process");
|
|
9668
10386
|
import_node_crypto7 = require("crypto");
|
|
9669
10387
|
init_devpod_environment();
|
|
9670
10388
|
init_devpod_workspaces();
|
|
10389
|
+
init_devsy_exec();
|
|
9671
10390
|
init_workspace();
|
|
10391
|
+
init_workspace_runtime();
|
|
9672
10392
|
DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC = Buffer.from(
|
|
9673
10393
|
"Error tunneling to container: wait: remote command exited without exit status or exit signal\n"
|
|
9674
10394
|
);
|
|
@@ -9849,7 +10569,7 @@ async function runOpenCommand(name) {
|
|
|
9849
10569
|
);
|
|
9850
10570
|
return;
|
|
9851
10571
|
}
|
|
9852
|
-
const result = (0,
|
|
10572
|
+
const result = (0, import_node_child_process23.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
9853
10573
|
if (result.status !== 0) {
|
|
9854
10574
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9855
10575
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -9857,11 +10577,11 @@ async function runOpenCommand(name) {
|
|
|
9857
10577
|
process.stdout.write(`Opened ${url}
|
|
9858
10578
|
`);
|
|
9859
10579
|
}
|
|
9860
|
-
var
|
|
10580
|
+
var import_node_child_process23;
|
|
9861
10581
|
var init_open = __esm({
|
|
9862
10582
|
"src/commands/open.ts"() {
|
|
9863
10583
|
"use strict";
|
|
9864
|
-
|
|
10584
|
+
import_node_child_process23 = require("child_process");
|
|
9865
10585
|
init_docker();
|
|
9866
10586
|
init_host_routes();
|
|
9867
10587
|
init_repo_config();
|
|
@@ -9884,7 +10604,7 @@ async function runLogsCommand(options) {
|
|
|
9884
10604
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
9885
10605
|
if (options.follow) {
|
|
9886
10606
|
args.splice(1, 0, "-f");
|
|
9887
|
-
const child = (0,
|
|
10607
|
+
const child = (0, import_node_child_process24.spawn)("docker", args, { stdio: "inherit" });
|
|
9888
10608
|
const onSignal = () => {
|
|
9889
10609
|
child.kill("SIGTERM");
|
|
9890
10610
|
};
|
|
@@ -9898,17 +10618,17 @@ async function runLogsCommand(options) {
|
|
|
9898
10618
|
});
|
|
9899
10619
|
});
|
|
9900
10620
|
} else {
|
|
9901
|
-
const result = (0,
|
|
10621
|
+
const result = (0, import_node_child_process24.spawnSync)("docker", args, { stdio: "inherit" });
|
|
9902
10622
|
if (result.status !== 0) {
|
|
9903
10623
|
throw new Error("Failed to retrieve router logs.");
|
|
9904
10624
|
}
|
|
9905
10625
|
}
|
|
9906
10626
|
}
|
|
9907
|
-
var
|
|
10627
|
+
var import_node_child_process24;
|
|
9908
10628
|
var init_logs = __esm({
|
|
9909
10629
|
"src/commands/logs.ts"() {
|
|
9910
10630
|
"use strict";
|
|
9911
|
-
|
|
10631
|
+
import_node_child_process24 = require("child_process");
|
|
9912
10632
|
init_docker();
|
|
9913
10633
|
init_router();
|
|
9914
10634
|
}
|
|
@@ -9948,17 +10668,17 @@ function asRecord3(value) {
|
|
|
9948
10668
|
return value;
|
|
9949
10669
|
}
|
|
9950
10670
|
function readJson(filePath) {
|
|
9951
|
-
if (!
|
|
10671
|
+
if (!import_node_fs24.default.existsSync(filePath)) {
|
|
9952
10672
|
return void 0;
|
|
9953
10673
|
}
|
|
9954
10674
|
try {
|
|
9955
|
-
return JSON.parse(
|
|
10675
|
+
return JSON.parse(import_node_fs24.default.readFileSync(filePath, "utf-8"));
|
|
9956
10676
|
} catch {
|
|
9957
10677
|
return void 0;
|
|
9958
10678
|
}
|
|
9959
10679
|
}
|
|
9960
10680
|
function relative(repoPath, filePath) {
|
|
9961
|
-
return
|
|
10681
|
+
return import_node_path24.default.relative(repoPath, filePath) || ".";
|
|
9962
10682
|
}
|
|
9963
10683
|
function redactEnvAssignments(value) {
|
|
9964
10684
|
return value.replace(
|
|
@@ -9999,7 +10719,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
9999
10719
|
["bun.lock", "bun"]
|
|
10000
10720
|
];
|
|
10001
10721
|
for (const [fileName, name] of lockfiles) {
|
|
10002
|
-
if (
|
|
10722
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10003
10723
|
return { name, source: fileName };
|
|
10004
10724
|
}
|
|
10005
10725
|
}
|
|
@@ -10014,9 +10734,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
10014
10734
|
if (typeof engines?.node === "string") {
|
|
10015
10735
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
10016
10736
|
}
|
|
10017
|
-
const nvmrc =
|
|
10018
|
-
if (
|
|
10019
|
-
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();
|
|
10020
10740
|
return { version, source: ".nvmrc" };
|
|
10021
10741
|
}
|
|
10022
10742
|
return void 0;
|
|
@@ -10077,7 +10797,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
10077
10797
|
const files = config.apps.filter(
|
|
10078
10798
|
(app) => app.runtime === "docker"
|
|
10079
10799
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
10080
|
-
(fileName) => !
|
|
10800
|
+
(fileName) => !import_node_path24.default.isAbsolute(fileName) && !import_node_path24.default.normalize(fileName).startsWith("..")
|
|
10081
10801
|
);
|
|
10082
10802
|
return Array.from(new Set(files));
|
|
10083
10803
|
} catch {
|
|
@@ -10095,7 +10815,7 @@ function composeFiles(repoPath) {
|
|
|
10095
10815
|
...configuredComposeFiles(repoPath)
|
|
10096
10816
|
];
|
|
10097
10817
|
return Array.from(new Set(candidates)).filter(
|
|
10098
|
-
(fileName) =>
|
|
10818
|
+
(fileName) => import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))
|
|
10099
10819
|
);
|
|
10100
10820
|
}
|
|
10101
10821
|
function stringArray2(value) {
|
|
@@ -10133,7 +10853,7 @@ function inspectServices(repoPath) {
|
|
|
10133
10853
|
const services = [];
|
|
10134
10854
|
for (const fileName of composeFiles(repoPath)) {
|
|
10135
10855
|
try {
|
|
10136
|
-
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"));
|
|
10137
10857
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
10138
10858
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
10139
10859
|
const service = asRecord3(value);
|
|
@@ -10168,8 +10888,8 @@ function inspectServices(repoPath) {
|
|
|
10168
10888
|
return services;
|
|
10169
10889
|
}
|
|
10170
10890
|
function inspectEnvFiles(repoPath) {
|
|
10171
|
-
const files =
|
|
10172
|
-
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");
|
|
10173
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();
|
|
10174
10894
|
return { path: fileName, names };
|
|
10175
10895
|
});
|
|
@@ -10183,18 +10903,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
10183
10903
|
};
|
|
10184
10904
|
}
|
|
10185
10905
|
function inspectDevcontainer(repoPath) {
|
|
10186
|
-
const dir =
|
|
10187
|
-
if (!
|
|
10906
|
+
const dir = import_node_path24.default.join(repoPath, ".devcontainer");
|
|
10907
|
+
if (!import_node_fs24.default.existsSync(dir)) {
|
|
10188
10908
|
return { exists: false, files: [] };
|
|
10189
10909
|
}
|
|
10190
10910
|
return {
|
|
10191
10911
|
exists: true,
|
|
10192
|
-
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}`)
|
|
10193
10913
|
};
|
|
10194
10914
|
}
|
|
10195
10915
|
function inspectDevrouter(repoPath) {
|
|
10196
10916
|
const configPath = getRepoConfigPath(repoPath);
|
|
10197
|
-
if (!
|
|
10917
|
+
if (!import_node_fs24.default.existsSync(configPath)) {
|
|
10198
10918
|
return {
|
|
10199
10919
|
exists: false,
|
|
10200
10920
|
configPath,
|
|
@@ -10238,15 +10958,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
10238
10958
|
["AGENTS.md", "agents"],
|
|
10239
10959
|
["CLAUDE.md", "claude"]
|
|
10240
10960
|
]) {
|
|
10241
|
-
if (
|
|
10961
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10242
10962
|
results.push({ path: fileName, kind });
|
|
10243
10963
|
}
|
|
10244
10964
|
}
|
|
10245
|
-
const skillsDir =
|
|
10246
|
-
if (
|
|
10247
|
-
for (const name of
|
|
10248
|
-
const skillPath =
|
|
10249
|
-
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)) {
|
|
10250
10970
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
10251
10971
|
}
|
|
10252
10972
|
}
|
|
@@ -10289,7 +11009,7 @@ function buildIssues(report) {
|
|
|
10289
11009
|
}
|
|
10290
11010
|
function inspectRepo(options = {}) {
|
|
10291
11011
|
const repoPath = resolveRepoPath(options.repo);
|
|
10292
|
-
const pkg = readJson(
|
|
11012
|
+
const pkg = readJson(import_node_path24.default.join(repoPath, "package.json"));
|
|
10293
11013
|
const scripts = inspectScripts(pkg);
|
|
10294
11014
|
const reportWithoutIssues = {
|
|
10295
11015
|
repoPath,
|
|
@@ -10308,12 +11028,12 @@ function inspectRepo(options = {}) {
|
|
|
10308
11028
|
issues: buildIssues(reportWithoutIssues)
|
|
10309
11029
|
};
|
|
10310
11030
|
}
|
|
10311
|
-
var
|
|
11031
|
+
var import_node_fs24, import_node_path24, import_yaml6;
|
|
10312
11032
|
var init_repo_inspect = __esm({
|
|
10313
11033
|
"src/core/repo-inspect.ts"() {
|
|
10314
11034
|
"use strict";
|
|
10315
|
-
|
|
10316
|
-
|
|
11035
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
11036
|
+
import_node_path24 = __toESM(require("path"));
|
|
10317
11037
|
import_yaml6 = __toESM(require("yaml"));
|
|
10318
11038
|
init_repo_config();
|
|
10319
11039
|
}
|
|
@@ -10418,7 +11138,7 @@ function requiredFileChecks(repoPath) {
|
|
|
10418
11138
|
".devcontainer/docker-compose.yml",
|
|
10419
11139
|
".devrouter.yml"
|
|
10420
11140
|
];
|
|
10421
|
-
const missing = required.filter((fileName) => !
|
|
11141
|
+
const missing = required.filter((fileName) => !import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName)));
|
|
10422
11142
|
return {
|
|
10423
11143
|
id: "repo.devcontainer.verify-files",
|
|
10424
11144
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -10637,12 +11357,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
10637
11357
|
nextSteps: collectNextSteps3(checks)
|
|
10638
11358
|
};
|
|
10639
11359
|
}
|
|
10640
|
-
var
|
|
11360
|
+
var import_node_fs25, import_node_path25;
|
|
10641
11361
|
var init_devcontainer_verify = __esm({
|
|
10642
11362
|
"src/core/devcontainer-verify.ts"() {
|
|
10643
11363
|
"use strict";
|
|
10644
|
-
|
|
10645
|
-
|
|
11364
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
11365
|
+
import_node_path25 = __toESM(require("path"));
|
|
10646
11366
|
init_capabilities();
|
|
10647
11367
|
init_doctor();
|
|
10648
11368
|
init_host_routes();
|
|
@@ -10938,7 +11658,7 @@ function packageManagerIssues(repo) {
|
|
|
10938
11658
|
}
|
|
10939
11659
|
function plannedFiles(repoPath, version) {
|
|
10940
11660
|
const repo = inspectRepo({ repo: repoPath });
|
|
10941
|
-
const projectName = sanitizeProjectName(
|
|
11661
|
+
const projectName = sanitizeProjectName(import_node_path26.default.basename(repo.repoPath));
|
|
10942
11662
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
10943
11663
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
10944
11664
|
const port = inferPort2(repo);
|
|
@@ -10983,8 +11703,8 @@ function plannedFiles(repoPath, version) {
|
|
|
10983
11703
|
};
|
|
10984
11704
|
}
|
|
10985
11705
|
function classifyFile(repoPath, file) {
|
|
10986
|
-
const absolutePath =
|
|
10987
|
-
if (!
|
|
11706
|
+
const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
|
|
11707
|
+
if (!import_node_fs26.default.existsSync(absolutePath)) {
|
|
10988
11708
|
return {
|
|
10989
11709
|
path: file.relativePath,
|
|
10990
11710
|
action: "create",
|
|
@@ -10992,7 +11712,7 @@ function classifyFile(repoPath, file) {
|
|
|
10992
11712
|
bytes: Buffer.byteLength(file.content)
|
|
10993
11713
|
};
|
|
10994
11714
|
}
|
|
10995
|
-
const current =
|
|
11715
|
+
const current = import_node_fs26.default.readFileSync(absolutePath, "utf-8");
|
|
10996
11716
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
10997
11717
|
return {
|
|
10998
11718
|
path: file.relativePath,
|
|
@@ -11049,11 +11769,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
11049
11769
|
};
|
|
11050
11770
|
}
|
|
11051
11771
|
function writeFile(repoPath, file) {
|
|
11052
|
-
const absolutePath =
|
|
11053
|
-
|
|
11054
|
-
|
|
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");
|
|
11055
11775
|
if (file.executable) {
|
|
11056
|
-
|
|
11776
|
+
import_node_fs26.default.chmodSync(absolutePath, 493);
|
|
11057
11777
|
}
|
|
11058
11778
|
}
|
|
11059
11779
|
function writeDevcontainer(options = {}) {
|
|
@@ -11091,12 +11811,12 @@ function writeDevcontainer(options = {}) {
|
|
|
11091
11811
|
nextSteps: postWriteNextSteps(repoPath)
|
|
11092
11812
|
};
|
|
11093
11813
|
}
|
|
11094
|
-
var
|
|
11814
|
+
var import_node_fs26, import_node_path26, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
11095
11815
|
var init_devcontainer_write = __esm({
|
|
11096
11816
|
"src/core/devcontainer-write.ts"() {
|
|
11097
11817
|
"use strict";
|
|
11098
|
-
|
|
11099
|
-
|
|
11818
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11819
|
+
import_node_path26 = __toESM(require("path"));
|
|
11100
11820
|
init_repo_config();
|
|
11101
11821
|
init_repo_inspect();
|
|
11102
11822
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -11487,7 +12207,7 @@ function sanitizeRouterId(value) {
|
|
|
11487
12207
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
11488
12208
|
}
|
|
11489
12209
|
function repoHash(repoPath) {
|
|
11490
|
-
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);
|
|
11491
12211
|
}
|
|
11492
12212
|
function asDockerApp(app) {
|
|
11493
12213
|
return app.runtime === "docker";
|
|
@@ -11566,11 +12286,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
11566
12286
|
if (dockerApps.length === 0) {
|
|
11567
12287
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
11568
12288
|
}
|
|
11569
|
-
const cachePath =
|
|
11570
|
-
|
|
11571
|
-
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");
|
|
11572
12292
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
11573
|
-
|
|
12293
|
+
import_node_fs27.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
11574
12294
|
return {
|
|
11575
12295
|
overlayPath,
|
|
11576
12296
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -11584,7 +12304,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11584
12304
|
fileArgs.push("-f", resolved);
|
|
11585
12305
|
}
|
|
11586
12306
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
11587
|
-
const result = (0,
|
|
12307
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11588
12308
|
encoding: "utf-8",
|
|
11589
12309
|
cwd: repoPath
|
|
11590
12310
|
});
|
|
@@ -11612,7 +12332,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
11612
12332
|
"--services",
|
|
11613
12333
|
...services
|
|
11614
12334
|
];
|
|
11615
|
-
const result = (0,
|
|
12335
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11616
12336
|
encoding: "utf-8",
|
|
11617
12337
|
cwd: repoPath
|
|
11618
12338
|
});
|
|
@@ -11638,7 +12358,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11638
12358
|
fileArgs.push("-f", resolved);
|
|
11639
12359
|
}
|
|
11640
12360
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
11641
|
-
const result = (0,
|
|
12361
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11642
12362
|
encoding: "utf-8",
|
|
11643
12363
|
cwd: repoPath
|
|
11644
12364
|
});
|
|
@@ -11664,7 +12384,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
11664
12384
|
String(tail),
|
|
11665
12385
|
...services
|
|
11666
12386
|
];
|
|
11667
|
-
(0,
|
|
12387
|
+
(0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11668
12388
|
stdio: "inherit",
|
|
11669
12389
|
cwd: repoPath
|
|
11670
12390
|
});
|
|
@@ -11676,7 +12396,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11676
12396
|
fileArgs.push("-f", resolved);
|
|
11677
12397
|
}
|
|
11678
12398
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
11679
|
-
const result = (0,
|
|
12399
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11680
12400
|
encoding: "utf-8",
|
|
11681
12401
|
cwd: repoPath
|
|
11682
12402
|
});
|
|
@@ -11690,14 +12410,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11690
12410
|
const port = Number(match[1]);
|
|
11691
12411
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
11692
12412
|
}
|
|
11693
|
-
var
|
|
12413
|
+
var import_node_child_process25, import_node_crypto8, import_node_fs27, import_node_path27, import_yaml7;
|
|
11694
12414
|
var init_docker_run = __esm({
|
|
11695
12415
|
"src/core/docker-run.ts"() {
|
|
11696
12416
|
"use strict";
|
|
11697
|
-
|
|
12417
|
+
import_node_child_process25 = require("child_process");
|
|
11698
12418
|
import_node_crypto8 = require("crypto");
|
|
11699
|
-
|
|
11700
|
-
|
|
12419
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
12420
|
+
import_node_path27 = __toESM(require("path"));
|
|
11701
12421
|
import_yaml7 = __toESM(require("yaml"));
|
|
11702
12422
|
init_docker_error_guidance();
|
|
11703
12423
|
init_paths();
|
|
@@ -11787,7 +12507,7 @@ function isProcessRunning(pid) {
|
|
|
11787
12507
|
}
|
|
11788
12508
|
}
|
|
11789
12509
|
function readProcessTree(rootPid) {
|
|
11790
|
-
const result = (0,
|
|
12510
|
+
const result = (0, import_node_child_process26.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
11791
12511
|
if (result.status !== 0) {
|
|
11792
12512
|
return [rootPid];
|
|
11793
12513
|
}
|
|
@@ -11898,12 +12618,12 @@ function detectListeningPorts(pids) {
|
|
|
11898
12618
|
if (pids.length === 0) {
|
|
11899
12619
|
return [];
|
|
11900
12620
|
}
|
|
11901
|
-
const result = (0,
|
|
12621
|
+
const result = (0, import_node_child_process26.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
11902
12622
|
encoding: "utf-8"
|
|
11903
12623
|
});
|
|
11904
12624
|
if (result.error && result.error.code === "ENOENT") {
|
|
11905
12625
|
if (process.platform === "linux") {
|
|
11906
|
-
const ssResult = (0,
|
|
12626
|
+
const ssResult = (0, import_node_child_process26.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
11907
12627
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
11908
12628
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
11909
12629
|
}
|
|
@@ -11962,7 +12682,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
11962
12682
|
app.hostRun.command,
|
|
11963
12683
|
true
|
|
11964
12684
|
) : app.hostRun.command;
|
|
11965
|
-
const child = (0,
|
|
12685
|
+
const child = (0, import_node_child_process26.spawn)(spawnCommand, {
|
|
11966
12686
|
cwd: commandCwd,
|
|
11967
12687
|
stdio: "inherit",
|
|
11968
12688
|
shell: true,
|
|
@@ -12330,7 +13050,7 @@ async function execWithAppEnv(options) {
|
|
|
12330
13050
|
options.command[0],
|
|
12331
13051
|
true
|
|
12332
13052
|
);
|
|
12333
|
-
child = (0,
|
|
13053
|
+
child = (0, import_node_child_process26.spawn)(wrapped, {
|
|
12334
13054
|
cwd: deps.repoPath,
|
|
12335
13055
|
stdio: "inherit",
|
|
12336
13056
|
shell: true,
|
|
@@ -12344,7 +13064,7 @@ async function execWithAppEnv(options) {
|
|
|
12344
13064
|
false
|
|
12345
13065
|
);
|
|
12346
13066
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
12347
|
-
child = (0,
|
|
13067
|
+
child = (0, import_node_child_process26.spawn)(cmd, wrappedArgs, {
|
|
12348
13068
|
cwd: deps.repoPath,
|
|
12349
13069
|
stdio: "inherit",
|
|
12350
13070
|
shell: false,
|
|
@@ -12352,7 +13072,7 @@ async function execWithAppEnv(options) {
|
|
|
12352
13072
|
});
|
|
12353
13073
|
}
|
|
12354
13074
|
} else if (options.shell) {
|
|
12355
|
-
child = (0,
|
|
13075
|
+
child = (0, import_node_child_process26.spawn)(options.command[0], {
|
|
12356
13076
|
cwd: deps.repoPath,
|
|
12357
13077
|
stdio: "inherit",
|
|
12358
13078
|
shell: true,
|
|
@@ -12360,7 +13080,7 @@ async function execWithAppEnv(options) {
|
|
|
12360
13080
|
});
|
|
12361
13081
|
} else {
|
|
12362
13082
|
const [command, ...args] = options.command;
|
|
12363
|
-
child = (0,
|
|
13083
|
+
child = (0, import_node_child_process26.spawn)(command, args, {
|
|
12364
13084
|
cwd: deps.repoPath,
|
|
12365
13085
|
stdio: "inherit",
|
|
12366
13086
|
shell: false,
|
|
@@ -12381,11 +13101,11 @@ async function execWithAppEnv(options) {
|
|
|
12381
13101
|
deps.stopDeps();
|
|
12382
13102
|
}
|
|
12383
13103
|
}
|
|
12384
|
-
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;
|
|
12385
13105
|
var init_app_run = __esm({
|
|
12386
13106
|
"src/core/app-run.ts"() {
|
|
12387
13107
|
"use strict";
|
|
12388
|
-
|
|
13108
|
+
import_node_child_process26 = require("child_process");
|
|
12389
13109
|
import_node_net = __toESM(require("net"));
|
|
12390
13110
|
import_node_process = require("process");
|
|
12391
13111
|
import_promises = require("readline/promises");
|
|
@@ -12519,13 +13239,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12519
13239
|
const startedAt = Date.now();
|
|
12520
13240
|
let rootStat;
|
|
12521
13241
|
try {
|
|
12522
|
-
rootStat =
|
|
13242
|
+
rootStat = import_node_fs28.default.lstatSync(worktreePath);
|
|
12523
13243
|
} catch (error) {
|
|
12524
13244
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12525
13245
|
}
|
|
12526
13246
|
let rootEntries;
|
|
12527
13247
|
try {
|
|
12528
|
-
rootEntries =
|
|
13248
|
+
rootEntries = import_node_fs28.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
12529
13249
|
} catch (error) {
|
|
12530
13250
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12531
13251
|
}
|
|
@@ -12551,10 +13271,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12551
13271
|
timedOut = true;
|
|
12552
13272
|
break;
|
|
12553
13273
|
}
|
|
12554
|
-
const entryPath =
|
|
13274
|
+
const entryPath = import_node_path28.default.join(dirPath, entry.name);
|
|
12555
13275
|
let entryStat;
|
|
12556
13276
|
try {
|
|
12557
|
-
entryStat =
|
|
13277
|
+
entryStat = import_node_fs28.default.lstatSync(entryPath);
|
|
12558
13278
|
} catch (error) {
|
|
12559
13279
|
if (error?.code === "ENOENT") continue;
|
|
12560
13280
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -12564,7 +13284,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12564
13284
|
if (!entryStat.isDirectory()) continue;
|
|
12565
13285
|
let childEntries;
|
|
12566
13286
|
try {
|
|
12567
|
-
childEntries =
|
|
13287
|
+
childEntries = import_node_fs28.default.readdirSync(entryPath, { withFileTypes: true });
|
|
12568
13288
|
} catch (error) {
|
|
12569
13289
|
unreadableReason = describeIncompleteWalk(error);
|
|
12570
13290
|
break;
|
|
@@ -12628,12 +13348,12 @@ function describeError(error, worktreePath) {
|
|
|
12628
13348
|
}
|
|
12629
13349
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
12630
13350
|
}
|
|
12631
|
-
var
|
|
13351
|
+
var import_node_fs28, import_node_path28, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
12632
13352
|
var init_workspace_consumption = __esm({
|
|
12633
13353
|
"src/core/workspace-consumption.ts"() {
|
|
12634
13354
|
"use strict";
|
|
12635
|
-
|
|
12636
|
-
|
|
13355
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
13356
|
+
import_node_path28 = __toESM(require("path"));
|
|
12637
13357
|
init_devpod_environment();
|
|
12638
13358
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
12639
13359
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -12734,7 +13454,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
12734
13454
|
}
|
|
12735
13455
|
function readGitSnapshot(worktree, commandRunner) {
|
|
12736
13456
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
12737
|
-
if (worktree.prunable || !
|
|
13457
|
+
if (worktree.prunable || !import_node_fs29.default.existsSync(comparablePath)) {
|
|
12738
13458
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
12739
13459
|
}
|
|
12740
13460
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -13243,12 +13963,6 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13243
13963
|
const cutoff = new Date(now.getTime() - duration.seconds * 1e3).toISOString();
|
|
13244
13964
|
const worktrees = (dependencies.listWorktrees ?? listGitWorktrees)(repoPath);
|
|
13245
13965
|
const records = (dependencies.listOwnership ?? listWorkspaceOwnership)(repoPath);
|
|
13246
|
-
let devpods;
|
|
13247
|
-
try {
|
|
13248
|
-
devpods = (dependencies.listDevpods ?? listDevpodWorkspaces)();
|
|
13249
|
-
} catch {
|
|
13250
|
-
devpods = void 0;
|
|
13251
|
-
}
|
|
13252
13966
|
let routes;
|
|
13253
13967
|
try {
|
|
13254
13968
|
routes = (dependencies.readRoutes ?? readHostRouteStateReadOnly)();
|
|
@@ -13265,7 +13979,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13265
13979
|
containers = measureContainersFn(worktreePaths);
|
|
13266
13980
|
} catch (error) {
|
|
13267
13981
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
13268
|
-
containers = new Map(worktreePaths.map((
|
|
13982
|
+
containers = new Map(worktreePaths.map((path28) => [path28, unknownContainers(reason)]));
|
|
13269
13983
|
}
|
|
13270
13984
|
}
|
|
13271
13985
|
const rows = records.map((record) => {
|
|
@@ -13280,6 +13994,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13280
13994
|
prunable: true
|
|
13281
13995
|
}
|
|
13282
13996
|
);
|
|
13997
|
+
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record.worktreePath);
|
|
13283
13998
|
const row = buildRow(
|
|
13284
13999
|
repoPath,
|
|
13285
14000
|
record,
|
|
@@ -13295,7 +14010,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13295
14010
|
return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
|
|
13296
14011
|
}),
|
|
13297
14012
|
dependencies.inspectIntegration,
|
|
13298
|
-
dependencies.inspectDevpodRuntime ?? inspectDevpodRuntimeStatus
|
|
14013
|
+
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record.worktreePath))
|
|
13299
14014
|
);
|
|
13300
14015
|
return measureSize ? {
|
|
13301
14016
|
...row,
|
|
@@ -13313,12 +14028,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13313
14028
|
workspaces: rows
|
|
13314
14029
|
};
|
|
13315
14030
|
}
|
|
13316
|
-
var
|
|
14031
|
+
var import_node_child_process27, import_node_fs29, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
13317
14032
|
var init_workspace_cleanup = __esm({
|
|
13318
14033
|
"src/core/workspace-cleanup.ts"() {
|
|
13319
14034
|
"use strict";
|
|
13320
|
-
|
|
13321
|
-
|
|
14035
|
+
import_node_child_process27 = require("child_process");
|
|
14036
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
13322
14037
|
init_devpod_workspaces();
|
|
13323
14038
|
init_host_routes();
|
|
13324
14039
|
init_repo_config();
|
|
@@ -13335,7 +14050,7 @@ var init_workspace_cleanup = __esm({
|
|
|
13335
14050
|
];
|
|
13336
14051
|
defaultCommandRunner = (command, args, input2) => {
|
|
13337
14052
|
const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
|
|
13338
|
-
const result = (0,
|
|
14053
|
+
const result = (0, import_node_child_process27.spawnSync)(command, args, {
|
|
13339
14054
|
encoding: "utf-8",
|
|
13340
14055
|
env: environment,
|
|
13341
14056
|
...input2 === void 0 ? {} : { input: input2 }
|
|
@@ -13495,7 +14210,7 @@ var init_version = __esm({
|
|
|
13495
14210
|
|
|
13496
14211
|
// src/cli.ts
|
|
13497
14212
|
var import_commander = require("commander");
|
|
13498
|
-
var CLI_VERSION = true ? "0.0.
|
|
14213
|
+
var CLI_VERSION = true ? "0.0.41" : "0.0.0-dev";
|
|
13499
14214
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
13500
14215
|
function withErrorHandling(action2) {
|
|
13501
14216
|
return async (...args) => {
|
|
@@ -13533,11 +14248,16 @@ program.command("upgrade").description(
|
|
|
13533
14248
|
}
|
|
13534
14249
|
)
|
|
13535
14250
|
);
|
|
13536
|
-
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("--
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
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
|
+
)
|
|
13541
14261
|
);
|
|
13542
14262
|
program.command("up").description("Ensure devnet and start shared Traefik (reserves 80/443/5432)").action(
|
|
13543
14263
|
withErrorHandling(async () => {
|
|
@@ -13557,7 +14277,7 @@ program.command("ensure").description("Start and prove a primary or linked check
|
|
|
13557
14277
|
});
|
|
13558
14278
|
})
|
|
13559
14279
|
);
|
|
13560
|
-
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(
|
|
13561
14281
|
withErrorHandling(async (repoPath, _options, command) => {
|
|
13562
14282
|
const options = command.opts();
|
|
13563
14283
|
const { runStopCommand: runStopCommand2 } = await Promise.resolve().then(() => (init_stop(), stop_exports));
|
|
@@ -13568,7 +14288,7 @@ program.command("stop").description("Stop one checkout's exact DevPod and free i
|
|
|
13568
14288
|
});
|
|
13569
14289
|
})
|
|
13570
14290
|
);
|
|
13571
|
-
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(
|
|
13572
14292
|
withErrorHandling(async () => {
|
|
13573
14293
|
const commandIndex = process.argv.indexOf("exec", 2);
|
|
13574
14294
|
const { parseExecInvocation: parseExecInvocation2, runExecCommand: runExecCommand2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
|
|
@@ -13737,7 +14457,7 @@ tlsCommand.command("install").description("Install mkcert certs and enable HTTPS
|
|
|
13737
14457
|
);
|
|
13738
14458
|
var workspaceCommand2 = program.command("workspace").description("Spin up / list / tear down isolated worktree+devcontainer workspaces");
|
|
13739
14459
|
workspaceCommand2.command("up").description(
|
|
13740
|
-
"Create a worktree for <branch>, bring up its
|
|
14460
|
+
"Create a worktree for <branch>, bring up its workspace runtime, and register namespaced routes"
|
|
13741
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(
|
|
13742
14462
|
withErrorHandling(async (branch, _options, command) => {
|
|
13743
14463
|
const options = command.opts();
|
|
@@ -13750,7 +14470,9 @@ workspaceCommand2.command("up").description(
|
|
|
13750
14470
|
});
|
|
13751
14471
|
})
|
|
13752
14472
|
);
|
|
13753
|
-
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(
|
|
13754
14476
|
withErrorHandling(
|
|
13755
14477
|
async (worktreePath, _options, command) => {
|
|
13756
14478
|
const options = command.opts();
|
|
@@ -13784,21 +14506,21 @@ workspaceCommand2.command("cleanup").description("Report managed workspace activ
|
|
|
13784
14506
|
});
|
|
13785
14507
|
})
|
|
13786
14508
|
);
|
|
13787
|
-
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(
|
|
13788
14510
|
withErrorHandling(async (_options, command) => {
|
|
13789
14511
|
const options = command.opts();
|
|
13790
14512
|
const { runWorkspaceGcCommand: runWorkspaceGcCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13791
14513
|
runWorkspaceGcCommand2(options);
|
|
13792
14514
|
})
|
|
13793
14515
|
);
|
|
13794
|
-
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(
|
|
13795
14517
|
withErrorHandling(async (target, _options, command) => {
|
|
13796
14518
|
const options = command.opts();
|
|
13797
14519
|
const { runWorkspaceStopCommand: runWorkspaceStopCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13798
14520
|
await runWorkspaceStopCommand2(target, options);
|
|
13799
14521
|
})
|
|
13800
14522
|
);
|
|
13801
|
-
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(
|
|
13802
14524
|
withErrorHandling(async (target, _options, command) => {
|
|
13803
14525
|
const options = command.opts();
|
|
13804
14526
|
const { runWorkspaceDownCommand: runWorkspaceDownCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|