@devrouter/cli 0.0.40 → 0.0.42
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 +1026 -299
- package/package.json +1 -1
- package/upgrade-prompts/0.0.41.md +43 -0
- package/upgrade-prompts/0.0.42.md +39 -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.42" : "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]",
|
|
@@ -5195,7 +5195,7 @@ function hasExactComposeIdentity(container, options) {
|
|
|
5195
5195
|
const actualFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").map((file) => file.trim()).filter(Boolean);
|
|
5196
5196
|
const providerGeneratedFiles = actualFiles.filter((file) => {
|
|
5197
5197
|
const normalized = file.replaceAll("\\", "/");
|
|
5198
|
-
return
|
|
5198
|
+
return /\/(?:\.devpod\/agent\/contexts\/[^/]+\/workspaces\/[^/]+|\.devsy\/contexts\/[^/]+\/workspaces\/[^/]+\/agent)\/\.docker-compose\/docker-compose\.devcontainer\.containerFeatures-[^/]+\.yml$/.test(
|
|
5199
5199
|
normalized
|
|
5200
5200
|
);
|
|
5201
5201
|
});
|
|
@@ -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,242 @@ 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
|
|
6761
|
+
function failedStartMayHaveAttached(devsyId, repoPath) {
|
|
6762
|
+
try {
|
|
6763
|
+
const attached = listDevsyWorkspaces();
|
|
6764
|
+
const attachedId = devsyId ?? selectDevsyWorkspace(attached, repoPath)?.id;
|
|
6765
|
+
if (!attachedId) return false;
|
|
6766
|
+
return inspectDevsyWorkspaceOwnership(attached, attachedId, repoPath).status !== "absent";
|
|
6767
|
+
} catch {
|
|
6768
|
+
return true;
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6329
6771
|
function withMutationLock(activity, target, operation) {
|
|
6330
|
-
|
|
6772
|
+
import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6773
|
+
return withFileLockSync(
|
|
6774
|
+
DEVSY_MUTATION_LOCK_FILE,
|
|
6775
|
+
{ activity, target: `'${target}'`, waitMs: DEVSY_MUTATION_WAIT_MS },
|
|
6776
|
+
operation
|
|
6777
|
+
);
|
|
6778
|
+
}
|
|
6779
|
+
function commandFailure2(result) {
|
|
6780
|
+
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6781
|
+
}
|
|
6782
|
+
function runDevsyAction(action2, devsyId, force = false) {
|
|
6783
|
+
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
6784
|
+
const result = (0, import_node_child_process14.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
6785
|
+
if (result.status !== 0) {
|
|
6786
|
+
throw new Error(
|
|
6787
|
+
`devsy workspace ${action2}${force ? " --force" : ""} failed for '${devsyId}': ${commandFailure2(result) || "unknown error"}`
|
|
6788
|
+
);
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6791
|
+
function inspectExactOwnership(devsyId, worktreePath) {
|
|
6792
|
+
const ownership = inspectDevsyWorkspaceOwnership(listDevsyWorkspaces(), devsyId, worktreePath);
|
|
6793
|
+
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6794
|
+
return ownership;
|
|
6795
|
+
}
|
|
6796
|
+
function mutateOwnedDevsyWorkspace(action2, devsyId, worktreePath) {
|
|
6797
|
+
return withMutationLock(`Devsy ${action2}`, worktreePath, () => {
|
|
6798
|
+
const before = inspectExactOwnership(devsyId, worktreePath);
|
|
6799
|
+
if (before.status === "absent") return { status: "absent" };
|
|
6800
|
+
runDevsyAction(action2, devsyId);
|
|
6801
|
+
let after = inspectExactOwnership(devsyId, worktreePath);
|
|
6802
|
+
if (action2 === "stop" && after.status !== "owned") {
|
|
6803
|
+
throw new Error(
|
|
6804
|
+
`Devsy workspace '${devsyId}' no longer owns '${worktreePath}' after provider stop.`
|
|
6805
|
+
);
|
|
6806
|
+
}
|
|
6807
|
+
if (action2 === "delete" && after.status === "owned") {
|
|
6808
|
+
const runtime = inspectDevsyRuntimeStatus(devsyId);
|
|
6809
|
+
if (runtime !== "not-found") {
|
|
6810
|
+
throw new Error(
|
|
6811
|
+
`Devsy workspace '${devsyId}' still owns '${worktreePath}' after provider delete (runtime=${runtime}).`
|
|
6812
|
+
);
|
|
6813
|
+
}
|
|
6814
|
+
after = inspectExactOwnership(devsyId, worktreePath);
|
|
6815
|
+
if (after.status === "owned") {
|
|
6816
|
+
runDevsyAction("delete", devsyId, true);
|
|
6817
|
+
after = inspectExactOwnership(devsyId, worktreePath);
|
|
6818
|
+
}
|
|
6819
|
+
if (after.status !== "absent") {
|
|
6820
|
+
throw new Error(
|
|
6821
|
+
`Devsy workspace '${devsyId}' still owns '${worktreePath}' after forced provider delete.`
|
|
6822
|
+
);
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6825
|
+
return { status: "changed" };
|
|
6826
|
+
});
|
|
6827
|
+
}
|
|
6828
|
+
function stopOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
6829
|
+
return mutateOwnedDevsyWorkspace("stop", devsyId, worktreePath);
|
|
6830
|
+
}
|
|
6831
|
+
function deleteOwnedDevsyWorkspace(devsyId, worktreePath) {
|
|
6832
|
+
return mutateOwnedDevsyWorkspace("delete", devsyId, worktreePath);
|
|
6833
|
+
}
|
|
6834
|
+
function assertDevsyTarget(devsyId, repoPath) {
|
|
6835
|
+
const workspaces = listDevsyWorkspaces();
|
|
6836
|
+
const existing = selectDevsyWorkspace(workspaces, repoPath);
|
|
6837
|
+
const id = devsyId ?? existing?.id;
|
|
6838
|
+
if (id) {
|
|
6839
|
+
const before = inspectDevsyWorkspaceOwnership(workspaces, id, repoPath);
|
|
6840
|
+
if (before.status === "conflict") throw new Error(before.reason);
|
|
6841
|
+
}
|
|
6842
|
+
return id;
|
|
6843
|
+
}
|
|
6844
|
+
function startDevsyWorkspace(options) {
|
|
6845
|
+
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
6846
|
+
return withMutationLock(activity, options.repoPath, () => {
|
|
6847
|
+
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
6848
|
+
if (devsyId && options.recreate) {
|
|
6849
|
+
const attached = listDevsyWorkspaces();
|
|
6850
|
+
const ownership = inspectDevsyWorkspaceOwnership(attached, devsyId, options.repoPath);
|
|
6851
|
+
if (ownership.status !== "owned") {
|
|
6852
|
+
throw new Error(`Cannot recreate Devsy workspace '${devsyId}' without one exact owner.`);
|
|
6853
|
+
}
|
|
6854
|
+
}
|
|
6855
|
+
if (!devsyId && options.recreate) {
|
|
6856
|
+
throw new Error("Cannot recreate a Devsy workspace before its exact id is known.");
|
|
6857
|
+
}
|
|
6858
|
+
const args = ["workspace", "up", options.repoPath];
|
|
6859
|
+
if (devsyId) args.push("--id", devsyId);
|
|
6860
|
+
if (options.devcontainerPath) args.push("--devcontainer", options.devcontainerPath);
|
|
6861
|
+
args.push("--ide-launch", "skip");
|
|
6862
|
+
if (options.workspace) {
|
|
6863
|
+
args.push(
|
|
6864
|
+
"--workspace-env",
|
|
6865
|
+
`WORKSPACE=${options.workspace.token}`,
|
|
6866
|
+
"--workspace-env",
|
|
6867
|
+
`DEVROUTER_WORKSPACE=${options.workspace.token}`
|
|
6868
|
+
);
|
|
6869
|
+
}
|
|
6870
|
+
if (options.inactivityTimeout) {
|
|
6871
|
+
args.push("--provider-option", `INACTIVITY_TIMEOUT=${options.inactivityTimeout}`);
|
|
6872
|
+
}
|
|
6873
|
+
if (options.recreate) args.push("--recreate");
|
|
6874
|
+
const env = { ...process.env };
|
|
6875
|
+
if (options.workspace) {
|
|
6876
|
+
env.WORKSPACE = options.workspace.token;
|
|
6877
|
+
env.DEVROUTER_WORKSPACE = options.workspace.token;
|
|
6878
|
+
env.DEVROUTER_GIT_COMMON_DIR = options.workspace.gitCommonDir;
|
|
6879
|
+
env.DEVCONTAINER_COMPOSE_OVERLAY = "docker-compose.devrouter.yml";
|
|
6880
|
+
} else {
|
|
6881
|
+
delete env.WORKSPACE;
|
|
6882
|
+
delete env.DEVROUTER_WORKSPACE;
|
|
6883
|
+
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
6884
|
+
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
6885
|
+
}
|
|
6886
|
+
const result = (0, import_node_child_process14.spawnSync)("devsy", args, {
|
|
6887
|
+
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
6888
|
+
env
|
|
6889
|
+
});
|
|
6890
|
+
if (result.status !== 0) {
|
|
6891
|
+
const message = `devsy workspace up failed for '${devsyId ?? options.repoPath}'.`;
|
|
6892
|
+
if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
|
|
6893
|
+
throw new DevsyStartPostconditionError(message);
|
|
6894
|
+
}
|
|
6895
|
+
throw new Error(message);
|
|
6896
|
+
}
|
|
6897
|
+
try {
|
|
6898
|
+
const attached = listDevsyWorkspaces();
|
|
6899
|
+
devsyId ??= selectDevsyWorkspace(attached, options.repoPath)?.id;
|
|
6900
|
+
if (!devsyId) {
|
|
6901
|
+
throw new Error(`Devsy did not attach '${options.repoPath}' after startup.`);
|
|
6902
|
+
}
|
|
6903
|
+
const ownership = inspectDevsyWorkspaceOwnership(attached, devsyId, options.repoPath);
|
|
6904
|
+
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6905
|
+
if (ownership.status !== "owned") {
|
|
6906
|
+
throw new Error(
|
|
6907
|
+
`Devsy did not attach '${options.repoPath}' as '${devsyId}' after startup.`
|
|
6908
|
+
);
|
|
6909
|
+
}
|
|
6910
|
+
return devsyId;
|
|
6911
|
+
} catch (error) {
|
|
6912
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6913
|
+
throw new DevsyStartPostconditionError(message);
|
|
6914
|
+
}
|
|
6915
|
+
});
|
|
6916
|
+
}
|
|
6917
|
+
var import_node_child_process14, import_node_fs18, import_node_path17, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError;
|
|
6918
|
+
var init_devsy_mutation = __esm({
|
|
6919
|
+
"src/core/devsy-mutation.ts"() {
|
|
6920
|
+
"use strict";
|
|
6921
|
+
import_node_child_process14 = require("child_process");
|
|
6922
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
6923
|
+
import_node_path17 = __toESM(require("path"));
|
|
6924
|
+
init_devsy_workspaces();
|
|
6925
|
+
init_file_lock();
|
|
6926
|
+
init_router();
|
|
6927
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
6928
|
+
DEVSY_MUTATION_WAIT_MS = 6e4;
|
|
6929
|
+
DevsyStartPostconditionError = class extends Error {
|
|
6930
|
+
};
|
|
6931
|
+
}
|
|
6932
|
+
});
|
|
6933
|
+
|
|
6934
|
+
// src/core/devpod-mutation.ts
|
|
6935
|
+
function withMutationLock2(activity, target, operation) {
|
|
6936
|
+
import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6331
6937
|
return withFileLockSync(
|
|
6332
6938
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
6333
6939
|
{ activity, target: `'${target}'`, waitMs: DEVPOD_MUTATION_WAIT_MS },
|
|
6334
6940
|
operation
|
|
6335
6941
|
);
|
|
6336
6942
|
}
|
|
6337
|
-
function
|
|
6943
|
+
function commandFailure3(result) {
|
|
6338
6944
|
return [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6339
6945
|
}
|
|
6340
6946
|
function runDevpodAction(action2, devpodId, force = false) {
|
|
6341
6947
|
const args = action2 === "delete" ? [action2, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action2, devpodId];
|
|
6342
|
-
const result = (0,
|
|
6948
|
+
const result = (0, import_node_child_process15.spawnSync)("devpod", args, { encoding: "utf-8" });
|
|
6343
6949
|
if (result.status !== 0) {
|
|
6344
6950
|
throw new Error(
|
|
6345
|
-
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${
|
|
6951
|
+
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${commandFailure3(result) || "unknown error"}`
|
|
6346
6952
|
);
|
|
6347
6953
|
}
|
|
6348
6954
|
}
|
|
6349
|
-
function
|
|
6350
|
-
const ownership = inspectDevpodWorkspaceOwnership(
|
|
6955
|
+
function inspectExactOwnership2(devpodId, worktreePath) {
|
|
6956
|
+
const ownership = inspectDevpodWorkspaceOwnership(
|
|
6957
|
+
listDevpodWorkspaces(worktreePath),
|
|
6958
|
+
devpodId,
|
|
6959
|
+
worktreePath
|
|
6960
|
+
);
|
|
6351
6961
|
if (ownership.status === "conflict") throw new Error(ownership.reason);
|
|
6352
6962
|
return ownership;
|
|
6353
6963
|
}
|
|
6354
6964
|
function mutateOwnedDevpodWorkspace(action2, devpodId, worktreePath) {
|
|
6355
|
-
return
|
|
6356
|
-
const before =
|
|
6965
|
+
return withMutationLock2(`DevPod ${action2}`, worktreePath, () => {
|
|
6966
|
+
const before = inspectExactOwnership2(devpodId, worktreePath);
|
|
6357
6967
|
if (before.status === "absent") return { status: "absent" };
|
|
6358
6968
|
runDevpodAction(action2, devpodId);
|
|
6359
|
-
let after =
|
|
6969
|
+
let after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6360
6970
|
if (action2 === "stop" && after.status !== "owned") {
|
|
6361
6971
|
throw new Error(`DevPod '${devpodId}' no longer owns '${worktreePath}' after provider stop.`);
|
|
6362
6972
|
}
|
|
6363
6973
|
if (action2 === "delete" && after.status === "owned") {
|
|
6364
|
-
const runtime = inspectDevpodRuntimeStatus(devpodId);
|
|
6974
|
+
const runtime = inspectDevpodRuntimeStatus(devpodId, worktreePath);
|
|
6365
6975
|
if (runtime !== "not-found") {
|
|
6366
6976
|
throw new Error(
|
|
6367
6977
|
`DevPod '${devpodId}' still owns '${worktreePath}' after provider delete (runtime=${runtime}).`
|
|
6368
6978
|
);
|
|
6369
6979
|
}
|
|
6370
|
-
after =
|
|
6980
|
+
after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6371
6981
|
if (after.status === "owned") {
|
|
6372
6982
|
runDevpodAction("delete", devpodId, true);
|
|
6373
|
-
after =
|
|
6983
|
+
after = inspectExactOwnership2(devpodId, worktreePath);
|
|
6374
6984
|
}
|
|
6375
6985
|
if (after.status !== "absent") {
|
|
6376
6986
|
throw new Error(
|
|
@@ -6382,15 +6992,50 @@ function mutateOwnedDevpodWorkspace(action2, devpodId, worktreePath) {
|
|
|
6382
6992
|
});
|
|
6383
6993
|
}
|
|
6384
6994
|
function stopOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
6385
|
-
|
|
6995
|
+
if (resolveWorkspaceRuntimeOrDefault(worktreePath) === "devsy") {
|
|
6996
|
+
const result2 = stopOwnedDevsyWorkspace(devpodId, worktreePath);
|
|
6997
|
+
resetWorkspaceRuntimeCaches();
|
|
6998
|
+
return result2;
|
|
6999
|
+
}
|
|
7000
|
+
const result = mutateOwnedDevpodWorkspace("stop", devpodId, worktreePath);
|
|
7001
|
+
resetWorkspaceRuntimeCaches();
|
|
7002
|
+
return result;
|
|
6386
7003
|
}
|
|
6387
7004
|
function deleteOwnedDevpodWorkspace(devpodId, worktreePath) {
|
|
6388
|
-
|
|
7005
|
+
if (resolveWorkspaceRuntimeOrDefault(worktreePath) === "devsy") {
|
|
7006
|
+
const result2 = deleteOwnedDevsyWorkspace(devpodId, worktreePath);
|
|
7007
|
+
resetWorkspaceRuntimeCaches();
|
|
7008
|
+
return result2;
|
|
7009
|
+
}
|
|
7010
|
+
const result = mutateOwnedDevpodWorkspace("delete", devpodId, worktreePath);
|
|
7011
|
+
resetWorkspaceRuntimeCaches();
|
|
7012
|
+
return result;
|
|
6389
7013
|
}
|
|
6390
7014
|
function startDevpodWorkspace(options) {
|
|
7015
|
+
if (resolveWorkspaceRuntimeOrDefault(options.repoPath) === "devsy") {
|
|
7016
|
+
try {
|
|
7017
|
+
const result = startDevsyWorkspace({
|
|
7018
|
+
repoPath: options.repoPath,
|
|
7019
|
+
devsyId: options.devpodId,
|
|
7020
|
+
devcontainerPath: options.devcontainerPath,
|
|
7021
|
+
recreate: options.recreate,
|
|
7022
|
+
quiet: options.quiet,
|
|
7023
|
+
workspace: options.workspace,
|
|
7024
|
+
inactivityTimeout: readWorkspaceRuntimeConfig().devsyInactivityTimeout
|
|
7025
|
+
});
|
|
7026
|
+
resetWorkspaceRuntimeCaches();
|
|
7027
|
+
return result;
|
|
7028
|
+
} catch (error) {
|
|
7029
|
+
if (error instanceof DevsyStartPostconditionError) {
|
|
7030
|
+
resetWorkspaceRuntimeCaches();
|
|
7031
|
+
throw new DevpodStartPostconditionError(error.message);
|
|
7032
|
+
}
|
|
7033
|
+
throw error;
|
|
7034
|
+
}
|
|
7035
|
+
}
|
|
6391
7036
|
const activity = options.recreate ? "DevPod recreate" : "DevPod start";
|
|
6392
|
-
return
|
|
6393
|
-
const workspaces = listDevpodWorkspaces();
|
|
7037
|
+
return withMutationLock2(activity, options.repoPath, () => {
|
|
7038
|
+
const workspaces = listDevpodWorkspaces(options.repoPath);
|
|
6394
7039
|
let devpodId = options.devpodId ?? selectDevpodWorkspace(workspaces, options.repoPath)?.id;
|
|
6395
7040
|
if (devpodId) {
|
|
6396
7041
|
const before = inspectDevpodWorkspaceOwnership(workspaces, devpodId, options.repoPath);
|
|
@@ -6428,7 +7073,7 @@ function startDevpodWorkspace(options) {
|
|
|
6428
7073
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
6429
7074
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
6430
7075
|
}
|
|
6431
|
-
const result = (0,
|
|
7076
|
+
const result = (0, import_node_child_process15.spawnSync)("devpod", args, {
|
|
6432
7077
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
6433
7078
|
env
|
|
6434
7079
|
});
|
|
@@ -6436,7 +7081,7 @@ function startDevpodWorkspace(options) {
|
|
|
6436
7081
|
throw new Error(`devpod up failed for '${devpodId ?? options.repoPath}'.`);
|
|
6437
7082
|
}
|
|
6438
7083
|
try {
|
|
6439
|
-
const attached = listDevpodWorkspaces();
|
|
7084
|
+
const attached = listDevpodWorkspaces(options.repoPath);
|
|
6440
7085
|
devpodId ??= selectDevpodWorkspace(attached, options.repoPath)?.id;
|
|
6441
7086
|
if (!devpodId) {
|
|
6442
7087
|
throw new Error(`DevPod did not attach '${options.repoPath}' after startup.`);
|
|
@@ -6448,6 +7093,7 @@ function startDevpodWorkspace(options) {
|
|
|
6448
7093
|
`DevPod did not attach '${options.repoPath}' as '${devpodId}' after startup.`
|
|
6449
7094
|
);
|
|
6450
7095
|
}
|
|
7096
|
+
resetWorkspaceRuntimeCaches();
|
|
6451
7097
|
return devpodId;
|
|
6452
7098
|
} catch (error) {
|
|
6453
7099
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -6455,17 +7101,19 @@ function startDevpodWorkspace(options) {
|
|
|
6455
7101
|
}
|
|
6456
7102
|
});
|
|
6457
7103
|
}
|
|
6458
|
-
var
|
|
7104
|
+
var import_node_child_process15, import_node_fs19, import_node_path18, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
|
|
6459
7105
|
var init_devpod_mutation = __esm({
|
|
6460
7106
|
"src/core/devpod-mutation.ts"() {
|
|
6461
7107
|
"use strict";
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
7108
|
+
import_node_child_process15 = require("child_process");
|
|
7109
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7110
|
+
import_node_path18 = __toESM(require("path"));
|
|
6465
7111
|
init_devpod_workspaces();
|
|
7112
|
+
init_devsy_mutation();
|
|
6466
7113
|
init_file_lock();
|
|
6467
7114
|
init_router();
|
|
6468
|
-
|
|
7115
|
+
init_workspace_runtime();
|
|
7116
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path18.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
6469
7117
|
DEVPOD_MUTATION_WAIT_MS = 6e4;
|
|
6470
7118
|
DevpodStartPostconditionError = class extends Error {
|
|
6471
7119
|
};
|
|
@@ -6479,7 +7127,7 @@ function commandError(command, repoPath, stderr) {
|
|
|
6479
7127
|
);
|
|
6480
7128
|
}
|
|
6481
7129
|
function resolveGitCommonDir(repoPath) {
|
|
6482
|
-
const result = (0,
|
|
7130
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
|
|
6483
7131
|
encoding: "utf-8",
|
|
6484
7132
|
env: READ_ONLY_GIT_ENV
|
|
6485
7133
|
});
|
|
@@ -6487,10 +7135,10 @@ function resolveGitCommonDir(repoPath) {
|
|
|
6487
7135
|
if (result.status !== 0 || !output2) {
|
|
6488
7136
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
6489
7137
|
}
|
|
6490
|
-
return comparableWorkspacePath(
|
|
7138
|
+
return comparableWorkspacePath(import_node_path19.default.isAbsolute(output2) ? output2 : import_node_path19.default.resolve(repoPath, output2));
|
|
6491
7139
|
}
|
|
6492
7140
|
function resolveGitTopLevel(repoPath) {
|
|
6493
|
-
const result = (0,
|
|
7141
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
6494
7142
|
encoding: "utf-8",
|
|
6495
7143
|
env: READ_ONLY_GIT_ENV
|
|
6496
7144
|
});
|
|
@@ -6501,7 +7149,7 @@ function resolveGitTopLevel(repoPath) {
|
|
|
6501
7149
|
return comparableWorkspacePath(output2);
|
|
6502
7150
|
}
|
|
6503
7151
|
function listGitWorktrees(repoPath) {
|
|
6504
|
-
const result = (0,
|
|
7152
|
+
const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
|
|
6505
7153
|
encoding: "utf-8",
|
|
6506
7154
|
env: READ_ONLY_GIT_ENV
|
|
6507
7155
|
});
|
|
@@ -6538,7 +7186,7 @@ function listGitWorktrees(repoPath) {
|
|
|
6538
7186
|
return worktrees;
|
|
6539
7187
|
}
|
|
6540
7188
|
function ownershipDirectory(repoPath) {
|
|
6541
|
-
return
|
|
7189
|
+
return import_node_path19.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
6542
7190
|
}
|
|
6543
7191
|
function validateWorkspace(value, label) {
|
|
6544
7192
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -6566,7 +7214,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
6566
7214
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
6567
7215
|
);
|
|
6568
7216
|
}
|
|
6569
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
7217
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path19.default.isAbsolute(candidate.worktreePath)) {
|
|
6570
7218
|
throw new Error("invalid workspace ownership worktreePath");
|
|
6571
7219
|
}
|
|
6572
7220
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -6586,7 +7234,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
6586
7234
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
6587
7235
|
let parsed;
|
|
6588
7236
|
try {
|
|
6589
|
-
parsed = JSON.parse(
|
|
7237
|
+
parsed = JSON.parse(import_node_fs20.default.readFileSync(filePath, "utf-8"));
|
|
6590
7238
|
} catch (error) {
|
|
6591
7239
|
if (error instanceof SyntaxError) {
|
|
6592
7240
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -6602,7 +7250,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
6602
7250
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
6603
7251
|
let entries;
|
|
6604
7252
|
try {
|
|
6605
|
-
entries =
|
|
7253
|
+
entries = import_node_fs20.default.readdirSync(directory, { withFileTypes: true });
|
|
6606
7254
|
} catch (error) {
|
|
6607
7255
|
if (error.code === "ENOENT") return [];
|
|
6608
7256
|
throw error;
|
|
@@ -6610,14 +7258,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
6610
7258
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
6611
7259
|
const workspace = entry.name.slice(0, -".json".length);
|
|
6612
7260
|
validateWorkspace(workspace, "filename");
|
|
6613
|
-
return readRecordFile(
|
|
7261
|
+
return readRecordFile(import_node_path19.default.join(directory, entry.name), workspace);
|
|
6614
7262
|
});
|
|
6615
7263
|
}
|
|
6616
7264
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
6617
7265
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
6618
7266
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
6619
7267
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
6620
|
-
const filePath =
|
|
7268
|
+
const filePath = import_node_path19.default.join(directory, `${workspace}.json`);
|
|
6621
7269
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6622
7270
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
6623
7271
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -6653,9 +7301,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
6653
7301
|
return record;
|
|
6654
7302
|
}
|
|
6655
7303
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
6656
|
-
const filePath =
|
|
7304
|
+
const filePath = import_node_path19.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
6657
7305
|
try {
|
|
6658
|
-
|
|
7306
|
+
import_node_fs20.default.rmSync(filePath);
|
|
6659
7307
|
return true;
|
|
6660
7308
|
} catch (error) {
|
|
6661
7309
|
if (error.code === "ENOENT") return false;
|
|
@@ -6666,7 +7314,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
6666
7314
|
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
7315
|
}
|
|
6668
7316
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
6669
|
-
const filePath =
|
|
7317
|
+
const filePath = import_node_path19.default.join(
|
|
6670
7318
|
directory,
|
|
6671
7319
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
6672
7320
|
);
|
|
@@ -6678,14 +7326,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
6678
7326
|
throw error;
|
|
6679
7327
|
}
|
|
6680
7328
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
6681
|
-
|
|
7329
|
+
import_node_fs20.default.rmSync(filePath);
|
|
6682
7330
|
return "removed";
|
|
6683
7331
|
}
|
|
6684
7332
|
function withWorkspaceOwnershipTransaction(repoPath, operation) {
|
|
6685
7333
|
const directory = ownershipDirectory(repoPath);
|
|
6686
|
-
|
|
7334
|
+
import_node_fs20.default.mkdirSync(directory, { recursive: true });
|
|
6687
7335
|
return withFileLockSync(
|
|
6688
|
-
|
|
7336
|
+
import_node_path19.default.join(directory, ".lock"),
|
|
6689
7337
|
{ activity: "workspace ownership transaction", target: `'${repoPath}'`, waitMs: 5e3 },
|
|
6690
7338
|
() => operation({
|
|
6691
7339
|
list: () => listWorkspaceOwnershipInDirectory(directory),
|
|
@@ -6716,7 +7364,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
|
|
|
6716
7364
|
if (worktree?.locked) {
|
|
6717
7365
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
6718
7366
|
}
|
|
6719
|
-
if ((!worktree || worktree.prunable) &&
|
|
7367
|
+
if ((!worktree || worktree.prunable) && import_node_fs20.default.existsSync(record.worktreePath)) {
|
|
6720
7368
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
6721
7369
|
}
|
|
6722
7370
|
if (!worktree || worktree.prunable) {
|
|
@@ -6740,20 +7388,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
6740
7388
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
6741
7389
|
);
|
|
6742
7390
|
}
|
|
6743
|
-
var
|
|
7391
|
+
var import_node_child_process16, import_node_fs20, import_node_path19, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
6744
7392
|
var init_workspace_ownership = __esm({
|
|
6745
7393
|
"src/core/workspace-ownership.ts"() {
|
|
6746
7394
|
"use strict";
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
7395
|
+
import_node_child_process16 = require("child_process");
|
|
7396
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
7397
|
+
import_node_path19 = __toESM(require("path"));
|
|
6750
7398
|
init_atomic_file();
|
|
6751
7399
|
init_devpod_workspaces();
|
|
6752
7400
|
init_file_lock();
|
|
6753
7401
|
init_workspace();
|
|
6754
7402
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
6755
7403
|
OWNERSHIP_VERSION = 1;
|
|
6756
|
-
OWNERSHIP_DIR =
|
|
7404
|
+
OWNERSHIP_DIR = import_node_path19.default.join("devrouter", "workspaces");
|
|
6757
7405
|
}
|
|
6758
7406
|
});
|
|
6759
7407
|
|
|
@@ -6767,10 +7415,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
6767
7415
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
6768
7416
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
6769
7417
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
6770
|
-
const localRoot =
|
|
7418
|
+
const localRoot = import_node_path20.default.join(comparableRepo, "trees") + import_node_path20.default.sep;
|
|
6771
7419
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
6772
|
-
const legacyPrefix = `${
|
|
6773
|
-
return
|
|
7420
|
+
const legacyPrefix = `${import_node_path20.default.basename(comparableRepo)}-`;
|
|
7421
|
+
return import_node_path20.default.dirname(comparableWorktree) === import_node_path20.default.dirname(comparableRepo) && import_node_path20.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
6774
7422
|
}
|
|
6775
7423
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
6776
7424
|
const actions = [
|
|
@@ -6886,7 +7534,7 @@ function revalidateCandidate(candidate, repoPath, transaction) {
|
|
|
6886
7534
|
const fresh = ownedCandidate(
|
|
6887
7535
|
record,
|
|
6888
7536
|
listGitWorktrees(repoPath),
|
|
6889
|
-
listDevpodWorkspaces(),
|
|
7537
|
+
listDevpodWorkspaces(record.worktreePath),
|
|
6890
7538
|
listHostRouteState()
|
|
6891
7539
|
);
|
|
6892
7540
|
if (fresh.eligible) return { status: "ready", candidate: fresh, record };
|
|
@@ -6987,11 +7635,20 @@ function inspectWorkspaceGc(repo) {
|
|
|
6987
7635
|
const repoPath = comparableWorkspacePath(resolveRepoPath(repo));
|
|
6988
7636
|
const worktrees = listGitWorktrees(repoPath);
|
|
6989
7637
|
const records = listWorkspaceOwnership(repoPath);
|
|
6990
|
-
const devpods = listDevpodWorkspaces();
|
|
6991
7638
|
const routes = listHostRouteState();
|
|
6992
7639
|
const candidates = [
|
|
6993
|
-
|
|
6994
|
-
|
|
7640
|
+
// Per-record resolution keeps mixed DevPod+Devsy fleets on the registry
|
|
7641
|
+
// that owns each record (snapshot reads: the apply path revalidates
|
|
7642
|
+
// live); legacy detection scans both registries.
|
|
7643
|
+
...records.map(
|
|
7644
|
+
(record) => ownedCandidate(
|
|
7645
|
+
record,
|
|
7646
|
+
worktrees,
|
|
7647
|
+
listDevpodWorkspacesFromSnapshots(record.worktreePath),
|
|
7648
|
+
routes
|
|
7649
|
+
)
|
|
7650
|
+
),
|
|
7651
|
+
...legacyCandidates(repoPath, records, worktrees, listMergedRuntimeWorkspaces(), routes)
|
|
6995
7652
|
];
|
|
6996
7653
|
return {
|
|
6997
7654
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7013,11 +7670,11 @@ function applyWorkspaceGc(plan) {
|
|
|
7013
7670
|
candidates
|
|
7014
7671
|
};
|
|
7015
7672
|
}
|
|
7016
|
-
var
|
|
7673
|
+
var import_node_path20;
|
|
7017
7674
|
var init_workspace_gc = __esm({
|
|
7018
7675
|
"src/core/workspace-gc.ts"() {
|
|
7019
7676
|
"use strict";
|
|
7020
|
-
|
|
7677
|
+
import_node_path20 = __toESM(require("path"));
|
|
7021
7678
|
init_devpod_mutation();
|
|
7022
7679
|
init_devpod_workspaces();
|
|
7023
7680
|
init_host_routes();
|
|
@@ -7087,11 +7744,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
7087
7744
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
7088
7745
|
continue;
|
|
7089
7746
|
}
|
|
7090
|
-
if (!
|
|
7747
|
+
if (!import_node_fs21.default.existsSync(absolutePath)) {
|
|
7091
7748
|
continue;
|
|
7092
7749
|
}
|
|
7093
7750
|
try {
|
|
7094
|
-
const raw =
|
|
7751
|
+
const raw = import_node_fs21.default.readFileSync(absolutePath, "utf-8");
|
|
7095
7752
|
const parsed = import_yaml5.default.parse(raw);
|
|
7096
7753
|
const root = asRecord2(parsed);
|
|
7097
7754
|
const services = asRecord2(root?.services);
|
|
@@ -7370,7 +8027,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
7370
8027
|
const config = runtimeConfig.config;
|
|
7371
8028
|
loadedConfig = config;
|
|
7372
8029
|
loadedWorkspace = runtimeConfig.workspace;
|
|
7373
|
-
const cliVersion = true ? "0.0.
|
|
8030
|
+
const cliVersion = true ? "0.0.42" : "0.0.0-dev";
|
|
7374
8031
|
const configVersion = config.devrouter?.version;
|
|
7375
8032
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
7376
8033
|
addCheck(checks, {
|
|
@@ -7400,9 +8057,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
7400
8057
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
7401
8058
|
app: app.name,
|
|
7402
8059
|
filePath,
|
|
7403
|
-
absolutePath:
|
|
8060
|
+
absolutePath: import_node_path21.default.resolve(repo.path, filePath)
|
|
7404
8061
|
}))
|
|
7405
|
-
).filter((entry) => !
|
|
8062
|
+
).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
|
|
7406
8063
|
addCheck(checks, {
|
|
7407
8064
|
id: "repo.compose-files",
|
|
7408
8065
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -7445,8 +8102,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
7445
8102
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
7446
8103
|
app: app.name,
|
|
7447
8104
|
cwd: app.hostRun.cwd,
|
|
7448
|
-
absolutePath:
|
|
7449
|
-
})).filter((entry) => !
|
|
8105
|
+
absolutePath: import_node_path21.default.resolve(repo.path, app.hostRun.cwd)
|
|
8106
|
+
})).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
|
|
7450
8107
|
addCheck(checks, {
|
|
7451
8108
|
id: "repo.host-cwd",
|
|
7452
8109
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -7590,12 +8247,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
7590
8247
|
nextSteps
|
|
7591
8248
|
};
|
|
7592
8249
|
}
|
|
7593
|
-
var
|
|
8250
|
+
var import_node_fs21, import_node_path21, import_yaml5, POSTGRES_DEFAULTS;
|
|
7594
8251
|
var init_doctor = __esm({
|
|
7595
8252
|
"src/core/doctor.ts"() {
|
|
7596
8253
|
"use strict";
|
|
7597
|
-
|
|
7598
|
-
|
|
8254
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
8255
|
+
import_node_path21 = __toESM(require("path"));
|
|
7599
8256
|
import_yaml5 = __toESM(require("yaml"));
|
|
7600
8257
|
init_docker();
|
|
7601
8258
|
init_host_routes();
|
|
@@ -7702,6 +8359,32 @@ async function runSetup(options = {}) {
|
|
|
7702
8359
|
})
|
|
7703
8360
|
);
|
|
7704
8361
|
}
|
|
8362
|
+
if (options.workspaceRuntime !== void 0 || options.devsyInactivityTimeout !== void 0) {
|
|
8363
|
+
try {
|
|
8364
|
+
const existing = readWorkspaceRuntimeConfig();
|
|
8365
|
+
const next = {
|
|
8366
|
+
runtime: options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : existing.runtime,
|
|
8367
|
+
devsyInactivityTimeout: options.devsyInactivityTimeout ?? existing.devsyInactivityTimeout
|
|
8368
|
+
};
|
|
8369
|
+
writeWorkspaceRuntimeConfig(next);
|
|
8370
|
+
actions.push(
|
|
8371
|
+
action("performed", {
|
|
8372
|
+
id: "global.workspace-runtime-config",
|
|
8373
|
+
summary: `Persisted workspace runtime preferences (runtime=${next.runtime ?? "unset"}, devsyInactivityTimeout=${next.devsyInactivityTimeout ?? "unset"}).`
|
|
8374
|
+
})
|
|
8375
|
+
);
|
|
8376
|
+
} catch (error) {
|
|
8377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8378
|
+
actions.push(
|
|
8379
|
+
action("failed", {
|
|
8380
|
+
id: "global.workspace-runtime-config",
|
|
8381
|
+
summary: "Failed to persist workspace runtime preferences.",
|
|
8382
|
+
details: message,
|
|
8383
|
+
suggestion: "Use: devrouter setup --yes --workspace-runtime <devpod|devsy> [--devsy-inactivity-timeout <duration>]"
|
|
8384
|
+
})
|
|
8385
|
+
);
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
7705
8388
|
try {
|
|
7706
8389
|
const existed = await networkExists(DEVNET_NAME);
|
|
7707
8390
|
await ensureNetwork(DEVNET_NAME);
|
|
@@ -7797,6 +8480,7 @@ var init_setup = __esm({
|
|
|
7797
8480
|
init_router();
|
|
7798
8481
|
init_tls();
|
|
7799
8482
|
init_tool_diagnostics();
|
|
8483
|
+
init_workspace_runtime();
|
|
7800
8484
|
}
|
|
7801
8485
|
});
|
|
7802
8486
|
|
|
@@ -7806,7 +8490,12 @@ __export(setup_exports, {
|
|
|
7806
8490
|
runSetupCommand: () => runSetupCommand
|
|
7807
8491
|
});
|
|
7808
8492
|
async function runSetupCommand(options) {
|
|
7809
|
-
const report = await runSetup({
|
|
8493
|
+
const report = await runSetup({
|
|
8494
|
+
repo: options.repo,
|
|
8495
|
+
yes: Boolean(options.yes),
|
|
8496
|
+
workspaceRuntime: options.workspaceRuntime,
|
|
8497
|
+
devsyInactivityTimeout: options.devsyInactivityTimeout
|
|
8498
|
+
});
|
|
7810
8499
|
if (options.json) {
|
|
7811
8500
|
printJSON(report);
|
|
7812
8501
|
} else {
|
|
@@ -7853,12 +8542,12 @@ function parseSsPortListeners(stdout, port) {
|
|
|
7853
8542
|
return listeners;
|
|
7854
8543
|
}
|
|
7855
8544
|
function findPortListeners(port) {
|
|
7856
|
-
const result = (0,
|
|
8545
|
+
const result = (0, import_node_child_process17.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
|
|
7857
8546
|
encoding: "utf-8"
|
|
7858
8547
|
});
|
|
7859
8548
|
if (result.error && result.error.code === "ENOENT") {
|
|
7860
8549
|
if (process.platform === "linux") {
|
|
7861
|
-
const ssResult = (0,
|
|
8550
|
+
const ssResult = (0, import_node_child_process17.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
7862
8551
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
7863
8552
|
return parseSsPortListeners(ssResult.stdout, port);
|
|
7864
8553
|
}
|
|
@@ -7887,11 +8576,11 @@ function findPortListeners(port) {
|
|
|
7887
8576
|
};
|
|
7888
8577
|
});
|
|
7889
8578
|
}
|
|
7890
|
-
var
|
|
8579
|
+
var import_node_child_process17;
|
|
7891
8580
|
var init_ports = __esm({
|
|
7892
8581
|
"src/util/ports.ts"() {
|
|
7893
8582
|
"use strict";
|
|
7894
|
-
|
|
8583
|
+
import_node_child_process17 = require("child_process");
|
|
7895
8584
|
}
|
|
7896
8585
|
});
|
|
7897
8586
|
|
|
@@ -7968,18 +8657,18 @@ function probeHttpRoute(host, options = {}) {
|
|
|
7968
8657
|
args.push("--cacert", CERT_FILE);
|
|
7969
8658
|
}
|
|
7970
8659
|
args.push(url);
|
|
7971
|
-
const result = (0,
|
|
8660
|
+
const result = (0, import_node_child_process18.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
7972
8661
|
const stdout = result.stdout?.trim() ?? "";
|
|
7973
8662
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
7974
8663
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
7975
8664
|
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
8665
|
return { ok, status, details };
|
|
7977
8666
|
}
|
|
7978
|
-
var
|
|
8667
|
+
var import_node_child_process18;
|
|
7979
8668
|
var init_http_route_probe = __esm({
|
|
7980
8669
|
"src/core/http-route-probe.ts"() {
|
|
7981
8670
|
"use strict";
|
|
7982
|
-
|
|
8671
|
+
import_node_child_process18 = require("child_process");
|
|
7983
8672
|
init_router();
|
|
7984
8673
|
init_tls();
|
|
7985
8674
|
}
|
|
@@ -8058,11 +8747,11 @@ var init_route_publication = __esm({
|
|
|
8058
8747
|
// src/core/workspace-ensure.ts
|
|
8059
8748
|
function assertOverlay(container, repoPath) {
|
|
8060
8749
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
8061
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
8750
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path22.default.join(repoPath, ".devcontainer"))) {
|
|
8062
8751
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
8063
8752
|
}
|
|
8064
8753
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
8065
|
-
const expectedOverlay =
|
|
8754
|
+
const expectedOverlay = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
8066
8755
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
8067
8756
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
8068
8757
|
}
|
|
@@ -8339,7 +9028,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8339
9028
|
target
|
|
8340
9029
|
});
|
|
8341
9030
|
if (target.kind === "linked") {
|
|
8342
|
-
const workspaceEnv = (0,
|
|
9031
|
+
const workspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8343
9032
|
"docker",
|
|
8344
9033
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
8345
9034
|
{ encoding: "utf-8" }
|
|
@@ -8349,7 +9038,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8349
9038
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
8350
9039
|
);
|
|
8351
9040
|
}
|
|
8352
|
-
const devrouterWorkspaceEnv = (0,
|
|
9041
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process19.spawnSync)(
|
|
8353
9042
|
"docker",
|
|
8354
9043
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
8355
9044
|
{ encoding: "utf-8" }
|
|
@@ -8360,7 +9049,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
8360
9049
|
);
|
|
8361
9050
|
}
|
|
8362
9051
|
}
|
|
8363
|
-
const gitCheck = (0,
|
|
9052
|
+
const gitCheck = (0, import_node_child_process19.spawnSync)(
|
|
8364
9053
|
"docker",
|
|
8365
9054
|
[
|
|
8366
9055
|
"exec",
|
|
@@ -8414,7 +9103,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
8414
9103
|
);
|
|
8415
9104
|
}
|
|
8416
9105
|
function resolveLinkedTarget(repoPath) {
|
|
8417
|
-
const devpods = listDevpodWorkspaces();
|
|
9106
|
+
const devpods = listDevpodWorkspaces(repoPath);
|
|
8418
9107
|
const existingDevpod = selectDevpodWorkspace(devpods, repoPath);
|
|
8419
9108
|
const persisted = readPersistedWorkspace(repoPath);
|
|
8420
9109
|
const candidate = existingDevpod?.id ?? persisted ?? resolveWorktreeWorkspace(repoPath);
|
|
@@ -8438,7 +9127,7 @@ function resolveLinkedTarget(repoPath) {
|
|
|
8438
9127
|
};
|
|
8439
9128
|
}
|
|
8440
9129
|
function resolvePrimaryTarget(repoPath) {
|
|
8441
|
-
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
9130
|
+
const existingDevpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
8442
9131
|
return {
|
|
8443
9132
|
kind: "primary",
|
|
8444
9133
|
devpodId: existingDevpod?.id,
|
|
@@ -8447,14 +9136,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
8447
9136
|
}
|
|
8448
9137
|
function isPrimaryCheckout(repoPath) {
|
|
8449
9138
|
try {
|
|
8450
|
-
return
|
|
9139
|
+
return import_node_fs22.default.statSync(import_node_path22.default.join(repoPath, ".git")).isDirectory();
|
|
8451
9140
|
} catch {
|
|
8452
9141
|
return false;
|
|
8453
9142
|
}
|
|
8454
9143
|
}
|
|
8455
9144
|
function openUrls(urls) {
|
|
8456
9145
|
for (const url of urls) {
|
|
8457
|
-
const opened = (0,
|
|
9146
|
+
const opened = (0, import_node_child_process19.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
8458
9147
|
if (opened.status !== 0) {
|
|
8459
9148
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
8460
9149
|
`);
|
|
@@ -8497,8 +9186,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8497
9186
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
8498
9187
|
let devpodId = target.devpodId;
|
|
8499
9188
|
if (target.kind === "linked") {
|
|
8500
|
-
const overlayPath =
|
|
8501
|
-
if (!
|
|
9189
|
+
const overlayPath = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9190
|
+
if (!import_node_fs22.default.existsSync(overlayPath)) {
|
|
8502
9191
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
8503
9192
|
}
|
|
8504
9193
|
}
|
|
@@ -8565,7 +9254,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
8565
9254
|
}
|
|
8566
9255
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
8567
9256
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
8568
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
9257
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path22.default.basename(repoPath)) ?? "app";
|
|
8569
9258
|
for (const [index, app] of apps.entries()) {
|
|
8570
9259
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
8571
9260
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9009,13 +9698,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9009
9698
|
}
|
|
9010
9699
|
});
|
|
9011
9700
|
}
|
|
9012
|
-
var
|
|
9701
|
+
var import_node_child_process19, import_node_fs22, import_node_path22, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
9013
9702
|
var init_workspace_ensure = __esm({
|
|
9014
9703
|
"src/core/workspace-ensure.ts"() {
|
|
9015
9704
|
"use strict";
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9705
|
+
import_node_child_process19 = require("child_process");
|
|
9706
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
9707
|
+
import_node_path22 = __toESM(require("path"));
|
|
9019
9708
|
init_devcontainer_profile();
|
|
9020
9709
|
init_devpod_environment();
|
|
9021
9710
|
init_devpod_mutation();
|
|
@@ -9095,20 +9784,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
9095
9784
|
);
|
|
9096
9785
|
}
|
|
9097
9786
|
function defaultWorktreePath(mainRepo, ws) {
|
|
9098
|
-
return
|
|
9787
|
+
return import_node_path23.default.join(mainRepo, "trees", ws);
|
|
9099
9788
|
}
|
|
9100
9789
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
9101
|
-
const ignored = (0,
|
|
9790
|
+
const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
9102
9791
|
encoding: "utf-8"
|
|
9103
9792
|
});
|
|
9104
9793
|
if (ignored.status !== 0) {
|
|
9105
9794
|
throw new Error(
|
|
9106
|
-
`Default worktree root '${
|
|
9795
|
+
`Default worktree root '${import_node_path23.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path23.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
9107
9796
|
);
|
|
9108
9797
|
}
|
|
9109
9798
|
}
|
|
9110
9799
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
9111
|
-
return
|
|
9800
|
+
return import_node_path23.default.join(import_node_path23.default.dirname(mainRepo), `${import_node_path23.default.basename(mainRepo)}-${ws}`);
|
|
9112
9801
|
}
|
|
9113
9802
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
9114
9803
|
const candidates = [
|
|
@@ -9220,13 +9909,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
9220
9909
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
9221
9910
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
9222
9911
|
}
|
|
9223
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
9912
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs23.default.existsSync(target.worktreePath)) return;
|
|
9224
9913
|
if (target.worktree.locked) {
|
|
9225
9914
|
throw new Error(
|
|
9226
9915
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
9227
9916
|
);
|
|
9228
9917
|
}
|
|
9229
|
-
const status = (0,
|
|
9918
|
+
const status = (0, import_node_child_process20.spawnSync)(
|
|
9230
9919
|
"git",
|
|
9231
9920
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
9232
9921
|
{ encoding: "utf-8" }
|
|
@@ -9247,8 +9936,8 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9247
9936
|
if (!ws) {
|
|
9248
9937
|
throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
|
|
9249
9938
|
}
|
|
9250
|
-
const worktreePath = opts.path ?
|
|
9251
|
-
if (
|
|
9939
|
+
const worktreePath = opts.path ? import_node_path23.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
|
|
9940
|
+
if (import_node_fs23.default.existsSync(worktreePath)) {
|
|
9252
9941
|
const registered = listGitWorktrees(mainRepo).find(
|
|
9253
9942
|
(worktree) => sameWorkspacePath(worktree.path, worktreePath)
|
|
9254
9943
|
);
|
|
@@ -9266,11 +9955,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
9266
9955
|
if (!opts.path) {
|
|
9267
9956
|
assertDefaultWorktreeRootIgnored(mainRepo);
|
|
9268
9957
|
}
|
|
9269
|
-
const add = (0,
|
|
9958
|
+
const add = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "add", worktreePath, branch], {
|
|
9270
9959
|
encoding: "utf-8"
|
|
9271
9960
|
});
|
|
9272
9961
|
if (add.status !== 0) {
|
|
9273
|
-
const addNew = (0,
|
|
9962
|
+
const addNew = (0, import_node_child_process20.spawnSync)(
|
|
9274
9963
|
"git",
|
|
9275
9964
|
["-C", mainRepo, "worktree", "add", "-b", branch, worktreePath],
|
|
9276
9965
|
{
|
|
@@ -9304,12 +9993,9 @@ function workspaceLs(repoPath) {
|
|
|
9304
9993
|
const mainRepo = resolveRepoPath(repoPath);
|
|
9305
9994
|
const worktrees = identifyGitWorktrees(mainRepo);
|
|
9306
9995
|
const records = listWorkspaceOwnership(mainRepo);
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
} catch {
|
|
9311
|
-
devpods = void 0;
|
|
9312
|
-
}
|
|
9996
|
+
const devpodsForPath = (worktreePath) => {
|
|
9997
|
+
return listDevpodWorkspacesFromSnapshots(worktreePath);
|
|
9998
|
+
};
|
|
9313
9999
|
const paths = Array.from(
|
|
9314
10000
|
/* @__PURE__ */ new Set([
|
|
9315
10001
|
...worktrees.map((worktree) => worktree.path),
|
|
@@ -9322,6 +10008,7 @@ function workspaceLs(repoPath) {
|
|
|
9322
10008
|
return paths.map((worktreePath) => {
|
|
9323
10009
|
const worktree = worktreesByPath.get(worktreePath);
|
|
9324
10010
|
const record = recordsByPath.get(worktreePath);
|
|
10011
|
+
const devpods = devpodsForPath(worktreePath);
|
|
9325
10012
|
const status = record ? inspectWorkspaceOwnership(record, worktrees, devpods) : void 0;
|
|
9326
10013
|
const matchingDevpods = devpods?.filter(
|
|
9327
10014
|
(devpod) => sameWorkspacePath(devpod.source.localFolder, worktreePath)
|
|
@@ -9340,7 +10027,7 @@ function workspaceLs(repoPath) {
|
|
|
9340
10027
|
});
|
|
9341
10028
|
}
|
|
9342
10029
|
function mutateWorkspaceRuntime(action2, resolved, worktrees, quiet = false) {
|
|
9343
|
-
const devpods = listDevpodWorkspaces();
|
|
10030
|
+
const devpods = listDevpodWorkspaces(resolved.worktreePath);
|
|
9344
10031
|
assertDevpodTargetSafe(resolved, worktrees, devpods);
|
|
9345
10032
|
const devpodId = resolved.record?.devpodId ?? resolved.workspace;
|
|
9346
10033
|
const mutation = action2 === "stop" ? stopOwnedDevpodWorkspace(devpodId, resolved.worktreePath) : deleteOwnedDevpodWorkspace(devpodId, resolved.worktreePath);
|
|
@@ -9375,8 +10062,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9375
10062
|
opts.quiet
|
|
9376
10063
|
);
|
|
9377
10064
|
if (removeWorktree) {
|
|
9378
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
9379
|
-
const rm = (0,
|
|
10065
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath)) {
|
|
10066
|
+
const rm = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
9380
10067
|
encoding: "utf-8"
|
|
9381
10068
|
});
|
|
9382
10069
|
if (rm.status !== 0) {
|
|
@@ -9394,7 +10081,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
9394
10081
|
}
|
|
9395
10082
|
return result;
|
|
9396
10083
|
};
|
|
9397
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
10084
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
|
|
9398
10085
|
}
|
|
9399
10086
|
async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
|
|
9400
10087
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -9429,13 +10116,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
9429
10116
|
async function workspaceDown(target, opts = {}) {
|
|
9430
10117
|
return runWorkspaceLifecycle("down", target, opts);
|
|
9431
10118
|
}
|
|
9432
|
-
var
|
|
10119
|
+
var import_node_child_process20, import_node_fs23, import_node_path23;
|
|
9433
10120
|
var init_workspace_lifecycle = __esm({
|
|
9434
10121
|
"src/core/workspace-lifecycle.ts"() {
|
|
9435
10122
|
"use strict";
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
10123
|
+
import_node_child_process20 = require("child_process");
|
|
10124
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
10125
|
+
import_node_path23 = __toESM(require("path"));
|
|
9439
10126
|
init_devpod_mutation();
|
|
9440
10127
|
init_devpod_workspaces();
|
|
9441
10128
|
init_repo_config();
|
|
@@ -9485,7 +10172,7 @@ async function environmentStop(repoPath, options = {}) {
|
|
|
9485
10172
|
}
|
|
9486
10173
|
}
|
|
9487
10174
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9488
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10175
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9489
10176
|
const mutation = devpod ? options.delete ? deleteOwnedDevpodWorkspace(devpod.id, repoPath) : stopOwnedDevpodWorkspace(devpod.id, repoPath) : { status: "absent" };
|
|
9490
10177
|
const removedRoutes = removeHostRoutesWhere(
|
|
9491
10178
|
(route) => sameWorkspacePath(route.repoPath, repoPath)
|
|
@@ -9546,6 +10233,39 @@ var init_stop = __esm({
|
|
|
9546
10233
|
}
|
|
9547
10234
|
});
|
|
9548
10235
|
|
|
10236
|
+
// src/core/devsy-exec.ts
|
|
10237
|
+
function ensureGuidance(repoPath) {
|
|
10238
|
+
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
10239
|
+
}
|
|
10240
|
+
async function devsyExec(repoPath, command) {
|
|
10241
|
+
if (command.length === 0) {
|
|
10242
|
+
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
10243
|
+
}
|
|
10244
|
+
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
10245
|
+
const workspace = selectDevsyWorkspace(listDevsyWorkspaces(), repoPath);
|
|
10246
|
+
if (!workspace) {
|
|
10247
|
+
throw new Error(
|
|
10248
|
+
`No exact Devsy workspace exists for '${repoPath}'. ${ensureGuidance(repoPath)}`
|
|
10249
|
+
);
|
|
10250
|
+
}
|
|
10251
|
+
const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
|
|
10252
|
+
return new Promise((resolve, reject) => {
|
|
10253
|
+
const child = (0, import_node_child_process21.spawn)("devsy", args, { stdio: "inherit" });
|
|
10254
|
+
child.once("error", (error) => reject(new Error(`devsy exec failed: ${error.message}`)));
|
|
10255
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
10256
|
+
});
|
|
10257
|
+
});
|
|
10258
|
+
}
|
|
10259
|
+
var import_node_child_process21;
|
|
10260
|
+
var init_devsy_exec = __esm({
|
|
10261
|
+
"src/core/devsy-exec.ts"() {
|
|
10262
|
+
"use strict";
|
|
10263
|
+
import_node_child_process21 = require("child_process");
|
|
10264
|
+
init_devsy_workspaces();
|
|
10265
|
+
init_workspace();
|
|
10266
|
+
}
|
|
10267
|
+
});
|
|
10268
|
+
|
|
9549
10269
|
// src/core/devpod-exec.ts
|
|
9550
10270
|
function stripDevpodCompletionDiagnostic(value) {
|
|
9551
10271
|
const index = value.indexOf(DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC);
|
|
@@ -9558,27 +10278,27 @@ function stripDevpodCompletionDiagnostic(value) {
|
|
|
9558
10278
|
function quotePosixArg(value) {
|
|
9559
10279
|
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
9560
10280
|
}
|
|
9561
|
-
function
|
|
10281
|
+
function ensureGuidance2(repoPath) {
|
|
9562
10282
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
9563
10283
|
}
|
|
9564
10284
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
9565
|
-
const result = (0,
|
|
10285
|
+
const result = (0, import_node_child_process22.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
9566
10286
|
encoding: "utf-8"
|
|
9567
10287
|
});
|
|
9568
10288
|
if (result.status !== 0) {
|
|
9569
10289
|
const details = [result.error?.message, result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9570
10290
|
throw new Error(
|
|
9571
|
-
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${
|
|
10291
|
+
`Could not prove DevPod '${devpodId}' is running${details ? `: ${details}` : "."} ${ensureGuidance2(repoPath)}`
|
|
9572
10292
|
);
|
|
9573
10293
|
}
|
|
9574
10294
|
let status;
|
|
9575
10295
|
try {
|
|
9576
10296
|
status = JSON.parse(result.stdout);
|
|
9577
10297
|
} catch {
|
|
9578
|
-
throw new Error(`DevPod '${devpodId}' returned invalid status. ${
|
|
10298
|
+
throw new Error(`DevPod '${devpodId}' returned invalid status. ${ensureGuidance2(repoPath)}`);
|
|
9579
10299
|
}
|
|
9580
10300
|
if (status.id !== devpodId || status.state !== "Running") {
|
|
9581
|
-
throw new Error(`DevPod '${devpodId}' is not running. ${
|
|
10301
|
+
throw new Error(`DevPod '${devpodId}' is not running. ${ensureGuidance2(repoPath)}`);
|
|
9582
10302
|
}
|
|
9583
10303
|
}
|
|
9584
10304
|
function resolveWorkspaceDirectory(repoPath) {
|
|
@@ -9586,17 +10306,20 @@ function resolveWorkspaceDirectory(repoPath) {
|
|
|
9586
10306
|
return resolveRunningWorkspaceContainer(repoPath).workspacePath;
|
|
9587
10307
|
} catch (error) {
|
|
9588
10308
|
const message = error instanceof Error ? error.message : String(error);
|
|
9589
|
-
throw new Error(`${message} ${
|
|
10309
|
+
throw new Error(`${message} ${ensureGuidance2(repoPath)}`);
|
|
9590
10310
|
}
|
|
9591
10311
|
}
|
|
9592
10312
|
async function devpodExec(repoPath, command) {
|
|
10313
|
+
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
10314
|
+
return devsyExec(repoPath, command);
|
|
10315
|
+
}
|
|
9593
10316
|
if (command.length === 0) {
|
|
9594
10317
|
throw new Error("No command provided. Use `devrouter exec [path] -- <command...>`.");
|
|
9595
10318
|
}
|
|
9596
10319
|
return withWorkspaceLifecycleLock(repoPath, async () => {
|
|
9597
|
-
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(), repoPath);
|
|
10320
|
+
const devpod = selectDevpodWorkspace(listDevpodWorkspaces(repoPath), repoPath);
|
|
9598
10321
|
if (!devpod) {
|
|
9599
|
-
throw new Error(`No exact DevPod exists for '${repoPath}'. ${
|
|
10322
|
+
throw new Error(`No exact DevPod exists for '${repoPath}'. ${ensureGuidance2(repoPath)}`);
|
|
9600
10323
|
}
|
|
9601
10324
|
assertRunningDevpod(devpod.id, repoPath);
|
|
9602
10325
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
@@ -9618,7 +10341,7 @@ async function devpodExec(repoPath, command) {
|
|
|
9618
10341
|
wrappedCommand
|
|
9619
10342
|
];
|
|
9620
10343
|
return new Promise((resolve, reject) => {
|
|
9621
|
-
const child = (0,
|
|
10344
|
+
const child = (0, import_node_child_process22.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
9622
10345
|
let pending = Buffer.alloc(0);
|
|
9623
10346
|
let statusBytes = Buffer.alloc(0);
|
|
9624
10347
|
let readingStatus = false;
|
|
@@ -9677,15 +10400,17 @@ async function devpodExec(repoPath, command) {
|
|
|
9677
10400
|
});
|
|
9678
10401
|
});
|
|
9679
10402
|
}
|
|
9680
|
-
var
|
|
10403
|
+
var import_node_child_process22, import_node_crypto7, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
9681
10404
|
var init_devpod_exec = __esm({
|
|
9682
10405
|
"src/core/devpod-exec.ts"() {
|
|
9683
10406
|
"use strict";
|
|
9684
|
-
|
|
10407
|
+
import_node_child_process22 = require("child_process");
|
|
9685
10408
|
import_node_crypto7 = require("crypto");
|
|
9686
10409
|
init_devpod_environment();
|
|
9687
10410
|
init_devpod_workspaces();
|
|
10411
|
+
init_devsy_exec();
|
|
9688
10412
|
init_workspace();
|
|
10413
|
+
init_workspace_runtime();
|
|
9689
10414
|
DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC = Buffer.from(
|
|
9690
10415
|
"Error tunneling to container: wait: remote command exited without exit status or exit signal\n"
|
|
9691
10416
|
);
|
|
@@ -9866,7 +10591,7 @@ async function runOpenCommand(name) {
|
|
|
9866
10591
|
);
|
|
9867
10592
|
return;
|
|
9868
10593
|
}
|
|
9869
|
-
const result = (0,
|
|
10594
|
+
const result = (0, import_node_child_process23.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
9870
10595
|
if (result.status !== 0) {
|
|
9871
10596
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
9872
10597
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -9874,11 +10599,11 @@ async function runOpenCommand(name) {
|
|
|
9874
10599
|
process.stdout.write(`Opened ${url}
|
|
9875
10600
|
`);
|
|
9876
10601
|
}
|
|
9877
|
-
var
|
|
10602
|
+
var import_node_child_process23;
|
|
9878
10603
|
var init_open = __esm({
|
|
9879
10604
|
"src/commands/open.ts"() {
|
|
9880
10605
|
"use strict";
|
|
9881
|
-
|
|
10606
|
+
import_node_child_process23 = require("child_process");
|
|
9882
10607
|
init_docker();
|
|
9883
10608
|
init_host_routes();
|
|
9884
10609
|
init_repo_config();
|
|
@@ -9901,7 +10626,7 @@ async function runLogsCommand(options) {
|
|
|
9901
10626
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
9902
10627
|
if (options.follow) {
|
|
9903
10628
|
args.splice(1, 0, "-f");
|
|
9904
|
-
const child = (0,
|
|
10629
|
+
const child = (0, import_node_child_process24.spawn)("docker", args, { stdio: "inherit" });
|
|
9905
10630
|
const onSignal = () => {
|
|
9906
10631
|
child.kill("SIGTERM");
|
|
9907
10632
|
};
|
|
@@ -9915,17 +10640,17 @@ async function runLogsCommand(options) {
|
|
|
9915
10640
|
});
|
|
9916
10641
|
});
|
|
9917
10642
|
} else {
|
|
9918
|
-
const result = (0,
|
|
10643
|
+
const result = (0, import_node_child_process24.spawnSync)("docker", args, { stdio: "inherit" });
|
|
9919
10644
|
if (result.status !== 0) {
|
|
9920
10645
|
throw new Error("Failed to retrieve router logs.");
|
|
9921
10646
|
}
|
|
9922
10647
|
}
|
|
9923
10648
|
}
|
|
9924
|
-
var
|
|
10649
|
+
var import_node_child_process24;
|
|
9925
10650
|
var init_logs = __esm({
|
|
9926
10651
|
"src/commands/logs.ts"() {
|
|
9927
10652
|
"use strict";
|
|
9928
|
-
|
|
10653
|
+
import_node_child_process24 = require("child_process");
|
|
9929
10654
|
init_docker();
|
|
9930
10655
|
init_router();
|
|
9931
10656
|
}
|
|
@@ -9965,17 +10690,17 @@ function asRecord3(value) {
|
|
|
9965
10690
|
return value;
|
|
9966
10691
|
}
|
|
9967
10692
|
function readJson(filePath) {
|
|
9968
|
-
if (!
|
|
10693
|
+
if (!import_node_fs24.default.existsSync(filePath)) {
|
|
9969
10694
|
return void 0;
|
|
9970
10695
|
}
|
|
9971
10696
|
try {
|
|
9972
|
-
return JSON.parse(
|
|
10697
|
+
return JSON.parse(import_node_fs24.default.readFileSync(filePath, "utf-8"));
|
|
9973
10698
|
} catch {
|
|
9974
10699
|
return void 0;
|
|
9975
10700
|
}
|
|
9976
10701
|
}
|
|
9977
10702
|
function relative(repoPath, filePath) {
|
|
9978
|
-
return
|
|
10703
|
+
return import_node_path24.default.relative(repoPath, filePath) || ".";
|
|
9979
10704
|
}
|
|
9980
10705
|
function redactEnvAssignments(value) {
|
|
9981
10706
|
return value.replace(
|
|
@@ -10016,7 +10741,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
10016
10741
|
["bun.lock", "bun"]
|
|
10017
10742
|
];
|
|
10018
10743
|
for (const [fileName, name] of lockfiles) {
|
|
10019
|
-
if (
|
|
10744
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10020
10745
|
return { name, source: fileName };
|
|
10021
10746
|
}
|
|
10022
10747
|
}
|
|
@@ -10031,9 +10756,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
10031
10756
|
if (typeof engines?.node === "string") {
|
|
10032
10757
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
10033
10758
|
}
|
|
10034
|
-
const nvmrc =
|
|
10035
|
-
if (
|
|
10036
|
-
const version =
|
|
10759
|
+
const nvmrc = import_node_path24.default.join(repoPath, ".nvmrc");
|
|
10760
|
+
if (import_node_fs24.default.existsSync(nvmrc)) {
|
|
10761
|
+
const version = import_node_fs24.default.readFileSync(nvmrc, "utf-8").trim();
|
|
10037
10762
|
return { version, source: ".nvmrc" };
|
|
10038
10763
|
}
|
|
10039
10764
|
return void 0;
|
|
@@ -10094,7 +10819,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
10094
10819
|
const files = config.apps.filter(
|
|
10095
10820
|
(app) => app.runtime === "docker"
|
|
10096
10821
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
10097
|
-
(fileName) => !
|
|
10822
|
+
(fileName) => !import_node_path24.default.isAbsolute(fileName) && !import_node_path24.default.normalize(fileName).startsWith("..")
|
|
10098
10823
|
);
|
|
10099
10824
|
return Array.from(new Set(files));
|
|
10100
10825
|
} catch {
|
|
@@ -10112,7 +10837,7 @@ function composeFiles(repoPath) {
|
|
|
10112
10837
|
...configuredComposeFiles(repoPath)
|
|
10113
10838
|
];
|
|
10114
10839
|
return Array.from(new Set(candidates)).filter(
|
|
10115
|
-
(fileName) =>
|
|
10840
|
+
(fileName) => import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))
|
|
10116
10841
|
);
|
|
10117
10842
|
}
|
|
10118
10843
|
function stringArray2(value) {
|
|
@@ -10150,7 +10875,7 @@ function inspectServices(repoPath) {
|
|
|
10150
10875
|
const services = [];
|
|
10151
10876
|
for (const fileName of composeFiles(repoPath)) {
|
|
10152
10877
|
try {
|
|
10153
|
-
const parsed = import_yaml6.default.parse(
|
|
10878
|
+
const parsed = import_yaml6.default.parse(import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8"));
|
|
10154
10879
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
10155
10880
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
10156
10881
|
const service = asRecord3(value);
|
|
@@ -10185,8 +10910,8 @@ function inspectServices(repoPath) {
|
|
|
10185
10910
|
return services;
|
|
10186
10911
|
}
|
|
10187
10912
|
function inspectEnvFiles(repoPath) {
|
|
10188
|
-
const files =
|
|
10189
|
-
const content =
|
|
10913
|
+
const files = import_node_fs24.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
10914
|
+
const content = import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8");
|
|
10190
10915
|
const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
|
|
10191
10916
|
return { path: fileName, names };
|
|
10192
10917
|
});
|
|
@@ -10200,18 +10925,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
10200
10925
|
};
|
|
10201
10926
|
}
|
|
10202
10927
|
function inspectDevcontainer(repoPath) {
|
|
10203
|
-
const dir =
|
|
10204
|
-
if (!
|
|
10928
|
+
const dir = import_node_path24.default.join(repoPath, ".devcontainer");
|
|
10929
|
+
if (!import_node_fs24.default.existsSync(dir)) {
|
|
10205
10930
|
return { exists: false, files: [] };
|
|
10206
10931
|
}
|
|
10207
10932
|
return {
|
|
10208
10933
|
exists: true,
|
|
10209
|
-
files:
|
|
10934
|
+
files: import_node_fs24.default.readdirSync(dir).filter((fileName) => import_node_fs24.default.statSync(import_node_path24.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
10210
10935
|
};
|
|
10211
10936
|
}
|
|
10212
10937
|
function inspectDevrouter(repoPath) {
|
|
10213
10938
|
const configPath = getRepoConfigPath(repoPath);
|
|
10214
|
-
if (!
|
|
10939
|
+
if (!import_node_fs24.default.existsSync(configPath)) {
|
|
10215
10940
|
return {
|
|
10216
10941
|
exists: false,
|
|
10217
10942
|
configPath,
|
|
@@ -10255,15 +10980,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
10255
10980
|
["AGENTS.md", "agents"],
|
|
10256
10981
|
["CLAUDE.md", "claude"]
|
|
10257
10982
|
]) {
|
|
10258
|
-
if (
|
|
10983
|
+
if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
|
|
10259
10984
|
results.push({ path: fileName, kind });
|
|
10260
10985
|
}
|
|
10261
10986
|
}
|
|
10262
|
-
const skillsDir =
|
|
10263
|
-
if (
|
|
10264
|
-
for (const name of
|
|
10265
|
-
const skillPath =
|
|
10266
|
-
if (
|
|
10987
|
+
const skillsDir = import_node_path24.default.join(repoPath, ".agents", "skills");
|
|
10988
|
+
if (import_node_fs24.default.existsSync(skillsDir)) {
|
|
10989
|
+
for (const name of import_node_fs24.default.readdirSync(skillsDir).sort()) {
|
|
10990
|
+
const skillPath = import_node_path24.default.join(skillsDir, name, "SKILL.md");
|
|
10991
|
+
if (import_node_fs24.default.existsSync(skillPath)) {
|
|
10267
10992
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
10268
10993
|
}
|
|
10269
10994
|
}
|
|
@@ -10306,7 +11031,7 @@ function buildIssues(report) {
|
|
|
10306
11031
|
}
|
|
10307
11032
|
function inspectRepo(options = {}) {
|
|
10308
11033
|
const repoPath = resolveRepoPath(options.repo);
|
|
10309
|
-
const pkg = readJson(
|
|
11034
|
+
const pkg = readJson(import_node_path24.default.join(repoPath, "package.json"));
|
|
10310
11035
|
const scripts = inspectScripts(pkg);
|
|
10311
11036
|
const reportWithoutIssues = {
|
|
10312
11037
|
repoPath,
|
|
@@ -10325,12 +11050,12 @@ function inspectRepo(options = {}) {
|
|
|
10325
11050
|
issues: buildIssues(reportWithoutIssues)
|
|
10326
11051
|
};
|
|
10327
11052
|
}
|
|
10328
|
-
var
|
|
11053
|
+
var import_node_fs24, import_node_path24, import_yaml6;
|
|
10329
11054
|
var init_repo_inspect = __esm({
|
|
10330
11055
|
"src/core/repo-inspect.ts"() {
|
|
10331
11056
|
"use strict";
|
|
10332
|
-
|
|
10333
|
-
|
|
11057
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
11058
|
+
import_node_path24 = __toESM(require("path"));
|
|
10334
11059
|
import_yaml6 = __toESM(require("yaml"));
|
|
10335
11060
|
init_repo_config();
|
|
10336
11061
|
}
|
|
@@ -10435,7 +11160,7 @@ function requiredFileChecks(repoPath) {
|
|
|
10435
11160
|
".devcontainer/docker-compose.yml",
|
|
10436
11161
|
".devrouter.yml"
|
|
10437
11162
|
];
|
|
10438
|
-
const missing = required.filter((fileName) => !
|
|
11163
|
+
const missing = required.filter((fileName) => !import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName)));
|
|
10439
11164
|
return {
|
|
10440
11165
|
id: "repo.devcontainer.verify-files",
|
|
10441
11166
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -10654,12 +11379,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
10654
11379
|
nextSteps: collectNextSteps3(checks)
|
|
10655
11380
|
};
|
|
10656
11381
|
}
|
|
10657
|
-
var
|
|
11382
|
+
var import_node_fs25, import_node_path25;
|
|
10658
11383
|
var init_devcontainer_verify = __esm({
|
|
10659
11384
|
"src/core/devcontainer-verify.ts"() {
|
|
10660
11385
|
"use strict";
|
|
10661
|
-
|
|
10662
|
-
|
|
11386
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
11387
|
+
import_node_path25 = __toESM(require("path"));
|
|
10663
11388
|
init_capabilities();
|
|
10664
11389
|
init_doctor();
|
|
10665
11390
|
init_host_routes();
|
|
@@ -10955,7 +11680,7 @@ function packageManagerIssues(repo) {
|
|
|
10955
11680
|
}
|
|
10956
11681
|
function plannedFiles(repoPath, version) {
|
|
10957
11682
|
const repo = inspectRepo({ repo: repoPath });
|
|
10958
|
-
const projectName = sanitizeProjectName(
|
|
11683
|
+
const projectName = sanitizeProjectName(import_node_path26.default.basename(repo.repoPath));
|
|
10959
11684
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
10960
11685
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
10961
11686
|
const port = inferPort2(repo);
|
|
@@ -11000,8 +11725,8 @@ function plannedFiles(repoPath, version) {
|
|
|
11000
11725
|
};
|
|
11001
11726
|
}
|
|
11002
11727
|
function classifyFile(repoPath, file) {
|
|
11003
|
-
const absolutePath =
|
|
11004
|
-
if (!
|
|
11728
|
+
const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
|
|
11729
|
+
if (!import_node_fs26.default.existsSync(absolutePath)) {
|
|
11005
11730
|
return {
|
|
11006
11731
|
path: file.relativePath,
|
|
11007
11732
|
action: "create",
|
|
@@ -11009,7 +11734,7 @@ function classifyFile(repoPath, file) {
|
|
|
11009
11734
|
bytes: Buffer.byteLength(file.content)
|
|
11010
11735
|
};
|
|
11011
11736
|
}
|
|
11012
|
-
const current =
|
|
11737
|
+
const current = import_node_fs26.default.readFileSync(absolutePath, "utf-8");
|
|
11013
11738
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
11014
11739
|
return {
|
|
11015
11740
|
path: file.relativePath,
|
|
@@ -11066,11 +11791,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
11066
11791
|
};
|
|
11067
11792
|
}
|
|
11068
11793
|
function writeFile(repoPath, file) {
|
|
11069
|
-
const absolutePath =
|
|
11070
|
-
|
|
11071
|
-
|
|
11794
|
+
const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
|
|
11795
|
+
import_node_fs26.default.mkdirSync(import_node_path26.default.dirname(absolutePath), { recursive: true });
|
|
11796
|
+
import_node_fs26.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
11072
11797
|
if (file.executable) {
|
|
11073
|
-
|
|
11798
|
+
import_node_fs26.default.chmodSync(absolutePath, 493);
|
|
11074
11799
|
}
|
|
11075
11800
|
}
|
|
11076
11801
|
function writeDevcontainer(options = {}) {
|
|
@@ -11108,12 +11833,12 @@ function writeDevcontainer(options = {}) {
|
|
|
11108
11833
|
nextSteps: postWriteNextSteps(repoPath)
|
|
11109
11834
|
};
|
|
11110
11835
|
}
|
|
11111
|
-
var
|
|
11836
|
+
var import_node_fs26, import_node_path26, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
11112
11837
|
var init_devcontainer_write = __esm({
|
|
11113
11838
|
"src/core/devcontainer-write.ts"() {
|
|
11114
11839
|
"use strict";
|
|
11115
|
-
|
|
11116
|
-
|
|
11840
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11841
|
+
import_node_path26 = __toESM(require("path"));
|
|
11117
11842
|
init_repo_config();
|
|
11118
11843
|
init_repo_inspect();
|
|
11119
11844
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -11504,7 +12229,7 @@ function sanitizeRouterId(value) {
|
|
|
11504
12229
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
11505
12230
|
}
|
|
11506
12231
|
function repoHash(repoPath) {
|
|
11507
|
-
return (0, import_node_crypto8.createHash)("sha1").update(
|
|
12232
|
+
return (0, import_node_crypto8.createHash)("sha1").update(import_node_path27.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
11508
12233
|
}
|
|
11509
12234
|
function asDockerApp(app) {
|
|
11510
12235
|
return app.runtime === "docker";
|
|
@@ -11583,11 +12308,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
11583
12308
|
if (dockerApps.length === 0) {
|
|
11584
12309
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
11585
12310
|
}
|
|
11586
|
-
const cachePath =
|
|
11587
|
-
|
|
11588
|
-
const overlayPath =
|
|
12311
|
+
const cachePath = import_node_path27.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
12312
|
+
import_node_fs27.default.mkdirSync(cachePath, { recursive: true });
|
|
12313
|
+
const overlayPath = import_node_path27.default.join(cachePath, "compose.devrouter.yml");
|
|
11589
12314
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
11590
|
-
|
|
12315
|
+
import_node_fs27.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
11591
12316
|
return {
|
|
11592
12317
|
overlayPath,
|
|
11593
12318
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -11601,7 +12326,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11601
12326
|
fileArgs.push("-f", resolved);
|
|
11602
12327
|
}
|
|
11603
12328
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
11604
|
-
const result = (0,
|
|
12329
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11605
12330
|
encoding: "utf-8",
|
|
11606
12331
|
cwd: repoPath
|
|
11607
12332
|
});
|
|
@@ -11629,7 +12354,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
11629
12354
|
"--services",
|
|
11630
12355
|
...services
|
|
11631
12356
|
];
|
|
11632
|
-
const result = (0,
|
|
12357
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11633
12358
|
encoding: "utf-8",
|
|
11634
12359
|
cwd: repoPath
|
|
11635
12360
|
});
|
|
@@ -11655,7 +12380,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
11655
12380
|
fileArgs.push("-f", resolved);
|
|
11656
12381
|
}
|
|
11657
12382
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
11658
|
-
const result = (0,
|
|
12383
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11659
12384
|
encoding: "utf-8",
|
|
11660
12385
|
cwd: repoPath
|
|
11661
12386
|
});
|
|
@@ -11681,7 +12406,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
11681
12406
|
String(tail),
|
|
11682
12407
|
...services
|
|
11683
12408
|
];
|
|
11684
|
-
(0,
|
|
12409
|
+
(0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11685
12410
|
stdio: "inherit",
|
|
11686
12411
|
cwd: repoPath
|
|
11687
12412
|
});
|
|
@@ -11693,7 +12418,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11693
12418
|
fileArgs.push("-f", resolved);
|
|
11694
12419
|
}
|
|
11695
12420
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
11696
|
-
const result = (0,
|
|
12421
|
+
const result = (0, import_node_child_process25.spawnSync)("docker", args, {
|
|
11697
12422
|
encoding: "utf-8",
|
|
11698
12423
|
cwd: repoPath
|
|
11699
12424
|
});
|
|
@@ -11707,14 +12432,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
11707
12432
|
const port = Number(match[1]);
|
|
11708
12433
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
11709
12434
|
}
|
|
11710
|
-
var
|
|
12435
|
+
var import_node_child_process25, import_node_crypto8, import_node_fs27, import_node_path27, import_yaml7;
|
|
11711
12436
|
var init_docker_run = __esm({
|
|
11712
12437
|
"src/core/docker-run.ts"() {
|
|
11713
12438
|
"use strict";
|
|
11714
|
-
|
|
12439
|
+
import_node_child_process25 = require("child_process");
|
|
11715
12440
|
import_node_crypto8 = require("crypto");
|
|
11716
|
-
|
|
11717
|
-
|
|
12441
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
12442
|
+
import_node_path27 = __toESM(require("path"));
|
|
11718
12443
|
import_yaml7 = __toESM(require("yaml"));
|
|
11719
12444
|
init_docker_error_guidance();
|
|
11720
12445
|
init_paths();
|
|
@@ -11804,7 +12529,7 @@ function isProcessRunning(pid) {
|
|
|
11804
12529
|
}
|
|
11805
12530
|
}
|
|
11806
12531
|
function readProcessTree(rootPid) {
|
|
11807
|
-
const result = (0,
|
|
12532
|
+
const result = (0, import_node_child_process26.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
11808
12533
|
if (result.status !== 0) {
|
|
11809
12534
|
return [rootPid];
|
|
11810
12535
|
}
|
|
@@ -11915,12 +12640,12 @@ function detectListeningPorts(pids) {
|
|
|
11915
12640
|
if (pids.length === 0) {
|
|
11916
12641
|
return [];
|
|
11917
12642
|
}
|
|
11918
|
-
const result = (0,
|
|
12643
|
+
const result = (0, import_node_child_process26.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
11919
12644
|
encoding: "utf-8"
|
|
11920
12645
|
});
|
|
11921
12646
|
if (result.error && result.error.code === "ENOENT") {
|
|
11922
12647
|
if (process.platform === "linux") {
|
|
11923
|
-
const ssResult = (0,
|
|
12648
|
+
const ssResult = (0, import_node_child_process26.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
11924
12649
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
11925
12650
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
11926
12651
|
}
|
|
@@ -11979,7 +12704,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
11979
12704
|
app.hostRun.command,
|
|
11980
12705
|
true
|
|
11981
12706
|
) : app.hostRun.command;
|
|
11982
|
-
const child = (0,
|
|
12707
|
+
const child = (0, import_node_child_process26.spawn)(spawnCommand, {
|
|
11983
12708
|
cwd: commandCwd,
|
|
11984
12709
|
stdio: "inherit",
|
|
11985
12710
|
shell: true,
|
|
@@ -12347,7 +13072,7 @@ async function execWithAppEnv(options) {
|
|
|
12347
13072
|
options.command[0],
|
|
12348
13073
|
true
|
|
12349
13074
|
);
|
|
12350
|
-
child = (0,
|
|
13075
|
+
child = (0, import_node_child_process26.spawn)(wrapped, {
|
|
12351
13076
|
cwd: deps.repoPath,
|
|
12352
13077
|
stdio: "inherit",
|
|
12353
13078
|
shell: true,
|
|
@@ -12361,7 +13086,7 @@ async function execWithAppEnv(options) {
|
|
|
12361
13086
|
false
|
|
12362
13087
|
);
|
|
12363
13088
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
12364
|
-
child = (0,
|
|
13089
|
+
child = (0, import_node_child_process26.spawn)(cmd, wrappedArgs, {
|
|
12365
13090
|
cwd: deps.repoPath,
|
|
12366
13091
|
stdio: "inherit",
|
|
12367
13092
|
shell: false,
|
|
@@ -12369,7 +13094,7 @@ async function execWithAppEnv(options) {
|
|
|
12369
13094
|
});
|
|
12370
13095
|
}
|
|
12371
13096
|
} else if (options.shell) {
|
|
12372
|
-
child = (0,
|
|
13097
|
+
child = (0, import_node_child_process26.spawn)(options.command[0], {
|
|
12373
13098
|
cwd: deps.repoPath,
|
|
12374
13099
|
stdio: "inherit",
|
|
12375
13100
|
shell: true,
|
|
@@ -12377,7 +13102,7 @@ async function execWithAppEnv(options) {
|
|
|
12377
13102
|
});
|
|
12378
13103
|
} else {
|
|
12379
13104
|
const [command, ...args] = options.command;
|
|
12380
|
-
child = (0,
|
|
13105
|
+
child = (0, import_node_child_process26.spawn)(command, args, {
|
|
12381
13106
|
cwd: deps.repoPath,
|
|
12382
13107
|
stdio: "inherit",
|
|
12383
13108
|
shell: false,
|
|
@@ -12398,11 +13123,11 @@ async function execWithAppEnv(options) {
|
|
|
12398
13123
|
deps.stopDeps();
|
|
12399
13124
|
}
|
|
12400
13125
|
}
|
|
12401
|
-
var
|
|
13126
|
+
var import_node_child_process26, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
|
|
12402
13127
|
var init_app_run = __esm({
|
|
12403
13128
|
"src/core/app-run.ts"() {
|
|
12404
13129
|
"use strict";
|
|
12405
|
-
|
|
13130
|
+
import_node_child_process26 = require("child_process");
|
|
12406
13131
|
import_node_net = __toESM(require("net"));
|
|
12407
13132
|
import_node_process = require("process");
|
|
12408
13133
|
import_promises = require("readline/promises");
|
|
@@ -12536,13 +13261,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12536
13261
|
const startedAt = Date.now();
|
|
12537
13262
|
let rootStat;
|
|
12538
13263
|
try {
|
|
12539
|
-
rootStat =
|
|
13264
|
+
rootStat = import_node_fs28.default.lstatSync(worktreePath);
|
|
12540
13265
|
} catch (error) {
|
|
12541
13266
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12542
13267
|
}
|
|
12543
13268
|
let rootEntries;
|
|
12544
13269
|
try {
|
|
12545
|
-
rootEntries =
|
|
13270
|
+
rootEntries = import_node_fs28.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
12546
13271
|
} catch (error) {
|
|
12547
13272
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
12548
13273
|
}
|
|
@@ -12568,10 +13293,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12568
13293
|
timedOut = true;
|
|
12569
13294
|
break;
|
|
12570
13295
|
}
|
|
12571
|
-
const entryPath =
|
|
13296
|
+
const entryPath = import_node_path28.default.join(dirPath, entry.name);
|
|
12572
13297
|
let entryStat;
|
|
12573
13298
|
try {
|
|
12574
|
-
entryStat =
|
|
13299
|
+
entryStat = import_node_fs28.default.lstatSync(entryPath);
|
|
12575
13300
|
} catch (error) {
|
|
12576
13301
|
if (error?.code === "ENOENT") continue;
|
|
12577
13302
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -12581,7 +13306,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
12581
13306
|
if (!entryStat.isDirectory()) continue;
|
|
12582
13307
|
let childEntries;
|
|
12583
13308
|
try {
|
|
12584
|
-
childEntries =
|
|
13309
|
+
childEntries = import_node_fs28.default.readdirSync(entryPath, { withFileTypes: true });
|
|
12585
13310
|
} catch (error) {
|
|
12586
13311
|
unreadableReason = describeIncompleteWalk(error);
|
|
12587
13312
|
break;
|
|
@@ -12645,12 +13370,12 @@ function describeError(error, worktreePath) {
|
|
|
12645
13370
|
}
|
|
12646
13371
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
12647
13372
|
}
|
|
12648
|
-
var
|
|
13373
|
+
var import_node_fs28, import_node_path28, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
12649
13374
|
var init_workspace_consumption = __esm({
|
|
12650
13375
|
"src/core/workspace-consumption.ts"() {
|
|
12651
13376
|
"use strict";
|
|
12652
|
-
|
|
12653
|
-
|
|
13377
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
13378
|
+
import_node_path28 = __toESM(require("path"));
|
|
12654
13379
|
init_devpod_environment();
|
|
12655
13380
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
12656
13381
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -12751,7 +13476,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
12751
13476
|
}
|
|
12752
13477
|
function readGitSnapshot(worktree, commandRunner) {
|
|
12753
13478
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
12754
|
-
if (worktree.prunable || !
|
|
13479
|
+
if (worktree.prunable || !import_node_fs29.default.existsSync(comparablePath)) {
|
|
12755
13480
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
12756
13481
|
}
|
|
12757
13482
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -13260,12 +13985,6 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13260
13985
|
const cutoff = new Date(now.getTime() - duration.seconds * 1e3).toISOString();
|
|
13261
13986
|
const worktrees = (dependencies.listWorktrees ?? listGitWorktrees)(repoPath);
|
|
13262
13987
|
const records = (dependencies.listOwnership ?? listWorkspaceOwnership)(repoPath);
|
|
13263
|
-
let devpods;
|
|
13264
|
-
try {
|
|
13265
|
-
devpods = (dependencies.listDevpods ?? listDevpodWorkspaces)();
|
|
13266
|
-
} catch {
|
|
13267
|
-
devpods = void 0;
|
|
13268
|
-
}
|
|
13269
13988
|
let routes;
|
|
13270
13989
|
try {
|
|
13271
13990
|
routes = (dependencies.readRoutes ?? readHostRouteStateReadOnly)();
|
|
@@ -13282,7 +14001,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13282
14001
|
containers = measureContainersFn(worktreePaths);
|
|
13283
14002
|
} catch (error) {
|
|
13284
14003
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
13285
|
-
containers = new Map(worktreePaths.map((
|
|
14004
|
+
containers = new Map(worktreePaths.map((path28) => [path28, unknownContainers(reason)]));
|
|
13286
14005
|
}
|
|
13287
14006
|
}
|
|
13288
14007
|
const rows = records.map((record) => {
|
|
@@ -13297,6 +14016,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13297
14016
|
prunable: true
|
|
13298
14017
|
}
|
|
13299
14018
|
);
|
|
14019
|
+
const devpods = dependencies.listDevpods ? dependencies.listDevpods() : listDevpodWorkspacesFromSnapshots(record.worktreePath);
|
|
13300
14020
|
const row = buildRow(
|
|
13301
14021
|
repoPath,
|
|
13302
14022
|
record,
|
|
@@ -13312,7 +14032,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13312
14032
|
return { ownerStatus: status.ownerStatus, devpodStatus: status.devpodStatus };
|
|
13313
14033
|
}),
|
|
13314
14034
|
dependencies.inspectIntegration,
|
|
13315
|
-
dependencies.inspectDevpodRuntime ?? inspectDevpodRuntimeStatus
|
|
14035
|
+
dependencies.inspectDevpodRuntime ?? ((devpodId) => inspectDevpodRuntimeStatus(devpodId, record.worktreePath))
|
|
13316
14036
|
);
|
|
13317
14037
|
return measureSize ? {
|
|
13318
14038
|
...row,
|
|
@@ -13330,12 +14050,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
13330
14050
|
workspaces: rows
|
|
13331
14051
|
};
|
|
13332
14052
|
}
|
|
13333
|
-
var
|
|
14053
|
+
var import_node_child_process27, import_node_fs29, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
13334
14054
|
var init_workspace_cleanup = __esm({
|
|
13335
14055
|
"src/core/workspace-cleanup.ts"() {
|
|
13336
14056
|
"use strict";
|
|
13337
|
-
|
|
13338
|
-
|
|
14057
|
+
import_node_child_process27 = require("child_process");
|
|
14058
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
13339
14059
|
init_devpod_workspaces();
|
|
13340
14060
|
init_host_routes();
|
|
13341
14061
|
init_repo_config();
|
|
@@ -13352,7 +14072,7 @@ var init_workspace_cleanup = __esm({
|
|
|
13352
14072
|
];
|
|
13353
14073
|
defaultCommandRunner = (command, args, input2) => {
|
|
13354
14074
|
const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
|
|
13355
|
-
const result = (0,
|
|
14075
|
+
const result = (0, import_node_child_process27.spawnSync)(command, args, {
|
|
13356
14076
|
encoding: "utf-8",
|
|
13357
14077
|
env: environment,
|
|
13358
14078
|
...input2 === void 0 ? {} : { input: input2 }
|
|
@@ -13512,7 +14232,7 @@ var init_version = __esm({
|
|
|
13512
14232
|
|
|
13513
14233
|
// src/cli.ts
|
|
13514
14234
|
var import_commander = require("commander");
|
|
13515
|
-
var CLI_VERSION = true ? "0.0.
|
|
14235
|
+
var CLI_VERSION = true ? "0.0.42" : "0.0.0-dev";
|
|
13516
14236
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
13517
14237
|
function withErrorHandling(action2) {
|
|
13518
14238
|
return async (...args) => {
|
|
@@ -13550,11 +14270,16 @@ program.command("upgrade").description(
|
|
|
13550
14270
|
}
|
|
13551
14271
|
)
|
|
13552
14272
|
);
|
|
13553
|
-
program.command("setup").description("Run first-time devrouter machine setup and report diagnostics").option("--repo <path>", "Repository path for final diagnostics (defaults to current directory)").option("--yes", "Confirm non-interactive setup actions").option("--
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
14273
|
+
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(
|
|
14274
|
+
"--devsy-inactivity-timeout <duration>",
|
|
14275
|
+
"Persist the Devsy inactivity shutdown duration (e.g. 30m)"
|
|
14276
|
+
).option("--json", "Output JSON").action(
|
|
14277
|
+
withErrorHandling(
|
|
14278
|
+
async (options) => {
|
|
14279
|
+
const { runSetupCommand: runSetupCommand2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports));
|
|
14280
|
+
await runSetupCommand2(options);
|
|
14281
|
+
}
|
|
14282
|
+
)
|
|
13558
14283
|
);
|
|
13559
14284
|
program.command("up").description("Ensure devnet and start shared Traefik (reserves 80/443/5432)").action(
|
|
13560
14285
|
withErrorHandling(async () => {
|
|
@@ -13574,7 +14299,7 @@ program.command("ensure").description("Start and prove a primary or linked check
|
|
|
13574
14299
|
});
|
|
13575
14300
|
})
|
|
13576
14301
|
);
|
|
13577
|
-
program.command("stop").description("Stop one checkout's exact DevPod and free its routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--json", "Output JSON").option("--delete", "Delete the exact
|
|
14302
|
+
program.command("stop").description("Stop one checkout's exact workspace runtime (DevPod/Devsy) and free its routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--json", "Output JSON").option("--delete", "Delete the exact workspace runtime instead of preserving its data").action(
|
|
13578
14303
|
withErrorHandling(async (repoPath, _options, command) => {
|
|
13579
14304
|
const options = command.opts();
|
|
13580
14305
|
const { runStopCommand: runStopCommand2 } = await Promise.resolve().then(() => (init_stop(), stop_exports));
|
|
@@ -13585,7 +14310,7 @@ program.command("stop").description("Stop one checkout's exact DevPod and free i
|
|
|
13585
14310
|
});
|
|
13586
14311
|
})
|
|
13587
14312
|
);
|
|
13588
|
-
program.command("exec").description("Run one literal command inside the exact checkout's running
|
|
14313
|
+
program.command("exec").description("Run one literal command inside the exact checkout's running workspace runtime").argument("[args...]", "[path] -- <command...>").allowUnknownOption(true).action(
|
|
13589
14314
|
withErrorHandling(async () => {
|
|
13590
14315
|
const commandIndex = process.argv.indexOf("exec", 2);
|
|
13591
14316
|
const { parseExecInvocation: parseExecInvocation2, runExecCommand: runExecCommand2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
|
|
@@ -13754,7 +14479,7 @@ tlsCommand.command("install").description("Install mkcert certs and enable HTTPS
|
|
|
13754
14479
|
);
|
|
13755
14480
|
var workspaceCommand2 = program.command("workspace").description("Spin up / list / tear down isolated worktree+devcontainer workspaces");
|
|
13756
14481
|
workspaceCommand2.command("up").description(
|
|
13757
|
-
"Create a worktree for <branch>, bring up its
|
|
14482
|
+
"Create a worktree for <branch>, bring up its workspace runtime, and register namespaced routes"
|
|
13758
14483
|
).argument("<branch>", "Git branch to base the workspace on").option("--path <dir>", "Worktree directory (default: <repo>/trees/<workspace>)").option("--no-devpod", "Only create the worktree; do not start the environment or change routes").option("--open", "Open the namespaced routes after registering").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13759
14484
|
withErrorHandling(async (branch, _options, command) => {
|
|
13760
14485
|
const options = command.opts();
|
|
@@ -13767,7 +14492,9 @@ workspaceCommand2.command("up").description(
|
|
|
13767
14492
|
});
|
|
13768
14493
|
})
|
|
13769
14494
|
);
|
|
13770
|
-
workspaceCommand2.command("ensure").description(
|
|
14495
|
+
workspaceCommand2.command("ensure").description(
|
|
14496
|
+
"Start and prove a primary or linked checkout's workspace runtime, upstreams, and routes"
|
|
14497
|
+
).argument("[path]", "Git checkout path (defaults to current directory)").option("--profile <name>", "Start only this profile's apps/dependencies from .devrouter.yml").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
13771
14498
|
withErrorHandling(
|
|
13772
14499
|
async (worktreePath, _options, command) => {
|
|
13773
14500
|
const options = command.opts();
|
|
@@ -13801,21 +14528,21 @@ workspaceCommand2.command("cleanup").description("Report managed workspace activ
|
|
|
13801
14528
|
});
|
|
13802
14529
|
})
|
|
13803
14530
|
);
|
|
13804
|
-
workspaceCommand2.command("gc").description("Report or clean missing ledger-owned workspace resources").option("--repo <path>", "Main repository path (defaults to current directory)").option("--json", "Output JSON").option("--yes", "Delete eligible
|
|
14531
|
+
workspaceCommand2.command("gc").description("Report or clean missing ledger-owned workspace resources").option("--repo <path>", "Main repository path (defaults to current directory)").option("--json", "Output JSON").option("--yes", "Delete eligible workspace runtime, route, and ownership resources").action(
|
|
13805
14532
|
withErrorHandling(async (_options, command) => {
|
|
13806
14533
|
const options = command.opts();
|
|
13807
14534
|
const { runWorkspaceGcCommand: runWorkspaceGcCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13808
14535
|
runWorkspaceGcCommand2(options);
|
|
13809
14536
|
})
|
|
13810
14537
|
);
|
|
13811
|
-
workspaceCommand2.command("stop").description("Stop a workspace's
|
|
14538
|
+
workspaceCommand2.command("stop").description("Stop a workspace's runtime and free routes while preserving its worktree and data").argument("<workspace>", "Workspace token or live branch name").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13812
14539
|
withErrorHandling(async (target, _options, command) => {
|
|
13813
14540
|
const options = command.opts();
|
|
13814
14541
|
const { runWorkspaceStopCommand: runWorkspaceStopCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|
|
13815
14542
|
await runWorkspaceStopCommand2(target, options);
|
|
13816
14543
|
})
|
|
13817
14544
|
);
|
|
13818
|
-
workspaceCommand2.command("down").description("Delete a workspace's
|
|
14545
|
+
workspaceCommand2.command("down").description("Delete a workspace's runtime and routes, then remove its clean Git worktree").argument("<workspace>", "Workspace token or live branch name").option("--keep-worktree", "Delete runtime resources but preserve the Git worktree and record").option("--repo <path>", "Main repository path (defaults to current directory)").action(
|
|
13819
14546
|
withErrorHandling(async (target, _options, command) => {
|
|
13820
14547
|
const options = command.opts();
|
|
13821
14548
|
const { runWorkspaceDownCommand: runWorkspaceDownCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
|