@devrouter/cli 0.0.46 → 0.0.48
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 +11 -0
- package/dist/devrouter.js +1071 -337
- package/package.json +1 -1
- package/upgrade-prompts/0.0.48.md +44 -0
package/dist/devrouter.js
CHANGED
|
@@ -44,6 +44,7 @@ function buildDevrouterSection() {
|
|
|
44
44
|
"",
|
|
45
45
|
"Quick validation sequence:",
|
|
46
46
|
"- Managed devcontainer consumer images contain no devrouter package or helper; `devrouter ensure` delivers the matching helper at runtime.",
|
|
47
|
+
"- Devsy runtime: run `devrouter setup --yes --workspace-runtime devsy` once; `doctor` then reports verified agent readiness without network access.",
|
|
47
48
|
"- `devrouter up`",
|
|
48
49
|
"- `devrouter tls install` (required when repo defines tcp/postgres apps)",
|
|
49
50
|
"- `devrouter app ls --repo .`",
|
|
@@ -322,7 +323,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
322
323
|
- **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.
|
|
323
324
|
- **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.
|
|
324
325
|
- **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>\`.
|
|
325
|
-
- **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, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. 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.
|
|
326
|
+
- **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, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. 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. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once so Devrouter acquires and verifies the pinned agent in its own machine cache. \`doctor\` reports \`ready\`, \`missing\`, \`stale\`, or \`invalid\` without network access, and \`ensure\` fails before the provider queue when readiness is not \`ready\`. An explicit \`DEVSY_AGENT_BINARY\` remains authoritative and must match a pinned official asset.
|
|
326
327
|
- **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.
|
|
327
328
|
- **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.
|
|
328
329
|
- **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.
|
|
@@ -376,8 +377,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
376
377
|
- \`devrouter init [--write-agents] [--write-skill]\`: print AI onboarding prompt (non-mutating by default)
|
|
377
378
|
- \`devrouter -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
|
|
378
379
|
- \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
|
|
379
|
-
- \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
|
|
380
|
+
- \`devrouter setup --yes [--repo .] [--json] [--workspace-runtime <devpod|devsy>]\`: first-run machine setup plus structured diagnostics; explicit Devsy selection acquires its verified agent
|
|
380
381
|
- \`devrouter ensure [path] [--profile <name>] [--open] [--json]\`: canonical startup/reconciliation for primary and linked checkouts; a managed profile selects its independent resource dimensions
|
|
382
|
+
- \`devrouter profile resolve --repo <path> [--profile <selection>] [--json]\`: resolve exact profile resources for automation without starting or inspecting a runtime
|
|
381
383
|
- \`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
|
|
382
384
|
- \`devrouter exec [path] -- <command...>\`: literal one-shot command inside the exact running workspace runtime
|
|
383
385
|
- \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
|
|
@@ -411,6 +413,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
|
|
|
411
413
|
|
|
412
414
|
For devcontainer onboarding:
|
|
413
415
|
|
|
416
|
+
When Devsy owns the workspace, first run
|
|
417
|
+
\`devrouter setup --repo . --yes --workspace-runtime devsy --json\`.
|
|
418
|
+
|
|
414
419
|
1. \`devrouter setup --repo . --yes --json\`
|
|
415
420
|
2. \`devrouter doctor --repo . --json\`
|
|
416
421
|
3. \`devrouter repo inspect --repo . --json\`
|
|
@@ -720,6 +725,9 @@ function runDockerCompose(args) {
|
|
|
720
725
|
function startRouterStack() {
|
|
721
726
|
runDockerCompose(["up", "-d"]);
|
|
722
727
|
}
|
|
728
|
+
function restartRouterStack() {
|
|
729
|
+
runDockerCompose(["restart", "traefik"]);
|
|
730
|
+
}
|
|
723
731
|
function stopRouterStack() {
|
|
724
732
|
runDockerCompose(["down"]);
|
|
725
733
|
}
|
|
@@ -1406,6 +1414,12 @@ function hostRouteFromInput(input2, existing, now) {
|
|
|
1406
1414
|
function sanitizeKey(value) {
|
|
1407
1415
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1408
1416
|
}
|
|
1417
|
+
function buildHostRouteRouterKey(routeId) {
|
|
1418
|
+
return `host-${sanitizeKey(routeId)}`;
|
|
1419
|
+
}
|
|
1420
|
+
function buildHostRouteRouterName(repoPath, name) {
|
|
1421
|
+
return `${buildHostRouteRouterKey(buildHostRouteId(repoPath, name))}@file`;
|
|
1422
|
+
}
|
|
1409
1423
|
function tcpTlsOptionName(tcpProtocol) {
|
|
1410
1424
|
return `devrouter-tcp-${tcpProtocol}`;
|
|
1411
1425
|
}
|
|
@@ -1416,7 +1430,7 @@ function buildHostRoutesDocument(routes, tlsEnabled) {
|
|
|
1416
1430
|
const tcpServices = {};
|
|
1417
1431
|
const tlsOptions = {};
|
|
1418
1432
|
for (const route of routes) {
|
|
1419
|
-
const key =
|
|
1433
|
+
const key = buildHostRouteRouterKey(route.id);
|
|
1420
1434
|
if (route.protocol === "tcp") {
|
|
1421
1435
|
const alpn = route.tcpProtocol ? TCP_TLS_ALPN_PROTOCOLS[route.tcpProtocol] : void 0;
|
|
1422
1436
|
let tls = {};
|
|
@@ -2654,7 +2668,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2654
2668
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2655
2669
|
const requiredVersion = config.devrouter?.version;
|
|
2656
2670
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2657
|
-
const cliVersion = true ? "0.0.
|
|
2671
|
+
const cliVersion = true ? "0.0.48" : "0.0.0-dev";
|
|
2658
2672
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2659
2673
|
hasWarnedVersionMismatch = true;
|
|
2660
2674
|
process.stderr.write(
|
|
@@ -3185,6 +3199,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3185
3199
|
`- 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.`,
|
|
3186
3200
|
"- 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.",
|
|
3187
3201
|
"- 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, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
|
|
3202
|
+
"- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
|
|
3188
3203
|
"- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
|
|
3189
3204
|
"- 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.",
|
|
3190
3205
|
"- 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.",
|
|
@@ -3216,7 +3231,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3216
3231
|
"- Warning: Do not run migration/seed until env probe confirms expected DB variables and values.",
|
|
3217
3232
|
"",
|
|
3218
3233
|
"Required workflow:",
|
|
3219
|
-
"1) Run `devrouter setup --yes --json` for devrouter-owned machine state
|
|
3234
|
+
"1) Run `devrouter setup --yes --json` for devrouter-owned machine state. When selecting Devsy, use `devrouter setup --yes --workspace-runtime devsy --json` to acquire its verified agent. Use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
|
|
3220
3235
|
"2) Run `devrouter repo inspect --repo <REPO_PATH> --json` before editing files.",
|
|
3221
3236
|
"3) For the supported Node/pnpm/Postgres devcontainer shape, run `devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json`, review the plan, then run `devrouter repo devcontainer write --repo <REPO_PATH> --yes`.",
|
|
3222
3237
|
"4) For unsupported shapes or custom existing files, make minimal manual edits and explain the assumptions.",
|
|
@@ -3226,6 +3241,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
3226
3241
|
"",
|
|
3227
3242
|
"Validation commands to run/report for the devcontainer path:",
|
|
3228
3243
|
"- devrouter setup --yes --json",
|
|
3244
|
+
"- Devsy runtime setup: devrouter setup --yes --workspace-runtime devsy --json",
|
|
3229
3245
|
"- devrouter doctor --repo <REPO_PATH> --json",
|
|
3230
3246
|
"- devrouter repo inspect --repo <REPO_PATH> --json",
|
|
3231
3247
|
"- devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json",
|
|
@@ -3353,6 +3369,10 @@ var init_ai_prompt = __esm({
|
|
|
3353
3369
|
command: "devrouter ensure [path] [--profile <name>] [--open] [--json]",
|
|
3354
3370
|
purpose: "Canonical startup and proof for an exact primary or linked checkout; atomically publishes routes after readiness."
|
|
3355
3371
|
},
|
|
3372
|
+
{
|
|
3373
|
+
command: "devrouter profile resolve --repo <path> [--profile <selection>] [--json]",
|
|
3374
|
+
purpose: "Resolve exact profile resources without starting or inspecting a runtime; fail closed on invalid selections or unknown report schemas."
|
|
3375
|
+
},
|
|
3356
3376
|
{
|
|
3357
3377
|
command: "devrouter stop [path] [--delete] [--json]",
|
|
3358
3378
|
purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
|
|
@@ -4055,6 +4075,384 @@ var init_upgrade2 = __esm({
|
|
|
4055
4075
|
}
|
|
4056
4076
|
});
|
|
4057
4077
|
|
|
4078
|
+
// src/core/devsy-agent.ts
|
|
4079
|
+
function parseVersion(output2) {
|
|
4080
|
+
return output2?.match(
|
|
4081
|
+
/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
|
|
4082
|
+
)?.[1];
|
|
4083
|
+
}
|
|
4084
|
+
function devsyAgentRepairSuggestion(inspection) {
|
|
4085
|
+
if (inspection.state === "stale") {
|
|
4086
|
+
return `Install Devsy ${SUPPORTED_DEVSY_VERSION} for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
4087
|
+
}
|
|
4088
|
+
return inspection.source === "explicit" ? `Fix or unset DEVSY_AGENT_BINARY, then run: ${DEVSY_AGENT_SETUP_COMMAND}` : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
4089
|
+
}
|
|
4090
|
+
function installedVersionOutput() {
|
|
4091
|
+
const result = (0, import_node_child_process4.spawnSync)("devsy", ["--version"], { encoding: "utf-8" });
|
|
4092
|
+
if (result.error || result.status !== 0) return void 0;
|
|
4093
|
+
return [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || void 0;
|
|
4094
|
+
}
|
|
4095
|
+
function nativeAssetName(platform, arch) {
|
|
4096
|
+
if (platform !== "darwin") return void 0;
|
|
4097
|
+
if (arch === "arm64") return "devsy-linux-arm64";
|
|
4098
|
+
if (arch === "x64") return "devsy-linux-amd64";
|
|
4099
|
+
return void 0;
|
|
4100
|
+
}
|
|
4101
|
+
function managedBinaryPath(cacheRoot, asset) {
|
|
4102
|
+
return import_node_path9.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
|
|
4103
|
+
}
|
|
4104
|
+
function hashOpenFile(file) {
|
|
4105
|
+
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
4106
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
4107
|
+
let position = 0;
|
|
4108
|
+
for (; ; ) {
|
|
4109
|
+
const bytesRead = import_node_fs9.default.readSync(file, buffer, 0, buffer.length, position);
|
|
4110
|
+
if (bytesRead === 0) break;
|
|
4111
|
+
digest.update(buffer.subarray(0, bytesRead));
|
|
4112
|
+
position += bytesRead;
|
|
4113
|
+
}
|
|
4114
|
+
return digest.digest("hex");
|
|
4115
|
+
}
|
|
4116
|
+
function inspectBinary(binaryPath, assets, missingState) {
|
|
4117
|
+
let file;
|
|
4118
|
+
try {
|
|
4119
|
+
file = import_node_fs9.default.openSync(binaryPath, import_node_fs9.default.constants.O_RDONLY | import_node_fs9.default.constants.O_NOFOLLOW);
|
|
4120
|
+
const stat = import_node_fs9.default.fstatSync(file);
|
|
4121
|
+
if (!stat.isFile()) {
|
|
4122
|
+
return { state: "invalid", reason: "the selected source is not a regular file" };
|
|
4123
|
+
}
|
|
4124
|
+
const sizeMatches = assets.filter((asset2) => asset2.size === stat.size);
|
|
4125
|
+
if (sizeMatches.length === 0) {
|
|
4126
|
+
return { state: "invalid", reason: "the selected source has an unexpected size" };
|
|
4127
|
+
}
|
|
4128
|
+
const sha2562 = hashOpenFile(file);
|
|
4129
|
+
const asset = sizeMatches.find((candidate) => candidate.sha256 === sha2562);
|
|
4130
|
+
return asset ? { state: "ready", reason: "the selected source matches the pinned manifest", asset } : { state: "invalid", reason: "the selected source has an unexpected digest" };
|
|
4131
|
+
} catch (error) {
|
|
4132
|
+
const code = error.code;
|
|
4133
|
+
return code === "ENOENT" ? { state: missingState, reason: "the selected source is missing" } : { state: "invalid", reason: "the selected source is not a readable regular file" };
|
|
4134
|
+
} finally {
|
|
4135
|
+
if (file !== void 0) import_node_fs9.default.closeSync(file);
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
function resolvedOptions(options) {
|
|
4139
|
+
return {
|
|
4140
|
+
env: options.env ?? process.env,
|
|
4141
|
+
platform: options.platform ?? process.platform,
|
|
4142
|
+
arch: options.arch ?? process.arch,
|
|
4143
|
+
versionOutput: options.versionOutput ?? installedVersionOutput(),
|
|
4144
|
+
cacheRoot: options.cacheRoot ?? import_node_path9.default.join(CACHE_DIR, "devsy", "agents"),
|
|
4145
|
+
lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
|
|
4146
|
+
assets: options.assets ?? DEVSY_AGENT_ASSETS,
|
|
4147
|
+
nativeAssetName: options.nativeAssetName
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
function inspectDevsyAgent(options = {}) {
|
|
4151
|
+
const resolved = resolvedOptions(options);
|
|
4152
|
+
const installedVersion = parseVersion(resolved.versionOutput);
|
|
4153
|
+
const explicitPath = resolved.env.DEVSY_AGENT_BINARY?.trim();
|
|
4154
|
+
const source = explicitPath ? "explicit" : "managed";
|
|
4155
|
+
if (installedVersion !== SUPPORTED_DEVSY_VERSION) {
|
|
4156
|
+
return {
|
|
4157
|
+
state: "stale",
|
|
4158
|
+
source,
|
|
4159
|
+
reason: installedVersion ? `installed Devsy ${installedVersion} is not supported by this Devrouter release` : "the supported Devsy version is not available",
|
|
4160
|
+
installedVersion
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
if (explicitPath) {
|
|
4164
|
+
return {
|
|
4165
|
+
...inspectBinary(explicitPath, resolved.assets, "invalid"),
|
|
4166
|
+
source,
|
|
4167
|
+
binaryPath: explicitPath,
|
|
4168
|
+
installedVersion
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
const expectedName = resolved.nativeAssetName ?? nativeAssetName(resolved.platform, resolved.arch);
|
|
4172
|
+
const asset = resolved.assets.find((candidate) => candidate.name === expectedName);
|
|
4173
|
+
if (!asset) {
|
|
4174
|
+
return {
|
|
4175
|
+
state: "stale",
|
|
4176
|
+
source,
|
|
4177
|
+
reason: `this Devrouter release has no pinned Devsy agent for ${resolved.platform}/${resolved.arch}`,
|
|
4178
|
+
installedVersion
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
const binaryPath = managedBinaryPath(resolved.cacheRoot, asset);
|
|
4182
|
+
return {
|
|
4183
|
+
...inspectBinary(binaryPath, [asset], "missing"),
|
|
4184
|
+
source,
|
|
4185
|
+
binaryPath,
|
|
4186
|
+
asset,
|
|
4187
|
+
installedVersion
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
async function writeAll(handle, chunk, position) {
|
|
4191
|
+
let offset = 0;
|
|
4192
|
+
while (offset < chunk.byteLength) {
|
|
4193
|
+
const { bytesWritten } = await handle.write(
|
|
4194
|
+
chunk,
|
|
4195
|
+
offset,
|
|
4196
|
+
chunk.byteLength - offset,
|
|
4197
|
+
position + offset
|
|
4198
|
+
);
|
|
4199
|
+
if (bytesWritten === 0) throw new Error("Devsy agent download stopped making progress");
|
|
4200
|
+
offset += bytesWritten;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function appendBoundedTail(current, chunk) {
|
|
4204
|
+
if (chunk.length >= GITHUB_CLI_STDERR_TAIL_BYTES) {
|
|
4205
|
+
return Buffer.from(chunk.subarray(chunk.length - GITHUB_CLI_STDERR_TAIL_BYTES));
|
|
4206
|
+
}
|
|
4207
|
+
const combined = Buffer.concat([current, chunk]);
|
|
4208
|
+
return combined.length > GITHUB_CLI_STDERR_TAIL_BYTES ? combined.subarray(combined.length - GITHUB_CLI_STDERR_TAIL_BYTES) : combined;
|
|
4209
|
+
}
|
|
4210
|
+
function errorMessage(error) {
|
|
4211
|
+
return error instanceof Error ? error.message : String(error);
|
|
4212
|
+
}
|
|
4213
|
+
async function downloadWithGitHubCli(asset, writeChunk) {
|
|
4214
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
4215
|
+
"gh",
|
|
4216
|
+
[
|
|
4217
|
+
"api",
|
|
4218
|
+
`repos/devsy-org/devsy/releases/assets/${asset.githubAssetId}`,
|
|
4219
|
+
"-H",
|
|
4220
|
+
"Accept: application/octet-stream"
|
|
4221
|
+
],
|
|
4222
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
4223
|
+
);
|
|
4224
|
+
if (!child.stdout || !child.stderr) {
|
|
4225
|
+
child.kill();
|
|
4226
|
+
throw new Error("GitHub CLI fallback did not create output pipes");
|
|
4227
|
+
}
|
|
4228
|
+
let stderrTail = Buffer.alloc(0);
|
|
4229
|
+
child.stderr.on("data", (chunk) => {
|
|
4230
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4231
|
+
stderrTail = appendBoundedTail(stderrTail, value);
|
|
4232
|
+
});
|
|
4233
|
+
let spawnError;
|
|
4234
|
+
const completion = new Promise((resolve) => {
|
|
4235
|
+
child.once("error", (error) => {
|
|
4236
|
+
spawnError = error;
|
|
4237
|
+
});
|
|
4238
|
+
child.once("close", (status) => {
|
|
4239
|
+
resolve(status);
|
|
4240
|
+
});
|
|
4241
|
+
});
|
|
4242
|
+
try {
|
|
4243
|
+
for await (const chunk of child.stdout) {
|
|
4244
|
+
await writeChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4245
|
+
}
|
|
4246
|
+
const status = await completion;
|
|
4247
|
+
if (spawnError) throw spawnError;
|
|
4248
|
+
if (status !== 0) {
|
|
4249
|
+
const details = stderrTail.toString("utf-8").trim();
|
|
4250
|
+
throw new Error(
|
|
4251
|
+
`gh api exited with status ${status ?? "unknown"}${details ? `: ${details}` : ""}`
|
|
4252
|
+
);
|
|
4253
|
+
}
|
|
4254
|
+
} catch (error) {
|
|
4255
|
+
if (child.exitCode === null) child.kill();
|
|
4256
|
+
await completion.catch(() => void 0);
|
|
4257
|
+
throw error;
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
function fsyncDirectory2(directory) {
|
|
4261
|
+
const handle = import_node_fs9.default.openSync(directory, "r");
|
|
4262
|
+
try {
|
|
4263
|
+
import_node_fs9.default.fsyncSync(handle);
|
|
4264
|
+
} finally {
|
|
4265
|
+
import_node_fs9.default.closeSync(handle);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
|
|
4269
|
+
const directory = import_node_path9.default.dirname(binaryPath);
|
|
4270
|
+
import_node_fs9.default.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
4271
|
+
const temporaryPath = import_node_path9.default.join(
|
|
4272
|
+
directory,
|
|
4273
|
+
`.${import_node_path9.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto4.randomUUID)()}.tmp`
|
|
4274
|
+
);
|
|
4275
|
+
let handle;
|
|
4276
|
+
try {
|
|
4277
|
+
let response;
|
|
4278
|
+
let directFailure;
|
|
4279
|
+
try {
|
|
4280
|
+
response = await fetcher(asset.url);
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
directFailure = error;
|
|
4283
|
+
}
|
|
4284
|
+
handle = await import_node_fs9.default.promises.open(temporaryPath, "wx", 384);
|
|
4285
|
+
const digest = (0, import_node_crypto4.createHash)("sha256");
|
|
4286
|
+
let size = 0;
|
|
4287
|
+
const writeChunk = async (chunk) => {
|
|
4288
|
+
size += chunk.byteLength;
|
|
4289
|
+
if (size > asset.size) {
|
|
4290
|
+
throw new Error("Devsy agent download exceeded the pinned size");
|
|
4291
|
+
}
|
|
4292
|
+
digest.update(chunk);
|
|
4293
|
+
await writeAll(handle, chunk, size - chunk.byteLength);
|
|
4294
|
+
};
|
|
4295
|
+
let transport;
|
|
4296
|
+
if (response) {
|
|
4297
|
+
if (!response.ok) {
|
|
4298
|
+
throw new Error(`Devsy agent download failed with HTTP ${response.status}`);
|
|
4299
|
+
}
|
|
4300
|
+
if (!response.body) throw new Error("Devsy agent download returned no response body");
|
|
4301
|
+
const reader = response.body.getReader();
|
|
4302
|
+
try {
|
|
4303
|
+
for (; ; ) {
|
|
4304
|
+
const { done, value } = await reader.read();
|
|
4305
|
+
if (done) break;
|
|
4306
|
+
await writeChunk(value);
|
|
4307
|
+
}
|
|
4308
|
+
} catch (error) {
|
|
4309
|
+
await reader.cancel().catch(() => void 0);
|
|
4310
|
+
throw error;
|
|
4311
|
+
}
|
|
4312
|
+
transport = "https";
|
|
4313
|
+
} else {
|
|
4314
|
+
try {
|
|
4315
|
+
await githubCliDownloader(asset, writeChunk);
|
|
4316
|
+
} catch (fallbackError) {
|
|
4317
|
+
throw new Error(
|
|
4318
|
+
`Devsy agent direct HTTPS download failed: ${errorMessage(directFailure)}; GitHub CLI fallback failed: ${errorMessage(fallbackError)}. Restore direct GitHub release access or install and authenticate gh.`
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
transport = "github-cli";
|
|
4322
|
+
}
|
|
4323
|
+
if (size !== asset.size) throw new Error("Devsy agent download has an unexpected size");
|
|
4324
|
+
if (digest.digest("hex") !== asset.sha256) {
|
|
4325
|
+
throw new Error("Devsy agent download has an unexpected digest");
|
|
4326
|
+
}
|
|
4327
|
+
await handle.sync();
|
|
4328
|
+
await handle.chmod(448);
|
|
4329
|
+
await handle.sync();
|
|
4330
|
+
await handle.close();
|
|
4331
|
+
handle = void 0;
|
|
4332
|
+
await import_node_fs9.default.promises.rename(temporaryPath, binaryPath);
|
|
4333
|
+
fsyncDirectory2(directory);
|
|
4334
|
+
return transport;
|
|
4335
|
+
} finally {
|
|
4336
|
+
if (handle) await handle.close();
|
|
4337
|
+
await import_node_fs9.default.promises.rm(temporaryPath, { force: true });
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
async function prepareDevsyAgent(options = {}) {
|
|
4341
|
+
const resolved = resolvedOptions(options);
|
|
4342
|
+
const inspectOptions = resolved;
|
|
4343
|
+
const before = inspectDevsyAgent(inspectOptions);
|
|
4344
|
+
if (before.state === "ready" && before.binaryPath && before.asset) {
|
|
4345
|
+
return {
|
|
4346
|
+
binaryPath: before.binaryPath,
|
|
4347
|
+
source: before.source,
|
|
4348
|
+
asset: before.asset,
|
|
4349
|
+
changed: false,
|
|
4350
|
+
transport: "existing"
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
if (before.source === "explicit" || before.state === "stale" || !before.binaryPath || !before.asset) {
|
|
4354
|
+
throw new DevsyAgentReadinessError(before);
|
|
4355
|
+
}
|
|
4356
|
+
const lock = options.withLock ?? ((operation) => {
|
|
4357
|
+
import_node_fs9.default.mkdirSync(import_node_path9.default.dirname(resolved.lockPath), { recursive: true });
|
|
4358
|
+
return withFileLock(
|
|
4359
|
+
resolved.lockPath,
|
|
4360
|
+
{
|
|
4361
|
+
activity: "Devsy agent acquisition",
|
|
4362
|
+
waitMs: DEVSY_AGENT_LOCK_WAIT_MS,
|
|
4363
|
+
fair: true,
|
|
4364
|
+
onWait: createStderrWaitReporter("Devsy agent acquisition", "the managed cache")
|
|
4365
|
+
},
|
|
4366
|
+
operation
|
|
4367
|
+
);
|
|
4368
|
+
});
|
|
4369
|
+
return lock(async () => {
|
|
4370
|
+
const current = inspectDevsyAgent(inspectOptions);
|
|
4371
|
+
if (current.state === "ready" && current.binaryPath && current.asset) {
|
|
4372
|
+
return {
|
|
4373
|
+
binaryPath: current.binaryPath,
|
|
4374
|
+
source: current.source,
|
|
4375
|
+
asset: current.asset,
|
|
4376
|
+
changed: false,
|
|
4377
|
+
transport: "existing"
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
if (current.source === "explicit" || current.state === "stale" || !current.binaryPath || !current.asset) {
|
|
4381
|
+
throw new DevsyAgentReadinessError(current);
|
|
4382
|
+
}
|
|
4383
|
+
const transport = await downloadAndPublish(
|
|
4384
|
+
current.asset,
|
|
4385
|
+
current.binaryPath,
|
|
4386
|
+
options.fetcher ?? fetch,
|
|
4387
|
+
options.githubCliDownloader ?? downloadWithGitHubCli
|
|
4388
|
+
);
|
|
4389
|
+
const published = inspectDevsyAgent(inspectOptions);
|
|
4390
|
+
if (published.state !== "ready" || !published.binaryPath || !published.asset) {
|
|
4391
|
+
throw new DevsyAgentReadinessError(published);
|
|
4392
|
+
}
|
|
4393
|
+
return {
|
|
4394
|
+
binaryPath: published.binaryPath,
|
|
4395
|
+
source: published.source,
|
|
4396
|
+
asset: published.asset,
|
|
4397
|
+
changed: true,
|
|
4398
|
+
transport
|
|
4399
|
+
};
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
function requireReadyDevsyAgent(options = {}) {
|
|
4403
|
+
const inspection = inspectDevsyAgent(options);
|
|
4404
|
+
if (inspection.state !== "ready" || !inspection.binaryPath || !inspection.asset) {
|
|
4405
|
+
throw new DevsyAgentReadinessError(inspection);
|
|
4406
|
+
}
|
|
4407
|
+
return {
|
|
4408
|
+
binaryPath: inspection.binaryPath,
|
|
4409
|
+
source: inspection.source,
|
|
4410
|
+
asset: inspection.asset,
|
|
4411
|
+
changed: false,
|
|
4412
|
+
transport: "existing"
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4415
|
+
var import_node_child_process4, import_node_crypto4, import_node_fs9, import_node_path9, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
|
|
4416
|
+
var init_devsy_agent = __esm({
|
|
4417
|
+
"src/core/devsy-agent.ts"() {
|
|
4418
|
+
"use strict";
|
|
4419
|
+
import_node_child_process4 = require("child_process");
|
|
4420
|
+
import_node_crypto4 = require("crypto");
|
|
4421
|
+
import_node_fs9 = __toESM(require("fs"));
|
|
4422
|
+
import_node_path9 = __toESM(require("path"));
|
|
4423
|
+
init_file_lock();
|
|
4424
|
+
init_router();
|
|
4425
|
+
SUPPORTED_DEVSY_VERSION = "1.16.2";
|
|
4426
|
+
DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
|
|
4427
|
+
DEVSY_AGENT_ASSETS = [
|
|
4428
|
+
{
|
|
4429
|
+
githubAssetId: 529830010,
|
|
4430
|
+
name: "devsy-linux-arm64",
|
|
4431
|
+
size: 124518562,
|
|
4432
|
+
sha256: "31060b96486b5398f2aa3ee0875b2555782a2db0954a799d387be38ed4b4990d",
|
|
4433
|
+
url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-arm64"
|
|
4434
|
+
},
|
|
4435
|
+
{
|
|
4436
|
+
githubAssetId: 529830011,
|
|
4437
|
+
name: "devsy-linux-amd64",
|
|
4438
|
+
size: 133505186,
|
|
4439
|
+
sha256: "4983c52a3536c5a91d1b5f356a1c3428778ebf3f896d9897f60bce3978abc839",
|
|
4440
|
+
url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-amd64"
|
|
4441
|
+
}
|
|
4442
|
+
];
|
|
4443
|
+
DevsyAgentReadinessError = class extends Error {
|
|
4444
|
+
constructor(inspection) {
|
|
4445
|
+
super(`Devsy agent is ${inspection.state}: ${inspection.reason}`);
|
|
4446
|
+
this.inspection = inspection;
|
|
4447
|
+
this.name = "DevsyAgentReadinessError";
|
|
4448
|
+
}
|
|
4449
|
+
};
|
|
4450
|
+
DEVSY_AGENT_CACHE_LOCK = import_node_path9.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
|
|
4451
|
+
DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
|
|
4452
|
+
GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
|
|
4453
|
+
}
|
|
4454
|
+
});
|
|
4455
|
+
|
|
4058
4456
|
// src/core/docker.ts
|
|
4059
4457
|
async function getDockerodeConstructor() {
|
|
4060
4458
|
if (!dockerodeConstructorPromise) {
|
|
@@ -4066,7 +4464,7 @@ async function getDockerodeConstructor() {
|
|
|
4066
4464
|
return dockerodeConstructorPromise;
|
|
4067
4465
|
}
|
|
4068
4466
|
function runDockerContextCommand(args) {
|
|
4069
|
-
const result = (0,
|
|
4467
|
+
const result = (0, import_node_child_process5.spawnSync)("docker", ["context", ...args], {
|
|
4070
4468
|
encoding: "utf-8"
|
|
4071
4469
|
});
|
|
4072
4470
|
if (result.status !== 0) {
|
|
@@ -4079,7 +4477,7 @@ function getCurrentDockerContext() {
|
|
|
4079
4477
|
return runDockerContextCommand(["show"]);
|
|
4080
4478
|
}
|
|
4081
4479
|
function getDockerHostFromContext(context) {
|
|
4082
|
-
const result = (0,
|
|
4480
|
+
const result = (0, import_node_child_process5.spawnSync)(
|
|
4083
4481
|
"docker",
|
|
4084
4482
|
["context", "inspect", context, "--format", "{{ .Endpoints.docker.Host }}"],
|
|
4085
4483
|
{ encoding: "utf-8" }
|
|
@@ -4147,11 +4545,11 @@ async function networkExists(name) {
|
|
|
4147
4545
|
return false;
|
|
4148
4546
|
}
|
|
4149
4547
|
}
|
|
4150
|
-
var
|
|
4548
|
+
var import_node_child_process5, dockerodeConstructorPromise;
|
|
4151
4549
|
var init_docker = __esm({
|
|
4152
4550
|
"src/core/docker.ts"() {
|
|
4153
4551
|
"use strict";
|
|
4154
|
-
|
|
4552
|
+
import_node_child_process5 = require("child_process");
|
|
4155
4553
|
dockerodeConstructorPromise = null;
|
|
4156
4554
|
}
|
|
4157
4555
|
});
|
|
@@ -4178,11 +4576,11 @@ function parseDevcontainerConfig(contents, sourcePath) {
|
|
|
4178
4576
|
return parsed;
|
|
4179
4577
|
}
|
|
4180
4578
|
function readDevcontainerConfig(repoPath) {
|
|
4181
|
-
const sourcePath =
|
|
4182
|
-
if (!
|
|
4579
|
+
const sourcePath = import_node_path10.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4580
|
+
if (!import_node_fs10.default.existsSync(sourcePath) || !import_node_fs10.default.lstatSync(sourcePath).isFile()) {
|
|
4183
4581
|
throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
|
|
4184
4582
|
}
|
|
4185
|
-
const sourceContents =
|
|
4583
|
+
const sourceContents = import_node_fs10.default.readFileSync(sourcePath, "utf-8");
|
|
4186
4584
|
return {
|
|
4187
4585
|
sourcePath,
|
|
4188
4586
|
sourceContents,
|
|
@@ -4198,12 +4596,12 @@ function assertManagedDevcontainerLifecycle(repoPath) {
|
|
|
4198
4596
|
`Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
|
|
4199
4597
|
);
|
|
4200
4598
|
}
|
|
4201
|
-
var
|
|
4599
|
+
var import_node_fs10, import_node_path10, import_jsonc_parser;
|
|
4202
4600
|
var init_devcontainer_config = __esm({
|
|
4203
4601
|
"src/core/devcontainer-config.ts"() {
|
|
4204
4602
|
"use strict";
|
|
4205
|
-
|
|
4206
|
-
|
|
4603
|
+
import_node_fs10 = __toESM(require("fs"));
|
|
4604
|
+
import_node_path10 = __toESM(require("path"));
|
|
4207
4605
|
import_jsonc_parser = require("jsonc-parser");
|
|
4208
4606
|
}
|
|
4209
4607
|
});
|
|
@@ -4213,7 +4611,7 @@ function renderDeliveryScript(targetPath) {
|
|
|
4213
4611
|
if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
|
|
4214
4612
|
throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
|
|
4215
4613
|
}
|
|
4216
|
-
const temporaryPrefix =
|
|
4614
|
+
const temporaryPrefix = import_node_path11.default.posix.basename(targetPath);
|
|
4217
4615
|
return `set -eu
|
|
4218
4616
|
umask 077
|
|
4219
4617
|
runtime_root=/tmp/devrouter
|
|
@@ -4237,7 +4635,7 @@ function commandFailure(result) {
|
|
|
4237
4635
|
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
|
|
4238
4636
|
}
|
|
4239
4637
|
function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
4240
|
-
const result = (0,
|
|
4638
|
+
const result = (0, import_node_child_process6.spawnSync)(
|
|
4241
4639
|
"docker",
|
|
4242
4640
|
["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
|
|
4243
4641
|
{ input: contents, encoding: "utf-8" }
|
|
@@ -4248,32 +4646,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
|
|
|
4248
4646
|
}
|
|
4249
4647
|
}
|
|
4250
4648
|
function readRegularFileBytes(filePath) {
|
|
4251
|
-
if (!
|
|
4252
|
-
return
|
|
4649
|
+
if (!import_node_fs11.default.existsSync(filePath) || !import_node_fs11.default.lstatSync(filePath).isFile()) return void 0;
|
|
4650
|
+
return import_node_fs11.default.readFileSync(filePath);
|
|
4253
4651
|
}
|
|
4254
4652
|
function readRegularFile(filePath) {
|
|
4255
4653
|
return readRegularFileBytes(filePath)?.toString("utf-8");
|
|
4256
4654
|
}
|
|
4257
4655
|
function adapterFingerprint(adapter) {
|
|
4258
|
-
return (0,
|
|
4656
|
+
return (0, import_node_crypto5.createHash)("sha256").update(adapter).digest("hex");
|
|
4259
4657
|
}
|
|
4260
4658
|
function resolveProcessHelperPath() {
|
|
4261
4659
|
const candidates = [
|
|
4262
|
-
|
|
4263
|
-
|
|
4660
|
+
import_node_path11.default.resolve(__dirname, "..", "bin", "devrouter-process"),
|
|
4661
|
+
import_node_path11.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
|
|
4264
4662
|
];
|
|
4265
|
-
const helperPath = candidates.find((candidate) =>
|
|
4663
|
+
const helperPath = candidates.find((candidate) => import_node_fs11.default.existsSync(candidate));
|
|
4266
4664
|
if (!helperPath) {
|
|
4267
4665
|
throw new Error("Could not locate the packaged devrouter-process helper.");
|
|
4268
4666
|
}
|
|
4269
4667
|
return helperPath;
|
|
4270
4668
|
}
|
|
4271
4669
|
function resolveManagedPostStartPlan(repoPath) {
|
|
4272
|
-
const adapterPath =
|
|
4670
|
+
const adapterPath = import_node_path11.default.join(repoPath, MANAGED_ADAPTER_PATH);
|
|
4273
4671
|
const adapterBytes = readRegularFileBytes(adapterPath);
|
|
4274
4672
|
const adapterText = adapterBytes?.toString("utf-8");
|
|
4275
|
-
const dockerfilePath =
|
|
4276
|
-
const devcontainerPath =
|
|
4673
|
+
const dockerfilePath = import_node_path11.default.join(repoPath, ".devcontainer", "Dockerfile");
|
|
4674
|
+
const devcontainerPath = import_node_path11.default.join(repoPath, ".devcontainer", "devcontainer.json");
|
|
4277
4675
|
const dockerfile = readRegularFile(dockerfilePath) ?? "";
|
|
4278
4676
|
const devcontainer = readRegularFile(devcontainerPath) ?? "";
|
|
4279
4677
|
const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
|
|
@@ -4314,7 +4712,7 @@ function resolveManagedPostStartPlan(repoPath) {
|
|
|
4314
4712
|
}
|
|
4315
4713
|
function runManagedPostStart(options) {
|
|
4316
4714
|
if (options.plan.kind !== "runtime") return;
|
|
4317
|
-
const helper =
|
|
4715
|
+
const helper = import_node_fs11.default.readFileSync(resolveProcessHelperPath());
|
|
4318
4716
|
deliverRuntimeFile(
|
|
4319
4717
|
options.container.id,
|
|
4320
4718
|
RUNTIME_HELPER_PATH,
|
|
@@ -4328,7 +4726,7 @@ function runManagedPostStart(options) {
|
|
|
4328
4726
|
options.plan.adapterContents,
|
|
4329
4727
|
"the managed post-start adapter"
|
|
4330
4728
|
);
|
|
4331
|
-
const started = (0,
|
|
4729
|
+
const started = (0, import_node_child_process6.spawnSync)(
|
|
4332
4730
|
"docker",
|
|
4333
4731
|
[
|
|
4334
4732
|
"exec",
|
|
@@ -4366,7 +4764,7 @@ function assertSafeProcessName(name) {
|
|
|
4366
4764
|
}
|
|
4367
4765
|
function runManagedProcessAction(options) {
|
|
4368
4766
|
assertSafeProcessName(options.name);
|
|
4369
|
-
const result = (0,
|
|
4767
|
+
const result = (0, import_node_child_process6.spawnSync)(
|
|
4370
4768
|
"docker",
|
|
4371
4769
|
[
|
|
4372
4770
|
"exec",
|
|
@@ -4402,14 +4800,14 @@ function runManagedProcessAction(options) {
|
|
|
4402
4800
|
}
|
|
4403
4801
|
return "drifted";
|
|
4404
4802
|
}
|
|
4405
|
-
var
|
|
4803
|
+
var import_node_child_process6, import_node_crypto5, import_node_fs11, import_node_path11, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
|
|
4406
4804
|
var init_managed_post_start = __esm({
|
|
4407
4805
|
"src/core/managed-post-start.ts"() {
|
|
4408
4806
|
"use strict";
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4807
|
+
import_node_child_process6 = require("child_process");
|
|
4808
|
+
import_node_crypto5 = require("crypto");
|
|
4809
|
+
import_node_fs11 = __toESM(require("fs"));
|
|
4810
|
+
import_node_path11 = __toESM(require("path"));
|
|
4413
4811
|
init_devcontainer_config();
|
|
4414
4812
|
MANAGED_MARKER = "devrouter:managed devcontainer";
|
|
4415
4813
|
MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
|
|
@@ -4476,7 +4874,7 @@ function isLoopbackHost(host) {
|
|
|
4476
4874
|
return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
|
|
4477
4875
|
}
|
|
4478
4876
|
function inspectCompose(composeFile, _workspace) {
|
|
4479
|
-
if (!
|
|
4877
|
+
if (!import_node_fs12.default.existsSync(composeFile)) {
|
|
4480
4878
|
return {
|
|
4481
4879
|
aliases: [],
|
|
4482
4880
|
publishedPorts: [],
|
|
@@ -4485,7 +4883,7 @@ function inspectCompose(composeFile, _workspace) {
|
|
|
4485
4883
|
};
|
|
4486
4884
|
}
|
|
4487
4885
|
try {
|
|
4488
|
-
const raw =
|
|
4886
|
+
const raw = import_node_fs12.default.readFileSync(composeFile, "utf-8");
|
|
4489
4887
|
const parsed = import_yaml3.default.parse(raw);
|
|
4490
4888
|
const root = asRecord(parsed);
|
|
4491
4889
|
const services = asRecord(root?.services);
|
|
@@ -4542,11 +4940,11 @@ function routedProxyApps(config) {
|
|
|
4542
4940
|
);
|
|
4543
4941
|
}
|
|
4544
4942
|
function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
4545
|
-
const devcontainerDir =
|
|
4546
|
-
if (!
|
|
4943
|
+
const devcontainerDir = import_node_path12.default.join(repoPath, ".devcontainer");
|
|
4944
|
+
if (!import_node_fs12.default.existsSync(devcontainerDir)) {
|
|
4547
4945
|
return [];
|
|
4548
4946
|
}
|
|
4549
|
-
const compose = inspectCompose(
|
|
4947
|
+
const compose = inspectCompose(import_node_path12.default.join(devcontainerDir, "docker-compose.yml"), workspace);
|
|
4550
4948
|
const checks = [];
|
|
4551
4949
|
if (compose.parseError) {
|
|
4552
4950
|
checks.push({
|
|
@@ -4573,9 +4971,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4573
4971
|
details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
|
|
4574
4972
|
suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
|
|
4575
4973
|
});
|
|
4576
|
-
const dockerfilePath =
|
|
4577
|
-
const dockerfileExists =
|
|
4578
|
-
const dockerfile = dockerfileExists ?
|
|
4974
|
+
const dockerfilePath = import_node_path12.default.join(devcontainerDir, "Dockerfile");
|
|
4975
|
+
const dockerfileExists = import_node_fs12.default.existsSync(dockerfilePath);
|
|
4976
|
+
const dockerfile = dockerfileExists ? import_node_fs12.default.readFileSync(dockerfilePath, "utf-8") : "";
|
|
4579
4977
|
const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
|
|
4580
4978
|
(artifact) => dockerfile.includes(artifact)
|
|
4581
4979
|
);
|
|
@@ -4630,12 +5028,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
|
|
|
4630
5028
|
});
|
|
4631
5029
|
return checks;
|
|
4632
5030
|
}
|
|
4633
|
-
var
|
|
5031
|
+
var import_node_fs12, import_node_path12, import_yaml3;
|
|
4634
5032
|
var init_devcontainer_diagnostics = __esm({
|
|
4635
5033
|
"src/core/devcontainer-diagnostics.ts"() {
|
|
4636
5034
|
"use strict";
|
|
4637
|
-
|
|
4638
|
-
|
|
5035
|
+
import_node_fs12 = __toESM(require("fs"));
|
|
5036
|
+
import_node_path12 = __toESM(require("path"));
|
|
4639
5037
|
import_yaml3 = __toESM(require("yaml"));
|
|
4640
5038
|
init_managed_post_start();
|
|
4641
5039
|
}
|
|
@@ -4643,18 +5041,18 @@ var init_devcontainer_diagnostics = __esm({
|
|
|
4643
5041
|
|
|
4644
5042
|
// src/core/paths.ts
|
|
4645
5043
|
function assertPathWithinRepo(filePath, repoRoot, label) {
|
|
4646
|
-
const resolvedRoot =
|
|
4647
|
-
const resolved =
|
|
4648
|
-
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot +
|
|
5044
|
+
const resolvedRoot = import_node_path13.default.resolve(repoRoot);
|
|
5045
|
+
const resolved = import_node_path13.default.resolve(repoRoot, filePath);
|
|
5046
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path13.default.sep)) {
|
|
4649
5047
|
throw new Error(`${label} path '${filePath}' escapes the repository root.`);
|
|
4650
5048
|
}
|
|
4651
5049
|
return resolved;
|
|
4652
5050
|
}
|
|
4653
|
-
var
|
|
5051
|
+
var import_node_path13;
|
|
4654
5052
|
var init_paths = __esm({
|
|
4655
5053
|
"src/core/paths.ts"() {
|
|
4656
5054
|
"use strict";
|
|
4657
|
-
|
|
5055
|
+
import_node_path13 = __toESM(require("path"));
|
|
4658
5056
|
}
|
|
4659
5057
|
});
|
|
4660
5058
|
|
|
@@ -4739,11 +5137,11 @@ function reconcileRouteRunConflict(repoPath, app) {
|
|
|
4739
5137
|
}
|
|
4740
5138
|
}
|
|
4741
5139
|
}
|
|
4742
|
-
var
|
|
5140
|
+
var import_node_fs13;
|
|
4743
5141
|
var init_route_state = __esm({
|
|
4744
5142
|
"src/core/route-state.ts"() {
|
|
4745
5143
|
"use strict";
|
|
4746
|
-
|
|
5144
|
+
import_node_fs13 = __toESM(require("fs"));
|
|
4747
5145
|
init_host_routes();
|
|
4748
5146
|
init_workspace();
|
|
4749
5147
|
}
|
|
@@ -4909,10 +5307,10 @@ var init_routes = __esm({
|
|
|
4909
5307
|
|
|
4910
5308
|
// src/core/managed-runtime-state.ts
|
|
4911
5309
|
function stateKey(repoPath, workspace) {
|
|
4912
|
-
return (0,
|
|
5310
|
+
return (0, import_node_crypto6.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
|
|
4913
5311
|
}
|
|
4914
5312
|
function managedRuntimeStatePath(repoPath, workspace) {
|
|
4915
|
-
return
|
|
5313
|
+
return import_node_path14.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
|
|
4916
5314
|
}
|
|
4917
5315
|
function isStringArray(value) {
|
|
4918
5316
|
return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
|
|
@@ -4975,10 +5373,10 @@ function validateState(value, repoPath, workspace) {
|
|
|
4975
5373
|
}
|
|
4976
5374
|
function readManagedRuntimeState(repoPath, workspace) {
|
|
4977
5375
|
const statePath = managedRuntimeStatePath(repoPath, workspace);
|
|
4978
|
-
if (!
|
|
5376
|
+
if (!import_node_fs14.default.existsSync(statePath)) return void 0;
|
|
4979
5377
|
let parsed;
|
|
4980
5378
|
try {
|
|
4981
|
-
parsed = JSON.parse(
|
|
5379
|
+
parsed = JSON.parse(import_node_fs14.default.readFileSync(statePath, "utf-8"));
|
|
4982
5380
|
} catch (error) {
|
|
4983
5381
|
throw new Error(`Could not parse managed runtime state: ${String(error)}`);
|
|
4984
5382
|
}
|
|
@@ -5000,13 +5398,13 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
|
|
|
5000
5398
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5001
5399
|
});
|
|
5002
5400
|
}
|
|
5003
|
-
var
|
|
5401
|
+
var import_node_crypto6, import_node_fs14, import_node_path14;
|
|
5004
5402
|
var init_managed_runtime_state = __esm({
|
|
5005
5403
|
"src/core/managed-runtime-state.ts"() {
|
|
5006
5404
|
"use strict";
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5405
|
+
import_node_crypto6 = require("crypto");
|
|
5406
|
+
import_node_fs14 = __toESM(require("fs"));
|
|
5407
|
+
import_node_path14 = __toESM(require("path"));
|
|
5010
5408
|
init_atomic_file();
|
|
5011
5409
|
init_router();
|
|
5012
5410
|
}
|
|
@@ -5057,14 +5455,14 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
5057
5455
|
"managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
|
|
5058
5456
|
);
|
|
5059
5457
|
}
|
|
5060
|
-
const directory =
|
|
5458
|
+
const directory = import_node_path15.default.join(repoPath, ".devcontainer");
|
|
5061
5459
|
const files = references.map((reference) => {
|
|
5062
5460
|
const resolved2 = assertPathWithinRepo(
|
|
5063
5461
|
resolveComposeReference(reference, linked),
|
|
5064
5462
|
directory,
|
|
5065
5463
|
"dockerComposeFile"
|
|
5066
5464
|
);
|
|
5067
|
-
if (!
|
|
5465
|
+
if (!import_node_fs15.default.existsSync(resolved2) || !import_node_fs15.default.lstatSync(resolved2).isFile()) {
|
|
5068
5466
|
throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
|
|
5069
5467
|
}
|
|
5070
5468
|
return resolved2;
|
|
@@ -5072,7 +5470,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
5072
5470
|
for (const file of files) {
|
|
5073
5471
|
let parsed;
|
|
5074
5472
|
try {
|
|
5075
|
-
parsed = import_yaml4.default.parse(
|
|
5473
|
+
parsed = import_yaml4.default.parse(import_node_fs15.default.readFileSync(file, "utf-8"));
|
|
5076
5474
|
} catch (error) {
|
|
5077
5475
|
throw new Error(
|
|
5078
5476
|
`Could not parse managed Dev Container compose file '${file}': ${String(error)}`
|
|
@@ -5083,7 +5481,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
5083
5481
|
}
|
|
5084
5482
|
}
|
|
5085
5483
|
const fileArgs = files.flatMap((file) => ["-f", file]);
|
|
5086
|
-
const resolved = (0,
|
|
5484
|
+
const resolved = (0, import_node_child_process7.spawnSync)(
|
|
5087
5485
|
"docker",
|
|
5088
5486
|
[
|
|
5089
5487
|
"compose",
|
|
@@ -5109,7 +5507,7 @@ function resolveComposeFiles(source, repoPath, linked) {
|
|
|
5109
5507
|
return { directory, files, services: services.sort() };
|
|
5110
5508
|
}
|
|
5111
5509
|
function assertIgnoredGeneratedPath(repoPath, generatedPath) {
|
|
5112
|
-
const ignored = (0,
|
|
5510
|
+
const ignored = (0, import_node_child_process7.spawnSync)(
|
|
5113
5511
|
"git",
|
|
5114
5512
|
["-C", repoPath, "check-ignore", "--quiet", "--no-index", "--", generatedPath],
|
|
5115
5513
|
{ encoding: "utf-8" }
|
|
@@ -5121,12 +5519,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
|
|
|
5121
5519
|
}
|
|
5122
5520
|
}
|
|
5123
5521
|
function assertGeneratedPathOwnership(generatedPath) {
|
|
5124
|
-
if (!
|
|
5125
|
-
const stat =
|
|
5522
|
+
if (!import_node_fs15.default.existsSync(generatedPath)) return;
|
|
5523
|
+
const stat = import_node_fs15.default.lstatSync(generatedPath);
|
|
5126
5524
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
5127
5525
|
throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
|
|
5128
5526
|
}
|
|
5129
|
-
const firstLine2 =
|
|
5527
|
+
const firstLine2 = import_node_fs15.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
|
|
5130
5528
|
if (firstLine2 !== MANAGED_DEVCONTAINER_MARKER) {
|
|
5131
5529
|
throw new Error(
|
|
5132
5530
|
`Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
|
|
@@ -5137,7 +5535,7 @@ function isWildcard(values) {
|
|
|
5137
5535
|
return values?.length === 1 && values[0] === "*";
|
|
5138
5536
|
}
|
|
5139
5537
|
function sha256(contents) {
|
|
5140
|
-
return (0,
|
|
5538
|
+
return (0, import_node_crypto7.createHash)("sha256").update(contents, "utf-8").digest("hex");
|
|
5141
5539
|
}
|
|
5142
5540
|
function safeComposeProject(value) {
|
|
5143
5541
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
@@ -5202,7 +5600,7 @@ function inspectManagedDevcontainerConfig(options) {
|
|
|
5202
5600
|
const contents = `${MANAGED_DEVCONTAINER_MARKER}
|
|
5203
5601
|
${JSON.stringify(effective, null, 2)}
|
|
5204
5602
|
`;
|
|
5205
|
-
const generatedPath =
|
|
5603
|
+
const generatedPath = import_node_path15.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
|
|
5206
5604
|
assertIgnoredGeneratedPath(options.repoPath, generatedPath);
|
|
5207
5605
|
assertGeneratedPathOwnership(generatedPath);
|
|
5208
5606
|
return {
|
|
@@ -5229,13 +5627,13 @@ function writeManagedDevcontainerConfig(plan) {
|
|
|
5229
5627
|
function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
5230
5628
|
let stat;
|
|
5231
5629
|
try {
|
|
5232
|
-
stat =
|
|
5630
|
+
stat = import_node_fs15.default.lstatSync(plan.generatedPath);
|
|
5233
5631
|
} catch (error) {
|
|
5234
5632
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
5235
5633
|
throw error;
|
|
5236
5634
|
}
|
|
5237
5635
|
if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
|
|
5238
|
-
const contents =
|
|
5636
|
+
const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
|
|
5239
5637
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
5240
5638
|
`)) {
|
|
5241
5639
|
return { status: "foreign" };
|
|
@@ -5247,17 +5645,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
|
|
|
5247
5645
|
};
|
|
5248
5646
|
}
|
|
5249
5647
|
function removeManagedDevcontainerConfig(plan) {
|
|
5250
|
-
if (!
|
|
5251
|
-
const stat =
|
|
5648
|
+
if (!import_node_fs15.default.existsSync(plan.generatedPath)) return;
|
|
5649
|
+
const stat = import_node_fs15.default.lstatSync(plan.generatedPath);
|
|
5252
5650
|
if (!stat.isFile()) {
|
|
5253
5651
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
|
|
5254
5652
|
}
|
|
5255
|
-
const contents =
|
|
5653
|
+
const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
|
|
5256
5654
|
if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
|
|
5257
5655
|
`)) {
|
|
5258
5656
|
throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
|
|
5259
5657
|
}
|
|
5260
|
-
|
|
5658
|
+
import_node_fs15.default.unlinkSync(plan.generatedPath);
|
|
5261
5659
|
}
|
|
5262
5660
|
function assertSafeContainerId(containerId) {
|
|
5263
5661
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
|
|
@@ -5284,7 +5682,7 @@ function startExactManagedServices(options) {
|
|
|
5284
5682
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
5285
5683
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
5286
5684
|
}
|
|
5287
|
-
const result = (0,
|
|
5685
|
+
const result = (0, import_node_child_process7.spawnSync)(
|
|
5288
5686
|
"docker",
|
|
5289
5687
|
[
|
|
5290
5688
|
"compose",
|
|
@@ -5314,7 +5712,7 @@ function startExactManagedServices(options) {
|
|
|
5314
5712
|
}
|
|
5315
5713
|
function stopExactManagedService(containerId, service) {
|
|
5316
5714
|
assertSafeContainerId(containerId);
|
|
5317
|
-
const result = (0,
|
|
5715
|
+
const result = (0, import_node_child_process7.spawnSync)("docker", ["stop", containerId], {
|
|
5318
5716
|
encoding: "utf-8",
|
|
5319
5717
|
stdio: "inherit"
|
|
5320
5718
|
});
|
|
@@ -5322,14 +5720,14 @@ function stopExactManagedService(containerId, service) {
|
|
|
5322
5720
|
throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
|
|
5323
5721
|
}
|
|
5324
5722
|
}
|
|
5325
|
-
var
|
|
5723
|
+
var import_node_child_process7, import_node_crypto7, import_node_fs15, import_node_path15, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
|
|
5326
5724
|
var init_devcontainer_profile = __esm({
|
|
5327
5725
|
"src/core/devcontainer-profile.ts"() {
|
|
5328
5726
|
"use strict";
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5727
|
+
import_node_child_process7 = require("child_process");
|
|
5728
|
+
import_node_crypto7 = require("crypto");
|
|
5729
|
+
import_node_fs15 = __toESM(require("fs"));
|
|
5730
|
+
import_node_path15 = __toESM(require("path"));
|
|
5333
5731
|
import_yaml4 = __toESM(require("yaml"));
|
|
5334
5732
|
init_atomic_file();
|
|
5335
5733
|
init_devcontainer_config();
|
|
@@ -5343,7 +5741,7 @@ var init_devcontainer_profile = __esm({
|
|
|
5343
5741
|
function inspectWorkspaceContainers(options) {
|
|
5344
5742
|
let ids = options?.ids;
|
|
5345
5743
|
if (!ids) {
|
|
5346
|
-
const listed = (0,
|
|
5744
|
+
const listed = (0, import_node_child_process8.spawnSync)(
|
|
5347
5745
|
"docker",
|
|
5348
5746
|
[
|
|
5349
5747
|
"ps",
|
|
@@ -5363,7 +5761,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5363
5761
|
}
|
|
5364
5762
|
if (ids.length === 0) return [];
|
|
5365
5763
|
const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
|
|
5366
|
-
const inspected = (0,
|
|
5764
|
+
const inspected = (0, import_node_child_process8.spawnSync)(
|
|
5367
5765
|
"docker",
|
|
5368
5766
|
["inspect", ...options?.withSize ? ["--size"] : [], "--format", template, ...ids],
|
|
5369
5767
|
{ encoding: "utf-8" }
|
|
@@ -5378,7 +5776,7 @@ function inspectWorkspaceContainers(options) {
|
|
|
5378
5776
|
function workspaceAppContainers(containers, repoPath) {
|
|
5379
5777
|
return containers.filter((container) => {
|
|
5380
5778
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
5381
|
-
return Boolean(workingDir && sameWorkspacePath(workingDir,
|
|
5779
|
+
return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path16.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
|
|
5382
5780
|
(mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
|
|
5383
5781
|
);
|
|
5384
5782
|
});
|
|
@@ -5386,7 +5784,7 @@ function workspaceAppContainers(containers, repoPath) {
|
|
|
5386
5784
|
function hasExactComposeIdentity(container, options) {
|
|
5387
5785
|
if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
|
|
5388
5786
|
container.labels["com.docker.compose.project.working_dir"] ?? "",
|
|
5389
|
-
|
|
5787
|
+
import_node_path16.default.join(options.repoPath, ".devcontainer")
|
|
5390
5788
|
)) {
|
|
5391
5789
|
return false;
|
|
5392
5790
|
}
|
|
@@ -5423,12 +5821,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
|
|
|
5423
5821
|
}
|
|
5424
5822
|
return { id: container.id, workspacePath: repoMount.Destination };
|
|
5425
5823
|
}
|
|
5426
|
-
var
|
|
5824
|
+
var import_node_child_process8, import_node_path16, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
|
|
5427
5825
|
var init_devpod_environment = __esm({
|
|
5428
5826
|
"src/core/devpod-environment.ts"() {
|
|
5429
5827
|
"use strict";
|
|
5430
|
-
|
|
5431
|
-
|
|
5828
|
+
import_node_child_process8 = require("child_process");
|
|
5829
|
+
import_node_path16 = __toESM(require("path"));
|
|
5432
5830
|
init_workspace();
|
|
5433
5831
|
SAFE_INSPECT_TEMPLATE = '{"id":{{json .Id}},"state":{"Running":{{json .State.Running}},"Health":{{with (index .State "Health")}}{"Status":{{json .Status}}}{{else}}null{{end}}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.service":{{json (index .Config.Labels "com.docker.compose.service")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"com.docker.compose.project.config_files":{{json (index .Config.Labels "com.docker.compose.project.config_files")}}},"mounts":{{json .Mounts}},"networks":{{json .NetworkSettings.Networks}}}';
|
|
5434
5832
|
SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
|
|
@@ -5882,7 +6280,7 @@ function toRepoStatus(repoPath) {
|
|
|
5882
6280
|
const resolvedRepoPath = resolveRepoPath(repoPath);
|
|
5883
6281
|
const configPath = getRepoConfigPath(resolvedRepoPath);
|
|
5884
6282
|
const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
|
|
5885
|
-
const configExists =
|
|
6283
|
+
const configExists = import_node_fs16.default.existsSync(configPath);
|
|
5886
6284
|
if (!explicitRepo && !configExists) {
|
|
5887
6285
|
return void 0;
|
|
5888
6286
|
}
|
|
@@ -6030,11 +6428,11 @@ async function collectRouterStatus(repoPath) {
|
|
|
6030
6428
|
}
|
|
6031
6429
|
};
|
|
6032
6430
|
}
|
|
6033
|
-
var
|
|
6431
|
+
var import_node_fs16;
|
|
6034
6432
|
var init_status = __esm({
|
|
6035
6433
|
"src/core/status.ts"() {
|
|
6036
6434
|
"use strict";
|
|
6037
|
-
|
|
6435
|
+
import_node_fs16 = __toESM(require("fs"));
|
|
6038
6436
|
init_docker();
|
|
6039
6437
|
init_managed_runtime_state();
|
|
6040
6438
|
init_managed_runtime_status();
|
|
@@ -6045,7 +6443,7 @@ var init_status = __esm({
|
|
|
6045
6443
|
|
|
6046
6444
|
// src/core/tls.ts
|
|
6047
6445
|
function runOrThrow(command, args) {
|
|
6048
|
-
const result = (0,
|
|
6446
|
+
const result = (0, import_node_child_process9.spawnSync)(command, args, {
|
|
6049
6447
|
encoding: "utf-8"
|
|
6050
6448
|
});
|
|
6051
6449
|
if (result.status !== 0) {
|
|
@@ -6055,7 +6453,7 @@ function runOrThrow(command, args) {
|
|
|
6055
6453
|
return result.stdout.trim();
|
|
6056
6454
|
}
|
|
6057
6455
|
function commandExists(command) {
|
|
6058
|
-
const result = (0,
|
|
6456
|
+
const result = (0, import_node_child_process9.spawnSync)("sh", ["-c", `command -v ${command}`], { encoding: "utf-8" });
|
|
6059
6457
|
return result.status === 0;
|
|
6060
6458
|
}
|
|
6061
6459
|
function ensureMkcert() {
|
|
@@ -6073,8 +6471,8 @@ function tlsSetupCommand(repoPath) {
|
|
|
6073
6471
|
}
|
|
6074
6472
|
function getMkcertRootCAPath(options = {}) {
|
|
6075
6473
|
ensureMkcert();
|
|
6076
|
-
const rootCAPath =
|
|
6077
|
-
if (!
|
|
6474
|
+
const rootCAPath = import_node_path17.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
|
|
6475
|
+
if (!import_node_fs17.default.existsSync(rootCAPath)) {
|
|
6078
6476
|
throw new Error(
|
|
6079
6477
|
`mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
|
|
6080
6478
|
);
|
|
@@ -6107,7 +6505,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
|
|
|
6107
6505
|
return normalizeUniqueHosts(names);
|
|
6108
6506
|
}
|
|
6109
6507
|
function parseCertificateDnsHosts(pem) {
|
|
6110
|
-
const certificate = new
|
|
6508
|
+
const certificate = new import_node_crypto8.X509Certificate(pem);
|
|
6111
6509
|
const subjectAltName = certificate.subjectAltName ?? "";
|
|
6112
6510
|
if (subjectAltName.length === 0) {
|
|
6113
6511
|
return [];
|
|
@@ -6175,11 +6573,11 @@ function compactTLSCertificateHosts(hosts) {
|
|
|
6175
6573
|
);
|
|
6176
6574
|
}
|
|
6177
6575
|
function readCurrentCertificateHosts(options = {}) {
|
|
6178
|
-
if (!
|
|
6576
|
+
if (!import_node_fs17.default.existsSync(CERT_FILE)) {
|
|
6179
6577
|
return [];
|
|
6180
6578
|
}
|
|
6181
6579
|
try {
|
|
6182
|
-
const pem =
|
|
6580
|
+
const pem = import_node_fs17.default.readFileSync(CERT_FILE, "utf-8");
|
|
6183
6581
|
return parseCertificateDnsHosts(pem);
|
|
6184
6582
|
} catch (error) {
|
|
6185
6583
|
if (options.replaceMalformed) {
|
|
@@ -6307,20 +6705,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
|
|
|
6307
6705
|
);
|
|
6308
6706
|
}
|
|
6309
6707
|
}
|
|
6310
|
-
var
|
|
6708
|
+
var import_node_child_process9, import_node_crypto8, import_node_fs17, import_node_path17, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
|
|
6311
6709
|
var init_tls = __esm({
|
|
6312
6710
|
"src/core/tls.ts"() {
|
|
6313
6711
|
"use strict";
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6712
|
+
import_node_child_process9 = require("child_process");
|
|
6713
|
+
import_node_crypto8 = require("crypto");
|
|
6714
|
+
import_node_fs17 = __toESM(require("fs"));
|
|
6715
|
+
import_node_path17 = __toESM(require("path"));
|
|
6318
6716
|
init_docker();
|
|
6319
6717
|
init_file_lock();
|
|
6320
6718
|
init_host_routes();
|
|
6321
6719
|
init_router();
|
|
6322
6720
|
DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
|
|
6323
|
-
TLS_CERTIFICATE_LOCK_FILE =
|
|
6721
|
+
TLS_CERTIFICATE_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "tls-certificate.lock");
|
|
6324
6722
|
TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
|
|
6325
6723
|
TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
|
|
6326
6724
|
}
|
|
@@ -6328,7 +6726,7 @@ var init_tls = __esm({
|
|
|
6328
6726
|
|
|
6329
6727
|
// src/core/devpod-registry.ts
|
|
6330
6728
|
function listDevpodWorkspacesRaw() {
|
|
6331
|
-
const result = (0,
|
|
6729
|
+
const result = (0, import_node_child_process10.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
|
|
6332
6730
|
encoding: "utf-8"
|
|
6333
6731
|
});
|
|
6334
6732
|
if (result.status !== 0) {
|
|
@@ -6363,17 +6761,17 @@ function listDevpodWorkspacesRaw() {
|
|
|
6363
6761
|
return workspace;
|
|
6364
6762
|
});
|
|
6365
6763
|
}
|
|
6366
|
-
var
|
|
6764
|
+
var import_node_child_process10;
|
|
6367
6765
|
var init_devpod_registry = __esm({
|
|
6368
6766
|
"src/core/devpod-registry.ts"() {
|
|
6369
6767
|
"use strict";
|
|
6370
|
-
|
|
6768
|
+
import_node_child_process10 = require("child_process");
|
|
6371
6769
|
}
|
|
6372
6770
|
});
|
|
6373
6771
|
|
|
6374
6772
|
// src/core/devsy-workspaces.ts
|
|
6375
6773
|
function listDevsyWorkspaces() {
|
|
6376
|
-
const result = (0,
|
|
6774
|
+
const result = (0, import_node_child_process11.spawnSync)(
|
|
6377
6775
|
"devsy",
|
|
6378
6776
|
["workspace", "list", "--result-format", "json", "--skip-pro"],
|
|
6379
6777
|
{
|
|
@@ -6439,7 +6837,7 @@ function parseDevsyRuntimeStatus(output2, expectedId) {
|
|
|
6439
6837
|
}
|
|
6440
6838
|
}
|
|
6441
6839
|
function inspectDevsyRuntimeStatus(devsyId) {
|
|
6442
|
-
const result = (0,
|
|
6840
|
+
const result = (0, import_node_child_process11.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
|
|
6443
6841
|
encoding: "utf-8",
|
|
6444
6842
|
timeout: 1e4
|
|
6445
6843
|
});
|
|
@@ -6472,11 +6870,11 @@ function selectDevsyWorkspace(workspaces, repoPath) {
|
|
|
6472
6870
|
}
|
|
6473
6871
|
return matches[0];
|
|
6474
6872
|
}
|
|
6475
|
-
var
|
|
6873
|
+
var import_node_child_process11;
|
|
6476
6874
|
var init_devsy_workspaces = __esm({
|
|
6477
6875
|
"src/core/devsy-workspaces.ts"() {
|
|
6478
6876
|
"use strict";
|
|
6479
|
-
|
|
6877
|
+
import_node_child_process11 = require("child_process");
|
|
6480
6878
|
init_workspace();
|
|
6481
6879
|
}
|
|
6482
6880
|
});
|
|
@@ -6493,13 +6891,13 @@ function parseWorkspaceRuntime(value) {
|
|
|
6493
6891
|
}
|
|
6494
6892
|
function isRuntimeInstalled(runtime) {
|
|
6495
6893
|
const probeArgs = runtime === "devsy" ? ["--version"] : ["version"];
|
|
6496
|
-
const probe = (0,
|
|
6894
|
+
const probe = (0, import_node_child_process12.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
|
|
6497
6895
|
return probe.status === 0 && !probe.error;
|
|
6498
6896
|
}
|
|
6499
6897
|
function readWorkspaceRuntimeConfig() {
|
|
6500
6898
|
let raw;
|
|
6501
6899
|
try {
|
|
6502
|
-
raw = JSON.parse(
|
|
6900
|
+
raw = JSON.parse(import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
|
|
6503
6901
|
} catch {
|
|
6504
6902
|
return {};
|
|
6505
6903
|
}
|
|
@@ -6520,7 +6918,7 @@ function readWorkspaceRuntimeConfig() {
|
|
|
6520
6918
|
function inspectWorkspaceRuntimeConfig() {
|
|
6521
6919
|
let rawText;
|
|
6522
6920
|
try {
|
|
6523
|
-
rawText =
|
|
6921
|
+
rawText = import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
|
|
6524
6922
|
} catch {
|
|
6525
6923
|
return { exists: false, config: {}, problems: [] };
|
|
6526
6924
|
}
|
|
@@ -6571,7 +6969,7 @@ function writeWorkspaceRuntimeConfig(config) {
|
|
|
6571
6969
|
}
|
|
6572
6970
|
next.devsyInactivityTimeout = timeout;
|
|
6573
6971
|
}
|
|
6574
|
-
|
|
6972
|
+
import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
6575
6973
|
writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
|
|
6576
6974
|
`);
|
|
6577
6975
|
}
|
|
@@ -6679,20 +7077,20 @@ function resetWorkspaceRuntimeCaches() {
|
|
|
6679
7077
|
cachedRuntime = void 0;
|
|
6680
7078
|
cachedSnapshots = void 0;
|
|
6681
7079
|
}
|
|
6682
|
-
var
|
|
7080
|
+
var import_node_child_process12, import_node_fs18, import_node_path18, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
|
|
6683
7081
|
var init_workspace_runtime = __esm({
|
|
6684
7082
|
"src/core/workspace-runtime.ts"() {
|
|
6685
7083
|
"use strict";
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
7084
|
+
import_node_child_process12 = require("child_process");
|
|
7085
|
+
import_node_fs18 = __toESM(require("fs"));
|
|
7086
|
+
import_node_path18 = __toESM(require("path"));
|
|
6689
7087
|
init_atomic_file();
|
|
6690
7088
|
init_devpod_registry();
|
|
6691
7089
|
init_devsy_workspaces();
|
|
6692
7090
|
init_router();
|
|
6693
7091
|
init_workspace();
|
|
6694
7092
|
SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
|
|
6695
|
-
RUNTIME_CONFIG_FILE =
|
|
7093
|
+
RUNTIME_CONFIG_FILE = import_node_path18.default.join(DEVROUTER_HOME, "workspace-runtime.json");
|
|
6696
7094
|
INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
|
|
6697
7095
|
UnsupportedWorkspaceRuntimeError = class extends Error {
|
|
6698
7096
|
};
|
|
@@ -6713,7 +7111,7 @@ function outputFromResult(result) {
|
|
|
6713
7111
|
return output2.length > 0 ? output2 : void 0;
|
|
6714
7112
|
}
|
|
6715
7113
|
function runTool(command, args = []) {
|
|
6716
|
-
const result = (0,
|
|
7114
|
+
const result = (0, import_node_child_process13.spawnSync)(command, args, {
|
|
6717
7115
|
encoding: "utf-8"
|
|
6718
7116
|
});
|
|
6719
7117
|
if (result.error) {
|
|
@@ -6770,12 +7168,12 @@ function parseMinimumNodeMajor(value) {
|
|
|
6770
7168
|
return Number(match[1]);
|
|
6771
7169
|
}
|
|
6772
7170
|
function readPackageJson(repoPath) {
|
|
6773
|
-
const packagePath =
|
|
6774
|
-
if (!
|
|
7171
|
+
const packagePath = import_node_path19.default.join(repoPath, "package.json");
|
|
7172
|
+
if (!import_node_fs19.default.existsSync(packagePath)) {
|
|
6775
7173
|
return void 0;
|
|
6776
7174
|
}
|
|
6777
7175
|
try {
|
|
6778
|
-
return JSON.parse(
|
|
7176
|
+
return JSON.parse(import_node_fs19.default.readFileSync(packagePath, "utf-8"));
|
|
6779
7177
|
} catch {
|
|
6780
7178
|
return void 0;
|
|
6781
7179
|
}
|
|
@@ -6840,7 +7238,44 @@ function nodeToolchainCheck(repoPath) {
|
|
|
6840
7238
|
details: details.join(", ")
|
|
6841
7239
|
};
|
|
6842
7240
|
}
|
|
7241
|
+
function devsyAgentCheck(inspection) {
|
|
7242
|
+
const details = [
|
|
7243
|
+
`state=${inspection.state}`,
|
|
7244
|
+
`source=${inspection.source}`,
|
|
7245
|
+
...inspection.installedVersion ? [`version=${inspection.installedVersion}`] : [],
|
|
7246
|
+
...inspection.asset ? [`asset=${inspection.asset.name}`] : []
|
|
7247
|
+
].join(", ");
|
|
7248
|
+
if (inspection.state === "ready") {
|
|
7249
|
+
return {
|
|
7250
|
+
id: "global.devsy-agent",
|
|
7251
|
+
level: "ok",
|
|
7252
|
+
summary: "Devsy agent source is ready.",
|
|
7253
|
+
details
|
|
7254
|
+
};
|
|
7255
|
+
}
|
|
7256
|
+
const summary = inspection.state === "missing" ? "Managed Devsy agent source is missing." : inspection.state === "stale" ? "Devsy agent source is stale for this Devrouter release." : "Devsy agent source is invalid.";
|
|
7257
|
+
return {
|
|
7258
|
+
id: "global.devsy-agent",
|
|
7259
|
+
level: "error",
|
|
7260
|
+
summary,
|
|
7261
|
+
details: `${details}, ${inspection.reason}`,
|
|
7262
|
+
suggestion: devsyAgentRepairSuggestion(inspection)
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
6843
7265
|
function buildGlobalToolChecks(repoPath) {
|
|
7266
|
+
const previous = process.env[DEVSY_DISABLE_TELEMETRY];
|
|
7267
|
+
process.env[DEVSY_DISABLE_TELEMETRY] = "true";
|
|
7268
|
+
try {
|
|
7269
|
+
return buildGlobalToolChecksWithoutTelemetry(repoPath);
|
|
7270
|
+
} finally {
|
|
7271
|
+
if (previous === void 0) {
|
|
7272
|
+
delete process.env[DEVSY_DISABLE_TELEMETRY];
|
|
7273
|
+
} else {
|
|
7274
|
+
process.env[DEVSY_DISABLE_TELEMETRY] = previous;
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
function buildGlobalToolChecksWithoutTelemetry(repoPath) {
|
|
6844
7279
|
const checks = [];
|
|
6845
7280
|
const compose = runTool("docker", ["compose", "version"]);
|
|
6846
7281
|
checks.push({
|
|
@@ -6894,6 +7329,13 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6894
7329
|
].filter(Boolean).join(", "),
|
|
6895
7330
|
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"
|
|
6896
7331
|
});
|
|
7332
|
+
if (workspaceRuntime === "devsy") {
|
|
7333
|
+
checks.push(
|
|
7334
|
+
devsyAgentCheck(
|
|
7335
|
+
inspectDevsyAgent({ versionOutput: runtimeTool.ok ? runtimeTool.output : "" })
|
|
7336
|
+
)
|
|
7337
|
+
);
|
|
7338
|
+
}
|
|
6897
7339
|
const configProblems = [...configInspection.problems];
|
|
6898
7340
|
const machineResolvedDevpod = workspaceRuntime === "devpod" && (runtimeResolution.source === "auto-detect" || runtimeResolution.source === "default");
|
|
6899
7341
|
if (machineConfig.devsyInactivityTimeout && machineResolvedDevpod) {
|
|
@@ -6915,14 +7357,16 @@ function buildGlobalToolChecks(repoPath) {
|
|
|
6915
7357
|
checks.push(nodeToolchainCheck(repoPath));
|
|
6916
7358
|
return checks;
|
|
6917
7359
|
}
|
|
6918
|
-
var
|
|
7360
|
+
var import_node_child_process13, import_node_fs19, import_node_path19, DEVSY_DISABLE_TELEMETRY;
|
|
6919
7361
|
var init_tool_diagnostics = __esm({
|
|
6920
7362
|
"src/core/tool-diagnostics.ts"() {
|
|
6921
7363
|
"use strict";
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
7364
|
+
import_node_child_process13 = require("child_process");
|
|
7365
|
+
import_node_fs19 = __toESM(require("fs"));
|
|
7366
|
+
import_node_path19 = __toESM(require("path"));
|
|
7367
|
+
init_devsy_agent();
|
|
6925
7368
|
init_workspace_runtime();
|
|
7369
|
+
DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
|
|
6926
7370
|
}
|
|
6927
7371
|
});
|
|
6928
7372
|
|
|
@@ -6991,7 +7435,7 @@ function inspectDevpodRuntimeStatus(devpodId, repoPath) {
|
|
|
6991
7435
|
if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
|
|
6992
7436
|
return inspectDevsyRuntimeStatus(devpodId);
|
|
6993
7437
|
}
|
|
6994
|
-
const result = (0,
|
|
7438
|
+
const result = (0, import_node_child_process14.spawnSync)("devpod", ["status", devpodId, "--output", "json", "--timeout", "5s"], {
|
|
6995
7439
|
encoding: "utf-8"
|
|
6996
7440
|
});
|
|
6997
7441
|
if (result.status !== 0 || result.error) return "unknown";
|
|
@@ -7024,11 +7468,11 @@ function selectDevpodWorkspace(workspaces, repoPath) {
|
|
|
7024
7468
|
}
|
|
7025
7469
|
return matches[0];
|
|
7026
7470
|
}
|
|
7027
|
-
var
|
|
7471
|
+
var import_node_child_process14;
|
|
7028
7472
|
var init_devpod_workspaces = __esm({
|
|
7029
7473
|
"src/core/devpod-workspaces.ts"() {
|
|
7030
7474
|
"use strict";
|
|
7031
|
-
|
|
7475
|
+
import_node_child_process14 = require("child_process");
|
|
7032
7476
|
init_devpod_registry();
|
|
7033
7477
|
init_devsy_workspaces();
|
|
7034
7478
|
init_workspace();
|
|
@@ -7048,7 +7492,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
|
|
|
7048
7492
|
}
|
|
7049
7493
|
}
|
|
7050
7494
|
function withMutationLock(activity, target, operation) {
|
|
7051
|
-
|
|
7495
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7052
7496
|
return withFileLockSync(
|
|
7053
7497
|
DEVSY_MUTATION_LOCK_FILE,
|
|
7054
7498
|
{
|
|
@@ -7062,7 +7506,7 @@ function withMutationLock(activity, target, operation) {
|
|
|
7062
7506
|
);
|
|
7063
7507
|
}
|
|
7064
7508
|
function withMutationLockAsync(activity, target, operation) {
|
|
7065
|
-
|
|
7509
|
+
import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7066
7510
|
return withFileLock(
|
|
7067
7511
|
DEVSY_MUTATION_LOCK_FILE,
|
|
7068
7512
|
{
|
|
@@ -7080,7 +7524,7 @@ function commandFailure2(result) {
|
|
|
7080
7524
|
}
|
|
7081
7525
|
function runDevsyUp(args, env, quiet) {
|
|
7082
7526
|
return new Promise((resolve) => {
|
|
7083
|
-
const child = (0,
|
|
7527
|
+
const child = (0, import_node_child_process15.spawn)("devsy", args, {
|
|
7084
7528
|
stdio: ["inherit", quiet ? 2 : "inherit", "pipe"],
|
|
7085
7529
|
env
|
|
7086
7530
|
});
|
|
@@ -7112,7 +7556,7 @@ function runDevsyUp(args, env, quiet) {
|
|
|
7112
7556
|
}
|
|
7113
7557
|
function runDevsyAction(action2, devsyId, force = false) {
|
|
7114
7558
|
const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
|
|
7115
|
-
const result = (0,
|
|
7559
|
+
const result = (0, import_node_child_process15.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
|
|
7116
7560
|
if (result.status !== 0) {
|
|
7117
7561
|
throw new Error(
|
|
7118
7562
|
`devsy workspace ${action2}${force ? " --force" : ""} failed for '${devsyId}': ${commandFailure2(result) || "unknown error"}`
|
|
@@ -7172,8 +7616,16 @@ function assertDevsyTarget(devsyId, repoPath) {
|
|
|
7172
7616
|
}
|
|
7173
7617
|
return id;
|
|
7174
7618
|
}
|
|
7175
|
-
function startDevsyWorkspace(options) {
|
|
7619
|
+
async function startDevsyWorkspace(options) {
|
|
7176
7620
|
const activity = options.recreate ? "Devsy recreate" : "Devsy start";
|
|
7621
|
+
let agent;
|
|
7622
|
+
try {
|
|
7623
|
+
agent = requireReadyDevsyAgent();
|
|
7624
|
+
} catch (error) {
|
|
7625
|
+
if (!(error instanceof DevsyAgentReadinessError)) throw error;
|
|
7626
|
+
const repair = devsyAgentRepairSuggestion(error.inspection);
|
|
7627
|
+
throw new Error(`${error.message}. ${repair}`);
|
|
7628
|
+
}
|
|
7177
7629
|
return withMutationLockAsync(activity, options.repoPath, async () => {
|
|
7178
7630
|
let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
|
|
7179
7631
|
if (devsyId && options.recreate) {
|
|
@@ -7203,6 +7655,7 @@ function startDevsyWorkspace(options) {
|
|
|
7203
7655
|
}
|
|
7204
7656
|
if (options.recreate) args.push("--recreate");
|
|
7205
7657
|
const env = { ...process.env };
|
|
7658
|
+
env.DEVSY_AGENT_BINARY = agent.binaryPath;
|
|
7206
7659
|
if (options.workspace) {
|
|
7207
7660
|
env.WORKSPACE = options.workspace.token;
|
|
7208
7661
|
env.DEVROUTER_WORKSPACE = options.workspace.token;
|
|
@@ -7219,7 +7672,7 @@ function startDevsyWorkspace(options) {
|
|
|
7219
7672
|
let message = `devsy workspace up failed for '${devsyId ?? options.repoPath}'.`;
|
|
7220
7673
|
if (result.error?.message) message += ` ${result.error.message}`;
|
|
7221
7674
|
if (AGENT_ACQUISITION_RE.test(result.stderrTail)) {
|
|
7222
|
-
message +=
|
|
7675
|
+
message += ` Devsy rejected the verified agent source. Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
|
|
7223
7676
|
}
|
|
7224
7677
|
if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
|
|
7225
7678
|
throw new DevsyStartPostconditionError(message);
|
|
@@ -7246,17 +7699,18 @@ function startDevsyWorkspace(options) {
|
|
|
7246
7699
|
}
|
|
7247
7700
|
});
|
|
7248
7701
|
}
|
|
7249
|
-
var
|
|
7702
|
+
var import_node_child_process15, import_node_fs20, import_node_path20, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError, AGENT_ACQUISITION_RE, DEVSY_STDERR_TAIL_BYTES;
|
|
7250
7703
|
var init_devsy_mutation = __esm({
|
|
7251
7704
|
"src/core/devsy-mutation.ts"() {
|
|
7252
7705
|
"use strict";
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7706
|
+
import_node_child_process15 = require("child_process");
|
|
7707
|
+
import_node_fs20 = __toESM(require("fs"));
|
|
7708
|
+
import_node_path20 = __toESM(require("path"));
|
|
7709
|
+
init_devsy_agent();
|
|
7256
7710
|
init_devsy_workspaces();
|
|
7257
7711
|
init_file_lock();
|
|
7258
7712
|
init_router();
|
|
7259
|
-
DEVSY_MUTATION_LOCK_FILE =
|
|
7713
|
+
DEVSY_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
|
|
7260
7714
|
DEVSY_MUTATION_WAIT_MS = 18e5;
|
|
7261
7715
|
DevsyStartPostconditionError = class extends Error {
|
|
7262
7716
|
};
|
|
@@ -7267,7 +7721,7 @@ var init_devsy_mutation = __esm({
|
|
|
7267
7721
|
|
|
7268
7722
|
// src/core/devpod-mutation.ts
|
|
7269
7723
|
function withMutationLock2(activity, target, operation) {
|
|
7270
|
-
|
|
7724
|
+
import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
7271
7725
|
return withFileLockSync(
|
|
7272
7726
|
DEVPOD_MUTATION_LOCK_FILE,
|
|
7273
7727
|
{
|
|
@@ -7285,7 +7739,7 @@ function commandFailure3(result) {
|
|
|
7285
7739
|
}
|
|
7286
7740
|
function runDevpodAction(action2, devpodId, force = false) {
|
|
7287
7741
|
const args = action2 === "delete" ? [action2, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action2, devpodId];
|
|
7288
|
-
const result = (0,
|
|
7742
|
+
const result = (0, import_node_child_process16.spawnSync)("devpod", args, { encoding: "utf-8" });
|
|
7289
7743
|
if (result.status !== 0) {
|
|
7290
7744
|
throw new Error(
|
|
7291
7745
|
`devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${commandFailure3(result) || "unknown error"}`
|
|
@@ -7413,7 +7867,7 @@ async function startDevpodWorkspace(options) {
|
|
|
7413
7867
|
delete env.DEVROUTER_GIT_COMMON_DIR;
|
|
7414
7868
|
delete env.DEVCONTAINER_COMPOSE_OVERLAY;
|
|
7415
7869
|
}
|
|
7416
|
-
const result = (0,
|
|
7870
|
+
const result = (0, import_node_child_process16.spawnSync)("devpod", args, {
|
|
7417
7871
|
stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
|
|
7418
7872
|
env
|
|
7419
7873
|
});
|
|
@@ -7441,19 +7895,19 @@ async function startDevpodWorkspace(options) {
|
|
|
7441
7895
|
}
|
|
7442
7896
|
});
|
|
7443
7897
|
}
|
|
7444
|
-
var
|
|
7898
|
+
var import_node_child_process16, import_node_fs21, import_node_path21, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
|
|
7445
7899
|
var init_devpod_mutation = __esm({
|
|
7446
7900
|
"src/core/devpod-mutation.ts"() {
|
|
7447
7901
|
"use strict";
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7902
|
+
import_node_child_process16 = require("child_process");
|
|
7903
|
+
import_node_fs21 = __toESM(require("fs"));
|
|
7904
|
+
import_node_path21 = __toESM(require("path"));
|
|
7451
7905
|
init_devpod_workspaces();
|
|
7452
7906
|
init_devsy_mutation();
|
|
7453
7907
|
init_file_lock();
|
|
7454
7908
|
init_router();
|
|
7455
7909
|
init_workspace_runtime();
|
|
7456
|
-
DEVPOD_MUTATION_LOCK_FILE =
|
|
7910
|
+
DEVPOD_MUTATION_LOCK_FILE = import_node_path21.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
|
|
7457
7911
|
DEVPOD_MUTATION_WAIT_MS = 18e5;
|
|
7458
7912
|
DevpodStartPostconditionError = class extends Error {
|
|
7459
7913
|
};
|
|
@@ -7467,7 +7921,7 @@ function commandError(command, repoPath, stderr) {
|
|
|
7467
7921
|
);
|
|
7468
7922
|
}
|
|
7469
7923
|
function resolveGitCommonDir(repoPath) {
|
|
7470
|
-
const result = (0,
|
|
7924
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
|
|
7471
7925
|
encoding: "utf-8",
|
|
7472
7926
|
env: READ_ONLY_GIT_ENV
|
|
7473
7927
|
});
|
|
@@ -7475,10 +7929,10 @@ function resolveGitCommonDir(repoPath) {
|
|
|
7475
7929
|
if (result.status !== 0 || !output2) {
|
|
7476
7930
|
throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
|
|
7477
7931
|
}
|
|
7478
|
-
return comparableWorkspacePath(
|
|
7932
|
+
return comparableWorkspacePath(import_node_path22.default.isAbsolute(output2) ? output2 : import_node_path22.default.resolve(repoPath, output2));
|
|
7479
7933
|
}
|
|
7480
7934
|
function resolveGitTopLevel(repoPath) {
|
|
7481
|
-
const result = (0,
|
|
7935
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
|
|
7482
7936
|
encoding: "utf-8",
|
|
7483
7937
|
env: READ_ONLY_GIT_ENV
|
|
7484
7938
|
});
|
|
@@ -7489,7 +7943,7 @@ function resolveGitTopLevel(repoPath) {
|
|
|
7489
7943
|
return comparableWorkspacePath(output2);
|
|
7490
7944
|
}
|
|
7491
7945
|
function listGitWorktrees(repoPath) {
|
|
7492
|
-
const result = (0,
|
|
7946
|
+
const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
|
|
7493
7947
|
encoding: "utf-8",
|
|
7494
7948
|
env: READ_ONLY_GIT_ENV
|
|
7495
7949
|
});
|
|
@@ -7526,7 +7980,7 @@ function listGitWorktrees(repoPath) {
|
|
|
7526
7980
|
return worktrees;
|
|
7527
7981
|
}
|
|
7528
7982
|
function ownershipDirectory(repoPath) {
|
|
7529
|
-
return
|
|
7983
|
+
return import_node_path22.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
|
|
7530
7984
|
}
|
|
7531
7985
|
function validateWorkspace(value, label) {
|
|
7532
7986
|
if (typeof value !== "string" || wsFromBranch(value) !== value) {
|
|
@@ -7554,7 +8008,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7554
8008
|
`workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
|
|
7555
8009
|
);
|
|
7556
8010
|
}
|
|
7557
|
-
if (typeof candidate.worktreePath !== "string" || !
|
|
8011
|
+
if (typeof candidate.worktreePath !== "string" || !import_node_path22.default.isAbsolute(candidate.worktreePath)) {
|
|
7558
8012
|
throw new Error("invalid workspace ownership worktreePath");
|
|
7559
8013
|
}
|
|
7560
8014
|
if (candidate.branch !== null && typeof candidate.branch !== "string") {
|
|
@@ -7574,7 +8028,7 @@ function validateRecord(value, expectedWorkspace) {
|
|
|
7574
8028
|
function readRecordFile(filePath, expectedWorkspace) {
|
|
7575
8029
|
let parsed;
|
|
7576
8030
|
try {
|
|
7577
|
-
parsed = JSON.parse(
|
|
8031
|
+
parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
|
|
7578
8032
|
} catch (error) {
|
|
7579
8033
|
if (error instanceof SyntaxError) {
|
|
7580
8034
|
throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
|
|
@@ -7590,7 +8044,7 @@ function listWorkspaceOwnership(repoPath) {
|
|
|
7590
8044
|
function listWorkspaceOwnershipInDirectory(directory) {
|
|
7591
8045
|
let entries;
|
|
7592
8046
|
try {
|
|
7593
|
-
entries =
|
|
8047
|
+
entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
|
|
7594
8048
|
} catch (error) {
|
|
7595
8049
|
if (error.code === "ENOENT") return [];
|
|
7596
8050
|
throw error;
|
|
@@ -7598,14 +8052,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
|
|
|
7598
8052
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
|
|
7599
8053
|
const workspace = entry.name.slice(0, -".json".length);
|
|
7600
8054
|
validateWorkspace(workspace, "filename");
|
|
7601
|
-
return readRecordFile(
|
|
8055
|
+
return readRecordFile(import_node_path22.default.join(directory, entry.name), workspace);
|
|
7602
8056
|
});
|
|
7603
8057
|
}
|
|
7604
8058
|
function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
7605
8059
|
const workspace = validateWorkspace(input2.workspace, "workspace");
|
|
7606
8060
|
const devpodId = validateWorkspace(input2.devpodId, "devpodId");
|
|
7607
8061
|
const worktreePath = comparableWorkspacePath(input2.worktreePath);
|
|
7608
|
-
const filePath =
|
|
8062
|
+
const filePath = import_node_path22.default.join(directory, `${workspace}.json`);
|
|
7609
8063
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7610
8064
|
const records = listWorkspaceOwnershipInDirectory(directory);
|
|
7611
8065
|
const existing = records.find((record2) => record2.workspace === workspace);
|
|
@@ -7641,9 +8095,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
|
|
|
7641
8095
|
return record;
|
|
7642
8096
|
}
|
|
7643
8097
|
function removeWorkspaceOwnershipInDirectory(directory, workspace) {
|
|
7644
|
-
const filePath =
|
|
8098
|
+
const filePath = import_node_path22.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
|
|
7645
8099
|
try {
|
|
7646
|
-
|
|
8100
|
+
import_node_fs22.default.rmSync(filePath);
|
|
7647
8101
|
return true;
|
|
7648
8102
|
} catch (error) {
|
|
7649
8103
|
if (error.code === "ENOENT") return false;
|
|
@@ -7654,7 +8108,7 @@ function sameOwnershipRecord(left, right) {
|
|
|
7654
8108
|
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;
|
|
7655
8109
|
}
|
|
7656
8110
|
function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
7657
|
-
const filePath =
|
|
8111
|
+
const filePath = import_node_path22.default.join(
|
|
7658
8112
|
directory,
|
|
7659
8113
|
`${validateWorkspace(expected.workspace, "workspace")}.json`
|
|
7660
8114
|
);
|
|
@@ -7666,14 +8120,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
|
|
|
7666
8120
|
throw error;
|
|
7667
8121
|
}
|
|
7668
8122
|
if (!sameOwnershipRecord(current, expected)) return "changed";
|
|
7669
|
-
|
|
8123
|
+
import_node_fs22.default.rmSync(filePath);
|
|
7670
8124
|
return "removed";
|
|
7671
8125
|
}
|
|
7672
8126
|
function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
|
|
7673
8127
|
const directory = ownershipDirectory(repoPath);
|
|
7674
|
-
|
|
8128
|
+
import_node_fs22.default.mkdirSync(directory, { recursive: true });
|
|
7675
8129
|
return withFileLockSync(
|
|
7676
|
-
|
|
8130
|
+
import_node_path22.default.join(directory, ".lock"),
|
|
7677
8131
|
{
|
|
7678
8132
|
activity: "workspace ownership transaction",
|
|
7679
8133
|
target: `'${repoPath}'`,
|
|
@@ -7706,7 +8160,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
|
|
|
7706
8160
|
try {
|
|
7707
8161
|
workspace = readPersistedWorkspace(worktree.path);
|
|
7708
8162
|
} catch (error) {
|
|
7709
|
-
if (!
|
|
8163
|
+
if (!import_node_fs22.default.existsSync(worktree.path)) continue;
|
|
7710
8164
|
throw error;
|
|
7711
8165
|
}
|
|
7712
8166
|
if (!workspace) continue;
|
|
@@ -7869,7 +8323,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
|
|
|
7869
8323
|
if (worktree?.locked) {
|
|
7870
8324
|
return { ownerStatus: "locked", devpodStatus, worktree };
|
|
7871
8325
|
}
|
|
7872
|
-
if ((!worktree || worktree.prunable) &&
|
|
8326
|
+
if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record.worktreePath)) {
|
|
7873
8327
|
return { ownerStatus: "conflict", devpodStatus, worktree };
|
|
7874
8328
|
}
|
|
7875
8329
|
if (!worktree || worktree.prunable) {
|
|
@@ -7893,20 +8347,20 @@ function listMissingWorkspaceOwnership(repoPath) {
|
|
|
7893
8347
|
(record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
|
|
7894
8348
|
);
|
|
7895
8349
|
}
|
|
7896
|
-
var
|
|
8350
|
+
var import_node_child_process17, import_node_fs22, import_node_path22, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
|
|
7897
8351
|
var init_workspace_ownership = __esm({
|
|
7898
8352
|
"src/core/workspace-ownership.ts"() {
|
|
7899
8353
|
"use strict";
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
8354
|
+
import_node_child_process17 = require("child_process");
|
|
8355
|
+
import_node_fs22 = __toESM(require("fs"));
|
|
8356
|
+
import_node_path22 = __toESM(require("path"));
|
|
7903
8357
|
init_atomic_file();
|
|
7904
8358
|
init_devpod_workspaces();
|
|
7905
8359
|
init_file_lock();
|
|
7906
8360
|
init_workspace();
|
|
7907
8361
|
READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
|
|
7908
8362
|
OWNERSHIP_VERSION = 1;
|
|
7909
|
-
OWNERSHIP_DIR =
|
|
8363
|
+
OWNERSHIP_DIR = import_node_path22.default.join("devrouter", "workspaces");
|
|
7910
8364
|
}
|
|
7911
8365
|
});
|
|
7912
8366
|
|
|
@@ -7920,10 +8374,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
|
|
|
7920
8374
|
if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
|
|
7921
8375
|
const comparableRepo = comparableWorkspacePath(repoPath);
|
|
7922
8376
|
const comparableWorktree = comparableWorkspacePath(worktreePath);
|
|
7923
|
-
const localRoot =
|
|
8377
|
+
const localRoot = import_node_path23.default.join(comparableRepo, "trees") + import_node_path23.default.sep;
|
|
7924
8378
|
if (comparableWorktree.startsWith(localRoot)) return true;
|
|
7925
|
-
const legacyPrefix = `${
|
|
7926
|
-
return
|
|
8379
|
+
const legacyPrefix = `${import_node_path23.default.basename(comparableRepo)}-`;
|
|
8380
|
+
return import_node_path23.default.dirname(comparableWorktree) === import_node_path23.default.dirname(comparableRepo) && import_node_path23.default.basename(comparableWorktree).startsWith(legacyPrefix);
|
|
7927
8381
|
}
|
|
7928
8382
|
function previewActions(devpodStatus, routeCount, includeRecord) {
|
|
7929
8383
|
const actions = [
|
|
@@ -8175,11 +8629,11 @@ function applyWorkspaceGc(plan) {
|
|
|
8175
8629
|
candidates
|
|
8176
8630
|
};
|
|
8177
8631
|
}
|
|
8178
|
-
var
|
|
8632
|
+
var import_node_path23;
|
|
8179
8633
|
var init_workspace_gc = __esm({
|
|
8180
8634
|
"src/core/workspace-gc.ts"() {
|
|
8181
8635
|
"use strict";
|
|
8182
|
-
|
|
8636
|
+
import_node_path23 = __toESM(require("path"));
|
|
8183
8637
|
init_devpod_mutation();
|
|
8184
8638
|
init_devpod_workspaces();
|
|
8185
8639
|
init_host_routes();
|
|
@@ -8249,11 +8703,11 @@ function inspectPostgresCredentials(repoPath, config) {
|
|
|
8249
8703
|
parseErrors.push(`${app.name}: ${composeFile} (${message})`);
|
|
8250
8704
|
continue;
|
|
8251
8705
|
}
|
|
8252
|
-
if (!
|
|
8706
|
+
if (!import_node_fs23.default.existsSync(absolutePath)) {
|
|
8253
8707
|
continue;
|
|
8254
8708
|
}
|
|
8255
8709
|
try {
|
|
8256
|
-
const raw =
|
|
8710
|
+
const raw = import_node_fs23.default.readFileSync(absolutePath, "utf-8");
|
|
8257
8711
|
const parsed = import_yaml5.default.parse(raw);
|
|
8258
8712
|
const root = asRecord2(parsed);
|
|
8259
8713
|
const services = asRecord2(root?.services);
|
|
@@ -8532,7 +8986,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
8532
8986
|
const config = runtimeConfig.config;
|
|
8533
8987
|
loadedConfig = config;
|
|
8534
8988
|
loadedWorkspace = runtimeConfig.workspace;
|
|
8535
|
-
const cliVersion = true ? "0.0.
|
|
8989
|
+
const cliVersion = true ? "0.0.48" : "0.0.0-dev";
|
|
8536
8990
|
const configVersion = config.devrouter?.version;
|
|
8537
8991
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
8538
8992
|
addCheck(checks, {
|
|
@@ -8562,9 +9016,9 @@ async function buildDoctorReport(options = {}) {
|
|
|
8562
9016
|
(app) => app.docker.composeFiles.map((filePath) => ({
|
|
8563
9017
|
app: app.name,
|
|
8564
9018
|
filePath,
|
|
8565
|
-
absolutePath:
|
|
9019
|
+
absolutePath: import_node_path24.default.resolve(repo.path, filePath)
|
|
8566
9020
|
}))
|
|
8567
|
-
).filter((entry) => !
|
|
9021
|
+
).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
|
|
8568
9022
|
addCheck(checks, {
|
|
8569
9023
|
id: "repo.compose-files",
|
|
8570
9024
|
level: missingComposeFiles.length === 0 ? "ok" : "error",
|
|
@@ -8607,8 +9061,8 @@ async function buildDoctorReport(options = {}) {
|
|
|
8607
9061
|
const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
|
|
8608
9062
|
app: app.name,
|
|
8609
9063
|
cwd: app.hostRun.cwd,
|
|
8610
|
-
absolutePath:
|
|
8611
|
-
})).filter((entry) => !
|
|
9064
|
+
absolutePath: import_node_path24.default.resolve(repo.path, app.hostRun.cwd)
|
|
9065
|
+
})).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
|
|
8612
9066
|
addCheck(checks, {
|
|
8613
9067
|
id: "repo.host-cwd",
|
|
8614
9068
|
level: missingHostCwds.length === 0 ? "ok" : "error",
|
|
@@ -8752,12 +9206,12 @@ async function buildDoctorReport(options = {}) {
|
|
|
8752
9206
|
nextSteps
|
|
8753
9207
|
};
|
|
8754
9208
|
}
|
|
8755
|
-
var
|
|
9209
|
+
var import_node_fs23, import_node_path24, import_yaml5, POSTGRES_DEFAULTS;
|
|
8756
9210
|
var init_doctor = __esm({
|
|
8757
9211
|
"src/core/doctor.ts"() {
|
|
8758
9212
|
"use strict";
|
|
8759
|
-
|
|
8760
|
-
|
|
9213
|
+
import_node_fs23 = __toESM(require("fs"));
|
|
9214
|
+
import_node_path24 = __toESM(require("path"));
|
|
8761
9215
|
import_yaml5 = __toESM(require("yaml"));
|
|
8762
9216
|
init_docker();
|
|
8763
9217
|
init_host_routes();
|
|
@@ -8864,11 +9318,13 @@ async function runSetup(options = {}) {
|
|
|
8864
9318
|
})
|
|
8865
9319
|
);
|
|
8866
9320
|
}
|
|
9321
|
+
let requestedWorkspaceRuntime;
|
|
8867
9322
|
if (options.workspaceRuntime !== void 0 || options.devsyInactivityTimeout !== void 0) {
|
|
8868
9323
|
try {
|
|
8869
9324
|
const existing = readWorkspaceRuntimeConfig();
|
|
9325
|
+
requestedWorkspaceRuntime = options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : void 0;
|
|
8870
9326
|
const next = {
|
|
8871
|
-
runtime:
|
|
9327
|
+
runtime: requestedWorkspaceRuntime ?? existing.runtime,
|
|
8872
9328
|
devsyInactivityTimeout: options.devsyInactivityTimeout ?? existing.devsyInactivityTimeout
|
|
8873
9329
|
};
|
|
8874
9330
|
writeWorkspaceRuntimeConfig(next);
|
|
@@ -8890,6 +9346,28 @@ async function runSetup(options = {}) {
|
|
|
8890
9346
|
);
|
|
8891
9347
|
}
|
|
8892
9348
|
}
|
|
9349
|
+
if (requestedWorkspaceRuntime === "devsy") {
|
|
9350
|
+
try {
|
|
9351
|
+
const prepared = await prepareDevsyAgent();
|
|
9352
|
+
actions.push(
|
|
9353
|
+
action(prepared.changed ? "performed" : "skipped", {
|
|
9354
|
+
id: "global.devsy-agent",
|
|
9355
|
+
summary: prepared.changed ? `Downloaded and verified ${prepared.asset.name} for Devsy.` : `Verified the ${prepared.source} Devsy agent source.`,
|
|
9356
|
+
details: `version=${SUPPORTED_DEVSY_VERSION}, asset=${prepared.asset.name}, source=${prepared.source}, transport=${prepared.transport}`
|
|
9357
|
+
})
|
|
9358
|
+
);
|
|
9359
|
+
} catch (error) {
|
|
9360
|
+
const details = error instanceof DevsyAgentReadinessError ? `state=${error.inspection.state}, source=${error.inspection.source}, ${error.inspection.reason}` : error instanceof Error ? error.message : String(error);
|
|
9361
|
+
actions.push(
|
|
9362
|
+
action("failed", {
|
|
9363
|
+
id: "global.devsy-agent",
|
|
9364
|
+
summary: "Failed to prepare a verified Devsy agent source.",
|
|
9365
|
+
details,
|
|
9366
|
+
suggestion: error instanceof DevsyAgentReadinessError ? devsyAgentRepairSuggestion(error.inspection) : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`
|
|
9367
|
+
})
|
|
9368
|
+
);
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
8893
9371
|
try {
|
|
8894
9372
|
const existed = await networkExists(DEVNET_NAME);
|
|
8895
9373
|
await ensureNetwork(DEVNET_NAME);
|
|
@@ -8979,6 +9457,7 @@ async function runSetup(options = {}) {
|
|
|
8979
9457
|
var init_setup = __esm({
|
|
8980
9458
|
"src/core/setup.ts"() {
|
|
8981
9459
|
"use strict";
|
|
9460
|
+
init_devsy_agent();
|
|
8982
9461
|
init_docker();
|
|
8983
9462
|
init_doctor();
|
|
8984
9463
|
init_repo_config();
|
|
@@ -9047,12 +9526,12 @@ function parseSsPortListeners(stdout, port) {
|
|
|
9047
9526
|
return listeners;
|
|
9048
9527
|
}
|
|
9049
9528
|
function findPortListeners(port) {
|
|
9050
|
-
const result = (0,
|
|
9529
|
+
const result = (0, import_node_child_process18.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
|
|
9051
9530
|
encoding: "utf-8"
|
|
9052
9531
|
});
|
|
9053
9532
|
if (result.error && result.error.code === "ENOENT") {
|
|
9054
9533
|
if (process.platform === "linux") {
|
|
9055
|
-
const ssResult = (0,
|
|
9534
|
+
const ssResult = (0, import_node_child_process18.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
9056
9535
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
9057
9536
|
return parseSsPortListeners(ssResult.stdout, port);
|
|
9058
9537
|
}
|
|
@@ -9081,11 +9560,11 @@ function findPortListeners(port) {
|
|
|
9081
9560
|
};
|
|
9082
9561
|
});
|
|
9083
9562
|
}
|
|
9084
|
-
var
|
|
9563
|
+
var import_node_child_process18;
|
|
9085
9564
|
var init_ports = __esm({
|
|
9086
9565
|
"src/util/ports.ts"() {
|
|
9087
9566
|
"use strict";
|
|
9088
|
-
|
|
9567
|
+
import_node_child_process18 = require("child_process");
|
|
9089
9568
|
}
|
|
9090
9569
|
});
|
|
9091
9570
|
|
|
@@ -9162,18 +9641,18 @@ function probeHttpRoute(host, options = {}) {
|
|
|
9162
9641
|
args.push("--cacert", CERT_FILE);
|
|
9163
9642
|
}
|
|
9164
9643
|
args.push(url);
|
|
9165
|
-
const result = (0,
|
|
9644
|
+
const result = (0, import_node_child_process19.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
9166
9645
|
const stdout = result.stdout?.trim() ?? "";
|
|
9167
9646
|
const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
|
|
9168
9647
|
const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
|
|
9169
9648
|
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"}`;
|
|
9170
9649
|
return { ok, status, details };
|
|
9171
9650
|
}
|
|
9172
|
-
var
|
|
9651
|
+
var import_node_child_process19;
|
|
9173
9652
|
var init_http_route_probe = __esm({
|
|
9174
9653
|
"src/core/http-route-probe.ts"() {
|
|
9175
9654
|
"use strict";
|
|
9176
|
-
|
|
9655
|
+
import_node_child_process19 = require("child_process");
|
|
9177
9656
|
init_router();
|
|
9178
9657
|
init_tls();
|
|
9179
9658
|
}
|
|
@@ -9249,14 +9728,135 @@ var init_route_publication = __esm({
|
|
|
9249
9728
|
}
|
|
9250
9729
|
});
|
|
9251
9730
|
|
|
9731
|
+
// src/core/traefik-route-health.ts
|
|
9732
|
+
function sleep(ms) {
|
|
9733
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9734
|
+
}
|
|
9735
|
+
function inspectRouterApi(protocol) {
|
|
9736
|
+
const result = (0, import_node_child_process20.spawnSync)(
|
|
9737
|
+
"curl",
|
|
9738
|
+
[
|
|
9739
|
+
"--silent",
|
|
9740
|
+
"--show-error",
|
|
9741
|
+
"--fail",
|
|
9742
|
+
"--max-time",
|
|
9743
|
+
"2",
|
|
9744
|
+
`${ROUTER_API_BASE}/${protocol}/routers`
|
|
9745
|
+
],
|
|
9746
|
+
{ encoding: "utf-8" }
|
|
9747
|
+
);
|
|
9748
|
+
if (result.status !== 0) {
|
|
9749
|
+
return {
|
|
9750
|
+
ok: false,
|
|
9751
|
+
details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9754
|
+
let value;
|
|
9755
|
+
try {
|
|
9756
|
+
value = JSON.parse(result.stdout);
|
|
9757
|
+
} catch {
|
|
9758
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
|
|
9759
|
+
}
|
|
9760
|
+
if (!Array.isArray(value)) {
|
|
9761
|
+
return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
|
|
9762
|
+
}
|
|
9763
|
+
return {
|
|
9764
|
+
ok: true,
|
|
9765
|
+
names: new Set(
|
|
9766
|
+
value.flatMap((item) => {
|
|
9767
|
+
if (!item || typeof item !== "object") return [];
|
|
9768
|
+
const name = item.name;
|
|
9769
|
+
return typeof name === "string" ? [name] : [];
|
|
9770
|
+
})
|
|
9771
|
+
)
|
|
9772
|
+
};
|
|
9773
|
+
}
|
|
9774
|
+
function inspectExpectedRoutes(routes) {
|
|
9775
|
+
const missing = [];
|
|
9776
|
+
const failures = [];
|
|
9777
|
+
for (const protocol of ["http", "tcp"]) {
|
|
9778
|
+
const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
|
|
9779
|
+
if (expected.length === 0) continue;
|
|
9780
|
+
const result = inspectRouterApi(protocol);
|
|
9781
|
+
if (!result.ok) {
|
|
9782
|
+
failures.push(result.details);
|
|
9783
|
+
missing.push(...expected);
|
|
9784
|
+
continue;
|
|
9785
|
+
}
|
|
9786
|
+
missing.push(...expected.filter((name) => !result.names.has(name)));
|
|
9787
|
+
}
|
|
9788
|
+
return missing.length === 0 ? { ok: true } : {
|
|
9789
|
+
ok: false,
|
|
9790
|
+
missing,
|
|
9791
|
+
details: failures.length > 0 ? failures.join("; ") : "router names are absent"
|
|
9792
|
+
};
|
|
9793
|
+
}
|
|
9794
|
+
async function waitForExpectedRoutes(routes, timeoutMs, pollIntervalMs) {
|
|
9795
|
+
const deadline = Date.now() + timeoutMs;
|
|
9796
|
+
let result;
|
|
9797
|
+
do {
|
|
9798
|
+
result = inspectExpectedRoutes(routes);
|
|
9799
|
+
if (result.ok) return result;
|
|
9800
|
+
if (Date.now() < deadline) {
|
|
9801
|
+
await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
|
|
9802
|
+
}
|
|
9803
|
+
} while (Date.now() < deadline);
|
|
9804
|
+
return result;
|
|
9805
|
+
}
|
|
9806
|
+
async function ensureTraefikRoutesLoaded(routes, options = {}) {
|
|
9807
|
+
if (routes.length === 0) return { restarted: false };
|
|
9808
|
+
const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
|
|
9809
|
+
const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
|
|
9810
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
9811
|
+
const initial = await waitForExpectedRoutes(routes, initialTimeoutMs, pollIntervalMs);
|
|
9812
|
+
if (initial.ok) return { restarted: false };
|
|
9813
|
+
import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
|
|
9814
|
+
return withFileLock(
|
|
9815
|
+
ROUTER_RELOAD_LOCK_FILE,
|
|
9816
|
+
{
|
|
9817
|
+
activity: "Traefik route reload recovery",
|
|
9818
|
+
waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
|
|
9819
|
+
fair: true,
|
|
9820
|
+
onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
|
|
9821
|
+
},
|
|
9822
|
+
async () => {
|
|
9823
|
+
const recheck = await waitForExpectedRoutes(routes, 0, pollIntervalMs);
|
|
9824
|
+
if (recheck.ok) return { restarted: false };
|
|
9825
|
+
restartRouterStack();
|
|
9826
|
+
const recovered = await waitForExpectedRoutes(routes, recoveryTimeoutMs, pollIntervalMs);
|
|
9827
|
+
if (recovered.ok) return { restarted: true };
|
|
9828
|
+
throw new Error(
|
|
9829
|
+
`Traefik did not load file-provider routes after one restart: ${recovered.missing.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
|
|
9830
|
+
);
|
|
9831
|
+
}
|
|
9832
|
+
);
|
|
9833
|
+
}
|
|
9834
|
+
var import_node_child_process20, import_node_fs24, import_node_path25, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS;
|
|
9835
|
+
var init_traefik_route_health = __esm({
|
|
9836
|
+
"src/core/traefik-route-health.ts"() {
|
|
9837
|
+
"use strict";
|
|
9838
|
+
import_node_child_process20 = require("child_process");
|
|
9839
|
+
import_node_fs24 = __toESM(require("fs"));
|
|
9840
|
+
import_node_path25 = __toESM(require("path"));
|
|
9841
|
+
init_file_lock();
|
|
9842
|
+
init_host_routes();
|
|
9843
|
+
init_router();
|
|
9844
|
+
ROUTER_API_BASE = "http://127.0.0.1:8080/api";
|
|
9845
|
+
ROUTER_RELOAD_LOCK_FILE = import_node_path25.default.join(DEVROUTER_HOME, "router-reload.lock");
|
|
9846
|
+
DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
|
|
9847
|
+
DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
|
|
9848
|
+
DEFAULT_POLL_INTERVAL_MS = 250;
|
|
9849
|
+
}
|
|
9850
|
+
});
|
|
9851
|
+
|
|
9252
9852
|
// src/core/workspace-ensure.ts
|
|
9253
9853
|
function assertOverlay(container, repoPath) {
|
|
9254
9854
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
9255
|
-
if (!workingDir || !sameWorkspacePath(workingDir,
|
|
9855
|
+
if (!workingDir || !sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))) {
|
|
9256
9856
|
throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
|
|
9257
9857
|
}
|
|
9258
9858
|
const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
|
|
9259
|
-
const expectedOverlay =
|
|
9859
|
+
const expectedOverlay = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
9260
9860
|
if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
|
|
9261
9861
|
throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
|
|
9262
9862
|
}
|
|
@@ -9330,7 +9930,7 @@ async function waitForManagedServices(plan, repoPath, composeProject, expectedSe
|
|
|
9330
9930
|
} catch (error) {
|
|
9331
9931
|
lastError = error;
|
|
9332
9932
|
}
|
|
9333
|
-
if (Date.now() < deadline) await
|
|
9933
|
+
if (Date.now() < deadline) await sleep2(POLL_INTERVAL_MS);
|
|
9334
9934
|
} while (Date.now() < deadline);
|
|
9335
9935
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
9336
9936
|
}
|
|
@@ -9439,7 +10039,7 @@ function hasExactManagedComposeProject(repoPath, state) {
|
|
|
9439
10039
|
}).some((container) => {
|
|
9440
10040
|
const workingDir = container.labels["com.docker.compose.project.working_dir"];
|
|
9441
10041
|
return Boolean(
|
|
9442
|
-
workingDir && sameWorkspacePath(workingDir,
|
|
10042
|
+
workingDir && sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))
|
|
9443
10043
|
);
|
|
9444
10044
|
});
|
|
9445
10045
|
} catch {
|
|
@@ -9533,7 +10133,7 @@ function validateWorkspaceContainers(containers, options) {
|
|
|
9533
10133
|
}
|
|
9534
10134
|
return { id: appContainer.id, workspacePath: repoMount.Destination };
|
|
9535
10135
|
}
|
|
9536
|
-
function
|
|
10136
|
+
function sleep2(ms) {
|
|
9537
10137
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9538
10138
|
}
|
|
9539
10139
|
async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeoutMs) {
|
|
@@ -9547,7 +10147,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9547
10147
|
target
|
|
9548
10148
|
});
|
|
9549
10149
|
if (target.kind === "linked") {
|
|
9550
|
-
const workspaceEnv = (0,
|
|
10150
|
+
const workspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
9551
10151
|
"docker",
|
|
9552
10152
|
["exec", appContainer.id, "printenv", "WORKSPACE"],
|
|
9553
10153
|
{ encoding: "utf-8" }
|
|
@@ -9557,7 +10157,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9557
10157
|
`Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
|
|
9558
10158
|
);
|
|
9559
10159
|
}
|
|
9560
|
-
const devrouterWorkspaceEnv = (0,
|
|
10160
|
+
const devrouterWorkspaceEnv = (0, import_node_child_process21.spawnSync)(
|
|
9561
10161
|
"docker",
|
|
9562
10162
|
["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
|
|
9563
10163
|
{ encoding: "utf-8" }
|
|
@@ -9568,7 +10168,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9568
10168
|
);
|
|
9569
10169
|
}
|
|
9570
10170
|
}
|
|
9571
|
-
const gitCheck = (0,
|
|
10171
|
+
const gitCheck = (0, import_node_child_process21.spawnSync)(
|
|
9572
10172
|
"docker",
|
|
9573
10173
|
[
|
|
9574
10174
|
"exec",
|
|
@@ -9589,7 +10189,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
|
|
|
9589
10189
|
lastError = error;
|
|
9590
10190
|
}
|
|
9591
10191
|
if (Date.now() < deadline) {
|
|
9592
|
-
await
|
|
10192
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
9593
10193
|
}
|
|
9594
10194
|
} while (Date.now() < deadline);
|
|
9595
10195
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
@@ -9614,7 +10214,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
|
|
|
9614
10214
|
return;
|
|
9615
10215
|
}
|
|
9616
10216
|
if (Date.now() < deadline) {
|
|
9617
|
-
await
|
|
10217
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
9618
10218
|
}
|
|
9619
10219
|
} while (Date.now() < deadline);
|
|
9620
10220
|
throw new Error(
|
|
@@ -9660,14 +10260,14 @@ function resolvePrimaryTarget(repoPath) {
|
|
|
9660
10260
|
}
|
|
9661
10261
|
function isPrimaryCheckout(repoPath) {
|
|
9662
10262
|
try {
|
|
9663
|
-
return
|
|
10263
|
+
return import_node_fs25.default.statSync(import_node_path26.default.join(repoPath, ".git")).isDirectory();
|
|
9664
10264
|
} catch {
|
|
9665
10265
|
return false;
|
|
9666
10266
|
}
|
|
9667
10267
|
}
|
|
9668
10268
|
function openUrls(urls) {
|
|
9669
10269
|
for (const url of urls) {
|
|
9670
|
-
const opened = (0,
|
|
10270
|
+
const opened = (0, import_node_child_process21.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
9671
10271
|
if (opened.status !== 0) {
|
|
9672
10272
|
process.stderr.write(`Warning: could not open '${url}'.
|
|
9673
10273
|
`);
|
|
@@ -9706,12 +10306,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9706
10306
|
let managedConfigWritten = false;
|
|
9707
10307
|
let managedWorkspaceEnv;
|
|
9708
10308
|
let firstTransitionBaseline;
|
|
10309
|
+
const routeLoadOptions = {
|
|
10310
|
+
initialTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 3e3),
|
|
10311
|
+
recoveryTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 1e4)
|
|
10312
|
+
};
|
|
9709
10313
|
try {
|
|
9710
10314
|
const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
|
|
9711
10315
|
let devpodId = target.devpodId;
|
|
9712
10316
|
if (target.kind === "linked") {
|
|
9713
|
-
const overlayPath =
|
|
9714
|
-
if (!
|
|
10317
|
+
const overlayPath = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
|
|
10318
|
+
if (!import_node_fs25.default.existsSync(overlayPath)) {
|
|
9715
10319
|
throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
|
|
9716
10320
|
}
|
|
9717
10321
|
}
|
|
@@ -9781,7 +10385,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9781
10385
|
}
|
|
9782
10386
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
9783
10387
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
9784
|
-
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ??
|
|
10388
|
+
const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path26.default.basename(repoPath)) ?? "app";
|
|
9785
10389
|
for (const [index, app] of apps.entries()) {
|
|
9786
10390
|
if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
|
|
9787
10391
|
const owner = target.kind === "linked" ? "workspace" : "checkout";
|
|
@@ -9945,6 +10549,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9945
10549
|
target.workspace
|
|
9946
10550
|
);
|
|
9947
10551
|
candidateRoutesPublished = true;
|
|
10552
|
+
await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
|
|
9948
10553
|
try {
|
|
9949
10554
|
await waitForHttpRoutes(
|
|
9950
10555
|
repoPath,
|
|
@@ -9968,6 +10573,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
9968
10573
|
});
|
|
9969
10574
|
recreated = true;
|
|
9970
10575
|
replaceHostRoutesForRepo(repoPath, publication.routes);
|
|
10576
|
+
await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
|
|
9971
10577
|
await waitForHttpRoutes(
|
|
9972
10578
|
repoPath,
|
|
9973
10579
|
apps,
|
|
@@ -10141,7 +10747,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
10141
10747
|
}
|
|
10142
10748
|
if (candidateRoutesPublished) {
|
|
10143
10749
|
try {
|
|
10144
|
-
|
|
10750
|
+
const rollbackRoutes = previousRoutes.map(routeInputFromState);
|
|
10751
|
+
replaceHostRoutesForRepo(repoPath, rollbackRoutes);
|
|
10752
|
+
await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
|
|
10145
10753
|
} catch (rollbackError) {
|
|
10146
10754
|
rollbackErrors.push(
|
|
10147
10755
|
`routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -10184,7 +10792,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
10184
10792
|
}
|
|
10185
10793
|
if (candidateRoutesPublished) {
|
|
10186
10794
|
try {
|
|
10187
|
-
|
|
10795
|
+
const rollbackRoutes = previousRoutes.map(routeInputFromState);
|
|
10796
|
+
replaceHostRoutesForRepo(repoPath, rollbackRoutes);
|
|
10797
|
+
await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
|
|
10188
10798
|
} catch (rollbackError) {
|
|
10189
10799
|
rollbackErrors.push(
|
|
10190
10800
|
`routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
|
|
@@ -10213,13 +10823,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
10213
10823
|
}
|
|
10214
10824
|
});
|
|
10215
10825
|
}
|
|
10216
|
-
var
|
|
10826
|
+
var import_node_child_process21, import_node_fs25, import_node_path26, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
|
|
10217
10827
|
var init_workspace_ensure = __esm({
|
|
10218
10828
|
"src/core/workspace-ensure.ts"() {
|
|
10219
10829
|
"use strict";
|
|
10220
|
-
|
|
10221
|
-
|
|
10222
|
-
|
|
10830
|
+
import_node_child_process21 = require("child_process");
|
|
10831
|
+
import_node_fs25 = __toESM(require("fs"));
|
|
10832
|
+
import_node_path26 = __toESM(require("path"));
|
|
10223
10833
|
init_devcontainer_profile();
|
|
10224
10834
|
init_devpod_environment();
|
|
10225
10835
|
init_devpod_mutation();
|
|
@@ -10232,6 +10842,7 @@ var init_workspace_ensure = __esm({
|
|
|
10232
10842
|
init_repo_config();
|
|
10233
10843
|
init_route_publication();
|
|
10234
10844
|
init_router();
|
|
10845
|
+
init_traefik_route_health();
|
|
10235
10846
|
init_workspace();
|
|
10236
10847
|
init_workspace_ownership();
|
|
10237
10848
|
init_workspace_runtime();
|
|
@@ -10305,20 +10916,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
|
|
|
10305
10916
|
);
|
|
10306
10917
|
}
|
|
10307
10918
|
function defaultWorktreePath(mainRepo, ws) {
|
|
10308
|
-
return
|
|
10919
|
+
return import_node_path27.default.join(mainRepo, "trees", ws);
|
|
10309
10920
|
}
|
|
10310
10921
|
function assertDefaultWorktreeRootIgnored(mainRepo) {
|
|
10311
|
-
const ignored = (0,
|
|
10922
|
+
const ignored = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
|
|
10312
10923
|
encoding: "utf-8"
|
|
10313
10924
|
});
|
|
10314
10925
|
if (ignored.status !== 0) {
|
|
10315
10926
|
throw new Error(
|
|
10316
|
-
`Default worktree root '${
|
|
10927
|
+
`Default worktree root '${import_node_path27.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path27.default.join(mainRepo, ".gitignore")}' or use --path.`
|
|
10317
10928
|
);
|
|
10318
10929
|
}
|
|
10319
10930
|
}
|
|
10320
10931
|
function legacyDefaultWorktreePath(mainRepo, ws) {
|
|
10321
|
-
return
|
|
10932
|
+
return import_node_path27.default.join(import_node_path27.default.dirname(mainRepo), `${import_node_path27.default.basename(mainRepo)}-${ws}`);
|
|
10322
10933
|
}
|
|
10323
10934
|
function teardownFallbackPath(mainRepo, workspace) {
|
|
10324
10935
|
const candidates = [
|
|
@@ -10430,13 +11041,13 @@ function assertFullDownPreflight(mainRepo, target) {
|
|
|
10430
11041
|
if (sameWorkspacePath(target.worktreePath, mainRepo)) {
|
|
10431
11042
|
throw new Error("Refusing to remove the primary Git checkout.");
|
|
10432
11043
|
}
|
|
10433
|
-
if (!target.worktree || target.worktree.prunable || !
|
|
11044
|
+
if (!target.worktree || target.worktree.prunable || !import_node_fs26.default.existsSync(target.worktreePath)) return;
|
|
10434
11045
|
if (target.worktree.locked) {
|
|
10435
11046
|
throw new Error(
|
|
10436
11047
|
`Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
|
|
10437
11048
|
);
|
|
10438
11049
|
}
|
|
10439
|
-
const status = (0,
|
|
11050
|
+
const status = (0, import_node_child_process22.spawnSync)(
|
|
10440
11051
|
"git",
|
|
10441
11052
|
["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
|
|
10442
11053
|
{ encoding: "utf-8" }
|
|
@@ -10470,9 +11081,9 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10470
11081
|
(candidate) => defaultWorktreePath(mainRepo, candidate)
|
|
10471
11082
|
);
|
|
10472
11083
|
const generatedPath = candidatePaths.find(
|
|
10473
|
-
(candidate) => !
|
|
11084
|
+
(candidate) => !import_node_fs26.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
|
|
10474
11085
|
);
|
|
10475
|
-
const selectedPath = opts.path ?
|
|
11086
|
+
const selectedPath = opts.path ? import_node_path27.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
|
|
10476
11087
|
if (!selectedPath) {
|
|
10477
11088
|
throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
|
|
10478
11089
|
}
|
|
@@ -10481,7 +11092,7 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10481
11092
|
`Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
|
|
10482
11093
|
);
|
|
10483
11094
|
}
|
|
10484
|
-
if (
|
|
11095
|
+
if (import_node_fs26.default.existsSync(selectedPath)) {
|
|
10485
11096
|
const registered = worktrees.find(
|
|
10486
11097
|
(worktree) => sameWorkspacePath(worktree.path, selectedPath)
|
|
10487
11098
|
);
|
|
@@ -10500,11 +11111,11 @@ async function workspaceUp(branch, opts = {}) {
|
|
|
10500
11111
|
return selectedPath;
|
|
10501
11112
|
}
|
|
10502
11113
|
if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
|
|
10503
|
-
const add = (0,
|
|
11114
|
+
const add = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
|
|
10504
11115
|
encoding: "utf-8"
|
|
10505
11116
|
});
|
|
10506
11117
|
if (add.status !== 0) {
|
|
10507
|
-
const addNew = (0,
|
|
11118
|
+
const addNew = (0, import_node_child_process22.spawnSync)(
|
|
10508
11119
|
"git",
|
|
10509
11120
|
["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
|
|
10510
11121
|
{ encoding: "utf-8" }
|
|
@@ -10606,8 +11217,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
10606
11217
|
opts.quiet
|
|
10607
11218
|
);
|
|
10608
11219
|
if (removeWorktree) {
|
|
10609
|
-
if (resolved.worktree && !resolved.worktree.prunable &&
|
|
10610
|
-
const rm = (0,
|
|
11220
|
+
if (resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath)) {
|
|
11221
|
+
const rm = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
|
|
10611
11222
|
encoding: "utf-8"
|
|
10612
11223
|
});
|
|
10613
11224
|
if (rm.status !== 0) {
|
|
@@ -10625,7 +11236,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
|
|
|
10625
11236
|
}
|
|
10626
11237
|
return result;
|
|
10627
11238
|
};
|
|
10628
|
-
return resolved.worktree && !resolved.worktree.prunable &&
|
|
11239
|
+
return resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
|
|
10629
11240
|
}
|
|
10630
11241
|
async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
|
|
10631
11242
|
const mainRepo = resolveRepoPath(opts.repoPath);
|
|
@@ -10660,13 +11271,13 @@ async function workspaceStop(target, opts = {}) {
|
|
|
10660
11271
|
async function workspaceDown(target, opts = {}) {
|
|
10661
11272
|
return runWorkspaceLifecycle("down", target, opts);
|
|
10662
11273
|
}
|
|
10663
|
-
var
|
|
11274
|
+
var import_node_child_process22, import_node_fs26, import_node_path27, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
|
|
10664
11275
|
var init_workspace_lifecycle = __esm({
|
|
10665
11276
|
"src/core/workspace-lifecycle.ts"() {
|
|
10666
11277
|
"use strict";
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
11278
|
+
import_node_child_process22 = require("child_process");
|
|
11279
|
+
import_node_fs26 = __toESM(require("fs"));
|
|
11280
|
+
import_node_path27 = __toESM(require("path"));
|
|
10670
11281
|
init_devpod_mutation();
|
|
10671
11282
|
init_devpod_workspaces();
|
|
10672
11283
|
init_repo_config();
|
|
@@ -10795,17 +11406,17 @@ async function devsyExec(repoPath, command) {
|
|
|
10795
11406
|
}
|
|
10796
11407
|
const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
|
|
10797
11408
|
return new Promise((resolve, reject) => {
|
|
10798
|
-
const child = (0,
|
|
11409
|
+
const child = (0, import_node_child_process23.spawn)("devsy", args, { stdio: "inherit" });
|
|
10799
11410
|
child.once("error", (error) => reject(new Error(`devsy exec failed: ${error.message}`)));
|
|
10800
11411
|
child.once("close", (code) => resolve(code ?? 1));
|
|
10801
11412
|
});
|
|
10802
11413
|
});
|
|
10803
11414
|
}
|
|
10804
|
-
var
|
|
11415
|
+
var import_node_child_process23;
|
|
10805
11416
|
var init_devsy_exec = __esm({
|
|
10806
11417
|
"src/core/devsy-exec.ts"() {
|
|
10807
11418
|
"use strict";
|
|
10808
|
-
|
|
11419
|
+
import_node_child_process23 = require("child_process");
|
|
10809
11420
|
init_devsy_workspaces();
|
|
10810
11421
|
init_workspace();
|
|
10811
11422
|
}
|
|
@@ -10827,7 +11438,7 @@ function ensureGuidance2(repoPath) {
|
|
|
10827
11438
|
return `Run 'devrouter ensure ${repoPath}' first.`;
|
|
10828
11439
|
}
|
|
10829
11440
|
function assertRunningDevpod(devpodId, repoPath) {
|
|
10830
|
-
const result = (0,
|
|
11441
|
+
const result = (0, import_node_child_process24.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
|
|
10831
11442
|
encoding: "utf-8"
|
|
10832
11443
|
});
|
|
10833
11444
|
if (result.status !== 0) {
|
|
@@ -10868,7 +11479,7 @@ async function devpodExec(repoPath, command) {
|
|
|
10868
11479
|
}
|
|
10869
11480
|
assertRunningDevpod(devpod.id, repoPath);
|
|
10870
11481
|
const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
|
|
10871
|
-
const statusMarker = `__DEVROUTER_EXIT_${(0,
|
|
11482
|
+
const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto9.randomUUID)()}__:`;
|
|
10872
11483
|
const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
|
|
10873
11484
|
const literalCommand = command.map(quotePosixArg).join(" ");
|
|
10874
11485
|
const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
|
|
@@ -10886,7 +11497,7 @@ async function devpodExec(repoPath, command) {
|
|
|
10886
11497
|
wrappedCommand
|
|
10887
11498
|
];
|
|
10888
11499
|
return new Promise((resolve, reject) => {
|
|
10889
|
-
const child = (0,
|
|
11500
|
+
const child = (0, import_node_child_process24.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
|
|
10890
11501
|
let pending = Buffer.alloc(0);
|
|
10891
11502
|
let statusBytes = Buffer.alloc(0);
|
|
10892
11503
|
let readingStatus = false;
|
|
@@ -10945,12 +11556,12 @@ async function devpodExec(repoPath, command) {
|
|
|
10945
11556
|
});
|
|
10946
11557
|
});
|
|
10947
11558
|
}
|
|
10948
|
-
var
|
|
11559
|
+
var import_node_child_process24, import_node_crypto9, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
|
|
10949
11560
|
var init_devpod_exec = __esm({
|
|
10950
11561
|
"src/core/devpod-exec.ts"() {
|
|
10951
11562
|
"use strict";
|
|
10952
|
-
|
|
10953
|
-
|
|
11563
|
+
import_node_child_process24 = require("child_process");
|
|
11564
|
+
import_node_crypto9 = require("crypto");
|
|
10954
11565
|
init_devpod_environment();
|
|
10955
11566
|
init_devpod_workspaces();
|
|
10956
11567
|
init_devsy_exec();
|
|
@@ -11136,7 +11747,7 @@ async function runOpenCommand(name) {
|
|
|
11136
11747
|
);
|
|
11137
11748
|
return;
|
|
11138
11749
|
}
|
|
11139
|
-
const result = (0,
|
|
11750
|
+
const result = (0, import_node_child_process25.spawnSync)("open", [url], { encoding: "utf-8" });
|
|
11140
11751
|
if (result.status !== 0) {
|
|
11141
11752
|
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
11142
11753
|
throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
|
|
@@ -11144,11 +11755,11 @@ async function runOpenCommand(name) {
|
|
|
11144
11755
|
process.stdout.write(`Opened ${url}
|
|
11145
11756
|
`);
|
|
11146
11757
|
}
|
|
11147
|
-
var
|
|
11758
|
+
var import_node_child_process25;
|
|
11148
11759
|
var init_open = __esm({
|
|
11149
11760
|
"src/commands/open.ts"() {
|
|
11150
11761
|
"use strict";
|
|
11151
|
-
|
|
11762
|
+
import_node_child_process25 = require("child_process");
|
|
11152
11763
|
init_docker();
|
|
11153
11764
|
init_host_routes();
|
|
11154
11765
|
init_repo_config();
|
|
@@ -11171,7 +11782,7 @@ async function runLogsCommand(options) {
|
|
|
11171
11782
|
const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
|
|
11172
11783
|
if (options.follow) {
|
|
11173
11784
|
args.splice(1, 0, "-f");
|
|
11174
|
-
const child = (0,
|
|
11785
|
+
const child = (0, import_node_child_process26.spawn)("docker", args, { stdio: "inherit" });
|
|
11175
11786
|
const onSignal = () => {
|
|
11176
11787
|
child.kill("SIGTERM");
|
|
11177
11788
|
};
|
|
@@ -11185,17 +11796,17 @@ async function runLogsCommand(options) {
|
|
|
11185
11796
|
});
|
|
11186
11797
|
});
|
|
11187
11798
|
} else {
|
|
11188
|
-
const result = (0,
|
|
11799
|
+
const result = (0, import_node_child_process26.spawnSync)("docker", args, { stdio: "inherit" });
|
|
11189
11800
|
if (result.status !== 0) {
|
|
11190
11801
|
throw new Error("Failed to retrieve router logs.");
|
|
11191
11802
|
}
|
|
11192
11803
|
}
|
|
11193
11804
|
}
|
|
11194
|
-
var
|
|
11805
|
+
var import_node_child_process26;
|
|
11195
11806
|
var init_logs = __esm({
|
|
11196
11807
|
"src/commands/logs.ts"() {
|
|
11197
11808
|
"use strict";
|
|
11198
|
-
|
|
11809
|
+
import_node_child_process26 = require("child_process");
|
|
11199
11810
|
init_docker();
|
|
11200
11811
|
init_router();
|
|
11201
11812
|
}
|
|
@@ -11235,17 +11846,17 @@ function asRecord3(value) {
|
|
|
11235
11846
|
return value;
|
|
11236
11847
|
}
|
|
11237
11848
|
function readJson(filePath) {
|
|
11238
|
-
if (!
|
|
11849
|
+
if (!import_node_fs27.default.existsSync(filePath)) {
|
|
11239
11850
|
return void 0;
|
|
11240
11851
|
}
|
|
11241
11852
|
try {
|
|
11242
|
-
return JSON.parse(
|
|
11853
|
+
return JSON.parse(import_node_fs27.default.readFileSync(filePath, "utf-8"));
|
|
11243
11854
|
} catch {
|
|
11244
11855
|
return void 0;
|
|
11245
11856
|
}
|
|
11246
11857
|
}
|
|
11247
11858
|
function relative(repoPath, filePath) {
|
|
11248
|
-
return
|
|
11859
|
+
return import_node_path28.default.relative(repoPath, filePath) || ".";
|
|
11249
11860
|
}
|
|
11250
11861
|
function redactEnvAssignments(value) {
|
|
11251
11862
|
return value.replace(
|
|
@@ -11286,7 +11897,7 @@ function inspectPackageManager(repoPath, pkg) {
|
|
|
11286
11897
|
["bun.lock", "bun"]
|
|
11287
11898
|
];
|
|
11288
11899
|
for (const [fileName, name] of lockfiles) {
|
|
11289
|
-
if (
|
|
11900
|
+
if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
|
|
11290
11901
|
return { name, source: fileName };
|
|
11291
11902
|
}
|
|
11292
11903
|
}
|
|
@@ -11301,9 +11912,9 @@ function inspectNode(repoPath, pkg) {
|
|
|
11301
11912
|
if (typeof engines?.node === "string") {
|
|
11302
11913
|
return { version: engines.node, source: "package.json:engines.node" };
|
|
11303
11914
|
}
|
|
11304
|
-
const nvmrc =
|
|
11305
|
-
if (
|
|
11306
|
-
const version =
|
|
11915
|
+
const nvmrc = import_node_path28.default.join(repoPath, ".nvmrc");
|
|
11916
|
+
if (import_node_fs27.default.existsSync(nvmrc)) {
|
|
11917
|
+
const version = import_node_fs27.default.readFileSync(nvmrc, "utf-8").trim();
|
|
11307
11918
|
return { version, source: ".nvmrc" };
|
|
11308
11919
|
}
|
|
11309
11920
|
return void 0;
|
|
@@ -11364,7 +11975,7 @@ function configuredComposeFiles(repoPath) {
|
|
|
11364
11975
|
const files = config.apps.filter(
|
|
11365
11976
|
(app) => app.runtime === "docker"
|
|
11366
11977
|
).flatMap((app) => app.docker.composeFiles).filter(
|
|
11367
|
-
(fileName) => !
|
|
11978
|
+
(fileName) => !import_node_path28.default.isAbsolute(fileName) && !import_node_path28.default.normalize(fileName).startsWith("..")
|
|
11368
11979
|
);
|
|
11369
11980
|
return Array.from(new Set(files));
|
|
11370
11981
|
} catch {
|
|
@@ -11382,7 +11993,7 @@ function composeFiles(repoPath) {
|
|
|
11382
11993
|
...configuredComposeFiles(repoPath)
|
|
11383
11994
|
];
|
|
11384
11995
|
return Array.from(new Set(candidates)).filter(
|
|
11385
|
-
(fileName) =>
|
|
11996
|
+
(fileName) => import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))
|
|
11386
11997
|
);
|
|
11387
11998
|
}
|
|
11388
11999
|
function stringArray2(value) {
|
|
@@ -11420,7 +12031,7 @@ function inspectServices(repoPath) {
|
|
|
11420
12031
|
const services = [];
|
|
11421
12032
|
for (const fileName of composeFiles(repoPath)) {
|
|
11422
12033
|
try {
|
|
11423
|
-
const parsed = import_yaml6.default.parse(
|
|
12034
|
+
const parsed = import_yaml6.default.parse(import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8"));
|
|
11424
12035
|
const serviceMap = asRecord3(asRecord3(parsed)?.services);
|
|
11425
12036
|
for (const [name, value] of Object.entries(serviceMap ?? {})) {
|
|
11426
12037
|
const service = asRecord3(value);
|
|
@@ -11455,8 +12066,8 @@ function inspectServices(repoPath) {
|
|
|
11455
12066
|
return services;
|
|
11456
12067
|
}
|
|
11457
12068
|
function inspectEnvFiles(repoPath) {
|
|
11458
|
-
const files =
|
|
11459
|
-
const content =
|
|
12069
|
+
const files = import_node_fs27.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
|
|
12070
|
+
const content = import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8");
|
|
11460
12071
|
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();
|
|
11461
12072
|
return { path: fileName, names };
|
|
11462
12073
|
});
|
|
@@ -11470,18 +12081,18 @@ function inspectEnvFiles(repoPath) {
|
|
|
11470
12081
|
};
|
|
11471
12082
|
}
|
|
11472
12083
|
function inspectDevcontainer(repoPath) {
|
|
11473
|
-
const dir =
|
|
11474
|
-
if (!
|
|
12084
|
+
const dir = import_node_path28.default.join(repoPath, ".devcontainer");
|
|
12085
|
+
if (!import_node_fs27.default.existsSync(dir)) {
|
|
11475
12086
|
return { exists: false, files: [] };
|
|
11476
12087
|
}
|
|
11477
12088
|
return {
|
|
11478
12089
|
exists: true,
|
|
11479
|
-
files:
|
|
12090
|
+
files: import_node_fs27.default.readdirSync(dir).filter((fileName) => import_node_fs27.default.statSync(import_node_path28.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
|
|
11480
12091
|
};
|
|
11481
12092
|
}
|
|
11482
12093
|
function inspectDevrouter(repoPath) {
|
|
11483
12094
|
const configPath = getRepoConfigPath(repoPath);
|
|
11484
|
-
if (!
|
|
12095
|
+
if (!import_node_fs27.default.existsSync(configPath)) {
|
|
11485
12096
|
return {
|
|
11486
12097
|
exists: false,
|
|
11487
12098
|
configPath,
|
|
@@ -11525,15 +12136,15 @@ function inspectAgentGuidance(repoPath) {
|
|
|
11525
12136
|
["AGENTS.md", "agents"],
|
|
11526
12137
|
["CLAUDE.md", "claude"]
|
|
11527
12138
|
]) {
|
|
11528
|
-
if (
|
|
12139
|
+
if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
|
|
11529
12140
|
results.push({ path: fileName, kind });
|
|
11530
12141
|
}
|
|
11531
12142
|
}
|
|
11532
|
-
const skillsDir =
|
|
11533
|
-
if (
|
|
11534
|
-
for (const name of
|
|
11535
|
-
const skillPath =
|
|
11536
|
-
if (
|
|
12143
|
+
const skillsDir = import_node_path28.default.join(repoPath, ".agents", "skills");
|
|
12144
|
+
if (import_node_fs27.default.existsSync(skillsDir)) {
|
|
12145
|
+
for (const name of import_node_fs27.default.readdirSync(skillsDir).sort()) {
|
|
12146
|
+
const skillPath = import_node_path28.default.join(skillsDir, name, "SKILL.md");
|
|
12147
|
+
if (import_node_fs27.default.existsSync(skillPath)) {
|
|
11537
12148
|
results.push({ path: relative(repoPath, skillPath), kind: "skill" });
|
|
11538
12149
|
}
|
|
11539
12150
|
}
|
|
@@ -11576,7 +12187,7 @@ function buildIssues(report) {
|
|
|
11576
12187
|
}
|
|
11577
12188
|
function inspectRepo(options = {}) {
|
|
11578
12189
|
const repoPath = resolveRepoPath(options.repo);
|
|
11579
|
-
const pkg = readJson(
|
|
12190
|
+
const pkg = readJson(import_node_path28.default.join(repoPath, "package.json"));
|
|
11580
12191
|
const scripts = inspectScripts(pkg);
|
|
11581
12192
|
const reportWithoutIssues = {
|
|
11582
12193
|
repoPath,
|
|
@@ -11595,12 +12206,12 @@ function inspectRepo(options = {}) {
|
|
|
11595
12206
|
issues: buildIssues(reportWithoutIssues)
|
|
11596
12207
|
};
|
|
11597
12208
|
}
|
|
11598
|
-
var
|
|
12209
|
+
var import_node_fs27, import_node_path28, import_yaml6;
|
|
11599
12210
|
var init_repo_inspect = __esm({
|
|
11600
12211
|
"src/core/repo-inspect.ts"() {
|
|
11601
12212
|
"use strict";
|
|
11602
|
-
|
|
11603
|
-
|
|
12213
|
+
import_node_fs27 = __toESM(require("fs"));
|
|
12214
|
+
import_node_path28 = __toESM(require("path"));
|
|
11604
12215
|
import_yaml6 = __toESM(require("yaml"));
|
|
11605
12216
|
init_repo_config();
|
|
11606
12217
|
}
|
|
@@ -11705,7 +12316,7 @@ function requiredFileChecks(repoPath) {
|
|
|
11705
12316
|
".devcontainer/docker-compose.yml",
|
|
11706
12317
|
".devrouter.yml"
|
|
11707
12318
|
];
|
|
11708
|
-
const missing = required.filter((fileName) => !
|
|
12319
|
+
const missing = required.filter((fileName) => !import_node_fs28.default.existsSync(import_node_path29.default.join(repoPath, fileName)));
|
|
11709
12320
|
return {
|
|
11710
12321
|
id: "repo.devcontainer.verify-files",
|
|
11711
12322
|
level: missing.length === 0 ? "ok" : "error",
|
|
@@ -11924,12 +12535,12 @@ async function verifyDevcontainer(options = {}) {
|
|
|
11924
12535
|
nextSteps: collectNextSteps3(checks)
|
|
11925
12536
|
};
|
|
11926
12537
|
}
|
|
11927
|
-
var
|
|
12538
|
+
var import_node_fs28, import_node_path29;
|
|
11928
12539
|
var init_devcontainer_verify = __esm({
|
|
11929
12540
|
"src/core/devcontainer-verify.ts"() {
|
|
11930
12541
|
"use strict";
|
|
11931
|
-
|
|
11932
|
-
|
|
12542
|
+
import_node_fs28 = __toESM(require("fs"));
|
|
12543
|
+
import_node_path29 = __toESM(require("path"));
|
|
11933
12544
|
init_capabilities();
|
|
11934
12545
|
init_doctor();
|
|
11935
12546
|
init_host_routes();
|
|
@@ -12226,7 +12837,7 @@ function packageManagerIssues(repo) {
|
|
|
12226
12837
|
}
|
|
12227
12838
|
function plannedFiles(repoPath, version) {
|
|
12228
12839
|
const repo = inspectRepo({ repo: repoPath });
|
|
12229
|
-
const projectName = sanitizeProjectName(
|
|
12840
|
+
const projectName = sanitizeProjectName(import_node_path30.default.basename(repo.repoPath));
|
|
12230
12841
|
const nodeMajor = majorVersion(repo.node?.version, "24");
|
|
12231
12842
|
const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
|
|
12232
12843
|
const port = inferPort2(repo);
|
|
@@ -12271,8 +12882,8 @@ function plannedFiles(repoPath, version) {
|
|
|
12271
12882
|
};
|
|
12272
12883
|
}
|
|
12273
12884
|
function classifyFile(repoPath, file) {
|
|
12274
|
-
const absolutePath =
|
|
12275
|
-
if (!
|
|
12885
|
+
const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
|
|
12886
|
+
if (!import_node_fs29.default.existsSync(absolutePath)) {
|
|
12276
12887
|
return {
|
|
12277
12888
|
path: file.relativePath,
|
|
12278
12889
|
action: "create",
|
|
@@ -12280,7 +12891,7 @@ function classifyFile(repoPath, file) {
|
|
|
12280
12891
|
bytes: Buffer.byteLength(file.content)
|
|
12281
12892
|
};
|
|
12282
12893
|
}
|
|
12283
|
-
const current =
|
|
12894
|
+
const current = import_node_fs29.default.readFileSync(absolutePath, "utf-8");
|
|
12284
12895
|
if (!current.includes(MANAGED_MARKER2)) {
|
|
12285
12896
|
return {
|
|
12286
12897
|
path: file.relativePath,
|
|
@@ -12337,11 +12948,11 @@ function buildPlan(repoPath, dryRun, version) {
|
|
|
12337
12948
|
};
|
|
12338
12949
|
}
|
|
12339
12950
|
function writeFile(repoPath, file) {
|
|
12340
|
-
const absolutePath =
|
|
12341
|
-
|
|
12342
|
-
|
|
12951
|
+
const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
|
|
12952
|
+
import_node_fs29.default.mkdirSync(import_node_path30.default.dirname(absolutePath), { recursive: true });
|
|
12953
|
+
import_node_fs29.default.writeFileSync(absolutePath, file.content, "utf-8");
|
|
12343
12954
|
if (file.executable) {
|
|
12344
|
-
|
|
12955
|
+
import_node_fs29.default.chmodSync(absolutePath, 493);
|
|
12345
12956
|
}
|
|
12346
12957
|
}
|
|
12347
12958
|
function writeDevcontainer(options = {}) {
|
|
@@ -12379,12 +12990,12 @@ function writeDevcontainer(options = {}) {
|
|
|
12379
12990
|
nextSteps: postWriteNextSteps(repoPath)
|
|
12380
12991
|
};
|
|
12381
12992
|
}
|
|
12382
|
-
var
|
|
12993
|
+
var import_node_fs29, import_node_path30, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
|
|
12383
12994
|
var init_devcontainer_write = __esm({
|
|
12384
12995
|
"src/core/devcontainer-write.ts"() {
|
|
12385
12996
|
"use strict";
|
|
12386
|
-
|
|
12387
|
-
|
|
12997
|
+
import_node_fs29 = __toESM(require("fs"));
|
|
12998
|
+
import_node_path30 = __toESM(require("path"));
|
|
12388
12999
|
init_repo_config();
|
|
12389
13000
|
init_repo_inspect();
|
|
12390
13001
|
MANAGED_MARKER2 = "devrouter:managed devcontainer";
|
|
@@ -12493,6 +13104,119 @@ var init_repo_devcontainer = __esm({
|
|
|
12493
13104
|
}
|
|
12494
13105
|
});
|
|
12495
13106
|
|
|
13107
|
+
// src/core/profile-resolution.ts
|
|
13108
|
+
function sortedUnique2(values) {
|
|
13109
|
+
return Array.from(new Set(values)).sort();
|
|
13110
|
+
}
|
|
13111
|
+
function expandsAll(values) {
|
|
13112
|
+
return values?.length === 1 && values[0] === "*";
|
|
13113
|
+
}
|
|
13114
|
+
function selectedManagedResources(profile, selected, registry) {
|
|
13115
|
+
if (!profile || expandsAll(selected)) return sortedUnique2(registry);
|
|
13116
|
+
return sortedUnique2(selected ?? []);
|
|
13117
|
+
}
|
|
13118
|
+
function selectedReadiness(config, profileName, profile, routedApps) {
|
|
13119
|
+
const selectedNames = profileName.split(",");
|
|
13120
|
+
if (selectedNames.length === 1) {
|
|
13121
|
+
return sortedUnique2(
|
|
13122
|
+
profile?.readiness ?? routedApps.filter((app) => app.protocol === "http").map((app) => app.name)
|
|
13123
|
+
);
|
|
13124
|
+
}
|
|
13125
|
+
const readiness = /* @__PURE__ */ new Set();
|
|
13126
|
+
for (const selectedName of selectedNames) {
|
|
13127
|
+
const selected = resolveProfile(config, selectedName);
|
|
13128
|
+
const selectedApps = applyProfile(config, selected.profile).apps.filter(
|
|
13129
|
+
(app) => app.kind !== "dependency"
|
|
13130
|
+
);
|
|
13131
|
+
const selectedTargets = selected.profile?.readiness ?? selectedApps.filter((app) => app.protocol === "http").map((app) => app.name);
|
|
13132
|
+
for (const target of selectedTargets) readiness.add(target);
|
|
13133
|
+
}
|
|
13134
|
+
return sortedUnique2(readiness);
|
|
13135
|
+
}
|
|
13136
|
+
function buildProfileResolutionReport(config, repoPath, profileOverride) {
|
|
13137
|
+
const resolved = resolveProfile(config, profileOverride);
|
|
13138
|
+
const filtered = applyProfile(config, resolved.profile);
|
|
13139
|
+
const routedApps = filtered.apps.filter((app) => app.kind !== "dependency");
|
|
13140
|
+
const dependencies = filtered.apps.filter((app) => app.kind === "dependency");
|
|
13141
|
+
const readiness = selectedReadiness(config, resolved.name, resolved.profile, routedApps);
|
|
13142
|
+
const managedRuntime = config.managedRuntime;
|
|
13143
|
+
const baseServices = sortedUnique2(managedRuntime?.devcontainer.baseServices ?? []);
|
|
13144
|
+
const profileServices = managedRuntime ? selectedManagedResources(
|
|
13145
|
+
resolved.profile,
|
|
13146
|
+
resolved.profile?.devcontainerServices,
|
|
13147
|
+
managedRuntime.devcontainer.profileServices
|
|
13148
|
+
) : [];
|
|
13149
|
+
const processes = managedRuntime ? selectedManagedResources(
|
|
13150
|
+
resolved.profile,
|
|
13151
|
+
resolved.profile?.processes,
|
|
13152
|
+
managedRuntime.processes
|
|
13153
|
+
) : [];
|
|
13154
|
+
return {
|
|
13155
|
+
schemaVersion: 1,
|
|
13156
|
+
repoPath,
|
|
13157
|
+
profile: resolved.name,
|
|
13158
|
+
apps: sortedUnique2(routedApps.map((app) => app.name)),
|
|
13159
|
+
dependencies: sortedUnique2(dependencies.map((app) => app.name)),
|
|
13160
|
+
readiness: sortedUnique2(readiness),
|
|
13161
|
+
managedRuntime: {
|
|
13162
|
+
baseServices,
|
|
13163
|
+
profileServices,
|
|
13164
|
+
services: sortedUnique2([...baseServices, ...profileServices]),
|
|
13165
|
+
processes
|
|
13166
|
+
}
|
|
13167
|
+
};
|
|
13168
|
+
}
|
|
13169
|
+
function resolveProfileReport(options) {
|
|
13170
|
+
const repoPath = resolveRepoPath(options.repo);
|
|
13171
|
+
return buildProfileResolutionReport(loadRepoConfig(repoPath), repoPath, options.profile);
|
|
13172
|
+
}
|
|
13173
|
+
var init_profile_resolution = __esm({
|
|
13174
|
+
"src/core/profile-resolution.ts"() {
|
|
13175
|
+
"use strict";
|
|
13176
|
+
init_repo_config();
|
|
13177
|
+
}
|
|
13178
|
+
});
|
|
13179
|
+
|
|
13180
|
+
// src/commands/profile.ts
|
|
13181
|
+
var profile_exports = {};
|
|
13182
|
+
__export(profile_exports, {
|
|
13183
|
+
runProfileResolveCommand: () => runProfileResolveCommand
|
|
13184
|
+
});
|
|
13185
|
+
async function runProfileResolveCommand(options) {
|
|
13186
|
+
const report = resolveProfileReport(options);
|
|
13187
|
+
if (options.json) {
|
|
13188
|
+
printJSON(report);
|
|
13189
|
+
return;
|
|
13190
|
+
}
|
|
13191
|
+
printProfileResolutionSummary(report);
|
|
13192
|
+
}
|
|
13193
|
+
function renderValues(values) {
|
|
13194
|
+
return values.join(", ") || "-";
|
|
13195
|
+
}
|
|
13196
|
+
function printProfileResolutionSummary(report) {
|
|
13197
|
+
process.stdout.write(`Repo: ${report.repoPath}
|
|
13198
|
+
`);
|
|
13199
|
+
process.stdout.write(`Profile: ${report.profile}
|
|
13200
|
+
`);
|
|
13201
|
+
process.stdout.write(`Apps: ${renderValues(report.apps)}
|
|
13202
|
+
`);
|
|
13203
|
+
process.stdout.write(`Dependencies: ${renderValues(report.dependencies)}
|
|
13204
|
+
`);
|
|
13205
|
+
process.stdout.write(`Readiness: ${renderValues(report.readiness)}
|
|
13206
|
+
`);
|
|
13207
|
+
process.stdout.write(`Managed services: ${renderValues(report.managedRuntime.services)}
|
|
13208
|
+
`);
|
|
13209
|
+
process.stdout.write(`Managed processes: ${renderValues(report.managedRuntime.processes)}
|
|
13210
|
+
`);
|
|
13211
|
+
}
|
|
13212
|
+
var init_profile = __esm({
|
|
13213
|
+
"src/commands/profile.ts"() {
|
|
13214
|
+
"use strict";
|
|
13215
|
+
init_output();
|
|
13216
|
+
init_profile_resolution();
|
|
13217
|
+
}
|
|
13218
|
+
});
|
|
13219
|
+
|
|
12496
13220
|
// src/commands/app-add.ts
|
|
12497
13221
|
var app_add_exports = {};
|
|
12498
13222
|
__export(app_add_exports, {
|
|
@@ -12775,7 +13499,7 @@ function sanitizeRouterId(value) {
|
|
|
12775
13499
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
12776
13500
|
}
|
|
12777
13501
|
function repoHash(repoPath) {
|
|
12778
|
-
return (0,
|
|
13502
|
+
return (0, import_node_crypto10.createHash)("sha1").update(import_node_path31.default.resolve(repoPath)).digest("hex").slice(0, 12);
|
|
12779
13503
|
}
|
|
12780
13504
|
function asDockerApp(app) {
|
|
12781
13505
|
return app.runtime === "docker";
|
|
@@ -12854,11 +13578,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
|
|
|
12854
13578
|
if (dockerApps.length === 0) {
|
|
12855
13579
|
throw new Error("No docker apps selected to prepare compose overlay.");
|
|
12856
13580
|
}
|
|
12857
|
-
const cachePath =
|
|
12858
|
-
|
|
12859
|
-
const overlayPath =
|
|
13581
|
+
const cachePath = import_node_path31.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
|
|
13582
|
+
import_node_fs30.default.mkdirSync(cachePath, { recursive: true });
|
|
13583
|
+
const overlayPath = import_node_path31.default.join(cachePath, "compose.devrouter.yml");
|
|
12860
13584
|
const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
|
|
12861
|
-
|
|
13585
|
+
import_node_fs30.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
|
|
12862
13586
|
return {
|
|
12863
13587
|
overlayPath,
|
|
12864
13588
|
composeFiles: ensureComposeFiles(dockerApps),
|
|
@@ -12872,7 +13596,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
|
|
|
12872
13596
|
fileArgs.push("-f", resolved);
|
|
12873
13597
|
}
|
|
12874
13598
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
|
|
12875
|
-
const result = (0,
|
|
13599
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12876
13600
|
encoding: "utf-8",
|
|
12877
13601
|
cwd: repoPath
|
|
12878
13602
|
});
|
|
@@ -12900,7 +13624,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
|
|
|
12900
13624
|
"--services",
|
|
12901
13625
|
...services
|
|
12902
13626
|
];
|
|
12903
|
-
const result = (0,
|
|
13627
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12904
13628
|
encoding: "utf-8",
|
|
12905
13629
|
cwd: repoPath
|
|
12906
13630
|
});
|
|
@@ -12926,7 +13650,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
|
|
|
12926
13650
|
fileArgs.push("-f", resolved);
|
|
12927
13651
|
}
|
|
12928
13652
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
|
|
12929
|
-
const result = (0,
|
|
13653
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12930
13654
|
encoding: "utf-8",
|
|
12931
13655
|
cwd: repoPath
|
|
12932
13656
|
});
|
|
@@ -12952,7 +13676,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
|
|
|
12952
13676
|
String(tail),
|
|
12953
13677
|
...services
|
|
12954
13678
|
];
|
|
12955
|
-
(0,
|
|
13679
|
+
(0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12956
13680
|
stdio: "inherit",
|
|
12957
13681
|
cwd: repoPath
|
|
12958
13682
|
});
|
|
@@ -12964,7 +13688,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12964
13688
|
fileArgs.push("-f", resolved);
|
|
12965
13689
|
}
|
|
12966
13690
|
const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
|
|
12967
|
-
const result = (0,
|
|
13691
|
+
const result = (0, import_node_child_process27.spawnSync)("docker", args, {
|
|
12968
13692
|
encoding: "utf-8",
|
|
12969
13693
|
cwd: repoPath
|
|
12970
13694
|
});
|
|
@@ -12978,14 +13702,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
|
|
|
12978
13702
|
const port = Number(match[1]);
|
|
12979
13703
|
return Number.isInteger(port) && port > 0 ? port : void 0;
|
|
12980
13704
|
}
|
|
12981
|
-
var
|
|
13705
|
+
var import_node_child_process27, import_node_crypto10, import_node_fs30, import_node_path31, import_yaml7;
|
|
12982
13706
|
var init_docker_run = __esm({
|
|
12983
13707
|
"src/core/docker-run.ts"() {
|
|
12984
13708
|
"use strict";
|
|
12985
|
-
|
|
12986
|
-
|
|
12987
|
-
|
|
12988
|
-
|
|
13709
|
+
import_node_child_process27 = require("child_process");
|
|
13710
|
+
import_node_crypto10 = require("crypto");
|
|
13711
|
+
import_node_fs30 = __toESM(require("fs"));
|
|
13712
|
+
import_node_path31 = __toESM(require("path"));
|
|
12989
13713
|
import_yaml7 = __toESM(require("yaml"));
|
|
12990
13714
|
init_docker_error_guidance();
|
|
12991
13715
|
init_paths();
|
|
@@ -13022,7 +13746,7 @@ function resolveSmCommand(command, defaultEnv, overrideEnv) {
|
|
|
13022
13746
|
}
|
|
13023
13747
|
return command.replace(/\{env\}/g, env);
|
|
13024
13748
|
}
|
|
13025
|
-
function
|
|
13749
|
+
function sleep3(ms) {
|
|
13026
13750
|
return new Promise((resolve) => {
|
|
13027
13751
|
setTimeout(resolve, ms);
|
|
13028
13752
|
});
|
|
@@ -13075,7 +13799,7 @@ function isProcessRunning(pid) {
|
|
|
13075
13799
|
}
|
|
13076
13800
|
}
|
|
13077
13801
|
function readProcessTree(rootPid) {
|
|
13078
|
-
const result = (0,
|
|
13802
|
+
const result = (0, import_node_child_process28.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
|
|
13079
13803
|
if (result.status !== 0) {
|
|
13080
13804
|
return [rootPid];
|
|
13081
13805
|
}
|
|
@@ -13133,7 +13857,7 @@ async function terminateProcessTree(rootPid) {
|
|
|
13133
13857
|
if (!isProcessRunning(rootPid)) {
|
|
13134
13858
|
return;
|
|
13135
13859
|
}
|
|
13136
|
-
await
|
|
13860
|
+
await sleep3(100);
|
|
13137
13861
|
}
|
|
13138
13862
|
if (isProcessRunning(rootPid)) {
|
|
13139
13863
|
killProcessTree(rootPid, "SIGKILL");
|
|
@@ -13186,12 +13910,12 @@ function detectListeningPorts(pids) {
|
|
|
13186
13910
|
if (pids.length === 0) {
|
|
13187
13911
|
return [];
|
|
13188
13912
|
}
|
|
13189
|
-
const result = (0,
|
|
13913
|
+
const result = (0, import_node_child_process28.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
|
|
13190
13914
|
encoding: "utf-8"
|
|
13191
13915
|
});
|
|
13192
13916
|
if (result.error && result.error.code === "ENOENT") {
|
|
13193
13917
|
if (process.platform === "linux") {
|
|
13194
|
-
const ssResult = (0,
|
|
13918
|
+
const ssResult = (0, import_node_child_process28.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
|
|
13195
13919
|
if (ssResult.status === 0 && ssResult.stdout) {
|
|
13196
13920
|
return parseSsListeningPorts(ssResult.stdout, new Set(pids));
|
|
13197
13921
|
}
|
|
@@ -13250,7 +13974,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
13250
13974
|
app.hostRun.command,
|
|
13251
13975
|
true
|
|
13252
13976
|
) : app.hostRun.command;
|
|
13253
|
-
const child = (0,
|
|
13977
|
+
const child = (0, import_node_child_process28.spawn)(spawnCommand, {
|
|
13254
13978
|
cwd: commandCwd,
|
|
13255
13979
|
stdio: "inherit",
|
|
13256
13980
|
shell: true,
|
|
@@ -13320,7 +14044,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
|
|
|
13320
14044
|
);
|
|
13321
14045
|
}
|
|
13322
14046
|
}
|
|
13323
|
-
await
|
|
14047
|
+
await sleep3(POLL_INTERVAL_MS2);
|
|
13324
14048
|
}
|
|
13325
14049
|
} catch (error) {
|
|
13326
14050
|
fatalError = toError(error);
|
|
@@ -13618,7 +14342,7 @@ async function execWithAppEnv(options) {
|
|
|
13618
14342
|
options.command[0],
|
|
13619
14343
|
true
|
|
13620
14344
|
);
|
|
13621
|
-
child = (0,
|
|
14345
|
+
child = (0, import_node_child_process28.spawn)(wrapped, {
|
|
13622
14346
|
cwd: deps.repoPath,
|
|
13623
14347
|
stdio: "inherit",
|
|
13624
14348
|
shell: true,
|
|
@@ -13632,7 +14356,7 @@ async function execWithAppEnv(options) {
|
|
|
13632
14356
|
false
|
|
13633
14357
|
);
|
|
13634
14358
|
const [cmd, ...wrappedArgs] = wrapped;
|
|
13635
|
-
child = (0,
|
|
14359
|
+
child = (0, import_node_child_process28.spawn)(cmd, wrappedArgs, {
|
|
13636
14360
|
cwd: deps.repoPath,
|
|
13637
14361
|
stdio: "inherit",
|
|
13638
14362
|
shell: false,
|
|
@@ -13640,7 +14364,7 @@ async function execWithAppEnv(options) {
|
|
|
13640
14364
|
});
|
|
13641
14365
|
}
|
|
13642
14366
|
} else if (options.shell) {
|
|
13643
|
-
child = (0,
|
|
14367
|
+
child = (0, import_node_child_process28.spawn)(options.command[0], {
|
|
13644
14368
|
cwd: deps.repoPath,
|
|
13645
14369
|
stdio: "inherit",
|
|
13646
14370
|
shell: true,
|
|
@@ -13648,7 +14372,7 @@ async function execWithAppEnv(options) {
|
|
|
13648
14372
|
});
|
|
13649
14373
|
} else {
|
|
13650
14374
|
const [command, ...args] = options.command;
|
|
13651
|
-
child = (0,
|
|
14375
|
+
child = (0, import_node_child_process28.spawn)(command, args, {
|
|
13652
14376
|
cwd: deps.repoPath,
|
|
13653
14377
|
stdio: "inherit",
|
|
13654
14378
|
shell: false,
|
|
@@ -13669,11 +14393,11 @@ async function execWithAppEnv(options) {
|
|
|
13669
14393
|
deps.stopDeps();
|
|
13670
14394
|
}
|
|
13671
14395
|
}
|
|
13672
|
-
var
|
|
14396
|
+
var import_node_child_process28, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
|
|
13673
14397
|
var init_app_run = __esm({
|
|
13674
14398
|
"src/core/app-run.ts"() {
|
|
13675
14399
|
"use strict";
|
|
13676
|
-
|
|
14400
|
+
import_node_child_process28 = require("child_process");
|
|
13677
14401
|
import_node_net = __toESM(require("net"));
|
|
13678
14402
|
import_node_process = require("process");
|
|
13679
14403
|
import_promises = require("readline/promises");
|
|
@@ -13807,13 +14531,13 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13807
14531
|
const startedAt = Date.now();
|
|
13808
14532
|
let rootStat;
|
|
13809
14533
|
try {
|
|
13810
|
-
rootStat =
|
|
14534
|
+
rootStat = import_node_fs31.default.lstatSync(worktreePath);
|
|
13811
14535
|
} catch (error) {
|
|
13812
14536
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
13813
14537
|
}
|
|
13814
14538
|
let rootEntries;
|
|
13815
14539
|
try {
|
|
13816
|
-
rootEntries =
|
|
14540
|
+
rootEntries = import_node_fs31.default.readdirSync(worktreePath, { withFileTypes: true });
|
|
13817
14541
|
} catch (error) {
|
|
13818
14542
|
return { status: "unknown", reason: describeError(error, worktreePath) };
|
|
13819
14543
|
}
|
|
@@ -13839,10 +14563,10 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13839
14563
|
timedOut = true;
|
|
13840
14564
|
break;
|
|
13841
14565
|
}
|
|
13842
|
-
const entryPath =
|
|
14566
|
+
const entryPath = import_node_path32.default.join(dirPath, entry.name);
|
|
13843
14567
|
let entryStat;
|
|
13844
14568
|
try {
|
|
13845
|
-
entryStat =
|
|
14569
|
+
entryStat = import_node_fs31.default.lstatSync(entryPath);
|
|
13846
14570
|
} catch (error) {
|
|
13847
14571
|
if (error?.code === "ENOENT") continue;
|
|
13848
14572
|
unreadableReason = describeIncompleteWalk(error);
|
|
@@ -13852,7 +14576,7 @@ function measureWorktreeConsumption(worktreePath, options) {
|
|
|
13852
14576
|
if (!entryStat.isDirectory()) continue;
|
|
13853
14577
|
let childEntries;
|
|
13854
14578
|
try {
|
|
13855
|
-
childEntries =
|
|
14579
|
+
childEntries = import_node_fs31.default.readdirSync(entryPath, { withFileTypes: true });
|
|
13856
14580
|
} catch (error) {
|
|
13857
14581
|
unreadableReason = describeIncompleteWalk(error);
|
|
13858
14582
|
break;
|
|
@@ -13916,12 +14640,12 @@ function describeError(error, worktreePath) {
|
|
|
13916
14640
|
}
|
|
13917
14641
|
return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
|
|
13918
14642
|
}
|
|
13919
|
-
var
|
|
14643
|
+
var import_node_fs31, import_node_path32, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
|
|
13920
14644
|
var init_workspace_consumption = __esm({
|
|
13921
14645
|
"src/core/workspace-consumption.ts"() {
|
|
13922
14646
|
"use strict";
|
|
13923
|
-
|
|
13924
|
-
|
|
14647
|
+
import_node_fs31 = __toESM(require("fs"));
|
|
14648
|
+
import_node_path32 = __toESM(require("path"));
|
|
13925
14649
|
init_devpod_environment();
|
|
13926
14650
|
DEFAULT_DEADLINE_MS = 1e4;
|
|
13927
14651
|
BLOCK_SIZE_BYTES = 512;
|
|
@@ -14022,7 +14746,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
|
|
|
14022
14746
|
}
|
|
14023
14747
|
function readGitSnapshot(worktree, commandRunner) {
|
|
14024
14748
|
const comparablePath = comparableWorkspacePath(worktree.path);
|
|
14025
|
-
if (worktree.prunable || !
|
|
14749
|
+
if (worktree.prunable || !import_node_fs32.default.existsSync(comparablePath)) {
|
|
14026
14750
|
return { worktree, checkout: "missing", head: null, committerDate: null };
|
|
14027
14751
|
}
|
|
14028
14752
|
const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
|
|
@@ -14547,7 +15271,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14547
15271
|
containers = measureContainersFn(worktreePaths);
|
|
14548
15272
|
} catch (error) {
|
|
14549
15273
|
const reason = `container measurement failed: ${describeCause(error)}`;
|
|
14550
|
-
containers = new Map(worktreePaths.map((
|
|
15274
|
+
containers = new Map(worktreePaths.map((path32) => [path32, unknownContainers(reason)]));
|
|
14551
15275
|
}
|
|
14552
15276
|
}
|
|
14553
15277
|
const rows = records.map((record) => {
|
|
@@ -14596,12 +15320,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
|
|
|
14596
15320
|
workspaces: rows
|
|
14597
15321
|
};
|
|
14598
15322
|
}
|
|
14599
|
-
var
|
|
15323
|
+
var import_node_child_process29, import_node_fs32, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
|
|
14600
15324
|
var init_workspace_cleanup = __esm({
|
|
14601
15325
|
"src/core/workspace-cleanup.ts"() {
|
|
14602
15326
|
"use strict";
|
|
14603
|
-
|
|
14604
|
-
|
|
15327
|
+
import_node_child_process29 = require("child_process");
|
|
15328
|
+
import_node_fs32 = __toESM(require("fs"));
|
|
14605
15329
|
init_devpod_workspaces();
|
|
14606
15330
|
init_host_routes();
|
|
14607
15331
|
init_repo_config();
|
|
@@ -14618,7 +15342,7 @@ var init_workspace_cleanup = __esm({
|
|
|
14618
15342
|
];
|
|
14619
15343
|
defaultCommandRunner = (command, args, input2) => {
|
|
14620
15344
|
const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
|
|
14621
|
-
const result = (0,
|
|
15345
|
+
const result = (0, import_node_child_process29.spawnSync)(command, args, {
|
|
14622
15346
|
encoding: "utf-8",
|
|
14623
15347
|
env: environment,
|
|
14624
15348
|
...input2 === void 0 ? {} : { input: input2 }
|
|
@@ -14778,7 +15502,7 @@ var init_version = __esm({
|
|
|
14778
15502
|
|
|
14779
15503
|
// src/cli.ts
|
|
14780
15504
|
var import_commander = require("commander");
|
|
14781
|
-
var CLI_VERSION = true ? "0.0.
|
|
15505
|
+
var CLI_VERSION = true ? "0.0.48" : "0.0.0-dev";
|
|
14782
15506
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
14783
15507
|
function withErrorHandling(action2) {
|
|
14784
15508
|
return async (...args) => {
|
|
@@ -14936,6 +15660,16 @@ repoDevcontainerCommand.command("verify").description("Verify devcontainer onboa
|
|
|
14936
15660
|
}
|
|
14937
15661
|
)
|
|
14938
15662
|
);
|
|
15663
|
+
var profileCommand = program.command("profile").description("Resolve profiles from `.devrouter.yml` without starting a runtime");
|
|
15664
|
+
profileCommand.command("resolve").description("Print the exact apps, dependencies, services, and processes for a profile").option("--repo <path>", "Repository path (defaults to current directory)").option(
|
|
15665
|
+
"--profile <name>",
|
|
15666
|
+
"Profile name or comma-separated selection (defaults to configured profile)"
|
|
15667
|
+
).option("--json", "Output stable JSON").action(
|
|
15668
|
+
withErrorHandling(async (options) => {
|
|
15669
|
+
const { runProfileResolveCommand: runProfileResolveCommand2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
15670
|
+
await runProfileResolveCommand2(options);
|
|
15671
|
+
})
|
|
15672
|
+
);
|
|
14939
15673
|
var appCommand = program.command("app").description("Manage app entries and runtime actions from `.devrouter.yml`");
|
|
14940
15674
|
appCommand.command("add").description("Add or update one app definition in `.devrouter.yml`").requiredOption("--name <name>", "App name").option("--kind <kind>", "app or dependency", "app").option("--host <host>", "Hostname ending with .localhost (required for --kind app)").option("--protocol <protocol>", "http or tcp (required for --kind app)").option(
|
|
14941
15675
|
"--runtime <runtime>",
|