stim 1.15.0 → 1.17.0

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.
Files changed (123) hide show
  1. package/dist/{activity-B2-kb7k0.mjs → activity-IRvJY_ru.mjs} +49 -16
  2. package/dist/agent-device-usage-output-BK41vnzN.mjs +411 -0
  3. package/dist/{android-Cu_L4RpL.mjs → android--GorydsK.mjs} +692 -321
  4. package/dist/{android-Db2eEMaf.mjs → android-B_YNZy61.mjs} +25 -60
  5. package/dist/{android-CYmq0jNi.mjs → android-DE4v59Mb.mjs} +12 -3
  6. package/dist/{android-cas-PnKfAdB2.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
  7. package/dist/{app-install-CAAplXAt.mjs → app-install-VPkI5WEg.mjs} +52 -8
  8. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  9. package/dist/{budget-CMiqghig.mjs → budget-DM984mnd.mjs} +372 -366
  10. package/dist/{build-plan-aeGMFX7e.mjs → build-plan-CmQCoyT-.mjs} +346 -88
  11. package/dist/{build-slots-Df1ApSuH.mjs → build-slots-41v_d2x9.mjs} +36 -21
  12. package/dist/{cli-0cX5s4NM.mjs → cli-Daxug5-K.mjs} +23 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/collector-run.mjs +5 -192
  15. package/dist/{command-output-CliHHn7a.mjs → command-output-CDAIFD-8.mjs} +3 -3
  16. package/dist/{created-devices-BwVb3liH.mjs → created-devices-CpvzBL25.mjs} +1 -1
  17. package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  18. package/dist/{deps-BFlKbOIp.mjs → deps-DNWaQs4V.mjs} +2 -2
  19. package/dist/{dev-client-CjzLoNs0.mjs → dev-client-B-XC4uRH.mjs} +3 -40
  20. package/dist/{device-BZ1IyS5A.mjs → device-C8IOoJPv.mjs} +7 -6
  21. package/dist/device-capacity-D0GWHnTM.mjs +580 -0
  22. package/dist/device-host-worker.mjs +587 -110
  23. package/dist/{device-ios-DMQBUPus.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
  24. package/dist/{device-lease-W5Pi1q38.mjs → device-lease-DpoS36_u.mjs} +2 -2
  25. package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-B8YltIH_.mjs} +5 -125
  26. package/dist/device-pool-3o7s5lo4.mjs +125 -0
  27. package/dist/{device-remote-9ulPsdhY.mjs → device-remote-hFSkpfQu.mjs} +13 -13
  28. package/dist/{doctor-BcE5xsBn.mjs → doctor-Cnk9d7Hm.mjs} +86 -59
  29. package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-C-NMnhrk.mjs} +11 -15
  30. package/dist/{gc-BuL1Genr.mjs → gc-DxCL2A46.mjs} +478 -33
  31. package/dist/{gradle-CmdOBq1d.mjs → gradle-D7-L2Th3.mjs} +4 -4
  32. package/dist/{guide-cCSGRFd3.mjs → guide-DjrTQGik.mjs} +2198 -566
  33. package/dist/{guide-status-CIoWK6mG.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  34. package/dist/hosted-android-DwLS4Mse.mjs +32 -0
  35. package/dist/hosted-client-wk1W_CNW.mjs +196 -0
  36. package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
  37. package/dist/hosted-logs-DqYmWtXc.mjs +187 -0
  38. package/dist/hosted-macos-dlHXEeF5.mjs +135 -0
  39. package/dist/hosted-native-C9xJ9rpA.mjs +255 -0
  40. package/dist/{build-progress-C2Jv1AtA.mjs → idle-B2m-Ovod.mjs} +300 -9
  41. package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
  42. package/dist/{in-use-CndPMilN.mjs → in-use-CvGOUn-x.mjs} +30 -10
  43. package/dist/ios-Cr1h5gnO.mjs +189 -0
  44. package/dist/{ios-Chob8ny7.mjs → ios-DE6duQoz.mjs} +819 -460
  45. package/dist/{ios-3CtCRhwr.mjs → ios-DSfnPfse.mjs} +4 -4
  46. package/dist/{ios-device-DERmMmk-.mjs → ios-device-pAN4t54-.mjs} +1 -1
  47. package/dist/ios-state-8L3lgum6.mjs +89 -0
  48. package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-lTnWZikR.mjs} +3 -3
  49. package/dist/logs-DUoKlDoS.mjs +384 -0
  50. package/dist/{client-C2IS36Qf.mjs → machines-C2XiNwh9.mjs} +330 -37
  51. package/dist/{macos-BwtbUtTN.mjs → macos-32po3kUq.mjs} +23 -18
  52. package/dist/macos-run.mjs +1 -1
  53. package/dist/maintenance-run.d.mts +4 -0
  54. package/dist/maintenance-run.mjs +254 -0
  55. package/dist/{metro-Di8EN2Tb.mjs → metro-g8NqiJp-.mjs} +1 -1
  56. package/dist/metro-gateway-DmslUM30.mjs +103 -0
  57. package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  58. package/dist/{native-run-DFDoIRbY.mjs → native-run-D1Q56rAD.mjs} +17 -6
  59. package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
  60. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  61. package/dist/offload-worker.d.mts +1 -0
  62. package/dist/offload-worker.mjs +13 -8
  63. package/dist/{teardown-BYuQafiA.mjs → ownership-BMLqyBgJ.mjs} +639 -53
  64. package/dist/{ownership-BkYxUtuj.mjs → ownership-DyRQru0q.mjs} +2 -2
  65. package/dist/{ports-DddwttKM.mjs → ports-4nYqxPWC.mjs} +2 -2
  66. package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D7RUMkwN.mjs} +4 -4
  67. package/dist/preview-CxPRIEht.mjs +318 -0
  68. package/dist/{project-CV3tDFHl.mjs → project-D47Rh0ly.mjs} +72 -3
  69. package/dist/{recordings-GEXp4iBT.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  70. package/dist/{reload-ChPiwvot.mjs → reload-qZei6iWn.mjs} +77 -13
  71. package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-Ci4DQYYj.mjs} +4 -4
  72. package/dist/{run-B-xqRrsg.mjs → run-CkE9y0nc.mjs} +2 -2
  73. package/dist/{server-bare-X7PUgizL.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  74. package/dist/{server-expo-DhtX6bom.mjs → server-expo-CHTyXD0G.mjs} +4 -4
  75. package/dist/server-expo-CeakhI8P.mjs +2 -0
  76. package/dist/settings-BBpMcDbD.mjs +25 -0
  77. package/dist/settings-Cfd-0r2d.mjs +1440 -0
  78. package/dist/{settings-DbL6uhH8.mjs → settings-SR9OmW9h.mjs} +10 -4
  79. package/dist/settings.schema.json +472 -50
  80. package/dist/{simslim-Ciluxlq4.mjs → simslim-BWNrc3M3.mjs} +3 -3
  81. package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-DmjBmpQV.mjs} +10 -7
  82. package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
  83. package/dist/{start-CPO4wRg7.mjs → start-BopZcr4n.mjs} +1 -1
  84. package/dist/{start-CUD9nasA.mjs → start-uee1wWjY.mjs} +21 -17
  85. package/dist/{state-CaeaHbDA.mjs → state-BKa8wNN5.mjs} +1 -1
  86. package/dist/{state-BxCgeTQu.mjs → state-DyaNTgfR.mjs} +2 -2
  87. package/dist/{state-DFm77wRn.mjs → state-PeQiJuIu.mjs} +5 -5
  88. package/dist/{stats-DBLLFmKd.mjs → stats-Bq4fJOdB.mjs} +19 -5
  89. package/dist/{status-l84Gbvp2.mjs → status-CzS4bPO9.mjs} +5 -3
  90. package/dist/status-DlKvVJBE.mjs +49 -0
  91. package/dist/{status-DYXxNOT0.mjs → status-dXX2JX1C.mjs} +244 -50
  92. package/dist/{stop-CceYh1yG.mjs → stop-BCwvvs7F.mjs} +2 -2
  93. package/dist/{stop-Bad61z_2.mjs → stop-BjizrQwv.mjs} +83 -21
  94. package/dist/{stop-Bvf1MflS.mjs → stop-Cr1d8DSE.mjs} +4 -4
  95. package/dist/stop-cause-rhmGfwvt.mjs +97 -0
  96. package/dist/supervisor-run.d.mts +7 -4
  97. package/dist/supervisor-run.mjs +156 -27
  98. package/dist/{support-WLOzVKUv.mjs → support-BJPgYCu-.mjs} +61 -11
  99. package/dist/{support-CDQbDkL5.mjs → support-F9-sr0Oi.mjs} +6 -5
  100. package/dist/{toolchain-DmeYjXD9.mjs → toolchain-CNIBTIOv.mjs} +11 -11
  101. package/dist/trigger-Bemu8ytw.mjs +65 -0
  102. package/dist/trigger-CXts6qt1.mjs +2 -0
  103. package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-DF4JRvYI.mjs} +274 -5
  104. package/dist/watchman-APYjXSQa.mjs +48 -0
  105. package/dist/{web-k6w8-HnE.mjs → web-exQtGkoc.mjs} +14 -16
  106. package/dist/web-run.mjs +5 -5
  107. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  108. package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-DqcCbpE7.mjs} +34 -34
  109. package/dist/workspaces-Bcib97-6.mjs +306 -0
  110. package/dist/{worktree-BT_uULFx.mjs → worktree-Dc98cE6_.mjs} +1 -1
  111. package/dist/{worktree-qXKzADSC.mjs → worktree-ZpZR3S1k.mjs} +601 -33
  112. package/package.json +5 -5
  113. package/dist/device-capacity-DMuKC7nx.mjs +0 -136
  114. package/dist/hosted-macos-DwowNU7W.mjs +0 -447
  115. package/dist/logs-h9h6MUvE.mjs +0 -568
  116. package/dist/machine-usage-D_YYMzfR.mjs +0 -283
  117. package/dist/machines-CYtOCqcj.mjs +0 -259
  118. package/dist/merge-state-BnxLNQiM.mjs +0 -272
  119. package/dist/server-expo-KJeD109I.mjs +0 -2
  120. package/dist/settings-CduVcoxj.mjs +0 -680
  121. package/dist/stop-cause-CZ_Mr-v9.mjs +0 -665
  122. package/dist/watchman-Bx0wceoF.mjs +0 -238
  123. package/dist/worktree-C0c5Udwa.mjs +0 -753
@@ -1,22 +1,25 @@
1
1
  import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- import { D as withConfigLock, H as projectDeviceSlots, _ as loadConfig, k as acquireAvdClaim, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
2
+ import { D as withConfigLock, H as projectDeviceSlots, _ as loadConfig, d as getConfigDir, k as acquireAvdClaim, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
3
  import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
- import { b as workspaceDir } from "./workspace-state-CL2V2Ons.mjs";
5
- import { y as repoRoot } from "./worktree-C0c5Udwa.mjs";
6
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
7
- import { o as readAgentDeviceRecords } from "./activity-B2-kb7k0.mjs";
8
- import { l as phaseLine } from "./command-output-CliHHn7a.mjs";
9
- import { B as ownedAvdSystemImage, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, M as listOrphanedAvdDirectories, O as listAdbDevices, X as shutdownAndroidEmulator, Z as sleepSync, _ as avdStorageRoots, ct as eraseParkedAfter, et as waitForAndroidEmulatorShutdown, f as assertOwnedAvdStopped, ft as readParked, h as avdPathExists, nt as wipeAvdUserData, pt as removeParkedAfter, rt as isStimOwnedAvd, ut as parkSim, x as deleteAvd } from "./android-Db2eEMaf.mjs";
10
- import { n as readCreatedDevices, r as recordCreatedDevice, t as forgetCreatedDevice } from "./created-devices-BwVb3liH.mjs";
11
- import { u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
12
- import { r as releaseBrowserPort } from "./named-ports-Dj_XBNaL.mjs";
13
- import { _ as removeSingletonFiles, c as webClaimRoot, f as webProfileDir, g as liveProfileHolder, h as chromeProcessState, i as clearWebRecord, o as readWebRecord, t as CDP_PORT_LABEL } from "./state-DFm77wRn.mjs";
14
- import { E as shutdownIosSim, _ as parseRuntimeVersion, a as deleteIosSim, b as renameIosSim, f as listIosDeviceTypes, g as parkedSimName, o as deleteParkedIosSim, s as eraseIosSim, w as resolveOwnedIosSim } from "./ios-3CtCRhwr.mjs";
15
- import { randomUUID } from "node:crypto";
16
- import { existsSync, lstatSync, realpathSync, renameSync, rmSync } from "node:fs";
17
- import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
4
+ import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
5
+ import { l as phaseLine } from "./command-output-CDAIFD-8.mjs";
6
+ import { gt as repoRoot, q as resolveTailscaleBinary } from "./settings-Cfd-0r2d.mjs";
7
+ import { o as pidExists, u as signalProcessTree } from "./metro-g8NqiJp-.mjs";
8
+ import { g as workspaceAgentDeviceDir, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
9
+ import { _ as removeSingletonFiles, c as webClaimRoot, f as webProfileDir, g as liveProfileHolder, h as chromeProcessState, i as clearWebRecord, o as readWebRecord, t as CDP_PORT_LABEL } from "./state-PeQiJuIu.mjs";
10
+ import { t as agentDeviceDaemon, u as readAgentDeviceRecords } from "./activity-IRvJY_ru.mjs";
11
+ import { n as readCreatedDevices, r as recordCreatedDevice, t as forgetCreatedDevice } from "./created-devices-CpvzBL25.mjs";
12
+ import { B as ownedAvdSystemImage, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, M as listOrphanedAvdDirectories, O as listAdbDevices, X as shutdownAndroidEmulator, Z as sleepSync, _ as avdStorageRoots, dt as parkSim, et as waitForAndroidEmulatorShutdown, f as assertOwnedAvdStopped, h as avdPathExists, it as isStimOwnedAvd, lt as eraseParkedAfter, mt as removeParkedAfter, nt as wipeAvdUserData, pt as readParked, x as deleteAvd } from "./android-B_YNZy61.mjs";
13
+ import { E as shutdownIosSim, _ as parseRuntimeVersion, a as deleteIosSim, b as renameIosSim, f as listIosDeviceTypes, g as parkedSimName, o as deleteParkedIosSim, s as eraseIosSim, w as resolveOwnedIosSim } from "./ios-DSfnPfse.mjs";
14
+ import { r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
15
+ import { r as releaseBrowserPort } from "./named-ports-C8m8EQs2.mjs";
16
+ import { createHash, randomUUID } from "node:crypto";
17
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, unlinkSync } from "node:fs";
18
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
18
19
  import { homedir } from "node:os";
19
20
  import { isDeepStrictEqual } from "node:util";
21
+ import { resolve4 } from "node:dns/promises";
22
+ import { request } from "node:https";
20
23
  //#region src/devices/agent-device-cleanup.ts
21
24
  function parseAgentDeviceSessions(output) {
22
25
  const result = JSON.parse(output);
@@ -59,31 +62,23 @@ function deviceClaims(device) {
59
62
  workspace: record.workspace && canonical(record.workspace)
60
63
  }));
61
64
  }
65
+ function explainFailure(error) {
66
+ const stdout = error?.stdout;
67
+ let failure;
68
+ try {
69
+ failure = typeof stdout === "string" ? JSON.parse(stdout)?.error : void 0;
70
+ } catch {
71
+ failure = void 0;
72
+ }
73
+ if (typeof failure?.message !== "string" || !failure.message) return error;
74
+ const reason = typeof failure.details?.reason === "string" ? ` (${failure.details.reason})` : "";
75
+ return /* @__PURE__ */ new Error(`agent-device refused: ${failure.message}${reason}`);
76
+ }
62
77
  function report(message) {
63
78
  process.stderr.write(`${phaseLine("device", message)}\n`);
64
79
  }
65
80
  function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot) {
66
81
  const exec = getExecutor();
67
- const deadline = Date.now() + 15e3;
68
- const run = (args) => {
69
- const remaining = deadline - Date.now();
70
- if (remaining <= 0) throw new Error("agent-device cleanup timed out");
71
- return exec.runFile("agent-device", [
72
- ...args,
73
- "--json",
74
- "--daemon-transport",
75
- "socket"
76
- ], {
77
- timeoutMs: Math.min(5e3, remaining),
78
- killSignal: "SIGKILL"
79
- });
80
- };
81
- const list = () => parseAgentDeviceSessions(run([
82
- "session",
83
- "list",
84
- "--session",
85
- "stim-teardown-inventory"
86
- ]));
87
82
  try {
88
83
  if (!exec.findExecutable("agent-device")) return;
89
84
  const root = workspace === void 0 ? void 0 : canonical(workspace);
@@ -93,24 +88,57 @@ function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot) {
93
88
  repoRoot: repoRoot && (canonical(repoRoot) ?? void 0),
94
89
  claims: deviceClaims(device)
95
90
  };
96
- const sessions = list().filter((session) => isOwnDeviceSession(session, device, owner));
97
- for (const session of sessions) try {
98
- if (!list().some((current) => current.name === session.name && current.platform === session.platform && current.id === session.id && current.createdAt === session.createdAt)) continue;
99
- if (!stillOwned(session.id)) continue;
100
- if (JSON.parse(run([
101
- "close",
91
+ const stateDir = root === void 0 ? void 0 : workspaceAgentDeviceDir(root);
92
+ const stateDirs = [void 0, ...stateDir && !["none", "dead"].includes(agentDeviceDaemon(stateDir)) ? [stateDir] : []];
93
+ for (const dir of stateDirs) {
94
+ const deadline = Date.now() + 15e3;
95
+ const run = (args) => {
96
+ const remaining = deadline - Date.now();
97
+ if (remaining <= 0) throw new Error("agent-device cleanup timed out");
98
+ try {
99
+ return exec.runFile("agent-device", [
100
+ ...args,
101
+ ...dir ? ["--state-dir", dir] : [],
102
+ "--json",
103
+ "--daemon-transport",
104
+ "socket"
105
+ ], {
106
+ timeoutMs: Math.min(5e3, remaining),
107
+ killSignal: "SIGKILL"
108
+ });
109
+ } catch (error) {
110
+ throw explainFailure(error);
111
+ }
112
+ };
113
+ const list = () => parseAgentDeviceSessions(run([
114
+ "session",
115
+ "list",
102
116
  "--session",
103
- session.name,
104
- "--session-lock",
105
- "reject",
106
- "--platform",
107
- device.platform,
108
- device.platform === "ios" ? "--udid" : "--serial",
109
- session.id
110
- ]))?.success !== true) throw new Error("agent-device did not confirm session close");
111
- report(`closed agent-device session ${session.name} on ${session.id}`);
112
- } catch (error) {
113
- report(`could not close agent-device session ${session.name} on ${session.id}: ${error.message}`);
117
+ "stim-teardown-inventory"
118
+ ]));
119
+ try {
120
+ const sessions = list().filter((session) => isOwnDeviceSession(session, device, owner));
121
+ for (const session of sessions) try {
122
+ if (!list().some((current) => current.name === session.name && current.platform === session.platform && current.id === session.id && current.createdAt === session.createdAt)) continue;
123
+ if (!stillOwned(session.id)) continue;
124
+ if (JSON.parse(run([
125
+ "close",
126
+ "--session",
127
+ session.name,
128
+ "--session-lock",
129
+ "reject",
130
+ "--platform",
131
+ device.platform,
132
+ device.platform === "ios" ? "--udid" : "--serial",
133
+ session.id
134
+ ]))?.success !== true) throw new Error("agent-device did not confirm session close");
135
+ report(`closed agent-device session ${session.name} on ${session.id}`);
136
+ } catch (error) {
137
+ report(`could not close agent-device session ${session.name} on ${session.id}: ${error.message}`);
138
+ }
139
+ } catch (error) {
140
+ report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
141
+ }
114
142
  }
115
143
  } catch (error) {
116
144
  report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
@@ -715,4 +743,562 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
715
743
  }
716
744
  }
717
745
  //#endregion
718
- export { teardownOwnedBrowser as a, teardownParkedIosSim as c, teardownOwnedAvd as i, eraseParkedIosSim as n, teardownOwnedIosSim as o, teardownBrowserHeld as r, teardownParkedAvd as s, eraseParkedAvd as t };
746
+ //#region src/engine/public-http-probe.ts
747
+ const DNS_TIMEOUT_MS = 5e3;
748
+ async function resolveAddresses(hostname, signal) {
749
+ if (signal.aborted) throw signal.reason;
750
+ let timer;
751
+ let onAbort;
752
+ const unavailable = new Promise((_resolve, reject) => {
753
+ onAbort = () => reject(signal.reason);
754
+ signal.addEventListener("abort", onAbort, { once: true });
755
+ if (signal.aborted) onAbort();
756
+ else timer = setTimeout(() => reject(/* @__PURE__ */ new Error("DNS lookup timed out")), DNS_TIMEOUT_MS);
757
+ });
758
+ try {
759
+ return await Promise.race([resolve4(hostname), unavailable]);
760
+ } finally {
761
+ clearTimeout(timer);
762
+ if (onAbort) signal.removeEventListener("abort", onAbort);
763
+ }
764
+ }
765
+ async function probePublicHttp(url, signal) {
766
+ try {
767
+ return (await fetch(url, {
768
+ signal,
769
+ redirect: "follow"
770
+ })).status;
771
+ } catch (error) {
772
+ const cause = error?.cause;
773
+ if (cause?.code !== "ENOTFOUND" && cause?.code !== "EAI_AGAIN") return null;
774
+ }
775
+ const hostname = new URL(url).hostname;
776
+ if (!hostname.endsWith(".trycloudflare.com")) return null;
777
+ let addresses;
778
+ try {
779
+ addresses = await resolveAddresses(hostname, signal);
780
+ } catch {
781
+ return null;
782
+ }
783
+ const firstAddress = addresses[0];
784
+ if (!firstAddress) return null;
785
+ return new Promise((resolve) => {
786
+ const request$1 = request(url, {
787
+ signal,
788
+ lookup: (_hostname, options, callback) => {
789
+ if (options.all) callback(null, addresses.map((address) => ({
790
+ address,
791
+ family: 4
792
+ })));
793
+ else callback(null, firstAddress, 4);
794
+ }
795
+ }, (response) => {
796
+ response.resume();
797
+ resolve(response.statusCode ?? null);
798
+ });
799
+ request$1.on("error", () => resolve(null));
800
+ request$1.end();
801
+ });
802
+ }
803
+ //#endregion
804
+ //#region src/engine/tunnel.ts
805
+ function tunnelArgv(provider, port, ngrokUrl, logFile, tailscaleBinary = "tailscale") {
806
+ if (provider === "tailscale") return {
807
+ bin: tailscaleBinary,
808
+ args: [
809
+ "serve",
810
+ `--https=${port}`,
811
+ `http://127.0.0.1:${port}`
812
+ ]
813
+ };
814
+ if (provider === "cloudflared") return {
815
+ bin: "cloudflared",
816
+ args: [
817
+ "tunnel",
818
+ "--url",
819
+ `http://127.0.0.1:${port}`,
820
+ ...logFile ? ["--logfile", logFile] : []
821
+ ]
822
+ };
823
+ return {
824
+ bin: "ngrok",
825
+ args: [
826
+ "http",
827
+ String(port),
828
+ `--log=${logFile || "stdout"}`,
829
+ "--log-format=json",
830
+ ...ngrokUrl ? ["--url", ngrokUrl] : []
831
+ ]
832
+ };
833
+ }
834
+ const CLOUDFLARED_URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
835
+ function parseCloudflaredLine(line) {
836
+ const match = line.match(CLOUDFLARED_URL_RE);
837
+ return match ? match[0] : null;
838
+ }
839
+ function parseNgrokLine(line) {
840
+ let data;
841
+ try {
842
+ data = JSON.parse(line);
843
+ } catch {
844
+ return null;
845
+ }
846
+ if (typeof data !== "object" || data === null) return null;
847
+ const url = data.url;
848
+ return typeof url === "string" && /^https?:\/\//i.test(url) ? url : null;
849
+ }
850
+ function parseTailscaleLine(line) {
851
+ return /^https:\/\/[^\s/]+\.ts\.net(:\d+)?\/?$/.test(line) ? line : null;
852
+ }
853
+ function parserFor(provider) {
854
+ if (provider === "tailscale") return parseTailscaleLine;
855
+ return provider === "cloudflared" ? parseCloudflaredLine : parseNgrokLine;
856
+ }
857
+ const URL_TIMEOUT_MS = 15e3;
858
+ const REACHABLE_TIMEOUT_MS = 24e4;
859
+ const REACHABLE_POLL_MS = 2e3;
860
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
861
+ async function withManagedTunnelLock(root, fn, options = {}) {
862
+ return withWorkspaceProcessLock(managedTunnelLockRoot(root), "metro-tunnel", fn, {
863
+ ...options,
864
+ rejectOwnerPurposes: ["workspace removal"]
865
+ });
866
+ }
867
+ async function withManagedTunnelRemovalLock(root, fn, options = {}) {
868
+ return withWorkspaceProcessLock(managedTunnelLockRoot(root), "metro-tunnel", fn, {
869
+ ...options,
870
+ ownerPurpose: "workspace removal"
871
+ });
872
+ }
873
+ async function withManagedRemoteWorktreeLock(worktreeRoot, fn, options = {}) {
874
+ return withWorkspaceProcessLock(managedRemoteWorktreeLockRoot(worktreeRoot), "managed-remote", fn, {
875
+ ...options,
876
+ external: true,
877
+ rejectOwnerPurposes: ["worktree removal"]
878
+ });
879
+ }
880
+ async function withManagedRemoteWorktreeRemovalLock(worktreeRoot, fn, options = {}) {
881
+ return withWorkspaceProcessLock(managedRemoteWorktreeLockRoot(worktreeRoot), "managed-remote", fn, {
882
+ ...options,
883
+ external: true,
884
+ ownerPurpose: "worktree removal"
885
+ });
886
+ }
887
+ function managedLockHolders(root) {
888
+ const locks = [{
889
+ label: "managed tunnel",
890
+ path: workspaceProcessLockPath(managedTunnelLockRoot(root), "metro-tunnel", false)
891
+ }, {
892
+ label: "managed remote",
893
+ path: workspaceProcessLockPath(managedRemoteWorktreeLockRoot(root), "managed-remote", true)
894
+ }];
895
+ const reasons = [];
896
+ for (const { label, path } of locks) {
897
+ const claims = (0, ownership_claim_exports.readClaimSet)(path);
898
+ const holder = claims.live[0];
899
+ if (holder) {
900
+ const purpose = typeof holder.details.purpose === "string" ? ` for ${holder.details.purpose}` : "";
901
+ reasons.push(`the ${label} lock is held${purpose}`);
902
+ } else if (claims.unresolved[0]) reasons.push(`the ${label} lock cannot be resolved: ${claims.unresolved[0].reason}`);
903
+ }
904
+ return reasons;
905
+ }
906
+ function managedTunnelLockRoot(root) {
907
+ const key = createHash("sha256").update(resolve(root)).digest("hex");
908
+ return join(getConfigDir(), "process-locks", key);
909
+ }
910
+ function managedRemoteWorktreeLockRoot(worktreeRoot) {
911
+ const key = createHash("sha256").update(resolve(worktreeRoot)).digest("hex");
912
+ return join(getConfigDir(), "process-locks", "worktrees", key);
913
+ }
914
+ async function defaultProbeReachable(url, signal) {
915
+ const status = await probePublicHttp(url, signal);
916
+ return status !== null && status > 0;
917
+ }
918
+ function waitForUrl(child, parseLine, timeoutMs) {
919
+ return new Promise((resolve) => {
920
+ let settled = false;
921
+ let lastLine = null;
922
+ let timer;
923
+ let onOut;
924
+ let onErr;
925
+ let onError;
926
+ let onExit;
927
+ const finish = (url, exited = false) => {
928
+ if (settled) return;
929
+ settled = true;
930
+ clearTimeout(timer);
931
+ child.stdout?.removeListener("data", onOut);
932
+ child.stderr?.removeListener("data", onErr);
933
+ child.removeListener("error", onError);
934
+ child.removeListener("exit", onExit);
935
+ resolve({
936
+ url,
937
+ exited,
938
+ lastLine
939
+ });
940
+ };
941
+ const onLine = (line) => {
942
+ if (settled) return;
943
+ if (line.trim()) lastLine = line.trim();
944
+ const url = parseLine(line);
945
+ if (url) finish(url);
946
+ };
947
+ const outReader = createLineReader(onLine);
948
+ const errReader = createLineReader(onLine);
949
+ onOut = (chunk) => outReader.push(chunk);
950
+ onErr = (chunk) => errReader.push(chunk);
951
+ onError = () => finish(null);
952
+ onExit = () => {
953
+ outReader.flush();
954
+ errReader.flush();
955
+ finish(null, true);
956
+ };
957
+ child.stdout?.setEncoding?.("utf-8");
958
+ child.stderr?.setEncoding?.("utf-8");
959
+ child.stdout?.on("data", onOut);
960
+ child.stderr?.on("data", onErr);
961
+ child.on("error", onError);
962
+ child.on("exit", onExit);
963
+ timer = setTimeout(() => finish(null), timeoutMs);
964
+ });
965
+ }
966
+ function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
967
+ return new Promise((resolve) => {
968
+ let settled = false;
969
+ const finish = (url, exited = false) => {
970
+ if (settled) return;
971
+ settled = true;
972
+ clearInterval(interval);
973
+ clearTimeout(timer);
974
+ child.removeListener("error", onError);
975
+ child.removeListener("exit", onExit);
976
+ resolve({
977
+ url,
978
+ exited
979
+ });
980
+ };
981
+ const probe = () => {
982
+ try {
983
+ for (const line of readFileSync(logFile, "utf-8").split(/\r?\n/)) {
984
+ const url = parseLine(line);
985
+ if (url) return finish(url);
986
+ }
987
+ } catch {}
988
+ };
989
+ const onError = () => finish(null);
990
+ const onExit = () => finish(null, true);
991
+ child.on("error", onError);
992
+ child.on("exit", onExit);
993
+ const interval = setInterval(probe, 25);
994
+ const timer = setTimeout(() => finish(null), timeoutMs);
995
+ probe();
996
+ });
997
+ }
998
+ function createTunnelLogFile(provider) {
999
+ const dir = join(getConfigDir(), "tunnel-logs");
1000
+ mkdirSync(dir, { recursive: true });
1001
+ const path = join(dir, `${provider}-${randomUUID()}.log`);
1002
+ closeSync(openSync(path, "wx", 384));
1003
+ return path;
1004
+ }
1005
+ function removeTunnelLogFile(path) {
1006
+ if (!path) return;
1007
+ try {
1008
+ unlinkSync(path);
1009
+ } catch {}
1010
+ }
1011
+ function removeRecordedTunnelLogFile(path) {
1012
+ if (!path) return;
1013
+ const dir = resolve(join(getConfigDir(), "tunnel-logs"));
1014
+ const resolved = resolve(path);
1015
+ if (!resolved.startsWith(`${dir}${sep}`) || resolve(join(dir, basename(resolved))) !== resolved) return;
1016
+ removeTunnelLogFile(resolved);
1017
+ }
1018
+ async function waitUntilReachable({ url, now, sleep, probe, timeoutMs }) {
1019
+ const deadline = now() + timeoutMs;
1020
+ for (;;) {
1021
+ if (await probe(url, new AbortController().signal).catch(() => false)) return true;
1022
+ if (now() >= deadline) return false;
1023
+ await sleep(REACHABLE_POLL_MS);
1024
+ }
1025
+ }
1026
+ const CLEANUP_TIMEOUT_MS = 1e3;
1027
+ const CLEANUP_POLL_MS = 25;
1028
+ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
1029
+ const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
1030
+ const outputFile = provider === "tailscale" ? null : logFile || (spawnFn ? null : createTunnelLogFile(provider));
1031
+ let child;
1032
+ try {
1033
+ const binary = provider === "tailscale" ? resolveTailscaleBinary((bin) => getExecutor().findExecutable(bin)) : provider;
1034
+ if (!binary) throw new Error("Install Tailscale and sign in to your tailnet.");
1035
+ const { bin, args } = tunnelArgv(provider, port, ngrokUrl, outputFile, binary);
1036
+ child = spawn(bin, args, {
1037
+ stdio: outputFile ? [
1038
+ "ignore",
1039
+ "ignore",
1040
+ "ignore"
1041
+ ] : [
1042
+ "ignore",
1043
+ "pipe",
1044
+ "pipe"
1045
+ ],
1046
+ detached: true
1047
+ });
1048
+ } catch (err) {
1049
+ removeTunnelLogFile(outputFile);
1050
+ return {
1051
+ failed: true,
1052
+ reason: `Could not start ${provider}: ${describe(err)}`
1053
+ };
1054
+ }
1055
+ let childExited = false;
1056
+ child.once("exit", () => {
1057
+ childExited = true;
1058
+ });
1059
+ const cleanup = async (alreadyExited = childExited) => {
1060
+ const stopped = await terminateChild(child, {
1061
+ alreadyExited,
1062
+ timeoutMs: cleanupTimeoutMs,
1063
+ now,
1064
+ sleep,
1065
+ isAlive: isChildAlive
1066
+ });
1067
+ if (stopped) removeTunnelLogFile(outputFile);
1068
+ return stopped ? { status: "stopped" } : {
1069
+ status: "failed",
1070
+ reason: `Sent SIGKILL but could not confirm that pid ${child.pid ?? "unknown"} exited.`
1071
+ };
1072
+ };
1073
+ const failAfterCleanup = async (reason, alreadyExited = childExited) => {
1074
+ const stopped = await cleanup(alreadyExited);
1075
+ return stopped.status === "stopped" ? {
1076
+ failed: true,
1077
+ reason
1078
+ } : {
1079
+ failed: true,
1080
+ reason: `${reason} ${stopped.reason}`,
1081
+ cleanupFailed: true
1082
+ };
1083
+ };
1084
+ const pid = child.pid;
1085
+ if (!pid) return failAfterCleanup(`${provider} started but reported no pid.`);
1086
+ const captureChildIdentity = () => {
1087
+ try {
1088
+ return readProcessToken(pid);
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ };
1093
+ const initialProcessToken = captureChildIdentity();
1094
+ if (!initialProcessToken) return failAfterCleanup(`${provider} started but its process identity token could not be read.`);
1095
+ const { url, exited, lastLine } = outputFile ? await waitForFileUrl(child, outputFile, parserFor(provider), urlTimeoutMs) : await waitForUrl(child, parserFor(provider), urlTimeoutMs);
1096
+ if (!url) return failAfterCleanup(exited ? provider === "tailscale" && lastLine ? `${provider} exited before printing a tunnel URL: ${lastLine}` : `${provider} exited before printing a tunnel URL.` : `${provider} did not print a tunnel URL within ${urlTimeoutMs}ms.`, exited || childExited);
1097
+ if (!outputFile) resumeChildPipes(child);
1098
+ if (requireReachable) {
1099
+ if (!await waitUntilReachable({
1100
+ url,
1101
+ now,
1102
+ sleep,
1103
+ probe: probeReachable,
1104
+ timeoutMs: reachableTimeoutMs
1105
+ })) return failAfterCleanup(`${url} did not become reachable within ${reachableTimeoutMs}ms; ${provider} may still be registering, or the network path is blocked.`);
1106
+ }
1107
+ const finalProcessToken = captureChildIdentity();
1108
+ if (!finalProcessToken || finalProcessToken !== initialProcessToken) return failAfterCleanup(`${provider} process identity changed before its tunnel could be recorded.`);
1109
+ if (!outputFile) unrefChildPipes(child);
1110
+ child.unref?.();
1111
+ if (childExited) return failAfterCleanup(`${provider} exited before its tunnel could be recorded.`, true);
1112
+ return {
1113
+ url,
1114
+ pid,
1115
+ processToken: initialProcessToken,
1116
+ logFile: outputFile,
1117
+ cleanup: () => cleanup()
1118
+ };
1119
+ }
1120
+ async function startTunnelSequence({ providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
1121
+ const failures = [];
1122
+ for (const provider of providers) {
1123
+ const result = await start({
1124
+ provider,
1125
+ port,
1126
+ ngrokUrl: provider === "ngrok" ? ngrokUrl : null,
1127
+ requireReachable
1128
+ });
1129
+ if (!("failed" in result)) return {
1130
+ provider,
1131
+ ...result
1132
+ };
1133
+ failures.push(`${provider}: ${result.reason}`);
1134
+ if (result.cleanupFailed) return {
1135
+ failed: true,
1136
+ reason: failures.join(" ")
1137
+ };
1138
+ }
1139
+ return {
1140
+ failed: true,
1141
+ reason: failures.join(" ") || "No managed tunnel provider was selected."
1142
+ };
1143
+ }
1144
+ function closeChildPipes(child) {
1145
+ child.stdout?.destroy?.();
1146
+ child.stderr?.destroy?.();
1147
+ }
1148
+ function unrefChildPipes(child) {
1149
+ resumeChildPipes(child);
1150
+ for (const stream of [child.stdout, child.stderr]) stream?.unref?.();
1151
+ }
1152
+ function resumeChildPipes(child) {
1153
+ child.stdout?.resume?.();
1154
+ child.stderr?.resume?.();
1155
+ }
1156
+ async function signalAndWaitForExit(child, signal, { timeoutMs, now, sleep, isAlive, platform }) {
1157
+ let exited = false;
1158
+ const onExit = () => {
1159
+ exited = true;
1160
+ };
1161
+ child.once("exit", onExit);
1162
+ try {
1163
+ if (platform === "win32" && child.pid) signalProcessTree(child.pid, signal, { platform });
1164
+ child.kill(signal);
1165
+ } catch (err) {
1166
+ child.removeListener("exit", onExit);
1167
+ return isEsrch(err);
1168
+ }
1169
+ const hasExited = () => exited;
1170
+ const deadline = now() + timeoutMs;
1171
+ while (!hasExited() && now() < deadline) await sleep(CLEANUP_POLL_MS);
1172
+ if (!hasExited() && child.pid && !isAlive(child.pid)) exited = true;
1173
+ child.removeListener("exit", onExit);
1174
+ return hasExited();
1175
+ }
1176
+ async function terminateChild(child, { alreadyExited, timeoutMs, now, sleep, isAlive, platform = process.platform }) {
1177
+ if (alreadyExited) {
1178
+ closeChildPipes(child);
1179
+ return true;
1180
+ }
1181
+ if (await signalAndWaitForExit(child, "SIGTERM", {
1182
+ timeoutMs,
1183
+ now,
1184
+ sleep,
1185
+ isAlive,
1186
+ platform
1187
+ })) {
1188
+ closeChildPipes(child);
1189
+ return true;
1190
+ }
1191
+ const killed = await signalAndWaitForExit(child, "SIGKILL", {
1192
+ timeoutMs,
1193
+ now,
1194
+ sleep,
1195
+ isAlive,
1196
+ platform
1197
+ });
1198
+ closeChildPipes(child);
1199
+ return killed;
1200
+ }
1201
+ function describe(err) {
1202
+ return err?.message || String(err);
1203
+ }
1204
+ const STOP_TIMEOUT_MS = 5e3;
1205
+ const STOP_POLL_MS = 100;
1206
+ function isEsrch(err) {
1207
+ return err?.code === "ESRCH";
1208
+ }
1209
+ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, kill = (pid) => signalProcessTree(pid, "SIGTERM", { platform }), now = Date.now, sleep = defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
1210
+ const missing = () => {
1211
+ removeRecordedTunnelLogFile(record?.logFile);
1212
+ return { status: "missing" };
1213
+ };
1214
+ const pid = record?.pid;
1215
+ if (!pid || !isAlive(pid)) return missing();
1216
+ if (!record.processToken) return {
1217
+ status: "failed",
1218
+ reason: `tunnel pid ${pid} has no process identity token, so Stim cannot verify ownership. Keep the record. Inspect the process, stop it with the provider tooling, remove the stale metroTunnel state, and retry.`
1219
+ };
1220
+ const identity = inspectIdentity(record);
1221
+ if (identity === "gone" || identity === "different") return missing();
1222
+ if (identity !== "same") return {
1223
+ status: "failed",
1224
+ reason: `could not verify the process identity for tunnel pid ${pid}; refusing to signal it.`
1225
+ };
1226
+ try {
1227
+ kill(pid);
1228
+ } catch (err) {
1229
+ return isEsrch(err) ? missing() : {
1230
+ status: "failed",
1231
+ reason: describe(err)
1232
+ };
1233
+ }
1234
+ const deadline = now() + timeoutMs;
1235
+ const exited = () => {
1236
+ if (!isAlive(pid)) return true;
1237
+ const current = inspectIdentity(record);
1238
+ return current === "gone" || current === "different";
1239
+ };
1240
+ while (now() < deadline && !exited()) await sleep(STOP_POLL_MS);
1241
+ if (!exited()) return {
1242
+ status: "failed",
1243
+ reason: `pid ${pid} did not exit within ${timeoutMs}ms.`
1244
+ };
1245
+ removeRecordedTunnelLogFile(record.logFile);
1246
+ return { status: "stopped" };
1247
+ }
1248
+ //#endregion
1249
+ //#region src/supervisor/ownership.ts
1250
+ function resolveSupervisorTarget({ state, record, reservedPort, isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity } = {}) {
1251
+ const statePid = numberOrNull(state?.pid);
1252
+ const recordPid = numberOrNull(record?.pid);
1253
+ const pid = statePid ?? recordPid;
1254
+ if (!pid) return { status: "none" };
1255
+ if (statePid && recordPid && statePid !== recordPid) return {
1256
+ status: "unverified",
1257
+ pid,
1258
+ reason: `workspace state.json records supervisor pid ${statePid} but the registry records pid ${recordPid}`
1259
+ };
1260
+ if (!isAlive(pid)) return {
1261
+ status: "stale",
1262
+ pid
1263
+ };
1264
+ const port = numberOrNull(state?.port) ?? numberOrNull(record?.port);
1265
+ if (reservedPort !== null && reservedPort !== void 0 && port !== null && port !== reservedPort) return {
1266
+ status: "unverified",
1267
+ pid,
1268
+ reason: `supervisor pid ${pid} records port ${port}, but this project reserved port ${reservedPort}`
1269
+ };
1270
+ if (reservedPort !== null && reservedPort !== void 0 && port === null) return {
1271
+ status: "unverified",
1272
+ pid,
1273
+ reason: `supervisor pid ${pid} has no recorded port, so it cannot be matched against reserved port ${reservedPort}`
1274
+ };
1275
+ const identityRecord = statePid ? state : record;
1276
+ if (statePid && recordPid && state?.processToken !== record?.processToken) return {
1277
+ status: "unverified",
1278
+ pid,
1279
+ reason: "workspace state and registry process identities disagree"
1280
+ };
1281
+ const identity = inspectIdentity(identityRecord);
1282
+ if (identity === "gone" || identity === "different") return {
1283
+ status: "stale",
1284
+ pid
1285
+ };
1286
+ if (identity !== "same") return {
1287
+ status: "unverified",
1288
+ pid,
1289
+ reason: "the recorded process identity could not be verified"
1290
+ };
1291
+ return {
1292
+ status: "ours",
1293
+ processToken: identityRecord.processToken,
1294
+ pid,
1295
+ port,
1296
+ mode: state?.mode ?? record?.mode ?? null,
1297
+ startedAt: state?.startedAt ?? record?.startedAt ?? null
1298
+ };
1299
+ }
1300
+ function numberOrNull(v) {
1301
+ return typeof v === "number" && Number.isSafeInteger(v) && v > 0 ? v : null;
1302
+ }
1303
+ //#endregion
1304
+ export { teardownParkedAvd as _, terminateChild as a, withManagedTunnelLock as c, eraseParkedAvd as d, eraseParkedIosSim as f, teardownOwnedIosSim as g, teardownOwnedBrowser as h, stopTunnel as i, withManagedTunnelRemovalLock as l, teardownOwnedAvd as m, managedLockHolders as n, withManagedRemoteWorktreeLock as o, teardownBrowserHeld as p, startTunnelSequence as r, withManagedRemoteWorktreeRemovalLock as s, resolveSupervisorTarget as t, probePublicHttp as u, teardownParkedIosSim as v };