@devrouter/cli 0.5.6 → 0.6.1

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.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/core/devpod-exec.ts
26
26
  var import_node_child_process33 = require("child_process");
27
- var import_node_crypto24 = require("crypto");
27
+ var import_node_crypto25 = require("crypto");
28
28
 
29
29
  // src/core/devpod-environment.ts
30
30
  var import_node_child_process3 = require("child_process");
@@ -2695,6 +2695,72 @@ function assertReadableComposeFile(file) {
2695
2695
  throw new Error("Managed container Compose configuration contains an unreadable file.");
2696
2696
  }
2697
2697
  }
2698
+ function resolveManagedServiceNetworkNamespace(options) {
2699
+ const { plan, containers, container, serviceConfig, env } = options;
2700
+ const service = container.labels["com.docker.compose.service"];
2701
+ const targetService = String(serviceConfig.network_mode).slice("service:".length);
2702
+ const target = containers.filter(
2703
+ (item) => item.labels["com.docker.compose.service"] === targetService
2704
+ );
2705
+ const primary = containers.filter(
2706
+ (item) => item.labels["com.docker.compose.service"] === plan.primaryService
2707
+ );
2708
+ const repoPath = import_node_path12.default.dirname(plan.composeDirectory);
2709
+ const owned = (item) => /^[a-f0-9]{64}$/.test(item.id) && containers.filter((candidate) => candidate.id === item.id).length === 1 && containers.filter(
2710
+ (candidate) => candidate.labels["com.docker.compose.service"] === item.labels["com.docker.compose.service"]
2711
+ ).length === 1 && plan.desiredServices.includes(item.labels["com.docker.compose.service"] ?? "") && hasExactComposeIdentity(item, {
2712
+ repoPath,
2713
+ service: item.labels["com.docker.compose.service"] ?? "",
2714
+ composeProject: container.labels["com.docker.compose.project"],
2715
+ composeFiles: plan.composeFiles
2716
+ });
2717
+ if (targetService === service || target.length !== 1 || primary.length !== 1 || !owned(container) || !owned(target[0]) || !owned(primary[0]) || primary[0].mounts.filter(
2718
+ (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
2719
+ ).length !== 1) {
2720
+ throw new Error("Managed service network namespace ownership is unavailable.");
2721
+ }
2722
+ const namespace = `container:${target[0].id}`;
2723
+ const ids2 = [container.id, target[0].id];
2724
+ const inspect = () => {
2725
+ const result = (0, import_node_child_process10.spawnSync)(
2726
+ "docker",
2727
+ [
2728
+ "inspect",
2729
+ "--type",
2730
+ "container",
2731
+ "--format",
2732
+ '{"id":{{json .Id}},"networkMode":{{json .HostConfig.NetworkMode}}}',
2733
+ ...ids2
2734
+ ],
2735
+ {
2736
+ env,
2737
+ encoding: "utf-8",
2738
+ stdio: ["ignore", "pipe", "ignore"],
2739
+ timeout: COMPOSE_HASH_TIMEOUT_MS,
2740
+ maxBuffer: 1024 * 1024
2741
+ }
2742
+ );
2743
+ if (result.status !== 0 || result.error || !result.stdout?.trim()) {
2744
+ throw new Error("Managed service network namespace inspection failed.");
2745
+ }
2746
+ const rows2 = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
2747
+ if (rows2.length !== ids2.length || ids2.some(
2748
+ (id5) => rows2.filter(
2749
+ (row) => isObject2(row) && row.id === id5 && typeof row.networkMode === "string"
2750
+ ).length !== 1
2751
+ ) || !rows2.some((row) => isObject2(row) && row.id === container.id && row.networkMode === namespace)) {
2752
+ throw new Error("Managed service network namespace identity changed.");
2753
+ }
2754
+ return ids2.map((id5) => rows2.find((row) => isObject2(row) && row.id === id5));
2755
+ };
2756
+ const before = JSON.stringify(inspect());
2757
+ serviceConfig.network_mode = namespace;
2758
+ return () => {
2759
+ if (JSON.stringify(inspect()) !== before) {
2760
+ throw new Error("Managed service network namespace changed during configuration inspection.");
2761
+ }
2762
+ };
2763
+ }
2698
2764
  function assertManagedContainerConfigUnchanged(options) {
2699
2765
  if (options.containers.length === 0) {
2700
2766
  throw new Error("Managed container configuration cannot be verified without containers.");
@@ -2730,6 +2796,10 @@ function assertManagedContainerConfigUnchanged(options) {
2730
2796
  workingDirectory
2731
2797
  ];
2732
2798
  const env = managedComposeEnvironment(options.workspace);
2799
+ if (options.endpoint) {
2800
+ env.DOCKER_HOST = options.endpoint;
2801
+ delete env.DOCKER_CONTEXT;
2802
+ }
2733
2803
  const rendered = (0, import_node_child_process10.spawnSync)(
2734
2804
  "docker",
2735
2805
  [...composeArgs, ...fileArgs, "config", "--format", "json"],
@@ -2745,19 +2815,38 @@ function assertManagedContainerConfigUnchanged(options) {
2745
2815
  if (rendered.status !== 0 || rendered.error || !rendered.stdout?.trim()) {
2746
2816
  throw new Error("Compose configuration rendering failed.");
2747
2817
  }
2748
- return (0, import_node_child_process10.spawnSync)(
2818
+ const hash = (input2) => (0, import_node_child_process10.spawnSync)(
2749
2819
  "docker",
2750
2820
  [...composeArgs, "-f", "-", "config", "--no-interpolate", "--hash", service],
2751
2821
  {
2752
2822
  cwd: workingDirectory,
2753
2823
  encoding: "utf-8",
2754
2824
  env,
2755
- input: rendered.stdout.replaceAll("$$", "$"),
2825
+ input: input2,
2756
2826
  stdio: ["pipe", "pipe", "ignore"],
2757
2827
  timeout: COMPOSE_HASH_TIMEOUT_MS,
2758
2828
  maxBuffer: 1024 * 1024
2759
2829
  }
2760
2830
  );
2831
+ const input = rendered.stdout.replaceAll("$$", "$");
2832
+ const result2 = hash(input);
2833
+ const match2 = typeof result2.stdout === "string" ? /^(\S+)\s+([a-f0-9]{64})$/.exec(result2.stdout.trim()) : null;
2834
+ if (result2.status !== 0 || result2.error || !match2 || match2[1] !== service || match2[2] === expectedHash)
2835
+ return result2;
2836
+ const model = JSON.parse(input);
2837
+ const serviceConfig = isObject2(model) && isObject2(model.services) ? model.services[service] : void 0;
2838
+ if (!isObject2(serviceConfig) || typeof serviceConfig.network_mode !== "string" || !serviceConfig.network_mode.startsWith("service:"))
2839
+ return result2;
2840
+ const revalidate = resolveManagedServiceNetworkNamespace({
2841
+ plan: options.plan,
2842
+ containers: options.containers,
2843
+ container,
2844
+ serviceConfig,
2845
+ env
2846
+ });
2847
+ const resolved = hash(JSON.stringify(model));
2848
+ revalidate();
2849
+ return resolved;
2761
2850
  } catch {
2762
2851
  throw new Error(`Could not verify managed Compose configuration for service '${service}'.`);
2763
2852
  }
@@ -4083,11 +4172,20 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4083
4172
  );
4084
4173
  if (currentOwner.status !== "owned") throw new Error("Stop recovery owner is unavailable.");
4085
4174
  proveLocalDockerSelection(currentOwner.workspace);
4086
- assertManagedStopContainersAbsent(
4087
- baseline.endpoint,
4088
- baseline.containers.map((container) => container.id)
4089
- );
4090
4175
  const containers = inspectManagedStopContainers(baseline.project, baseline.endpoint);
4176
+ const missingIds = [];
4177
+ for (const recorded of baseline.containers) {
4178
+ const retained = containers.find((container) => container.id === recorded.id);
4179
+ if (!retained) {
4180
+ missingIds.push(recorded.id);
4181
+ } else if (!(0, import_node_util3.isDeepStrictEqual)(
4182
+ { ...recorded, mounts: orderedMounts(recorded.mounts) },
4183
+ identity(retained)
4184
+ )) {
4185
+ throw new Error("Stop recovery retained container identity changed.");
4186
+ }
4187
+ }
4188
+ if (missingIds.length > 0) assertManagedStopContainersAbsent(baseline.endpoint, missingIds);
4091
4189
  if (!(0, import_node_util3.isDeepStrictEqual)(
4092
4190
  containers.map((container) => container.labels["com.docker.compose.service"]).sort(),
4093
4191
  [...plan.desiredServices].sort()
@@ -4107,6 +4205,7 @@ function recoverManagedStopBaseline(state, readPlan, beforePersist) {
4107
4205
  assertManagedContainerConfigUnchanged({
4108
4206
  plan,
4109
4207
  containers,
4208
+ endpoint: baseline.endpoint,
4110
4209
  workspace: state.workspace ? {
4111
4210
  token: state.workspace,
4112
4211
  gitCommonDir: resolveGitCommonDir(state.repoPath)
@@ -5490,7 +5589,7 @@ function retain(session, previous) {
5490
5589
 
5491
5590
  // src/core/reliability-operation-store.ts
5492
5591
  function installedCliVersion() {
5493
- return true ? "0.5.6" : "0.0.0-dev";
5592
+ return true ? "0.6.1" : "0.0.0-dev";
5494
5593
  }
5495
5594
  function compareCliVersions(left, right) {
5496
5595
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -7578,6 +7677,9 @@ function resolveRepoPath(repoPath) {
7578
7677
  function getRepoConfigPath(repoPath) {
7579
7678
  return import_node_path23.default.join(resolveRepoPath(repoPath), CONFIG_FILE_NAME);
7580
7679
  }
7680
+ function parseRepoConfigSource(source, configPath) {
7681
+ return parseConfig(import_yaml3.default.parse(source) ?? {}, configPath);
7682
+ }
7581
7683
  function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.readFileSync(file, "utf-8")) {
7582
7684
  const resolvedRepoPath = resolveRepoPath(repoPath);
7583
7685
  const configPath = getRepoConfigPath(resolvedRepoPath);
@@ -7587,11 +7689,10 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs19.default.rea
7587
7689
  );
7588
7690
  }
7589
7691
  const raw = read2(configPath);
7590
- const parsed = import_yaml3.default.parse(raw);
7591
- const config = parseConfig(parsed ?? {}, configPath);
7692
+ const config = parseRepoConfigSource(raw, configPath);
7592
7693
  const requiredVersion = config.devrouter?.version;
7593
7694
  if (requiredVersion && !hasWarnedVersionMismatch) {
7594
- const cliVersion = true ? "0.5.6" : "0.0.0-dev";
7695
+ const cliVersion = true ? "0.6.1" : "0.0.0-dev";
7595
7696
  if (cliVersion !== "0.0.0-dev" && compareSemver2(requiredVersion, cliVersion) > 0) {
7596
7697
  hasWarnedVersionMismatch = true;
7597
7698
  process.stderr.write(
@@ -8634,6 +8735,7 @@ function stopInitialManagedDevsyWorkspace(repoPath, devsyId) {
8634
8735
  assertManagedContainerConfigUnchanged({
8635
8736
  plan,
8636
8737
  containers: population,
8738
+ endpoint,
8637
8739
  workspace: workspaceEnv
8638
8740
  });
8639
8741
  if (!(0, import_node_util5.isDeepStrictEqual)(readAuthority(), authority) || !(0, import_node_util5.isDeepStrictEqual)(registration2(), owner) || resolveManagedStopEndpoint() !== endpoint || inspectManagedStopDaemon(endpoint) !== daemon)
@@ -10648,9 +10750,9 @@ var ExecutionOutcomeError = class extends Error {
10648
10750
  };
10649
10751
 
10650
10752
  // src/core/reliability-lifecycle.ts
10651
- var import_node_crypto23 = require("crypto");
10652
- var import_node_path41 = __toESM(require("path"));
10653
- var import_node_util7 = require("util");
10753
+ var import_node_crypto24 = require("crypto");
10754
+ var import_node_path42 = __toESM(require("path"));
10755
+ var import_node_util8 = require("util");
10654
10756
 
10655
10757
  // src/core/capacity-policy.ts
10656
10758
  var import_node_fs27 = __toESM(require("fs"));
@@ -12481,9 +12583,15 @@ function stepReliability(state, event, nowMs) {
12481
12583
 
12482
12584
  // src/core/reliability-worker.ts
12483
12585
  var import_node_child_process21 = require("child_process");
12586
+ var import_node_crypto20 = require("crypto");
12587
+ var import_node_fs31 = __toESM(require("fs"));
12588
+ var import_node_path36 = __toESM(require("path"));
12589
+
12590
+ // src/core/lifecycle-diagnostics.ts
12484
12591
  var import_node_crypto19 = require("crypto");
12485
12592
  var import_node_fs30 = __toESM(require("fs"));
12486
12593
  var import_node_path35 = __toESM(require("path"));
12594
+ var import_node_util7 = require("util");
12487
12595
 
12488
12596
  // src/core/lifecycle-progress.ts
12489
12597
  var ROLES = {
@@ -12538,6 +12646,89 @@ function reportLifecycleProgress(phase) {
12538
12646
  sender?.(phase);
12539
12647
  }
12540
12648
 
12649
+ // src/core/lifecycle-diagnostics.ts
12650
+ var MAX_TAIL_BYTES = 32768;
12651
+ var MAX_RECEIPT_BYTES = 65536;
12652
+ function privateDirectory(directory) {
12653
+ const stat = import_node_fs30.default.lstatSync(directory);
12654
+ return stat.isDirectory() && stat.uid === process.getuid?.() && (stat.mode & 63) === 0;
12655
+ }
12656
+ function lifecycleDiagnosticsPath(identity2) {
12657
+ return import_node_path35.default.join(
12658
+ DEVROUTER_HOME,
12659
+ "lifecycle-diagnostics",
12660
+ `${(0, import_node_crypto19.createHash)("sha256").update(identity2.repoPath).digest("hex")}.json`
12661
+ );
12662
+ }
12663
+ function readReceipt(identity2) {
12664
+ let descriptor;
12665
+ try {
12666
+ const file = lifecycleDiagnosticsPath(identity2);
12667
+ if (!privateDirectory(import_node_path35.default.dirname(file))) return void 0;
12668
+ descriptor = import_node_fs30.default.openSync(
12669
+ file,
12670
+ import_node_fs30.default.constants.O_RDONLY | import_node_fs30.default.constants.O_NOFOLLOW | import_node_fs30.default.constants.O_NONBLOCK
12671
+ );
12672
+ const stat = import_node_fs30.default.fstatSync(descriptor);
12673
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > MAX_RECEIPT_BYTES)
12674
+ return void 0;
12675
+ const buffer = Buffer.alloc(MAX_RECEIPT_BYTES + 1);
12676
+ const size = import_node_fs30.default.readSync(descriptor, buffer, 0, buffer.length, 0);
12677
+ if (size > MAX_RECEIPT_BYTES) return void 0;
12678
+ const receipt = JSON.parse(buffer.subarray(0, size).toString("utf8"));
12679
+ const timestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
12680
+ const optionalTime = (value) => value === null || timestamp(value);
12681
+ if (receipt.version !== 1 || !(0, import_node_util7.isDeepStrictEqual)(receipt.identity, identity2) || typeof receipt.operationId !== "string" || typeof receipt.workerId !== "string" || !timestamp(receipt.startedAtMs) || !timestamp(receipt.updatedAtMs) || receipt.updatedAtMs < receipt.startedAtMs || !(receipt.phase === null || isLifecycleProgressPhase(receipt.phase)) || !optionalTime(receipt.phaseAtMs) || !optionalTime(receipt.lastOutputAtMs) || !optionalTime(receipt.lastProgressAtMs) || !(receipt.buildPackage === null || typeof receipt.buildPackage === "string" && receipt.buildPackage.length <= 128 && /^(?:@[a-zA-Z0-9_.-]+\/)?[a-zA-Z0-9_.-]+$/.test(receipt.buildPackage)) || typeof receipt.cancellationRequested !== "boolean" || typeof receipt.closed !== "boolean" || !Array.isArray(receipt.output) || receipt.output.length > 64)
12682
+ return void 0;
12683
+ let bytes2 = 0;
12684
+ for (const chunk of receipt.output) {
12685
+ if (!chunk || !["stdout", "stderr"].includes(chunk.stream) || typeof chunk.data !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(chunk.data))
12686
+ return void 0;
12687
+ bytes2 += Buffer.byteLength(chunk.data, "base64");
12688
+ }
12689
+ return bytes2 <= MAX_TAIL_BYTES ? receipt : void 0;
12690
+ } catch {
12691
+ return void 0;
12692
+ } finally {
12693
+ if (descriptor !== void 0) import_node_fs30.default.closeSync(descriptor);
12694
+ }
12695
+ }
12696
+ function readLifecycleDiagnostics(identity2, now = Date.now()) {
12697
+ try {
12698
+ const before = readReliabilityOperation(identity2);
12699
+ const receipt = readReceipt(identity2);
12700
+ const after = readReliabilityOperation(identity2);
12701
+ if (!before || !after || !receipt || before.revision !== after.revision || !(0, import_node_util7.isDeepStrictEqual)(before, after) || !(0, import_node_util7.isDeepStrictEqual)(receipt.fence, reliabilityFence(after.state)) || after.state.operation?.id !== receipt.operationId || after.state.operation.kind !== "ensure" || after.worker !== null && after.worker.id !== receipt.workerId || !receipt.closed && !after.worker || now < receipt.updatedAtMs || [receipt.phaseAtMs, receipt.lastOutputAtMs, receipt.lastProgressAtMs].some(
12702
+ (time) => time !== null && (time < receipt.startedAtMs || time > receipt.updatedAtMs)
12703
+ ))
12704
+ return void 0;
12705
+ const age = (time) => time === null ? null : now - time;
12706
+ const metadata = {
12707
+ stage: receipt.phase ?? "unknown",
12708
+ elapsedMs: Math.max(0, (receipt.closed ? receipt.updatedAtMs : now) - receipt.startedAtMs),
12709
+ stageAgeMs: age(receipt.phaseAtMs),
12710
+ outputAgeMs: age(receipt.lastOutputAtMs),
12711
+ progressAgeMs: age(receipt.lastProgressAtMs),
12712
+ evidence: now - receipt.updatedAtMs < 3e4 ? "recent" : "stale",
12713
+ buildPackage: receipt.buildPackage,
12714
+ liveness: "unknown",
12715
+ executionDeadlineAtMs: null,
12716
+ handshakeTimeoutMs: 3e4,
12717
+ cancellationGraceMs: 2e3,
12718
+ cancellationRequested: receipt.cancellationRequested,
12719
+ workerDrained: after.state.operation.drained
12720
+ };
12721
+ return {
12722
+ operationId: receipt.operationId,
12723
+ revision: after.revision,
12724
+ metadata,
12725
+ output: receipt.output
12726
+ };
12727
+ } catch {
12728
+ return void 0;
12729
+ }
12730
+ }
12731
+
12541
12732
  // src/core/reliability-worker.ts
12542
12733
  var DEFAULT_OUTPUT_PAGE_BYTES = 48 * 1024;
12543
12734
  function workerGroupAbsent(pid) {
@@ -12549,11 +12740,11 @@ function workerGroupAbsent(pid) {
12549
12740
  if (process.platform !== "linux") return false;
12550
12741
  let observedZombie = false;
12551
12742
  try {
12552
- for (const entry of import_node_fs30.default.readdirSync("/proc")) {
12743
+ for (const entry of import_node_fs31.default.readdirSync("/proc")) {
12553
12744
  if (!/^\d+$/.test(entry)) continue;
12554
12745
  let stat;
12555
12746
  try {
12556
- stat = import_node_fs30.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12747
+ stat = import_node_fs31.default.readFileSync(`/proc/${entry}/stat`, "utf8");
12557
12748
  } catch (error) {
12558
12749
  if (error.code === "ENOENT") continue;
12559
12750
  return false;
@@ -12574,19 +12765,19 @@ function workerGroupAbsent(pid) {
12574
12765
 
12575
12766
  // src/core/workspace-ensure.ts
12576
12767
  var import_node_child_process31 = require("child_process");
12577
- var import_node_fs35 = __toESM(require("fs"));
12578
- var import_node_path40 = __toESM(require("path"));
12768
+ var import_node_fs36 = __toESM(require("fs"));
12769
+ var import_node_path41 = __toESM(require("path"));
12579
12770
 
12580
12771
  // src/core/devpod-mutation.ts
12581
12772
  var import_node_child_process22 = require("child_process");
12582
- var import_node_fs31 = __toESM(require("fs"));
12583
- var import_node_path36 = __toESM(require("path"));
12584
- var DEVPOD_MUTATION_LOCK_FILE = import_node_path36.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
12773
+ var import_node_fs32 = __toESM(require("fs"));
12774
+ var import_node_path37 = __toESM(require("path"));
12775
+ var DEVPOD_MUTATION_LOCK_FILE = import_node_path37.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
12585
12776
  var DEVPOD_MUTATION_WAIT_MS = 18e5;
12586
12777
  var DevpodStartPostconditionError = class extends Error {
12587
12778
  };
12588
12779
  function withMutationLock2(activity, target, operation) {
12589
- import_node_fs31.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12780
+ import_node_fs32.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
12590
12781
  return withFileLockSync(
12591
12782
  DEVPOD_MUTATION_LOCK_FILE,
12592
12783
  {
@@ -12788,7 +12979,7 @@ async function startDevpodWorkspace(options) {
12788
12979
 
12789
12980
  // src/core/host-port-claims.ts
12790
12981
  var import_node_child_process23 = require("child_process");
12791
- var import_node_path37 = __toESM(require("path"));
12982
+ var import_node_path38 = __toESM(require("path"));
12792
12983
  var COMPOSE_RENDER_TIMEOUT_MS = 3e4;
12793
12984
  var COMPOSE_RENDER_MAX_BUFFER = 8 * 1024 * 1024;
12794
12985
  var DOCKER_TIMEOUT_MS = 1e4;
@@ -12996,8 +13187,8 @@ function renderManagedComposeModel(plan, workspace) {
12996
13187
  }
12997
13188
  }
12998
13189
  function attributeHolderWorktree(workingDir) {
12999
- if (import_node_path37.default.basename(workingDir) !== ".devcontainer") return void 0;
13000
- return comparableWorkspacePath(import_node_path37.default.dirname(workingDir));
13190
+ if (import_node_path38.default.basename(workingDir) !== ".devcontainer") return void 0;
13191
+ return comparableWorkspacePath(import_node_path38.default.dirname(workingDir));
13001
13192
  }
13002
13193
  function holderAttribution(holder, repoPath) {
13003
13194
  const attribution = {};
@@ -13032,7 +13223,7 @@ function detectHostPortClaimConflicts(options) {
13032
13223
  );
13033
13224
  if (desired.length === 0) return [];
13034
13225
  const holders = listHolderSnapshots();
13035
- const devcontainerDir = import_node_path37.default.join(options.repoPath, ".devcontainer");
13226
+ const devcontainerDir = import_node_path38.default.join(options.repoPath, ".devcontainer");
13036
13227
  const conflicts = [];
13037
13228
  for (const binding of desired) {
13038
13229
  for (const holder of holders) {
@@ -13193,7 +13384,7 @@ function transportFailure() {
13193
13384
 
13194
13385
  // src/core/managed-host-preparation.ts
13195
13386
  var import_node_child_process25 = require("child_process");
13196
- var import_node_fs32 = __toESM(require("fs"));
13387
+ var import_node_fs33 = __toESM(require("fs"));
13197
13388
  var PREPARATION_TIMEOUT_MS = 6e4;
13198
13389
  var CONFIG_READ_ERROR = "Managed host preparation could not read .devrouter.yml.";
13199
13390
  var CONFIG_CHANGED_ERROR = "Managed host preparation changed .devrouter.yml; refusing to continue.";
@@ -13236,7 +13427,7 @@ function runPreparationCommand(argv, cwd) {
13236
13427
  }
13237
13428
  function readConfigBytes(configPath) {
13238
13429
  try {
13239
- return import_node_fs32.default.readFileSync(configPath);
13430
+ return import_node_fs33.default.readFileSync(configPath);
13240
13431
  } catch {
13241
13432
  return void 0;
13242
13433
  }
@@ -13269,6 +13460,43 @@ async function runManagedHostPreparation(repoPath, config) {
13269
13460
  }
13270
13461
  }
13271
13462
 
13463
+ // src/core/lifecycle-operation-status.ts
13464
+ function projectLifecycleOperation(operation) {
13465
+ const result = {
13466
+ operationId: operation.id,
13467
+ phase: "terminal",
13468
+ outcome: null,
13469
+ reason: null,
13470
+ exitCode: null
13471
+ };
13472
+ switch (operation.status) {
13473
+ case "NOT_STARTED":
13474
+ if (operation.drained) result.outcome = "NOT_STARTED";
13475
+ else result.phase = "queued";
13476
+ break;
13477
+ case "DISPATCH_PENDING":
13478
+ case "DISPATCH_RECORDED":
13479
+ case "RUNNING":
13480
+ if (operation.drained) {
13481
+ result.outcome = "COMPLETION_UNKNOWN";
13482
+ result.reason = "worker-drained-without-result";
13483
+ } else result.phase = operation.status === "RUNNING" ? "running" : "dispatching";
13484
+ break;
13485
+ case "COMPLETED":
13486
+ result.outcome = "COMPLETED";
13487
+ result.exitCode = operation.exitCode;
13488
+ break;
13489
+ case "NOT_LAUNCHED":
13490
+ result.outcome = "NOT_STARTED";
13491
+ break;
13492
+ case "INTERRUPTED":
13493
+ case "COMPLETION_UNKNOWN":
13494
+ result.outcome = operation.status;
13495
+ break;
13496
+ }
13497
+ return result;
13498
+ }
13499
+
13272
13500
  // src/core/managed-mount-nesting.ts
13273
13501
  var import_node_child_process26 = require("child_process");
13274
13502
  var NESTED_MOUNT_REASON = "no configured mount is nested inside another configured mount";
@@ -13421,6 +13649,8 @@ function readReliabilityStatus(options) {
13421
13649
  return void 0;
13422
13650
  }
13423
13651
  if (!record5) return void 0;
13652
+ const diagnosticReceipt = record5.state.operation ? readLifecycleDiagnostics(record5.identity) : void 0;
13653
+ const diagnostics = diagnosticReceipt?.operationId === record5.state.operation?.id && diagnosticReceipt?.revision === record5.revision ? diagnosticReceipt?.metadata : void 0;
13424
13654
  const reason2 = reliabilityAttention(record5.state) ?? (record5.state.desired === "running" && refusedManagedStart(record5.result) ? "start-refused" : void 0);
13425
13655
  return {
13426
13656
  desired: record5.state.desired,
@@ -13431,6 +13661,14 @@ function readReliabilityStatus(options) {
13431
13661
  correctiveActionsTaken: record5.state.incident.correctiveActionsTaken,
13432
13662
  actionLimit: record5.state.incident.actionLimit
13433
13663
  } : null,
13664
+ ...record5.state.operation ? {
13665
+ operation: {
13666
+ ...projectLifecycleOperation(record5.state.operation),
13667
+ kind: record5.state.operation.kind,
13668
+ drained: record5.state.operation.drained,
13669
+ ...diagnostics ? { diagnostics } : {}
13670
+ }
13671
+ } : {},
13434
13672
  ...reason2 ? {
13435
13673
  attention: {
13436
13674
  reason: reason2,
@@ -14058,33 +14296,33 @@ var import_node_child_process28 = require("child_process");
14058
14296
  var import_node_net3 = require("net");
14059
14297
 
14060
14298
  // src/core/network-local-daemon.ts
14061
- var import_node_fs33 = __toESM(require("fs"));
14062
- var import_node_path38 = __toESM(require("path"));
14299
+ var import_node_fs34 = __toESM(require("fs"));
14300
+ var import_node_path39 = __toESM(require("path"));
14063
14301
  function hasLocalDockerNetworkNamespace(endpoint, procRoot = "/proc") {
14064
14302
  const deadline = Date.now() + 2e3;
14065
14303
  try {
14066
14304
  if (!endpoint.startsWith("unix:///")) return false;
14067
14305
  const socket = endpoint.slice("unix://".length);
14068
- const table = import_node_fs33.default.readFileSync(import_node_path38.default.join(procRoot, "net/unix"), "utf8");
14306
+ const table = import_node_fs34.default.readFileSync(import_node_path39.default.join(procRoot, "net/unix"), "utf8");
14069
14307
  if (Buffer.byteLength(table) > 1024 * 1024) return false;
14070
14308
  const rows2 = table.split("\n").map((line) => line.trim().split(/\s+/)).filter((row) => row[7] === socket && row[3] === "00010000");
14071
14309
  if (rows2.length !== 1 || !/^\d+$/.test(rows2[0][6])) return false;
14072
14310
  const inode = `socket:[${rows2[0][6]}]`;
14073
- const ownNamespace = import_node_fs33.default.readlinkSync(import_node_path38.default.join(procRoot, "self/ns/net"));
14074
- const pids = import_node_fs33.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14311
+ const ownNamespace = import_node_fs34.default.readlinkSync(import_node_path39.default.join(procRoot, "self/ns/net"));
14312
+ const pids = import_node_fs34.default.readdirSync(procRoot).filter((name) => /^\d+$/.test(name));
14075
14313
  if (pids.length > 4096) return false;
14076
14314
  for (const pid of pids) {
14077
14315
  if (Date.now() > deadline) return false;
14078
14316
  try {
14079
- const root = import_node_path38.default.join(procRoot, pid);
14080
- if (import_node_path38.default.basename(import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "exe"))) !== "dockerd") continue;
14081
- if (import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "ns/net")) !== ownNamespace) continue;
14082
- const descriptors = import_node_fs33.default.readdirSync(import_node_path38.default.join(root, "fd"));
14317
+ const root = import_node_path39.default.join(procRoot, pid);
14318
+ if (import_node_path39.default.basename(import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "exe"))) !== "dockerd") continue;
14319
+ if (import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "ns/net")) !== ownNamespace) continue;
14320
+ const descriptors = import_node_fs34.default.readdirSync(import_node_path39.default.join(root, "fd"));
14083
14321
  if (descriptors.length > 4096) continue;
14084
14322
  if (descriptors.some((fd) => {
14085
14323
  if (Date.now() > deadline) return false;
14086
14324
  try {
14087
- return import_node_fs33.default.readlinkSync(import_node_path38.default.join(root, "fd", fd)) === inode;
14325
+ return import_node_fs34.default.readlinkSync(import_node_path39.default.join(root, "fd", fd)) === inode;
14088
14326
  } catch {
14089
14327
  return false;
14090
14328
  }
@@ -14159,10 +14397,10 @@ function inspectLegacyNetworkCapacity(input) {
14159
14397
  }
14160
14398
 
14161
14399
  // src/core/network-managed.ts
14162
- var import_node_crypto22 = require("crypto");
14400
+ var import_node_crypto23 = require("crypto");
14163
14401
 
14164
14402
  // src/core/network-compose.ts
14165
- var import_node_crypto20 = require("crypto");
14403
+ var import_node_crypto21 = require("crypto");
14166
14404
  var SAFE_ENDPOINT_UPPER_BOUND_SEMANTICS = "full-lifecycle-demand";
14167
14405
  function isRecord5(value) {
14168
14406
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -14968,14 +15206,14 @@ function stableValue(value) {
14968
15206
  }
14969
15207
  function fingerprintComposeNetworkModel(input) {
14970
15208
  const projection = normalizedFingerprintProjection(input);
14971
- return (0, import_node_crypto20.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
15209
+ return (0, import_node_crypto21.createHash)("sha256").update(stableValue(projection), "utf8").digest("hex");
14972
15210
  }
14973
15211
 
14974
15212
  // src/core/network-compose-files.ts
14975
15213
  var import_node_child_process29 = require("child_process");
14976
- var import_node_crypto21 = require("crypto");
14977
- var import_node_fs34 = __toESM(require("fs"));
14978
- var import_node_path39 = __toESM(require("path"));
15214
+ var import_node_crypto22 = require("crypto");
15215
+ var import_node_fs35 = __toESM(require("fs"));
15216
+ var import_node_path40 = __toESM(require("path"));
14979
15217
  var import_yaml4 = __toESM(require("yaml"));
14980
15218
  var OVERLAY = "docker-compose.devrouter-network.yml";
14981
15219
  var MARKER = "# devrouter:managed workspace network";
@@ -14988,12 +15226,12 @@ function object3(value) {
14988
15226
  function inspectNetworkComposeFiles(input) {
14989
15227
  const authoredNetworks = {};
14990
15228
  for (const file of input.plan.composeFiles) {
14991
- const stat = import_node_fs34.default.lstatSync(file);
15229
+ const stat = import_node_fs35.default.lstatSync(file);
14992
15230
  if (!stat.isFile() || stat.size > MAX_BYTES2)
14993
15231
  throw new Error("Network Compose source is unavailable or too large.");
14994
15232
  let source;
14995
15233
  try {
14996
- source = object3(import_yaml4.default.parse(import_node_fs34.default.readFileSync(file, "utf8")));
15234
+ source = object3(import_yaml4.default.parse(import_node_fs35.default.readFileSync(file, "utf8")));
14997
15235
  } catch {
14998
15236
  throw new Error("Network Compose source could not be parsed safely.");
14999
15237
  }
@@ -15043,7 +15281,7 @@ function inspectNetworkComposeFiles(input) {
15043
15281
  function prepareNetworkComposeFiles(plan, subnet) {
15044
15282
  const overlay = deriveComposeNetworkOverlay(subnet);
15045
15283
  if (overlay.status !== "ready" || !overlay.overlay) throw new Error("Network subnet is invalid.");
15046
- const overlayPath = import_node_path39.default.join(plan.composeDirectory, OVERLAY);
15284
+ const overlayPath = import_node_path40.default.join(plan.composeDirectory, OVERLAY);
15047
15285
  const ignored = (0, import_node_child_process29.spawnSync)("git", [
15048
15286
  "-C",
15049
15287
  plan.composeDirectory,
@@ -15058,9 +15296,9 @@ function prepareNetworkComposeFiles(plan, subnet) {
15058
15296
  const bytes2 = `${MARKER}
15059
15297
  ${import_yaml4.default.stringify(overlay.overlay)}`;
15060
15298
  const assertOwnership = () => {
15061
- if (!import_node_fs34.default.existsSync(overlayPath)) return;
15062
- const stat = import_node_fs34.default.lstatSync(overlayPath);
15063
- if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs34.default.readFileSync(overlayPath, "utf8") !== bytes2)
15299
+ if (!import_node_fs35.default.existsSync(overlayPath)) return;
15300
+ const stat = import_node_fs35.default.lstatSync(overlayPath);
15301
+ if (!stat.isFile() || stat.size > MAX_BYTES2 || import_node_fs35.default.readFileSync(overlayPath, "utf8") !== bytes2)
15064
15302
  throw new Error("Retained network overlay changed; repair is required.");
15065
15303
  };
15066
15304
  assertOwnership();
@@ -15074,7 +15312,7 @@ ${JSON.stringify(extended.config, null, 2)}
15074
15312
  ...plan,
15075
15313
  contents,
15076
15314
  composeFiles: [...plan.composeFiles, overlayPath],
15077
- effectiveConfigSha256: (0, import_node_crypto21.createHash)("sha256").update(contents).digest("hex")
15315
+ effectiveConfigSha256: (0, import_node_crypto22.createHash)("sha256").update(contents).digest("hex")
15078
15316
  };
15079
15317
  return {
15080
15318
  plan: result,
@@ -15392,7 +15630,7 @@ function createManagedNetworkSession(input) {
15392
15630
  "Insufficient endpoint headroom; request an allowed larger network before first allocation."
15393
15631
  );
15394
15632
  const authority = readNetworkOperationAuthority(identity2);
15395
- const fingerprint = (0, import_node_crypto22.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
15633
+ const fingerprint = (0, import_node_crypto23.createHash)("sha256").update(input.plan().sourceConfigSha256).update(fingerprintComposeNetworkModel(model)).digest("hex");
15396
15634
  if (!saved) {
15397
15635
  if (policy.status !== "valid")
15398
15636
  throw new Error("New network allocation requires valid machine policy.");
@@ -15576,11 +15814,11 @@ var DEFAULT_READINESS_TIMEOUT_MS = 12e4;
15576
15814
  var POLL_INTERVAL_MS = 1e3;
15577
15815
  function assertOverlay(container, repoPath) {
15578
15816
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15579
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))) {
15817
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))) {
15580
15818
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
15581
15819
  }
15582
15820
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
15583
- const expectedOverlay = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15821
+ const expectedOverlay = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
15584
15822
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
15585
15823
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
15586
15824
  }
@@ -15594,7 +15832,7 @@ function assertWorktreeOwnership(container, repoPath) {
15594
15832
  function isInsideWorktree(candidate, repoPath) {
15595
15833
  const normalizedCandidate = comparableWorkspacePath(candidate);
15596
15834
  const normalizedRoot = comparableWorkspacePath(repoPath);
15597
- return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path40.default.sep}`);
15835
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}${import_node_path41.default.sep}`);
15598
15836
  }
15599
15837
  function assertReady(container, label) {
15600
15838
  if (!container.state.Running) {
@@ -15786,7 +16024,7 @@ function hasExactManagedComposeProject(repoPath, state) {
15786
16024
  }).some((container) => {
15787
16025
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
15788
16026
  return Boolean(
15789
- workingDir && sameWorkspacePath(workingDir, import_node_path40.default.join(repoPath, ".devcontainer"))
16027
+ workingDir && sameWorkspacePath(workingDir, import_node_path41.default.join(repoPath, ".devcontainer"))
15790
16028
  );
15791
16029
  });
15792
16030
  } catch {
@@ -16191,7 +16429,7 @@ function resolvePrimaryTarget(repoPath) {
16191
16429
  }
16192
16430
  function isPrimaryCheckout(repoPath) {
16193
16431
  try {
16194
- return import_node_fs35.default.statSync(import_node_path40.default.join(repoPath, ".git")).isDirectory();
16432
+ return import_node_fs36.default.statSync(import_node_path41.default.join(repoPath, ".git")).isDirectory();
16195
16433
  } catch {
16196
16434
  return false;
16197
16435
  }
@@ -16255,8 +16493,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16255
16493
  const target = options.repair ? resolveRepairTarget(repoPath, linked) : linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
16256
16494
  let devpodId = target.devpodId;
16257
16495
  if (target.kind === "linked") {
16258
- const overlayPath = import_node_path40.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16259
- if (!import_node_fs35.default.existsSync(overlayPath)) {
16496
+ const overlayPath = import_node_path41.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
16497
+ if (!import_node_fs36.default.existsSync(overlayPath)) {
16260
16498
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
16261
16499
  }
16262
16500
  }
@@ -16276,6 +16514,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16276
16514
  if (variant !== "default" && managedPostStart.kind !== "runtime")
16277
16515
  throw new Error("Runtime variants require a managed runtime post-start adapter.");
16278
16516
  const managedRuntime = runtime.config.managedRuntime;
16517
+ const hostPortDeclarationsSha256 = managedHostPortDeclarationsIdentity(
16518
+ managedRuntime?.devcontainer.hostPorts ?? {}
16519
+ );
16279
16520
  if (options.repair && !managedRuntime)
16280
16521
  throw new Error("Repair requires managedRuntime configuration.");
16281
16522
  previousRoutes = listHostRouteState().filter(
@@ -16363,7 +16604,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16363
16604
  }
16364
16605
  const apps = proxyAppsFromConfig(runtime.config);
16365
16606
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
16366
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path40.default.basename(repoPath)) ?? "app";
16607
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path41.default.basename(repoPath)) ?? "app";
16367
16608
  for (const [index, app] of apps.entries()) {
16368
16609
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
16369
16610
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -16600,9 +16841,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16600
16841
  composeProject: captureProject,
16601
16842
  profile: runtime.profile,
16602
16843
  variant,
16603
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16604
- managedRuntime?.devcontainer.hostPorts ?? {}
16605
- ),
16844
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16606
16845
  desired: {
16607
16846
  apps: apps.map((app) => app.name).sort(),
16608
16847
  services: [...capturePlan.desiredProfileServices].sort(),
@@ -16813,9 +17052,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
16813
17052
  composeProject: managedComposeProject,
16814
17053
  profile: runtime.profile,
16815
17054
  variant,
16816
- hostPortDeclarationsSha256: managedHostPortDeclarationsIdentity(
16817
- managedRuntime?.devcontainer.hostPorts ?? {}
16818
- ),
17055
+ ...hostPortDeclarationsSha256 !== void 0 ? { hostPortDeclarationsSha256 } : {},
16819
17056
  desired: {
16820
17057
  apps: apps.map((app) => app.name).sort(),
16821
17058
  services: [...managedPlan.desiredProfileServices].sort(),
@@ -17252,9 +17489,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
17252
17489
  }
17253
17490
  const repairLocked = () => {
17254
17491
  const runtime = resolveWorkspaceRuntimeOrDefault(repoPath);
17255
- import_node_fs35.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17492
+ import_node_fs36.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
17256
17493
  return withFileLock(
17257
- import_node_path40.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17494
+ import_node_path41.default.join(DEVROUTER_HOME, `${runtime}-mutation.lock`),
17258
17495
  {
17259
17496
  activity: "Managed runtime repair",
17260
17497
  target: repoPath,
@@ -17432,7 +17669,7 @@ function preflightRuntimeVariant(repoPath, options) {
17432
17669
  return variant;
17433
17670
  }
17434
17671
  function capacityPolicyTargets(repoPath) {
17435
- const policy = readCapacityPolicy(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
17672
+ const policy = readCapacityPolicy(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
17436
17673
  return policy?.admissions === "enabled" && policy.enrollments.some((entry) => entry.repoPath === repoPath);
17437
17674
  }
17438
17675
  function clearActiveLifecycleStartupWitness() {
@@ -17538,7 +17775,7 @@ async function executeLifecycleWorker(request, operation) {
17538
17775
  const owned = containers.filter(
17539
17776
  (container) => sameWorkspacePath(
17540
17777
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17541
- import_node_path41.default.join(request.repoPath, ".devcontainer")
17778
+ import_node_path42.default.join(request.repoPath, ".devcontainer")
17542
17779
  )
17543
17780
  );
17544
17781
  const projects = owned.map((container) => container.labels["com.docker.compose.project"]);
@@ -17625,7 +17862,7 @@ function proveLifecycleStopped() {
17625
17862
  throw new Error("Full stop proof requires an explicit stop worker.");
17626
17863
  const settle = () => {
17627
17864
  claimLifecycleEffect();
17628
- if (initialStopAbsence && !(0, import_node_util7.isDeepStrictEqual)(
17865
+ if (initialStopAbsence && !(0, import_node_util8.isDeepStrictEqual)(
17629
17866
  proveInitialManagedDevsyAbsence(request.repoPath, initialStopAbsence.record.devpodId),
17630
17867
  initialStopAbsence
17631
17868
  ))
@@ -17645,7 +17882,7 @@ function proveLifecycleStopped() {
17645
17882
  const remaining = inspectWorkspaceContainers().filter(
17646
17883
  (container) => sameWorkspacePath(
17647
17884
  container.labels["com.docker.compose.project.working_dir"] ?? "",
17648
- import_node_path41.default.join(request.repoPath, ".devcontainer")
17885
+ import_node_path42.default.join(request.repoPath, ".devcontainer")
17649
17886
  )
17650
17887
  );
17651
17888
  if (remaining.some(
@@ -17675,7 +17912,7 @@ function proveLifecycleStopped() {
17675
17912
  return void 0;
17676
17913
  });
17677
17914
  if (settlement) {
17678
- const capacity = createLifecycleCapacityStore(import_node_path41.default.join(DEVROUTER_HOME, "controller"));
17915
+ const capacity = createLifecycleCapacityStore(import_node_path42.default.join(DEVROUTER_HOME, "controller"));
17679
17916
  for (let attempt = 0; ; attempt++) {
17680
17917
  try {
17681
17918
  const state = capacity.inspect();
@@ -17849,7 +18086,7 @@ async function devpodExecOutcome(repoPath, command) {
17849
18086
  }
17850
18087
  assertRunningDevpod(devpod.id, repoPath);
17851
18088
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
17852
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto24.randomUUID)()}__:`;
18089
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto25.randomUUID)()}__:`;
17853
18090
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
17854
18091
  const literalCommand = command.map(quotePosixArg).join(" ");
17855
18092
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -18039,11 +18276,11 @@ async function devpodExecOutcome(repoPath, command) {
18039
18276
 
18040
18277
  // src/core/workspace-lifecycle.ts
18041
18278
  var import_node_child_process34 = require("child_process");
18042
- var import_node_fs37 = __toESM(require("fs"));
18043
- var import_node_path42 = __toESM(require("path"));
18279
+ var import_node_fs38 = __toESM(require("fs"));
18280
+ var import_node_path43 = __toESM(require("path"));
18044
18281
 
18045
18282
  // src/core/route-state.ts
18046
- var import_node_fs36 = __toESM(require("fs"));
18283
+ var import_node_fs37 = __toESM(require("fs"));
18047
18284
  function listRoutesForWorktreePath(worktreePath, routes = listHostRouteState()) {
18048
18285
  return routes.filter((route) => sameWorkspacePath(route.repoPath, worktreePath));
18049
18286
  }