@devrouter/cli 0.0.42 → 0.0.43

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/dist/devrouter.js CHANGED
@@ -51,6 +51,7 @@ function buildDevrouterSection() {
51
51
  "- Managed selective profile: `devrouter ensure . --profile <name> --json`",
52
52
  "- Host/docker runtime app only: `devrouter app run <host-app> --repo . --yes`",
53
53
  "- `devrouter ls`",
54
+ "- Managed devcontainer source configs with `postCreateCommand` and a managed post-start adapter must set `waitFor` exactly to `postCreateCommand` or `postStartCommand`; generated managed configs preserve lifecycle fields and change only `runServices`.",
54
55
  DEVROUTER_END_SENTINEL
55
56
  ].join("\n");
56
57
  }
@@ -293,6 +294,10 @@ profiles:
293
294
  - Inspect managed desired, active, and drift state with \`devrouter status\` and
294
295
  \`devrouter doctor\`; values such as credentials and environment contents are
295
296
  never written to managed runtime state.
297
+ - A managed adapter paired with \`postCreateCommand\` requires \`waitFor\` exactly
298
+ \`postCreateCommand\` or \`postStartCommand\` before provider mutation. Generated
299
+ selective configuration preserves lifecycle fields and changes only
300
+ \`runServices\`.
296
301
 
297
302
  ## Env var injection
298
303
 
@@ -1658,12 +1663,12 @@ var init_host_routes = __esm({
1658
1663
 
1659
1664
  // src/core/repo-config.ts
1660
1665
  function compareSemver(a, b) {
1661
- const parse = (v) => {
1666
+ const parse2 = (v) => {
1662
1667
  const match = v.trim().match(/^v?(\d+)\.(\d+)\.(\d+)$/);
1663
1668
  return match ? { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) } : { major: 0, minor: 0, patch: 0 };
1664
1669
  };
1665
- const left = parse(a);
1666
- const right = parse(b);
1670
+ const left = parse2(a);
1671
+ const right = parse2(b);
1667
1672
  if (left.major !== right.major) return left.major - right.major;
1668
1673
  if (left.minor !== right.minor) return left.minor - right.minor;
1669
1674
  return left.patch - right.patch;
@@ -2504,7 +2509,7 @@ function loadRepoConfig(repoPath) {
2504
2509
  const config = parseConfig(parsed ?? {}, configPath);
2505
2510
  const requiredVersion = config.devrouter?.version;
2506
2511
  if (requiredVersion && !hasWarnedVersionMismatch) {
2507
- const cliVersion = true ? "0.0.42" : "0.0.0-dev";
2512
+ const cliVersion = true ? "0.0.43" : "0.0.0-dev";
2508
2513
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
2509
2514
  hasWarnedVersionMismatch = true;
2510
2515
  process.stderr.write(
@@ -3035,6 +3040,7 @@ function buildOnboardingPrompt(options = {}) {
3035
3040
  `- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
3036
3041
  "- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
3037
3042
  "- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
3043
+ "- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
3038
3044
  "- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
3039
3045
  "- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
3040
3046
  "- `devrouter workspace cleanup --repo . --inactive-for 30d --check-merged --json` is report-only and never mutates the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. It reports ownership, workspace runtime registration, runtime state (`running|stopped|busy|not-found|absent|unknown`), checkout, advisory activity, route, and integration evidence. Local DevPod/Devsy list/status checks always run; `--check-merged` alone enables read-only origin and matching GitHub/GitLab checks. Treat `not-found` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit `gc`/`down` can remove exact stale registration only after expected-ID `NotFound` proof and ownership revalidation. Runtime `lastUsed` remains advisory.",
@@ -4005,12 +4011,64 @@ var init_docker = __esm({
4005
4011
  }
4006
4012
  });
4007
4013
 
4014
+ // src/core/devcontainer-config.ts
4015
+ function isObject(value) {
4016
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4017
+ }
4018
+ function parseDevcontainerConfig(contents, sourcePath) {
4019
+ const errors = [];
4020
+ const parsed = (0, import_jsonc_parser.parse)(contents, errors, {
4021
+ allowTrailingComma: true,
4022
+ disallowComments: false
4023
+ });
4024
+ if (errors.length > 0) {
4025
+ const first = errors[0];
4026
+ throw new Error(
4027
+ `Could not parse Dev Container source config '${sourcePath}' as JSONC at offset ${first.offset}.`
4028
+ );
4029
+ }
4030
+ if (!isObject(parsed)) {
4031
+ throw new Error(`Dev Container source config '${sourcePath}' must contain an object.`);
4032
+ }
4033
+ return parsed;
4034
+ }
4035
+ function readDevcontainerConfig(repoPath) {
4036
+ const sourcePath = import_node_path8.default.join(repoPath, ".devcontainer", "devcontainer.json");
4037
+ if (!import_node_fs9.default.existsSync(sourcePath) || !import_node_fs9.default.lstatSync(sourcePath).isFile()) {
4038
+ throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
4039
+ }
4040
+ const sourceContents = import_node_fs9.default.readFileSync(sourcePath, "utf-8");
4041
+ return {
4042
+ sourcePath,
4043
+ sourceContents,
4044
+ source: parseDevcontainerConfig(sourceContents, sourcePath)
4045
+ };
4046
+ }
4047
+ function assertManagedDevcontainerLifecycle(repoPath) {
4048
+ const { sourcePath, source } = readDevcontainerConfig(repoPath);
4049
+ if (source.postCreateCommand === void 0) return;
4050
+ if (source.waitFor === "postCreateCommand" || source.waitFor === "postStartCommand") return;
4051
+ const actual = source.waitFor === void 0 ? "is missing" : typeof source.waitFor === "string" ? "is an unsupported string" : "is not a string";
4052
+ throw new Error(
4053
+ `Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
4054
+ );
4055
+ }
4056
+ var import_node_fs9, import_node_path8, import_jsonc_parser;
4057
+ var init_devcontainer_config = __esm({
4058
+ "src/core/devcontainer-config.ts"() {
4059
+ "use strict";
4060
+ import_node_fs9 = __toESM(require("fs"));
4061
+ import_node_path8 = __toESM(require("path"));
4062
+ import_jsonc_parser = require("jsonc-parser");
4063
+ }
4064
+ });
4065
+
4008
4066
  // src/core/managed-post-start.ts
4009
4067
  function renderDeliveryScript(targetPath) {
4010
4068
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
4011
4069
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
4012
4070
  }
4013
- const temporaryPrefix = import_node_path8.default.posix.basename(targetPath);
4071
+ const temporaryPrefix = import_node_path9.default.posix.basename(targetPath);
4014
4072
  return `set -eu
4015
4073
  umask 077
4016
4074
  runtime_root=/tmp/devrouter
@@ -4045,8 +4103,8 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
4045
4103
  }
4046
4104
  }
4047
4105
  function readRegularFileBytes(filePath) {
4048
- if (!import_node_fs9.default.existsSync(filePath) || !import_node_fs9.default.lstatSync(filePath).isFile()) return void 0;
4049
- return import_node_fs9.default.readFileSync(filePath);
4106
+ if (!import_node_fs10.default.existsSync(filePath) || !import_node_fs10.default.lstatSync(filePath).isFile()) return void 0;
4107
+ return import_node_fs10.default.readFileSync(filePath);
4050
4108
  }
4051
4109
  function readRegularFile(filePath) {
4052
4110
  return readRegularFileBytes(filePath)?.toString("utf-8");
@@ -4056,21 +4114,21 @@ function adapterFingerprint(adapter) {
4056
4114
  }
4057
4115
  function resolveProcessHelperPath() {
4058
4116
  const candidates = [
4059
- import_node_path8.default.resolve(__dirname, "..", "bin", "devrouter-process"),
4060
- import_node_path8.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
4117
+ import_node_path9.default.resolve(__dirname, "..", "bin", "devrouter-process"),
4118
+ import_node_path9.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
4061
4119
  ];
4062
- const helperPath = candidates.find((candidate) => import_node_fs9.default.existsSync(candidate));
4120
+ const helperPath = candidates.find((candidate) => import_node_fs10.default.existsSync(candidate));
4063
4121
  if (!helperPath) {
4064
4122
  throw new Error("Could not locate the packaged devrouter-process helper.");
4065
4123
  }
4066
4124
  return helperPath;
4067
4125
  }
4068
4126
  function resolveManagedPostStartPlan(repoPath) {
4069
- const adapterPath = import_node_path8.default.join(repoPath, MANAGED_ADAPTER_PATH);
4127
+ const adapterPath = import_node_path9.default.join(repoPath, MANAGED_ADAPTER_PATH);
4070
4128
  const adapterBytes = readRegularFileBytes(adapterPath);
4071
4129
  const adapterText = adapterBytes?.toString("utf-8");
4072
- const dockerfilePath = import_node_path8.default.join(repoPath, ".devcontainer", "Dockerfile");
4073
- const devcontainerPath = import_node_path8.default.join(repoPath, ".devcontainer", "devcontainer.json");
4130
+ const dockerfilePath = import_node_path9.default.join(repoPath, ".devcontainer", "Dockerfile");
4131
+ const devcontainerPath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
4074
4132
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
4075
4133
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
4076
4134
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -4093,6 +4151,7 @@ function resolveManagedPostStartPlan(repoPath) {
4093
4151
  }
4094
4152
  return { kind: "unmanaged" };
4095
4153
  }
4154
+ assertManagedDevcontainerLifecycle(repoPath);
4096
4155
  if (adapter.includes("DEVROUTER_PROCESS_HELPER")) {
4097
4156
  return {
4098
4157
  kind: "runtime",
@@ -4110,7 +4169,7 @@ function resolveManagedPostStartPlan(repoPath) {
4110
4169
  }
4111
4170
  function runManagedPostStart(options) {
4112
4171
  if (options.plan.kind !== "runtime") return;
4113
- const helper = import_node_fs9.default.readFileSync(resolveProcessHelperPath());
4172
+ const helper = import_node_fs10.default.readFileSync(resolveProcessHelperPath());
4114
4173
  deliverRuntimeFile(
4115
4174
  options.container.id,
4116
4175
  RUNTIME_HELPER_PATH,
@@ -4198,14 +4257,15 @@ function runManagedProcessAction(options) {
4198
4257
  }
4199
4258
  return "drifted";
4200
4259
  }
4201
- var import_node_child_process5, import_node_crypto3, import_node_fs9, import_node_path8, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
4260
+ var import_node_child_process5, import_node_crypto3, import_node_fs10, import_node_path9, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
4202
4261
  var init_managed_post_start = __esm({
4203
4262
  "src/core/managed-post-start.ts"() {
4204
4263
  "use strict";
4205
4264
  import_node_child_process5 = require("child_process");
4206
4265
  import_node_crypto3 = require("crypto");
4207
- import_node_fs9 = __toESM(require("fs"));
4208
- import_node_path8 = __toESM(require("path"));
4266
+ import_node_fs10 = __toESM(require("fs"));
4267
+ import_node_path9 = __toESM(require("path"));
4268
+ init_devcontainer_config();
4209
4269
  MANAGED_MARKER = "devrouter:managed devcontainer";
4210
4270
  MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
4211
4271
  RUNTIME_HELPER_PATH = "/tmp/devrouter/bin/devrouter-process";
@@ -4271,7 +4331,7 @@ function isLoopbackHost(host) {
4271
4331
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
4272
4332
  }
4273
4333
  function inspectCompose(composeFile, _workspace) {
4274
- if (!import_node_fs10.default.existsSync(composeFile)) {
4334
+ if (!import_node_fs11.default.existsSync(composeFile)) {
4275
4335
  return {
4276
4336
  aliases: [],
4277
4337
  publishedPorts: [],
@@ -4280,7 +4340,7 @@ function inspectCompose(composeFile, _workspace) {
4280
4340
  };
4281
4341
  }
4282
4342
  try {
4283
- const raw = import_node_fs10.default.readFileSync(composeFile, "utf-8");
4343
+ const raw = import_node_fs11.default.readFileSync(composeFile, "utf-8");
4284
4344
  const parsed = import_yaml3.default.parse(raw);
4285
4345
  const root = asRecord(parsed);
4286
4346
  const services = asRecord(root?.services);
@@ -4337,11 +4397,11 @@ function routedProxyApps(config) {
4337
4397
  );
4338
4398
  }
4339
4399
  function buildDevcontainerChecks(repoPath, config, workspace) {
4340
- const devcontainerDir = import_node_path9.default.join(repoPath, ".devcontainer");
4341
- if (!import_node_fs10.default.existsSync(devcontainerDir)) {
4400
+ const devcontainerDir = import_node_path10.default.join(repoPath, ".devcontainer");
4401
+ if (!import_node_fs11.default.existsSync(devcontainerDir)) {
4342
4402
  return [];
4343
4403
  }
4344
- const compose = inspectCompose(import_node_path9.default.join(devcontainerDir, "docker-compose.yml"), workspace);
4404
+ const compose = inspectCompose(import_node_path10.default.join(devcontainerDir, "docker-compose.yml"), workspace);
4345
4405
  const checks = [];
4346
4406
  if (compose.parseError) {
4347
4407
  checks.push({
@@ -4368,9 +4428,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
4368
4428
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
4369
4429
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
4370
4430
  });
4371
- const dockerfilePath = import_node_path9.default.join(devcontainerDir, "Dockerfile");
4372
- const dockerfileExists = import_node_fs10.default.existsSync(dockerfilePath);
4373
- const dockerfile = dockerfileExists ? import_node_fs10.default.readFileSync(dockerfilePath, "utf-8") : "";
4431
+ const dockerfilePath = import_node_path10.default.join(devcontainerDir, "Dockerfile");
4432
+ const dockerfileExists = import_node_fs11.default.existsSync(dockerfilePath);
4433
+ const dockerfile = dockerfileExists ? import_node_fs11.default.readFileSync(dockerfilePath, "utf-8") : "";
4374
4434
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
4375
4435
  (artifact) => dockerfile.includes(artifact)
4376
4436
  );
@@ -4425,12 +4485,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
4425
4485
  });
4426
4486
  return checks;
4427
4487
  }
4428
- var import_node_fs10, import_node_path9, import_yaml3;
4488
+ var import_node_fs11, import_node_path10, import_yaml3;
4429
4489
  var init_devcontainer_diagnostics = __esm({
4430
4490
  "src/core/devcontainer-diagnostics.ts"() {
4431
4491
  "use strict";
4432
- import_node_fs10 = __toESM(require("fs"));
4433
- import_node_path9 = __toESM(require("path"));
4492
+ import_node_fs11 = __toESM(require("fs"));
4493
+ import_node_path10 = __toESM(require("path"));
4434
4494
  import_yaml3 = __toESM(require("yaml"));
4435
4495
  init_managed_post_start();
4436
4496
  }
@@ -4438,18 +4498,18 @@ var init_devcontainer_diagnostics = __esm({
4438
4498
 
4439
4499
  // src/core/paths.ts
4440
4500
  function assertPathWithinRepo(filePath, repoRoot, label) {
4441
- const resolvedRoot = import_node_path10.default.resolve(repoRoot);
4442
- const resolved = import_node_path10.default.resolve(repoRoot, filePath);
4443
- if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path10.default.sep)) {
4501
+ const resolvedRoot = import_node_path11.default.resolve(repoRoot);
4502
+ const resolved = import_node_path11.default.resolve(repoRoot, filePath);
4503
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path11.default.sep)) {
4444
4504
  throw new Error(`${label} path '${filePath}' escapes the repository root.`);
4445
4505
  }
4446
4506
  return resolved;
4447
4507
  }
4448
- var import_node_path10;
4508
+ var import_node_path11;
4449
4509
  var init_paths = __esm({
4450
4510
  "src/core/paths.ts"() {
4451
4511
  "use strict";
4452
- import_node_path10 = __toESM(require("path"));
4512
+ import_node_path11 = __toESM(require("path"));
4453
4513
  }
4454
4514
  });
4455
4515
 
@@ -4534,11 +4594,11 @@ function reconcileRouteRunConflict(repoPath, app) {
4534
4594
  }
4535
4595
  }
4536
4596
  }
4537
- var import_node_fs11;
4597
+ var import_node_fs12;
4538
4598
  var init_route_state = __esm({
4539
4599
  "src/core/route-state.ts"() {
4540
4600
  "use strict";
4541
- import_node_fs11 = __toESM(require("fs"));
4601
+ import_node_fs12 = __toESM(require("fs"));
4542
4602
  init_host_routes();
4543
4603
  init_workspace();
4544
4604
  }
@@ -4707,7 +4767,7 @@ function stateKey(repoPath, workspace) {
4707
4767
  return (0, import_node_crypto4.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
4708
4768
  }
4709
4769
  function managedRuntimeStatePath(repoPath, workspace) {
4710
- return import_node_path11.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
4770
+ return import_node_path12.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
4711
4771
  }
4712
4772
  function isStringArray(value) {
4713
4773
  return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
@@ -4770,10 +4830,10 @@ function validateState(value, repoPath, workspace) {
4770
4830
  }
4771
4831
  function readManagedRuntimeState(repoPath, workspace) {
4772
4832
  const statePath = managedRuntimeStatePath(repoPath, workspace);
4773
- if (!import_node_fs12.default.existsSync(statePath)) return void 0;
4833
+ if (!import_node_fs13.default.existsSync(statePath)) return void 0;
4774
4834
  let parsed;
4775
4835
  try {
4776
- parsed = JSON.parse(import_node_fs12.default.readFileSync(statePath, "utf-8"));
4836
+ parsed = JSON.parse(import_node_fs13.default.readFileSync(statePath, "utf-8"));
4777
4837
  } catch (error) {
4778
4838
  throw new Error(`Could not parse managed runtime state: ${String(error)}`);
4779
4839
  }
@@ -4795,20 +4855,20 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
4795
4855
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4796
4856
  });
4797
4857
  }
4798
- var import_node_crypto4, import_node_fs12, import_node_path11;
4858
+ var import_node_crypto4, import_node_fs13, import_node_path12;
4799
4859
  var init_managed_runtime_state = __esm({
4800
4860
  "src/core/managed-runtime-state.ts"() {
4801
4861
  "use strict";
4802
4862
  import_node_crypto4 = require("crypto");
4803
- import_node_fs12 = __toESM(require("fs"));
4804
- import_node_path11 = __toESM(require("path"));
4863
+ import_node_fs13 = __toESM(require("fs"));
4864
+ import_node_path12 = __toESM(require("path"));
4805
4865
  init_atomic_file();
4806
4866
  init_router();
4807
4867
  }
4808
4868
  });
4809
4869
 
4810
4870
  // src/core/devcontainer-profile.ts
4811
- function isObject(value) {
4871
+ function isObject2(value) {
4812
4872
  return value !== null && typeof value === "object" && !Array.isArray(value);
4813
4873
  }
4814
4874
  function nonEmptyString(value, label) {
@@ -4852,14 +4912,14 @@ function resolveComposeFiles(source, repoPath, linked) {
4852
4912
  "managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
4853
4913
  );
4854
4914
  }
4855
- const directory = import_node_path12.default.join(repoPath, ".devcontainer");
4915
+ const directory = import_node_path13.default.join(repoPath, ".devcontainer");
4856
4916
  const files = references.map((reference) => {
4857
4917
  const resolved2 = assertPathWithinRepo(
4858
4918
  resolveComposeReference(reference, linked),
4859
4919
  directory,
4860
4920
  "dockerComposeFile"
4861
4921
  );
4862
- if (!import_node_fs13.default.existsSync(resolved2) || !import_node_fs13.default.lstatSync(resolved2).isFile()) {
4922
+ if (!import_node_fs14.default.existsSync(resolved2) || !import_node_fs14.default.lstatSync(resolved2).isFile()) {
4863
4923
  throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
4864
4924
  }
4865
4925
  return resolved2;
@@ -4867,13 +4927,13 @@ function resolveComposeFiles(source, repoPath, linked) {
4867
4927
  for (const file of files) {
4868
4928
  let parsed;
4869
4929
  try {
4870
- parsed = import_yaml4.default.parse(import_node_fs13.default.readFileSync(file, "utf-8"));
4930
+ parsed = import_yaml4.default.parse(import_node_fs14.default.readFileSync(file, "utf-8"));
4871
4931
  } catch (error) {
4872
4932
  throw new Error(
4873
4933
  `Could not parse managed Dev Container compose file '${file}': ${String(error)}`
4874
4934
  );
4875
4935
  }
4876
- if (!isObject(parsed) || !isObject(parsed.services)) {
4936
+ if (!isObject2(parsed) || !isObject2(parsed.services)) {
4877
4937
  throw new Error(`Managed Dev Container compose file '${file}' has no services map.`);
4878
4938
  }
4879
4939
  }
@@ -4916,12 +4976,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
4916
4976
  }
4917
4977
  }
4918
4978
  function assertGeneratedPathOwnership(generatedPath) {
4919
- if (!import_node_fs13.default.existsSync(generatedPath)) return;
4920
- const stat = import_node_fs13.default.lstatSync(generatedPath);
4979
+ if (!import_node_fs14.default.existsSync(generatedPath)) return;
4980
+ const stat = import_node_fs14.default.lstatSync(generatedPath);
4921
4981
  if (!stat.isFile() || stat.isSymbolicLink()) {
4922
4982
  throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
4923
4983
  }
4924
- const firstLine2 = import_node_fs13.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
4984
+ const firstLine2 = import_node_fs14.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
4925
4985
  if (firstLine2 !== MANAGED_DEVCONTAINER_MARKER) {
4926
4986
  throw new Error(
4927
4987
  `Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
@@ -4945,21 +5005,7 @@ function inspectManagedDevcontainerConfig(options) {
4945
5005
  if (!managedRuntime) {
4946
5006
  throw new Error("Cannot prepare a managed Dev Container without managedRuntime.");
4947
5007
  }
4948
- const sourcePath = import_node_path12.default.join(options.repoPath, ".devcontainer/devcontainer.json");
4949
- if (!import_node_fs13.default.existsSync(sourcePath) || !import_node_fs13.default.lstatSync(sourcePath).isFile()) {
4950
- throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
4951
- }
4952
- const sourceContents = import_node_fs13.default.readFileSync(sourcePath, "utf-8");
4953
- let parsed;
4954
- try {
4955
- parsed = import_yaml4.default.parse(sourceContents);
4956
- } catch (error) {
4957
- throw new Error(`Could not parse managed Dev Container source config: ${String(error)}`);
4958
- }
4959
- if (!isObject(parsed)) {
4960
- throw new Error("Managed Dev Container source config must contain an object.");
4961
- }
4962
- const source = parsed;
5008
+ const { sourcePath, sourceContents, source } = readDevcontainerConfig(options.repoPath);
4963
5009
  const primaryService = nonEmptyString(source.service, "devcontainer.service");
4964
5010
  const compose = resolveComposeFiles(source, options.repoPath, options.linked);
4965
5011
  const composeServiceSet = new Set(compose.services);
@@ -5011,7 +5057,7 @@ function inspectManagedDevcontainerConfig(options) {
5011
5057
  const contents = `${MANAGED_DEVCONTAINER_MARKER}
5012
5058
  ${JSON.stringify(effective, null, 2)}
5013
5059
  `;
5014
- const generatedPath = import_node_path12.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
5060
+ const generatedPath = import_node_path13.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
5015
5061
  assertIgnoredGeneratedPath(options.repoPath, generatedPath);
5016
5062
  assertGeneratedPathOwnership(generatedPath);
5017
5063
  return {
@@ -5038,13 +5084,13 @@ function writeManagedDevcontainerConfig(plan) {
5038
5084
  function inspectManagedDevcontainerGeneratedConfig(plan) {
5039
5085
  let stat;
5040
5086
  try {
5041
- stat = import_node_fs13.default.lstatSync(plan.generatedPath);
5087
+ stat = import_node_fs14.default.lstatSync(plan.generatedPath);
5042
5088
  } catch (error) {
5043
5089
  if (error.code === "ENOENT") return { status: "missing" };
5044
5090
  throw error;
5045
5091
  }
5046
5092
  if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
5047
- const contents = import_node_fs13.default.readFileSync(plan.generatedPath, "utf-8");
5093
+ const contents = import_node_fs14.default.readFileSync(plan.generatedPath, "utf-8");
5048
5094
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
5049
5095
  `)) {
5050
5096
  return { status: "foreign" };
@@ -5056,17 +5102,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
5056
5102
  };
5057
5103
  }
5058
5104
  function removeManagedDevcontainerConfig(plan) {
5059
- if (!import_node_fs13.default.existsSync(plan.generatedPath)) return;
5060
- const stat = import_node_fs13.default.lstatSync(plan.generatedPath);
5105
+ if (!import_node_fs14.default.existsSync(plan.generatedPath)) return;
5106
+ const stat = import_node_fs14.default.lstatSync(plan.generatedPath);
5061
5107
  if (!stat.isFile()) {
5062
5108
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
5063
5109
  }
5064
- const contents = import_node_fs13.default.readFileSync(plan.generatedPath, "utf-8");
5110
+ const contents = import_node_fs14.default.readFileSync(plan.generatedPath, "utf-8");
5065
5111
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
5066
5112
  `)) {
5067
5113
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
5068
5114
  }
5069
- import_node_fs13.default.unlinkSync(plan.generatedPath);
5115
+ import_node_fs14.default.unlinkSync(plan.generatedPath);
5070
5116
  }
5071
5117
  function assertSafeContainerId(containerId) {
5072
5118
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
@@ -5131,16 +5177,17 @@ function stopExactManagedService(containerId, service) {
5131
5177
  throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
5132
5178
  }
5133
5179
  }
5134
- var import_node_child_process6, import_node_crypto5, import_node_fs13, import_node_path12, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
5180
+ var import_node_child_process6, import_node_crypto5, import_node_fs14, import_node_path13, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
5135
5181
  var init_devcontainer_profile = __esm({
5136
5182
  "src/core/devcontainer-profile.ts"() {
5137
5183
  "use strict";
5138
5184
  import_node_child_process6 = require("child_process");
5139
5185
  import_node_crypto5 = require("crypto");
5140
- import_node_fs13 = __toESM(require("fs"));
5141
- import_node_path12 = __toESM(require("path"));
5186
+ import_node_fs14 = __toESM(require("fs"));
5187
+ import_node_path13 = __toESM(require("path"));
5142
5188
  import_yaml4 = __toESM(require("yaml"));
5143
5189
  init_atomic_file();
5190
+ init_devcontainer_config();
5144
5191
  init_paths();
5145
5192
  MANAGED_DEVCONTAINER_PATH = ".devcontainer/devcontainer.devrouter.json";
5146
5193
  MANAGED_DEVCONTAINER_MARKER = "// devrouter:managed devcontainer profile";
@@ -5178,7 +5225,7 @@ function inspectWorkspaceContainers(options) {
5178
5225
  function workspaceAppContainers(containers, repoPath) {
5179
5226
  return containers.filter((container) => {
5180
5227
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
5181
- return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path13.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
5228
+ return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path14.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
5182
5229
  (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
5183
5230
  );
5184
5231
  });
@@ -5186,7 +5233,7 @@ function workspaceAppContainers(containers, repoPath) {
5186
5233
  function hasExactComposeIdentity(container, options) {
5187
5234
  if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
5188
5235
  container.labels["com.docker.compose.project.working_dir"] ?? "",
5189
- import_node_path13.default.join(options.repoPath, ".devcontainer")
5236
+ import_node_path14.default.join(options.repoPath, ".devcontainer")
5190
5237
  )) {
5191
5238
  return false;
5192
5239
  }
@@ -5223,12 +5270,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
5223
5270
  }
5224
5271
  return { id: container.id, workspacePath: repoMount.Destination };
5225
5272
  }
5226
- var import_node_child_process7, import_node_path13, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
5273
+ var import_node_child_process7, import_node_path14, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
5227
5274
  var init_devpod_environment = __esm({
5228
5275
  "src/core/devpod-environment.ts"() {
5229
5276
  "use strict";
5230
5277
  import_node_child_process7 = require("child_process");
5231
- import_node_path13 = __toESM(require("path"));
5278
+ import_node_path14 = __toESM(require("path"));
5232
5279
  init_workspace();
5233
5280
  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}}}';
5234
5281
  SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
@@ -5682,7 +5729,7 @@ function toRepoStatus(repoPath) {
5682
5729
  const resolvedRepoPath = resolveRepoPath(repoPath);
5683
5730
  const configPath = getRepoConfigPath(resolvedRepoPath);
5684
5731
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
5685
- const configExists = import_node_fs14.default.existsSync(configPath);
5732
+ const configExists = import_node_fs15.default.existsSync(configPath);
5686
5733
  if (!explicitRepo && !configExists) {
5687
5734
  return void 0;
5688
5735
  }
@@ -5830,11 +5877,11 @@ async function collectRouterStatus(repoPath) {
5830
5877
  }
5831
5878
  };
5832
5879
  }
5833
- var import_node_fs14;
5880
+ var import_node_fs15;
5834
5881
  var init_status = __esm({
5835
5882
  "src/core/status.ts"() {
5836
5883
  "use strict";
5837
- import_node_fs14 = __toESM(require("fs"));
5884
+ import_node_fs15 = __toESM(require("fs"));
5838
5885
  init_docker();
5839
5886
  init_managed_runtime_state();
5840
5887
  init_managed_runtime_status();
@@ -5873,8 +5920,8 @@ function tlsSetupCommand(repoPath) {
5873
5920
  }
5874
5921
  function getMkcertRootCAPath(options = {}) {
5875
5922
  ensureMkcert();
5876
- const rootCAPath = import_node_path14.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
5877
- if (!import_node_fs15.default.existsSync(rootCAPath)) {
5923
+ const rootCAPath = import_node_path15.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
5924
+ if (!import_node_fs16.default.existsSync(rootCAPath)) {
5878
5925
  throw new Error(
5879
5926
  `mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
5880
5927
  );
@@ -5940,10 +5987,10 @@ function findUncoveredCertificateHosts(requiredHosts, certificateHosts) {
5940
5987
  );
5941
5988
  }
5942
5989
  function readCurrentCertificateHosts() {
5943
- if (!import_node_fs15.default.existsSync(CERT_FILE)) {
5990
+ if (!import_node_fs16.default.existsSync(CERT_FILE)) {
5944
5991
  return [];
5945
5992
  }
5946
- const pem = import_node_fs15.default.readFileSync(CERT_FILE, "utf-8");
5993
+ const pem = import_node_fs16.default.readFileSync(CERT_FILE, "utf-8");
5947
5994
  return parseCertificateDnsHosts(pem);
5948
5995
  }
5949
5996
  function currentCertificateHostsOrEmpty() {
@@ -6032,14 +6079,14 @@ Run: ${tlsSetupCommand(options.repoPath)}`
6032
6079
  );
6033
6080
  }
6034
6081
  }
6035
- var import_node_child_process8, import_node_crypto6, import_node_fs15, import_node_path14, DEFAULT_TLS_CERT_HOSTS;
6082
+ var import_node_child_process8, import_node_crypto6, import_node_fs16, import_node_path15, DEFAULT_TLS_CERT_HOSTS;
6036
6083
  var init_tls = __esm({
6037
6084
  "src/core/tls.ts"() {
6038
6085
  "use strict";
6039
6086
  import_node_child_process8 = require("child_process");
6040
6087
  import_node_crypto6 = require("crypto");
6041
- import_node_fs15 = __toESM(require("fs"));
6042
- import_node_path14 = __toESM(require("path"));
6088
+ import_node_fs16 = __toESM(require("fs"));
6089
+ import_node_path15 = __toESM(require("path"));
6043
6090
  init_docker();
6044
6091
  init_host_routes();
6045
6092
  init_router();
@@ -6220,7 +6267,7 @@ function isRuntimeInstalled(runtime) {
6220
6267
  function readWorkspaceRuntimeConfig() {
6221
6268
  let raw;
6222
6269
  try {
6223
- raw = JSON.parse(import_node_fs16.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
6270
+ raw = JSON.parse(import_node_fs17.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
6224
6271
  } catch {
6225
6272
  return {};
6226
6273
  }
@@ -6241,7 +6288,7 @@ function readWorkspaceRuntimeConfig() {
6241
6288
  function inspectWorkspaceRuntimeConfig() {
6242
6289
  let rawText;
6243
6290
  try {
6244
- rawText = import_node_fs16.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
6291
+ rawText = import_node_fs17.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
6245
6292
  } catch {
6246
6293
  return { exists: false, config: {}, problems: [] };
6247
6294
  }
@@ -6292,7 +6339,7 @@ function writeWorkspaceRuntimeConfig(config) {
6292
6339
  }
6293
6340
  next.devsyInactivityTimeout = timeout;
6294
6341
  }
6295
- import_node_fs16.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6342
+ import_node_fs17.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6296
6343
  writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
6297
6344
  `);
6298
6345
  }
@@ -6400,20 +6447,20 @@ function resetWorkspaceRuntimeCaches() {
6400
6447
  cachedRuntime = void 0;
6401
6448
  cachedSnapshots = void 0;
6402
6449
  }
6403
- var import_node_child_process11, import_node_fs16, import_node_path15, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
6450
+ var import_node_child_process11, import_node_fs17, import_node_path16, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
6404
6451
  var init_workspace_runtime = __esm({
6405
6452
  "src/core/workspace-runtime.ts"() {
6406
6453
  "use strict";
6407
6454
  import_node_child_process11 = require("child_process");
6408
- import_node_fs16 = __toESM(require("fs"));
6409
- import_node_path15 = __toESM(require("path"));
6455
+ import_node_fs17 = __toESM(require("fs"));
6456
+ import_node_path16 = __toESM(require("path"));
6410
6457
  init_atomic_file();
6411
6458
  init_devpod_registry();
6412
6459
  init_devsy_workspaces();
6413
6460
  init_router();
6414
6461
  init_workspace();
6415
6462
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
6416
- RUNTIME_CONFIG_FILE = import_node_path15.default.join(DEVROUTER_HOME, "workspace-runtime.json");
6463
+ RUNTIME_CONFIG_FILE = import_node_path16.default.join(DEVROUTER_HOME, "workspace-runtime.json");
6417
6464
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
6418
6465
  UnsupportedWorkspaceRuntimeError = class extends Error {
6419
6466
  };
@@ -6491,12 +6538,12 @@ function parseMinimumNodeMajor(value) {
6491
6538
  return Number(match[1]);
6492
6539
  }
6493
6540
  function readPackageJson(repoPath) {
6494
- const packagePath = import_node_path16.default.join(repoPath, "package.json");
6495
- if (!import_node_fs17.default.existsSync(packagePath)) {
6541
+ const packagePath = import_node_path17.default.join(repoPath, "package.json");
6542
+ if (!import_node_fs18.default.existsSync(packagePath)) {
6496
6543
  return void 0;
6497
6544
  }
6498
6545
  try {
6499
- return JSON.parse(import_node_fs17.default.readFileSync(packagePath, "utf-8"));
6546
+ return JSON.parse(import_node_fs18.default.readFileSync(packagePath, "utf-8"));
6500
6547
  } catch {
6501
6548
  return void 0;
6502
6549
  }
@@ -6636,13 +6683,13 @@ function buildGlobalToolChecks(repoPath) {
6636
6683
  checks.push(nodeToolchainCheck(repoPath));
6637
6684
  return checks;
6638
6685
  }
6639
- var import_node_child_process12, import_node_fs17, import_node_path16;
6686
+ var import_node_child_process12, import_node_fs18, import_node_path17;
6640
6687
  var init_tool_diagnostics = __esm({
6641
6688
  "src/core/tool-diagnostics.ts"() {
6642
6689
  "use strict";
6643
6690
  import_node_child_process12 = require("child_process");
6644
- import_node_fs17 = __toESM(require("fs"));
6645
- import_node_path16 = __toESM(require("path"));
6691
+ import_node_fs18 = __toESM(require("fs"));
6692
+ import_node_path17 = __toESM(require("path"));
6646
6693
  init_workspace_runtime();
6647
6694
  }
6648
6695
  });
@@ -6769,7 +6816,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
6769
6816
  }
6770
6817
  }
6771
6818
  function withMutationLock(activity, target, operation) {
6772
- import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6819
+ import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6773
6820
  return withFileLockSync(
6774
6821
  DEVSY_MUTATION_LOCK_FILE,
6775
6822
  { activity, target: `'${target}'`, waitMs: DEVSY_MUTATION_WAIT_MS },
@@ -6914,17 +6961,17 @@ function startDevsyWorkspace(options) {
6914
6961
  }
6915
6962
  });
6916
6963
  }
6917
- var import_node_child_process14, import_node_fs18, import_node_path17, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError;
6964
+ var import_node_child_process14, import_node_fs19, import_node_path18, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError;
6918
6965
  var init_devsy_mutation = __esm({
6919
6966
  "src/core/devsy-mutation.ts"() {
6920
6967
  "use strict";
6921
6968
  import_node_child_process14 = require("child_process");
6922
- import_node_fs18 = __toESM(require("fs"));
6923
- import_node_path17 = __toESM(require("path"));
6969
+ import_node_fs19 = __toESM(require("fs"));
6970
+ import_node_path18 = __toESM(require("path"));
6924
6971
  init_devsy_workspaces();
6925
6972
  init_file_lock();
6926
6973
  init_router();
6927
- DEVSY_MUTATION_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
6974
+ DEVSY_MUTATION_LOCK_FILE = import_node_path18.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
6928
6975
  DEVSY_MUTATION_WAIT_MS = 6e4;
6929
6976
  DevsyStartPostconditionError = class extends Error {
6930
6977
  };
@@ -6933,7 +6980,7 @@ var init_devsy_mutation = __esm({
6933
6980
 
6934
6981
  // src/core/devpod-mutation.ts
6935
6982
  function withMutationLock2(activity, target, operation) {
6936
- import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6983
+ import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6937
6984
  return withFileLockSync(
6938
6985
  DEVPOD_MUTATION_LOCK_FILE,
6939
6986
  { activity, target: `'${target}'`, waitMs: DEVPOD_MUTATION_WAIT_MS },
@@ -7101,19 +7148,19 @@ function startDevpodWorkspace(options) {
7101
7148
  }
7102
7149
  });
7103
7150
  }
7104
- var import_node_child_process15, import_node_fs19, import_node_path18, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
7151
+ var import_node_child_process15, import_node_fs20, import_node_path19, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
7105
7152
  var init_devpod_mutation = __esm({
7106
7153
  "src/core/devpod-mutation.ts"() {
7107
7154
  "use strict";
7108
7155
  import_node_child_process15 = require("child_process");
7109
- import_node_fs19 = __toESM(require("fs"));
7110
- import_node_path18 = __toESM(require("path"));
7156
+ import_node_fs20 = __toESM(require("fs"));
7157
+ import_node_path19 = __toESM(require("path"));
7111
7158
  init_devpod_workspaces();
7112
7159
  init_devsy_mutation();
7113
7160
  init_file_lock();
7114
7161
  init_router();
7115
7162
  init_workspace_runtime();
7116
- DEVPOD_MUTATION_LOCK_FILE = import_node_path18.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
7163
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path19.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
7117
7164
  DEVPOD_MUTATION_WAIT_MS = 6e4;
7118
7165
  DevpodStartPostconditionError = class extends Error {
7119
7166
  };
@@ -7135,7 +7182,7 @@ function resolveGitCommonDir(repoPath) {
7135
7182
  if (result.status !== 0 || !output2) {
7136
7183
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
7137
7184
  }
7138
- return comparableWorkspacePath(import_node_path19.default.isAbsolute(output2) ? output2 : import_node_path19.default.resolve(repoPath, output2));
7185
+ return comparableWorkspacePath(import_node_path20.default.isAbsolute(output2) ? output2 : import_node_path20.default.resolve(repoPath, output2));
7139
7186
  }
7140
7187
  function resolveGitTopLevel(repoPath) {
7141
7188
  const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
@@ -7186,7 +7233,7 @@ function listGitWorktrees(repoPath) {
7186
7233
  return worktrees;
7187
7234
  }
7188
7235
  function ownershipDirectory(repoPath) {
7189
- return import_node_path19.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
7236
+ return import_node_path20.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
7190
7237
  }
7191
7238
  function validateWorkspace(value, label) {
7192
7239
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -7214,7 +7261,7 @@ function validateRecord(value, expectedWorkspace) {
7214
7261
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
7215
7262
  );
7216
7263
  }
7217
- if (typeof candidate.worktreePath !== "string" || !import_node_path19.default.isAbsolute(candidate.worktreePath)) {
7264
+ if (typeof candidate.worktreePath !== "string" || !import_node_path20.default.isAbsolute(candidate.worktreePath)) {
7218
7265
  throw new Error("invalid workspace ownership worktreePath");
7219
7266
  }
7220
7267
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -7234,7 +7281,7 @@ function validateRecord(value, expectedWorkspace) {
7234
7281
  function readRecordFile(filePath, expectedWorkspace) {
7235
7282
  let parsed;
7236
7283
  try {
7237
- parsed = JSON.parse(import_node_fs20.default.readFileSync(filePath, "utf-8"));
7284
+ parsed = JSON.parse(import_node_fs21.default.readFileSync(filePath, "utf-8"));
7238
7285
  } catch (error) {
7239
7286
  if (error instanceof SyntaxError) {
7240
7287
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -7250,7 +7297,7 @@ function listWorkspaceOwnership(repoPath) {
7250
7297
  function listWorkspaceOwnershipInDirectory(directory) {
7251
7298
  let entries;
7252
7299
  try {
7253
- entries = import_node_fs20.default.readdirSync(directory, { withFileTypes: true });
7300
+ entries = import_node_fs21.default.readdirSync(directory, { withFileTypes: true });
7254
7301
  } catch (error) {
7255
7302
  if (error.code === "ENOENT") return [];
7256
7303
  throw error;
@@ -7258,14 +7305,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
7258
7305
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
7259
7306
  const workspace = entry.name.slice(0, -".json".length);
7260
7307
  validateWorkspace(workspace, "filename");
7261
- return readRecordFile(import_node_path19.default.join(directory, entry.name), workspace);
7308
+ return readRecordFile(import_node_path20.default.join(directory, entry.name), workspace);
7262
7309
  });
7263
7310
  }
7264
7311
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
7265
7312
  const workspace = validateWorkspace(input2.workspace, "workspace");
7266
7313
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
7267
7314
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
7268
- const filePath = import_node_path19.default.join(directory, `${workspace}.json`);
7315
+ const filePath = import_node_path20.default.join(directory, `${workspace}.json`);
7269
7316
  const now = (/* @__PURE__ */ new Date()).toISOString();
7270
7317
  const records = listWorkspaceOwnershipInDirectory(directory);
7271
7318
  const existing = records.find((record2) => record2.workspace === workspace);
@@ -7301,9 +7348,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
7301
7348
  return record;
7302
7349
  }
7303
7350
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
7304
- const filePath = import_node_path19.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
7351
+ const filePath = import_node_path20.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
7305
7352
  try {
7306
- import_node_fs20.default.rmSync(filePath);
7353
+ import_node_fs21.default.rmSync(filePath);
7307
7354
  return true;
7308
7355
  } catch (error) {
7309
7356
  if (error.code === "ENOENT") return false;
@@ -7314,7 +7361,7 @@ function sameOwnershipRecord(left, right) {
7314
7361
  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;
7315
7362
  }
7316
7363
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
7317
- const filePath = import_node_path19.default.join(
7364
+ const filePath = import_node_path20.default.join(
7318
7365
  directory,
7319
7366
  `${validateWorkspace(expected.workspace, "workspace")}.json`
7320
7367
  );
@@ -7326,14 +7373,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
7326
7373
  throw error;
7327
7374
  }
7328
7375
  if (!sameOwnershipRecord(current, expected)) return "changed";
7329
- import_node_fs20.default.rmSync(filePath);
7376
+ import_node_fs21.default.rmSync(filePath);
7330
7377
  return "removed";
7331
7378
  }
7332
7379
  function withWorkspaceOwnershipTransaction(repoPath, operation) {
7333
7380
  const directory = ownershipDirectory(repoPath);
7334
- import_node_fs20.default.mkdirSync(directory, { recursive: true });
7381
+ import_node_fs21.default.mkdirSync(directory, { recursive: true });
7335
7382
  return withFileLockSync(
7336
- import_node_path19.default.join(directory, ".lock"),
7383
+ import_node_path20.default.join(directory, ".lock"),
7337
7384
  { activity: "workspace ownership transaction", target: `'${repoPath}'`, waitMs: 5e3 },
7338
7385
  () => operation({
7339
7386
  list: () => listWorkspaceOwnershipInDirectory(directory),
@@ -7364,7 +7411,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
7364
7411
  if (worktree?.locked) {
7365
7412
  return { ownerStatus: "locked", devpodStatus, worktree };
7366
7413
  }
7367
- if ((!worktree || worktree.prunable) && import_node_fs20.default.existsSync(record.worktreePath)) {
7414
+ if ((!worktree || worktree.prunable) && import_node_fs21.default.existsSync(record.worktreePath)) {
7368
7415
  return { ownerStatus: "conflict", devpodStatus, worktree };
7369
7416
  }
7370
7417
  if (!worktree || worktree.prunable) {
@@ -7388,20 +7435,20 @@ function listMissingWorkspaceOwnership(repoPath) {
7388
7435
  (record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
7389
7436
  );
7390
7437
  }
7391
- var import_node_child_process16, import_node_fs20, import_node_path19, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
7438
+ var import_node_child_process16, import_node_fs21, import_node_path20, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
7392
7439
  var init_workspace_ownership = __esm({
7393
7440
  "src/core/workspace-ownership.ts"() {
7394
7441
  "use strict";
7395
7442
  import_node_child_process16 = require("child_process");
7396
- import_node_fs20 = __toESM(require("fs"));
7397
- import_node_path19 = __toESM(require("path"));
7443
+ import_node_fs21 = __toESM(require("fs"));
7444
+ import_node_path20 = __toESM(require("path"));
7398
7445
  init_atomic_file();
7399
7446
  init_devpod_workspaces();
7400
7447
  init_file_lock();
7401
7448
  init_workspace();
7402
7449
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
7403
7450
  OWNERSHIP_VERSION = 1;
7404
- OWNERSHIP_DIR = import_node_path19.default.join("devrouter", "workspaces");
7451
+ OWNERSHIP_DIR = import_node_path20.default.join("devrouter", "workspaces");
7405
7452
  }
7406
7453
  });
7407
7454
 
@@ -7415,10 +7462,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
7415
7462
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
7416
7463
  const comparableRepo = comparableWorkspacePath(repoPath);
7417
7464
  const comparableWorktree = comparableWorkspacePath(worktreePath);
7418
- const localRoot = import_node_path20.default.join(comparableRepo, "trees") + import_node_path20.default.sep;
7465
+ const localRoot = import_node_path21.default.join(comparableRepo, "trees") + import_node_path21.default.sep;
7419
7466
  if (comparableWorktree.startsWith(localRoot)) return true;
7420
- const legacyPrefix = `${import_node_path20.default.basename(comparableRepo)}-`;
7421
- return import_node_path20.default.dirname(comparableWorktree) === import_node_path20.default.dirname(comparableRepo) && import_node_path20.default.basename(comparableWorktree).startsWith(legacyPrefix);
7467
+ const legacyPrefix = `${import_node_path21.default.basename(comparableRepo)}-`;
7468
+ return import_node_path21.default.dirname(comparableWorktree) === import_node_path21.default.dirname(comparableRepo) && import_node_path21.default.basename(comparableWorktree).startsWith(legacyPrefix);
7422
7469
  }
7423
7470
  function previewActions(devpodStatus, routeCount, includeRecord) {
7424
7471
  const actions = [
@@ -7670,11 +7717,11 @@ function applyWorkspaceGc(plan) {
7670
7717
  candidates
7671
7718
  };
7672
7719
  }
7673
- var import_node_path20;
7720
+ var import_node_path21;
7674
7721
  var init_workspace_gc = __esm({
7675
7722
  "src/core/workspace-gc.ts"() {
7676
7723
  "use strict";
7677
- import_node_path20 = __toESM(require("path"));
7724
+ import_node_path21 = __toESM(require("path"));
7678
7725
  init_devpod_mutation();
7679
7726
  init_devpod_workspaces();
7680
7727
  init_host_routes();
@@ -7744,11 +7791,11 @@ function inspectPostgresCredentials(repoPath, config) {
7744
7791
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
7745
7792
  continue;
7746
7793
  }
7747
- if (!import_node_fs21.default.existsSync(absolutePath)) {
7794
+ if (!import_node_fs22.default.existsSync(absolutePath)) {
7748
7795
  continue;
7749
7796
  }
7750
7797
  try {
7751
- const raw = import_node_fs21.default.readFileSync(absolutePath, "utf-8");
7798
+ const raw = import_node_fs22.default.readFileSync(absolutePath, "utf-8");
7752
7799
  const parsed = import_yaml5.default.parse(raw);
7753
7800
  const root = asRecord2(parsed);
7754
7801
  const services = asRecord2(root?.services);
@@ -8027,7 +8074,7 @@ async function buildDoctorReport(options = {}) {
8027
8074
  const config = runtimeConfig.config;
8028
8075
  loadedConfig = config;
8029
8076
  loadedWorkspace = runtimeConfig.workspace;
8030
- const cliVersion = true ? "0.0.42" : "0.0.0-dev";
8077
+ const cliVersion = true ? "0.0.43" : "0.0.0-dev";
8031
8078
  const configVersion = config.devrouter?.version;
8032
8079
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
8033
8080
  addCheck(checks, {
@@ -8057,9 +8104,9 @@ async function buildDoctorReport(options = {}) {
8057
8104
  (app) => app.docker.composeFiles.map((filePath) => ({
8058
8105
  app: app.name,
8059
8106
  filePath,
8060
- absolutePath: import_node_path21.default.resolve(repo.path, filePath)
8107
+ absolutePath: import_node_path22.default.resolve(repo.path, filePath)
8061
8108
  }))
8062
- ).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
8109
+ ).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
8063
8110
  addCheck(checks, {
8064
8111
  id: "repo.compose-files",
8065
8112
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -8102,8 +8149,8 @@ async function buildDoctorReport(options = {}) {
8102
8149
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
8103
8150
  app: app.name,
8104
8151
  cwd: app.hostRun.cwd,
8105
- absolutePath: import_node_path21.default.resolve(repo.path, app.hostRun.cwd)
8106
- })).filter((entry) => !import_node_fs21.default.existsSync(entry.absolutePath));
8152
+ absolutePath: import_node_path22.default.resolve(repo.path, app.hostRun.cwd)
8153
+ })).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
8107
8154
  addCheck(checks, {
8108
8155
  id: "repo.host-cwd",
8109
8156
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -8247,12 +8294,12 @@ async function buildDoctorReport(options = {}) {
8247
8294
  nextSteps
8248
8295
  };
8249
8296
  }
8250
- var import_node_fs21, import_node_path21, import_yaml5, POSTGRES_DEFAULTS;
8297
+ var import_node_fs22, import_node_path22, import_yaml5, POSTGRES_DEFAULTS;
8251
8298
  var init_doctor = __esm({
8252
8299
  "src/core/doctor.ts"() {
8253
8300
  "use strict";
8254
- import_node_fs21 = __toESM(require("fs"));
8255
- import_node_path21 = __toESM(require("path"));
8301
+ import_node_fs22 = __toESM(require("fs"));
8302
+ import_node_path22 = __toESM(require("path"));
8256
8303
  import_yaml5 = __toESM(require("yaml"));
8257
8304
  init_docker();
8258
8305
  init_host_routes();
@@ -8747,11 +8794,11 @@ var init_route_publication = __esm({
8747
8794
  // src/core/workspace-ensure.ts
8748
8795
  function assertOverlay(container, repoPath) {
8749
8796
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
8750
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path22.default.join(repoPath, ".devcontainer"))) {
8797
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path23.default.join(repoPath, ".devcontainer"))) {
8751
8798
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
8752
8799
  }
8753
8800
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
8754
- const expectedOverlay = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
8801
+ const expectedOverlay = import_node_path23.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
8755
8802
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
8756
8803
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
8757
8804
  }
@@ -9136,7 +9183,7 @@ function resolvePrimaryTarget(repoPath) {
9136
9183
  }
9137
9184
  function isPrimaryCheckout(repoPath) {
9138
9185
  try {
9139
- return import_node_fs22.default.statSync(import_node_path22.default.join(repoPath, ".git")).isDirectory();
9186
+ return import_node_fs23.default.statSync(import_node_path23.default.join(repoPath, ".git")).isDirectory();
9140
9187
  } catch {
9141
9188
  return false;
9142
9189
  }
@@ -9186,8 +9233,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9186
9233
  const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
9187
9234
  let devpodId = target.devpodId;
9188
9235
  if (target.kind === "linked") {
9189
- const overlayPath = import_node_path22.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
9190
- if (!import_node_fs22.default.existsSync(overlayPath)) {
9236
+ const overlayPath = import_node_path23.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
9237
+ if (!import_node_fs23.default.existsSync(overlayPath)) {
9191
9238
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
9192
9239
  }
9193
9240
  }
@@ -9254,7 +9301,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9254
9301
  }
9255
9302
  const apps = proxyAppsFromConfig(runtime.config);
9256
9303
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
9257
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path22.default.basename(repoPath)) ?? "app";
9304
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path23.default.basename(repoPath)) ?? "app";
9258
9305
  for (const [index, app] of apps.entries()) {
9259
9306
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
9260
9307
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -9698,13 +9745,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9698
9745
  }
9699
9746
  });
9700
9747
  }
9701
- var import_node_child_process19, import_node_fs22, import_node_path22, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
9748
+ var import_node_child_process19, import_node_fs23, import_node_path23, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
9702
9749
  var init_workspace_ensure = __esm({
9703
9750
  "src/core/workspace-ensure.ts"() {
9704
9751
  "use strict";
9705
9752
  import_node_child_process19 = require("child_process");
9706
- import_node_fs22 = __toESM(require("fs"));
9707
- import_node_path22 = __toESM(require("path"));
9753
+ import_node_fs23 = __toESM(require("fs"));
9754
+ import_node_path23 = __toESM(require("path"));
9708
9755
  init_devcontainer_profile();
9709
9756
  init_devpod_environment();
9710
9757
  init_devpod_mutation();
@@ -9784,7 +9831,7 @@ function warnMissingWorkspaceOwnership(repoPath) {
9784
9831
  );
9785
9832
  }
9786
9833
  function defaultWorktreePath(mainRepo, ws) {
9787
- return import_node_path23.default.join(mainRepo, "trees", ws);
9834
+ return import_node_path24.default.join(mainRepo, "trees", ws);
9788
9835
  }
9789
9836
  function assertDefaultWorktreeRootIgnored(mainRepo) {
9790
9837
  const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
@@ -9792,12 +9839,12 @@ function assertDefaultWorktreeRootIgnored(mainRepo) {
9792
9839
  });
9793
9840
  if (ignored.status !== 0) {
9794
9841
  throw new Error(
9795
- `Default worktree root '${import_node_path23.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path23.default.join(mainRepo, ".gitignore")}' or use --path.`
9842
+ `Default worktree root '${import_node_path24.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path24.default.join(mainRepo, ".gitignore")}' or use --path.`
9796
9843
  );
9797
9844
  }
9798
9845
  }
9799
9846
  function legacyDefaultWorktreePath(mainRepo, ws) {
9800
- return import_node_path23.default.join(import_node_path23.default.dirname(mainRepo), `${import_node_path23.default.basename(mainRepo)}-${ws}`);
9847
+ return import_node_path24.default.join(import_node_path24.default.dirname(mainRepo), `${import_node_path24.default.basename(mainRepo)}-${ws}`);
9801
9848
  }
9802
9849
  function teardownFallbackPath(mainRepo, workspace) {
9803
9850
  const candidates = [
@@ -9909,7 +9956,7 @@ function assertFullDownPreflight(mainRepo, target) {
9909
9956
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
9910
9957
  throw new Error("Refusing to remove the primary Git checkout.");
9911
9958
  }
9912
- if (!target.worktree || target.worktree.prunable || !import_node_fs23.default.existsSync(target.worktreePath)) return;
9959
+ if (!target.worktree || target.worktree.prunable || !import_node_fs24.default.existsSync(target.worktreePath)) return;
9913
9960
  if (target.worktree.locked) {
9914
9961
  throw new Error(
9915
9962
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
@@ -9936,8 +9983,8 @@ async function workspaceUp(branch, opts = {}) {
9936
9983
  if (!ws) {
9937
9984
  throw new Error(`Branch '${branch}' does not yield a valid workspace token.`);
9938
9985
  }
9939
- const worktreePath = opts.path ? import_node_path23.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
9940
- if (import_node_fs23.default.existsSync(worktreePath)) {
9986
+ const worktreePath = opts.path ? import_node_path24.default.resolve(opts.path) : defaultWorktreePath(mainRepo, ws);
9987
+ if (import_node_fs24.default.existsSync(worktreePath)) {
9941
9988
  const registered = listGitWorktrees(mainRepo).find(
9942
9989
  (worktree) => sameWorkspacePath(worktree.path, worktreePath)
9943
9990
  );
@@ -10062,7 +10109,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
10062
10109
  opts.quiet
10063
10110
  );
10064
10111
  if (removeWorktree) {
10065
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath)) {
10112
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs24.default.existsSync(resolved.worktreePath)) {
10066
10113
  const rm = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
10067
10114
  encoding: "utf-8"
10068
10115
  });
@@ -10081,7 +10128,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
10081
10128
  }
10082
10129
  return result;
10083
10130
  };
10084
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs23.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
10131
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs24.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
10085
10132
  }
10086
10133
  async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
10087
10134
  const mainRepo = resolveRepoPath(opts.repoPath);
@@ -10116,13 +10163,13 @@ async function workspaceStop(target, opts = {}) {
10116
10163
  async function workspaceDown(target, opts = {}) {
10117
10164
  return runWorkspaceLifecycle("down", target, opts);
10118
10165
  }
10119
- var import_node_child_process20, import_node_fs23, import_node_path23;
10166
+ var import_node_child_process20, import_node_fs24, import_node_path24;
10120
10167
  var init_workspace_lifecycle = __esm({
10121
10168
  "src/core/workspace-lifecycle.ts"() {
10122
10169
  "use strict";
10123
10170
  import_node_child_process20 = require("child_process");
10124
- import_node_fs23 = __toESM(require("fs"));
10125
- import_node_path23 = __toESM(require("path"));
10171
+ import_node_fs24 = __toESM(require("fs"));
10172
+ import_node_path24 = __toESM(require("path"));
10126
10173
  init_devpod_mutation();
10127
10174
  init_devpod_workspaces();
10128
10175
  init_repo_config();
@@ -10690,17 +10737,17 @@ function asRecord3(value) {
10690
10737
  return value;
10691
10738
  }
10692
10739
  function readJson(filePath) {
10693
- if (!import_node_fs24.default.existsSync(filePath)) {
10740
+ if (!import_node_fs25.default.existsSync(filePath)) {
10694
10741
  return void 0;
10695
10742
  }
10696
10743
  try {
10697
- return JSON.parse(import_node_fs24.default.readFileSync(filePath, "utf-8"));
10744
+ return JSON.parse(import_node_fs25.default.readFileSync(filePath, "utf-8"));
10698
10745
  } catch {
10699
10746
  return void 0;
10700
10747
  }
10701
10748
  }
10702
10749
  function relative(repoPath, filePath) {
10703
- return import_node_path24.default.relative(repoPath, filePath) || ".";
10750
+ return import_node_path25.default.relative(repoPath, filePath) || ".";
10704
10751
  }
10705
10752
  function redactEnvAssignments(value) {
10706
10753
  return value.replace(
@@ -10741,7 +10788,7 @@ function inspectPackageManager(repoPath, pkg) {
10741
10788
  ["bun.lock", "bun"]
10742
10789
  ];
10743
10790
  for (const [fileName, name] of lockfiles) {
10744
- if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
10791
+ if (import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName))) {
10745
10792
  return { name, source: fileName };
10746
10793
  }
10747
10794
  }
@@ -10756,9 +10803,9 @@ function inspectNode(repoPath, pkg) {
10756
10803
  if (typeof engines?.node === "string") {
10757
10804
  return { version: engines.node, source: "package.json:engines.node" };
10758
10805
  }
10759
- const nvmrc = import_node_path24.default.join(repoPath, ".nvmrc");
10760
- if (import_node_fs24.default.existsSync(nvmrc)) {
10761
- const version = import_node_fs24.default.readFileSync(nvmrc, "utf-8").trim();
10806
+ const nvmrc = import_node_path25.default.join(repoPath, ".nvmrc");
10807
+ if (import_node_fs25.default.existsSync(nvmrc)) {
10808
+ const version = import_node_fs25.default.readFileSync(nvmrc, "utf-8").trim();
10762
10809
  return { version, source: ".nvmrc" };
10763
10810
  }
10764
10811
  return void 0;
@@ -10819,7 +10866,7 @@ function configuredComposeFiles(repoPath) {
10819
10866
  const files = config.apps.filter(
10820
10867
  (app) => app.runtime === "docker"
10821
10868
  ).flatMap((app) => app.docker.composeFiles).filter(
10822
- (fileName) => !import_node_path24.default.isAbsolute(fileName) && !import_node_path24.default.normalize(fileName).startsWith("..")
10869
+ (fileName) => !import_node_path25.default.isAbsolute(fileName) && !import_node_path25.default.normalize(fileName).startsWith("..")
10823
10870
  );
10824
10871
  return Array.from(new Set(files));
10825
10872
  } catch {
@@ -10837,7 +10884,7 @@ function composeFiles(repoPath) {
10837
10884
  ...configuredComposeFiles(repoPath)
10838
10885
  ];
10839
10886
  return Array.from(new Set(candidates)).filter(
10840
- (fileName) => import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))
10887
+ (fileName) => import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName))
10841
10888
  );
10842
10889
  }
10843
10890
  function stringArray2(value) {
@@ -10875,7 +10922,7 @@ function inspectServices(repoPath) {
10875
10922
  const services = [];
10876
10923
  for (const fileName of composeFiles(repoPath)) {
10877
10924
  try {
10878
- const parsed = import_yaml6.default.parse(import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8"));
10925
+ const parsed = import_yaml6.default.parse(import_node_fs25.default.readFileSync(import_node_path25.default.join(repoPath, fileName), "utf-8"));
10879
10926
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
10880
10927
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
10881
10928
  const service = asRecord3(value);
@@ -10910,8 +10957,8 @@ function inspectServices(repoPath) {
10910
10957
  return services;
10911
10958
  }
10912
10959
  function inspectEnvFiles(repoPath) {
10913
- const files = import_node_fs24.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
10914
- const content = import_node_fs24.default.readFileSync(import_node_path24.default.join(repoPath, fileName), "utf-8");
10960
+ const files = import_node_fs25.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
10961
+ const content = import_node_fs25.default.readFileSync(import_node_path25.default.join(repoPath, fileName), "utf-8");
10915
10962
  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();
10916
10963
  return { path: fileName, names };
10917
10964
  });
@@ -10925,18 +10972,18 @@ function inspectEnvFiles(repoPath) {
10925
10972
  };
10926
10973
  }
10927
10974
  function inspectDevcontainer(repoPath) {
10928
- const dir = import_node_path24.default.join(repoPath, ".devcontainer");
10929
- if (!import_node_fs24.default.existsSync(dir)) {
10975
+ const dir = import_node_path25.default.join(repoPath, ".devcontainer");
10976
+ if (!import_node_fs25.default.existsSync(dir)) {
10930
10977
  return { exists: false, files: [] };
10931
10978
  }
10932
10979
  return {
10933
10980
  exists: true,
10934
- files: import_node_fs24.default.readdirSync(dir).filter((fileName) => import_node_fs24.default.statSync(import_node_path24.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
10981
+ files: import_node_fs25.default.readdirSync(dir).filter((fileName) => import_node_fs25.default.statSync(import_node_path25.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
10935
10982
  };
10936
10983
  }
10937
10984
  function inspectDevrouter(repoPath) {
10938
10985
  const configPath = getRepoConfigPath(repoPath);
10939
- if (!import_node_fs24.default.existsSync(configPath)) {
10986
+ if (!import_node_fs25.default.existsSync(configPath)) {
10940
10987
  return {
10941
10988
  exists: false,
10942
10989
  configPath,
@@ -10980,15 +11027,15 @@ function inspectAgentGuidance(repoPath) {
10980
11027
  ["AGENTS.md", "agents"],
10981
11028
  ["CLAUDE.md", "claude"]
10982
11029
  ]) {
10983
- if (import_node_fs24.default.existsSync(import_node_path24.default.join(repoPath, fileName))) {
11030
+ if (import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName))) {
10984
11031
  results.push({ path: fileName, kind });
10985
11032
  }
10986
11033
  }
10987
- const skillsDir = import_node_path24.default.join(repoPath, ".agents", "skills");
10988
- if (import_node_fs24.default.existsSync(skillsDir)) {
10989
- for (const name of import_node_fs24.default.readdirSync(skillsDir).sort()) {
10990
- const skillPath = import_node_path24.default.join(skillsDir, name, "SKILL.md");
10991
- if (import_node_fs24.default.existsSync(skillPath)) {
11034
+ const skillsDir = import_node_path25.default.join(repoPath, ".agents", "skills");
11035
+ if (import_node_fs25.default.existsSync(skillsDir)) {
11036
+ for (const name of import_node_fs25.default.readdirSync(skillsDir).sort()) {
11037
+ const skillPath = import_node_path25.default.join(skillsDir, name, "SKILL.md");
11038
+ if (import_node_fs25.default.existsSync(skillPath)) {
10992
11039
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
10993
11040
  }
10994
11041
  }
@@ -11031,7 +11078,7 @@ function buildIssues(report) {
11031
11078
  }
11032
11079
  function inspectRepo(options = {}) {
11033
11080
  const repoPath = resolveRepoPath(options.repo);
11034
- const pkg = readJson(import_node_path24.default.join(repoPath, "package.json"));
11081
+ const pkg = readJson(import_node_path25.default.join(repoPath, "package.json"));
11035
11082
  const scripts = inspectScripts(pkg);
11036
11083
  const reportWithoutIssues = {
11037
11084
  repoPath,
@@ -11050,12 +11097,12 @@ function inspectRepo(options = {}) {
11050
11097
  issues: buildIssues(reportWithoutIssues)
11051
11098
  };
11052
11099
  }
11053
- var import_node_fs24, import_node_path24, import_yaml6;
11100
+ var import_node_fs25, import_node_path25, import_yaml6;
11054
11101
  var init_repo_inspect = __esm({
11055
11102
  "src/core/repo-inspect.ts"() {
11056
11103
  "use strict";
11057
- import_node_fs24 = __toESM(require("fs"));
11058
- import_node_path24 = __toESM(require("path"));
11104
+ import_node_fs25 = __toESM(require("fs"));
11105
+ import_node_path25 = __toESM(require("path"));
11059
11106
  import_yaml6 = __toESM(require("yaml"));
11060
11107
  init_repo_config();
11061
11108
  }
@@ -11160,7 +11207,7 @@ function requiredFileChecks(repoPath) {
11160
11207
  ".devcontainer/docker-compose.yml",
11161
11208
  ".devrouter.yml"
11162
11209
  ];
11163
- const missing = required.filter((fileName) => !import_node_fs25.default.existsSync(import_node_path25.default.join(repoPath, fileName)));
11210
+ const missing = required.filter((fileName) => !import_node_fs26.default.existsSync(import_node_path26.default.join(repoPath, fileName)));
11164
11211
  return {
11165
11212
  id: "repo.devcontainer.verify-files",
11166
11213
  level: missing.length === 0 ? "ok" : "error",
@@ -11379,12 +11426,12 @@ async function verifyDevcontainer(options = {}) {
11379
11426
  nextSteps: collectNextSteps3(checks)
11380
11427
  };
11381
11428
  }
11382
- var import_node_fs25, import_node_path25;
11429
+ var import_node_fs26, import_node_path26;
11383
11430
  var init_devcontainer_verify = __esm({
11384
11431
  "src/core/devcontainer-verify.ts"() {
11385
11432
  "use strict";
11386
- import_node_fs25 = __toESM(require("fs"));
11387
- import_node_path25 = __toESM(require("path"));
11433
+ import_node_fs26 = __toESM(require("fs"));
11434
+ import_node_path26 = __toESM(require("path"));
11388
11435
  init_capabilities();
11389
11436
  init_doctor();
11390
11437
  init_host_routes();
@@ -11530,6 +11577,7 @@ function renderDevcontainerJson(projectName) {
11530
11577
  "service": "app",
11531
11578
  "workspaceFolder": "/workspaces/${projectName}",
11532
11579
  "postCreateCommand": "bash .devcontainer/post-create.sh",
11580
+ "waitFor": "postCreateCommand",
11533
11581
  "customizations": {
11534
11582
  "devrouter": {
11535
11583
  "managed": "${MANAGED_MARKER2}"
@@ -11680,7 +11728,7 @@ function packageManagerIssues(repo) {
11680
11728
  }
11681
11729
  function plannedFiles(repoPath, version) {
11682
11730
  const repo = inspectRepo({ repo: repoPath });
11683
- const projectName = sanitizeProjectName(import_node_path26.default.basename(repo.repoPath));
11731
+ const projectName = sanitizeProjectName(import_node_path27.default.basename(repo.repoPath));
11684
11732
  const nodeMajor = majorVersion(repo.node?.version, "24");
11685
11733
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
11686
11734
  const port = inferPort2(repo);
@@ -11725,8 +11773,8 @@ function plannedFiles(repoPath, version) {
11725
11773
  };
11726
11774
  }
11727
11775
  function classifyFile(repoPath, file) {
11728
- const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
11729
- if (!import_node_fs26.default.existsSync(absolutePath)) {
11776
+ const absolutePath = import_node_path27.default.join(repoPath, file.relativePath);
11777
+ if (!import_node_fs27.default.existsSync(absolutePath)) {
11730
11778
  return {
11731
11779
  path: file.relativePath,
11732
11780
  action: "create",
@@ -11734,7 +11782,7 @@ function classifyFile(repoPath, file) {
11734
11782
  bytes: Buffer.byteLength(file.content)
11735
11783
  };
11736
11784
  }
11737
- const current = import_node_fs26.default.readFileSync(absolutePath, "utf-8");
11785
+ const current = import_node_fs27.default.readFileSync(absolutePath, "utf-8");
11738
11786
  if (!current.includes(MANAGED_MARKER2)) {
11739
11787
  return {
11740
11788
  path: file.relativePath,
@@ -11791,11 +11839,11 @@ function buildPlan(repoPath, dryRun, version) {
11791
11839
  };
11792
11840
  }
11793
11841
  function writeFile(repoPath, file) {
11794
- const absolutePath = import_node_path26.default.join(repoPath, file.relativePath);
11795
- import_node_fs26.default.mkdirSync(import_node_path26.default.dirname(absolutePath), { recursive: true });
11796
- import_node_fs26.default.writeFileSync(absolutePath, file.content, "utf-8");
11842
+ const absolutePath = import_node_path27.default.join(repoPath, file.relativePath);
11843
+ import_node_fs27.default.mkdirSync(import_node_path27.default.dirname(absolutePath), { recursive: true });
11844
+ import_node_fs27.default.writeFileSync(absolutePath, file.content, "utf-8");
11797
11845
  if (file.executable) {
11798
- import_node_fs26.default.chmodSync(absolutePath, 493);
11846
+ import_node_fs27.default.chmodSync(absolutePath, 493);
11799
11847
  }
11800
11848
  }
11801
11849
  function writeDevcontainer(options = {}) {
@@ -11833,12 +11881,12 @@ function writeDevcontainer(options = {}) {
11833
11881
  nextSteps: postWriteNextSteps(repoPath)
11834
11882
  };
11835
11883
  }
11836
- var import_node_fs26, import_node_path26, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
11884
+ var import_node_fs27, import_node_path27, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
11837
11885
  var init_devcontainer_write = __esm({
11838
11886
  "src/core/devcontainer-write.ts"() {
11839
11887
  "use strict";
11840
- import_node_fs26 = __toESM(require("fs"));
11841
- import_node_path26 = __toESM(require("path"));
11888
+ import_node_fs27 = __toESM(require("fs"));
11889
+ import_node_path27 = __toESM(require("path"));
11842
11890
  init_repo_config();
11843
11891
  init_repo_inspect();
11844
11892
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -12229,7 +12277,7 @@ function sanitizeRouterId(value) {
12229
12277
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
12230
12278
  }
12231
12279
  function repoHash(repoPath) {
12232
- return (0, import_node_crypto8.createHash)("sha1").update(import_node_path27.default.resolve(repoPath)).digest("hex").slice(0, 12);
12280
+ return (0, import_node_crypto8.createHash)("sha1").update(import_node_path28.default.resolve(repoPath)).digest("hex").slice(0, 12);
12233
12281
  }
12234
12282
  function asDockerApp(app) {
12235
12283
  return app.runtime === "docker";
@@ -12308,11 +12356,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
12308
12356
  if (dockerApps.length === 0) {
12309
12357
  throw new Error("No docker apps selected to prepare compose overlay.");
12310
12358
  }
12311
- const cachePath = import_node_path27.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
12312
- import_node_fs27.default.mkdirSync(cachePath, { recursive: true });
12313
- const overlayPath = import_node_path27.default.join(cachePath, "compose.devrouter.yml");
12359
+ const cachePath = import_node_path28.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
12360
+ import_node_fs28.default.mkdirSync(cachePath, { recursive: true });
12361
+ const overlayPath = import_node_path28.default.join(cachePath, "compose.devrouter.yml");
12314
12362
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
12315
- import_node_fs27.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
12363
+ import_node_fs28.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
12316
12364
  return {
12317
12365
  overlayPath,
12318
12366
  composeFiles: ensureComposeFiles(dockerApps),
@@ -12432,14 +12480,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
12432
12480
  const port = Number(match[1]);
12433
12481
  return Number.isInteger(port) && port > 0 ? port : void 0;
12434
12482
  }
12435
- var import_node_child_process25, import_node_crypto8, import_node_fs27, import_node_path27, import_yaml7;
12483
+ var import_node_child_process25, import_node_crypto8, import_node_fs28, import_node_path28, import_yaml7;
12436
12484
  var init_docker_run = __esm({
12437
12485
  "src/core/docker-run.ts"() {
12438
12486
  "use strict";
12439
12487
  import_node_child_process25 = require("child_process");
12440
12488
  import_node_crypto8 = require("crypto");
12441
- import_node_fs27 = __toESM(require("fs"));
12442
- import_node_path27 = __toESM(require("path"));
12489
+ import_node_fs28 = __toESM(require("fs"));
12490
+ import_node_path28 = __toESM(require("path"));
12443
12491
  import_yaml7 = __toESM(require("yaml"));
12444
12492
  init_docker_error_guidance();
12445
12493
  init_paths();
@@ -13261,13 +13309,13 @@ function measureWorktreeConsumption(worktreePath, options) {
13261
13309
  const startedAt = Date.now();
13262
13310
  let rootStat;
13263
13311
  try {
13264
- rootStat = import_node_fs28.default.lstatSync(worktreePath);
13312
+ rootStat = import_node_fs29.default.lstatSync(worktreePath);
13265
13313
  } catch (error) {
13266
13314
  return { status: "unknown", reason: describeError(error, worktreePath) };
13267
13315
  }
13268
13316
  let rootEntries;
13269
13317
  try {
13270
- rootEntries = import_node_fs28.default.readdirSync(worktreePath, { withFileTypes: true });
13318
+ rootEntries = import_node_fs29.default.readdirSync(worktreePath, { withFileTypes: true });
13271
13319
  } catch (error) {
13272
13320
  return { status: "unknown", reason: describeError(error, worktreePath) };
13273
13321
  }
@@ -13293,10 +13341,10 @@ function measureWorktreeConsumption(worktreePath, options) {
13293
13341
  timedOut = true;
13294
13342
  break;
13295
13343
  }
13296
- const entryPath = import_node_path28.default.join(dirPath, entry.name);
13344
+ const entryPath = import_node_path29.default.join(dirPath, entry.name);
13297
13345
  let entryStat;
13298
13346
  try {
13299
- entryStat = import_node_fs28.default.lstatSync(entryPath);
13347
+ entryStat = import_node_fs29.default.lstatSync(entryPath);
13300
13348
  } catch (error) {
13301
13349
  if (error?.code === "ENOENT") continue;
13302
13350
  unreadableReason = describeIncompleteWalk(error);
@@ -13306,7 +13354,7 @@ function measureWorktreeConsumption(worktreePath, options) {
13306
13354
  if (!entryStat.isDirectory()) continue;
13307
13355
  let childEntries;
13308
13356
  try {
13309
- childEntries = import_node_fs28.default.readdirSync(entryPath, { withFileTypes: true });
13357
+ childEntries = import_node_fs29.default.readdirSync(entryPath, { withFileTypes: true });
13310
13358
  } catch (error) {
13311
13359
  unreadableReason = describeIncompleteWalk(error);
13312
13360
  break;
@@ -13370,12 +13418,12 @@ function describeError(error, worktreePath) {
13370
13418
  }
13371
13419
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
13372
13420
  }
13373
- var import_node_fs28, import_node_path28, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
13421
+ var import_node_fs29, import_node_path29, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
13374
13422
  var init_workspace_consumption = __esm({
13375
13423
  "src/core/workspace-consumption.ts"() {
13376
13424
  "use strict";
13377
- import_node_fs28 = __toESM(require("fs"));
13378
- import_node_path28 = __toESM(require("path"));
13425
+ import_node_fs29 = __toESM(require("fs"));
13426
+ import_node_path29 = __toESM(require("path"));
13379
13427
  init_devpod_environment();
13380
13428
  DEFAULT_DEADLINE_MS = 1e4;
13381
13429
  BLOCK_SIZE_BYTES = 512;
@@ -13476,7 +13524,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
13476
13524
  }
13477
13525
  function readGitSnapshot(worktree, commandRunner) {
13478
13526
  const comparablePath = comparableWorkspacePath(worktree.path);
13479
- if (worktree.prunable || !import_node_fs29.default.existsSync(comparablePath)) {
13527
+ if (worktree.prunable || !import_node_fs30.default.existsSync(comparablePath)) {
13480
13528
  return { worktree, checkout: "missing", head: null, committerDate: null };
13481
13529
  }
13482
13530
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -14001,7 +14049,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
14001
14049
  containers = measureContainersFn(worktreePaths);
14002
14050
  } catch (error) {
14003
14051
  const reason = `container measurement failed: ${describeCause(error)}`;
14004
- containers = new Map(worktreePaths.map((path28) => [path28, unknownContainers(reason)]));
14052
+ containers = new Map(worktreePaths.map((path29) => [path29, unknownContainers(reason)]));
14005
14053
  }
14006
14054
  }
14007
14055
  const rows = records.map((record) => {
@@ -14050,12 +14098,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
14050
14098
  workspaces: rows
14051
14099
  };
14052
14100
  }
14053
- var import_node_child_process27, import_node_fs29, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
14101
+ var import_node_child_process27, import_node_fs30, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
14054
14102
  var init_workspace_cleanup = __esm({
14055
14103
  "src/core/workspace-cleanup.ts"() {
14056
14104
  "use strict";
14057
14105
  import_node_child_process27 = require("child_process");
14058
- import_node_fs29 = __toESM(require("fs"));
14106
+ import_node_fs30 = __toESM(require("fs"));
14059
14107
  init_devpod_workspaces();
14060
14108
  init_host_routes();
14061
14109
  init_repo_config();
@@ -14232,7 +14280,7 @@ var init_version = __esm({
14232
14280
 
14233
14281
  // src/cli.ts
14234
14282
  var import_commander = require("commander");
14235
- var CLI_VERSION = true ? "0.0.42" : "0.0.0-dev";
14283
+ var CLI_VERSION = true ? "0.0.43" : "0.0.0-dev";
14236
14284
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
14237
14285
  function withErrorHandling(action2) {
14238
14286
  return async (...args) => {