stim 1.23.1 → 1.24.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 (169) hide show
  1. package/README.md +3 -2
  2. package/dist/{acquire-artifact-BzYRDgaP.mjs → acquire-artifact-CEs02U2o.mjs} +2 -2
  3. package/dist/{act-vxiMwdf5.mjs → act-Cyvm8xYc.mjs} +40 -25
  4. package/dist/{activity-BpWUPgUm.mjs → activity-Dq3ZcdD8.mjs} +6 -6
  5. package/dist/{agent-device-usage-output-CUlLsbRt.mjs → agent-device-usage-output-CccomeE0.mjs} +5 -5
  6. package/dist/{android-C7sVmyI9.mjs → android-882m4hxX.mjs} +140 -46
  7. package/dist/{android-DtfRGLcc.mjs → android-BZppyPe_.mjs} +66 -46
  8. package/dist/{android-DR5lZ2Az.mjs → android-CX5IcRuf.mjs} +13 -3
  9. package/dist/{android-CLi6DSiS.mjs → android-DcztblCT.mjs} +5 -5
  10. package/dist/{android-cas-ngferVq2.mjs → android-cas-BtCOrNqb.mjs} +3 -3
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/api.d.mts +21 -4
  13. package/dist/api.mjs +32 -31
  14. package/dist/{artifact-Xvgibgzq.mjs → artifact-BR5VNArK.mjs} +17 -16
  15. package/dist/{artifact-DBI3DALs.mjs → artifact-Bdt_n9Jc.mjs} +17 -16
  16. package/dist/{artifact-platform-CXcmX_j3.mjs → artifact-platform-CMRc0M_r.mjs} +3 -3
  17. package/dist/{browser-web-BJN9ZHxB.mjs → browser-web-ycg9qWB8.mjs} +16 -17
  18. package/dist/{budget-3ivAK99e.mjs → budget-dPGuhNai.mjs} +101 -40
  19. package/dist/{build-Dv9s8Fz-.mjs → build-CaF574eS.mjs} +31 -28
  20. package/dist/{build-cache-Dv7RN200.mjs → build-cache-HFUQ9eLz.mjs} +152 -19
  21. package/dist/{build-plan-BmbdHQbr.mjs → build-plan-JsDUq4w1.mjs} +8 -7
  22. package/dist/{cache-manifest-DSeQjt-Q.mjs → cache-manifest-BPXRONJz.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/{ccache-CH2EWGPB.mjs → ccache-CCn1S6qG.mjs} +6 -6
  25. package/dist/{cdp-3fwICjzi.mjs → cdp-BG3FJCCx.mjs} +1 -1
  26. package/dist/{chrome-Bbw689-6.mjs → chrome-BvV8ssI3.mjs} +1 -1
  27. package/dist/{cli-BkK6Wr5i.mjs → cli-xqAidP7O.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-B4MMzfo_.mjs → client-18-ZowoT.mjs} +254 -28
  30. package/dist/collector-run.mjs +6 -6
  31. package/dist/{command-output-YKGWivpG.mjs → command-output-DxC0k2rk.mjs} +3 -2
  32. package/dist/{config-DlrsduwG.mjs → config-BZhXY15H.mjs} +3 -3
  33. package/dist/{created-devices-x9rN6bhs.mjs → created-devices-D2Aca-xu.mjs} +6 -2
  34. package/dist/{dependency-state-1I9KZT1Y.mjs → dependency-state-C56ll9hY.mjs} +2 -2
  35. package/dist/{detached-entry-BwNEtIrP.mjs → detached-entry-KmieOKgN.mjs} +1 -1
  36. package/dist/{dev-client-BFxiDpkf.mjs → dev-client-w_fok0Ek.mjs} +3 -3
  37. package/dist/{device-B7ljwccX.mjs → device-4RUugw5X.mjs} +16 -16
  38. package/dist/{device-CiNYxqp6.mjs → device-BktRGdjS.mjs} +40 -44
  39. package/dist/{device-capacity-DH0QsZYZ.mjs → device-capacity-Ic-kdmEV.mjs} +20 -16
  40. package/dist/device-host-worker.mjs +17 -17
  41. package/dist/{device-ios-CbVDtDIy.mjs → device-ios-DzpSlZyd.mjs} +23 -52
  42. package/dist/{device-lease-run-DXPSwdbb.mjs → device-lease-run-DUtkbqBQ.mjs} +5 -5
  43. package/dist/{device-pool-B4iOXIrD.mjs → device-pool-CgBHk401.mjs} +3 -3
  44. package/dist/{doctor-DRu-8ld6.mjs → doctor-BZ6oxNum.mjs} +1 -1
  45. package/dist/{doctor-HUC1igzw.mjs → doctor-C33AzVfR.mjs} +28 -28
  46. package/dist/{doctor-C6t4Maue.mjs → doctor-DZgpqR6J.mjs} +14 -14
  47. package/dist/{doctor-fingerprint-DZLoYFVb.mjs → doctor-fingerprint-zlujA1Gs.mjs} +1 -1
  48. package/dist/doctor-fingerprint.mjs +1 -1
  49. package/dist/{doctor-watchman-iGBpC6KM.mjs → doctor-watchman-B453A231.mjs} +5 -5
  50. package/dist/{eas-build-DMWSSvxq.mjs → eas-build-CxKUNHsb.mjs} +4 -4
  51. package/dist/{eas-fallback-vKlrqxgE.mjs → eas-fallback-LMxk9Kw0.mjs} +14 -14
  52. package/dist/{eas-session-ledger-BQF9Yyy9.mjs → eas-session-ledger-Ck6YXPAO.mjs} +12 -8
  53. package/dist/{error-diagnostics-DYsjJIHz.mjs → error-diagnostics-BkKCZq4P.mjs} +17 -42
  54. package/dist/{exec-DEoAhcze.mjs → exec-VFRpu1iA.mjs} +116 -28
  55. package/dist/{gc-dDE4QPVu.mjs → gc-DDWEAj0w.mjs} +2 -2
  56. package/dist/{gc-BW8E1uSC.mjs → gc-yVKpAOKM.mjs} +205 -28
  57. package/dist/{guide-DYYEO3Wz.mjs → guide-Bggftwc5.mjs} +222 -59
  58. package/dist/{hosted-android-xcfgCgSF.mjs → hosted-android-D4dweBof.mjs} +4 -4
  59. package/dist/{hosted-client-Bt-tndxV.mjs → hosted-client-BBTVe3X6.mjs} +4 -4
  60. package/dist/{hosted-ios-y19jCYFF.mjs → hosted-ios-Sn0djzg8.mjs} +1 -1
  61. package/dist/{hosted-logs-DzJKzKlj.mjs → hosted-logs-CYIZ5ubJ.mjs} +11 -6
  62. package/dist/{hosted-macos-BJusKvqL.mjs → hosted-macos-CXjsUNBR.mjs} +7 -7
  63. package/dist/{hosted-native-BmQC6U_x.mjs → hosted-native-Doxbfzem.mjs} +58 -11
  64. package/dist/{idle-shutdown-CJmUOV5x.mjs → idle-shutdown-BInjLKqm.mjs} +10 -10
  65. package/dist/{ios-CWjqfSm_.mjs → ios-B1FXRCMX.mjs} +71 -44
  66. package/dist/{ios-DTOYZGMJ.mjs → ios-BItP6lkn.mjs} +8 -8
  67. package/dist/{ios-hcBpnEGk.mjs → ios-CVUFFZ6C.mjs} +70 -13
  68. package/dist/{ios-xtlRHaeB.mjs → ios-ENC4sRPA.mjs} +1 -1
  69. package/dist/{ios-device-BXZ0c7Kb.mjs → ios-device-B6HK951C.mjs} +1 -1
  70. package/dist/{ios-device-DFGuwF4U.mjs → ios-device-BI38NFFH.mjs} +3 -3
  71. package/dist/{ios-project-CSZYI1iu.mjs → ios-project-KYqeJURj.mjs} +5 -5
  72. package/dist/{ios-signing-BtAffm0A.mjs → ios-signing-4c_qXvQm.mjs} +2 -2
  73. package/dist/{ios-state-Dtw5K2aI.mjs → ios-state-DlElquYD.mjs} +15 -7
  74. package/dist/{js-swap-BSBHldmm.mjs → js-swap-BvrQvXHY.mjs} +7 -6
  75. package/dist/{launch-CP2wDCVd.mjs → launch-BVlyQw3T.mjs} +51 -49
  76. package/dist/{launch-D-zq8SHG.mjs → launch-SfzGH_s9.mjs} +48 -29
  77. package/dist/{logs-pNvA2hHK.mjs → logs-DXE32kyb.mjs} +16 -9
  78. package/dist/{logs-By5aw3g9.mjs → logs-ovlU7m7N.mjs} +8 -6
  79. package/dist/{macos-CPMahOC2.mjs → macos-B3BveDrU.mjs} +1 -1
  80. package/dist/{macos-C67FZ2ft.mjs → macos-CCfhb-jT.mjs} +49 -42
  81. package/dist/macos-run.mjs +1 -1
  82. package/dist/maintenance-run.mjs +7 -7
  83. package/dist/{metro-DiXtA3Ty.mjs → metro-Bu8LBfzS.mjs} +4 -4
  84. package/dist/{metro-gateway-CKZqKqx7.mjs → metro-gateway-DtsOy-0P.mjs} +3 -4
  85. package/dist/{named-ports-DkEzOfzO.mjs → named-ports-BWDuU5oQ.mjs} +3 -3
  86. package/dist/{native-android-_MWSs3PP.mjs → native-android-96BBizbq.mjs} +17 -17
  87. package/dist/{native-gradle-artifact-inputs-DHFnmrVf.mjs → native-gradle-artifact-inputs-BcLYBZil.mjs} +5 -5
  88. package/dist/{native-run-Dv88i0aU.mjs → native-run-BmH7U8pr.mjs} +6 -6
  89. package/dist/{native-runtime-DBRfY_PN.mjs → native-runtime-DgtJkzIa.mjs} +10 -9
  90. package/dist/{native-source-Y0yLhFyQ.mjs → native-source-D-4Dh4JU.mjs} +7 -4
  91. package/dist/{native-xcode-inputs-CqViUh3r.mjs → native-xcode-inputs-irplbQb0.mjs} +2 -2
  92. package/dist/{native-xcode-ios-vFgqL_yS.mjs → native-xcode-ios-BY-6hh6M.mjs} +18 -18
  93. package/dist/offload-worker.d.mts +7 -2
  94. package/dist/offload-worker.mjs +75 -41
  95. package/dist/{operation-exec-BW0em61G.mjs → operation-exec-CUZifp6Z.mjs} +2 -25
  96. package/dist/{ownership-C5fo5Edb.mjs → ownership-DxlZGDcV.mjs} +3 -3
  97. package/dist/{page-DBL2PiUU.mjs → page-CzEA_-5_.mjs} +2 -3
  98. package/dist/phone-reverses-BdsdesVF.mjs +57 -0
  99. package/dist/{placement-log-BnGgJRRZ.mjs → placement-log-DUvqqsB-.mjs} +1 -1
  100. package/dist/{plan-placement-cr0fEU7b.mjs → plan-placement-4axc0Hs7.mjs} +3 -3
  101. package/dist/{ports-DpXjKkSV.mjs → ports-D-E_OiAO.mjs} +3 -3
  102. package/dist/{prebuild-BligW0QS.mjs → prebuild-DmVCf5xb.mjs} +14 -9
  103. package/dist/{project-DhCtQWWU.mjs → project-Dz-hWkb-.mjs} +21 -17
  104. package/dist/{pull-request-W1IuLXDt.mjs → pull-request-CbNlkFok.mjs} +2 -2
  105. package/dist/pull-requests.mjs +1 -1
  106. package/dist/{react-native-android-ZlhQfhvq.mjs → react-native-android-DOinxgwB.mjs} +75 -32
  107. package/dist/{react-native-build-CelbTO_y.mjs → react-native-build-AuCu-aAP.mjs} +22 -19
  108. package/dist/{react-native-doctor-LTIaP7lv.mjs → react-native-doctor-2ivJUmwZ.mjs} +10 -9
  109. package/dist/{react-native-ios-C5W0SA6f.mjs → react-native-ios-D5zTIuH8.mjs} +38 -22
  110. package/dist/{recordings-CQmfzAh7.mjs → recordings-CdPwYwvs.mjs} +1 -1
  111. package/dist/{registration-BcRgF6Ox.mjs → registration-CN6nlhcD.mjs} +1 -1
  112. package/dist/{reload-COJWTbae.mjs → reload-C1TTVesC.mjs} +144 -42
  113. package/dist/{reload-QcwnYLYV.mjs → reload-Ck9RJeSq.mjs} +1 -1
  114. package/dist/{remote-DR8qUERM.mjs → remote-DEZ7AOVq.mjs} +7 -7
  115. package/dist/{result-utnBea7x.mjs → result-DqBrb12U.mjs} +40 -17
  116. package/dist/{run-DidHghD1.mjs → run-BIwoU44q.mjs} +2 -2
  117. package/dist/{selection-DSMbWEX2.mjs → selection-BLtvaqxc.mjs} +11 -5
  118. package/dist/{server-bare-CafzuyFB.mjs → server-bare-Z1ToVLPn.mjs} +2 -2
  119. package/dist/{server-command-B9o92sLM.mjs → server-command-C0pmjNdG.mjs} +5 -5
  120. package/dist/server-expo-CEntXzRw.mjs +2 -0
  121. package/dist/{server-expo-BXrWxNYh.mjs → server-expo-Tt_hLRpy.mjs} +8 -5
  122. package/dist/{settings-C7O8A6ZI.mjs → settings-RKwY33wv.mjs} +114 -19
  123. package/dist/{settings-PfJx8uK1.mjs → settings-kkt0vP4w.mjs} +1 -1
  124. package/dist/{settings-write-Chuy7Cte.mjs → settings-write--gj6MACV.mjs} +7 -7
  125. package/dist/settings.schema.json +13 -0
  126. package/dist/{simslim-BNckWpXi.mjs → simslim-CdWKT3vA.mjs} +2 -2
  127. package/dist/{slot-launch-B7AuoWJv.mjs → slot-launch-A1BgotaM.mjs} +6 -6
  128. package/dist/{stage-C4u17aES.mjs → stage-ERTMh88n.mjs} +2 -3
  129. package/dist/{start-CU8mCPC7.mjs → start-BKGrEhzG.mjs} +75 -35
  130. package/dist/{start-BL_BsiWt.mjs → start-Bmg_qmHi.mjs} +1 -1
  131. package/dist/{state--smjFSTL.mjs → state-B1ig0Kqy.mjs} +4 -4
  132. package/dist/{state-08I3hw3_.mjs → state-BfAltYJP.mjs} +2 -2
  133. package/dist/{stats-CO6sK0Nh.mjs → stats-B-ckuiUg.mjs} +5 -5
  134. package/dist/{stats-report-BzaPrRm6.mjs → stats-report-n2U5plhk.mjs} +1 -1
  135. package/dist/{status-_WPHzVft.mjs → status-DfFp6a76.mjs} +24 -24
  136. package/dist/{status-BRROykC8.mjs → status-Fect2w9R.mjs} +1 -1
  137. package/dist/{status-CrJutl6-.mjs → status-Pw-807f0.mjs} +1 -1
  138. package/dist/{status-watch-BHFasOZN.mjs → status-watch-XC7F-AMf.mjs} +5 -5
  139. package/dist/status-worker.mjs +1 -1
  140. package/dist/stim-clonefile.c +18 -0
  141. package/dist/{stim-desktop-D5OGpYZM.mjs → stim-desktop-DS0mzR8U.mjs} +2 -2
  142. package/dist/{stop-iuFHjUST.mjs → stop-BFu7X_Do.mjs} +2 -2
  143. package/dist/{stop-BJ06ipTZ.mjs → stop-CL1kDHds.mjs} +55 -21
  144. package/dist/{stop-CR5689oB.mjs → stop-DhclRwOO.mjs} +5 -5
  145. package/dist/supervisor-run.mjs +16 -16
  146. package/dist/{support-TWuQlHQo.mjs → support-B1W-hmC2.mjs} +8 -8
  147. package/dist/{support-CbXPbUMJ.mjs → support-B3tzVhKn.mjs} +6 -6
  148. package/dist/{swiftpm-artifact-inputs-DFcGsXDP.mjs → swiftpm-artifact-inputs-BWYPj9Sn.mjs} +4 -4
  149. package/dist/{swiftpm-macos-DbwGCpZg.mjs → swiftpm-macos-BVCP5mO4.mjs} +14 -13
  150. package/dist/{toolchain-XkA2PfJZ.mjs → toolchain-BjgKdoEf.mjs} +1 -1
  151. package/dist/{toolchain-B9hCFCQw.mjs → toolchain-D3bmRMia.mjs} +57 -36
  152. package/dist/trigger-BLsHgAGR.mjs +2 -0
  153. package/dist/{trigger-Cbyc41d_.mjs → trigger-CBMe2YLH.mjs} +2 -2
  154. package/dist/{warm-progress-CA3TgT3-.mjs → warm-progress-ol0ZypEu.mjs} +6 -6
  155. package/dist/{web-DRF_oFXk.mjs → web-BH7peScT.mjs} +12 -10
  156. package/dist/web-BdOFlTJr.mjs +2 -0
  157. package/dist/web-run.mjs +8 -9
  158. package/dist/windows-report.mjs +12 -1
  159. package/dist/windows-report.ps1 +1 -1
  160. package/dist/{workspace-state-U08dEzhG.mjs → workspace-state-CeBpIZgr.mjs} +11 -4
  161. package/dist/{workspaces-e41xsjKr.mjs → workspaces-CSV2Orf4.mjs} +36 -32
  162. package/dist/{worktree-BEwxVwKF.mjs → worktree-DEDUzO_L.mjs} +1 -1
  163. package/dist/{worktree-B4Nj3EGS.mjs → worktree-Dw8kfacb.mjs} +324 -99
  164. package/dist/{xcode-Rq744mBF.mjs → xcode-DHu41qQ4.mjs} +41 -27
  165. package/package.json +7 -7
  166. package/dist/native-gradle-inputs-Dnh84Zbd.mjs +0 -211
  167. package/dist/server-expo-Ddr8cG_c.mjs +0 -2
  168. package/dist/trigger-yb_nFzPF.mjs +0 -2
  169. package/dist/web-kDE9Myoy.mjs +0 -2
@@ -1,12 +1,13 @@
1
- import { n as isTimeoutError, r as WorkspaceProcessCleanupError, t as getExecutor } from "./exec-DEoAhcze.mjs";
2
- import { _ as loadConfig$1, d as getConfigDir, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-DlrsduwG.mjs";
3
- import { c as gitQuiet, f as throwIfCancelled, l as gitQuietAsync, o as git, r as childEnvironment, s as gitAsync } from "./operation-exec-BW0em61G.mjs";
1
+ import { c as throwIfCancelled, l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-VFRpu1iA.mjs";
2
+ import { _ as loadConfig$1, d as getConfigDir, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-BZhXY15H.mjs";
3
+ import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, o as git, r as childEnvironment, s as gitAsync, t as runOperationFile } from "./operation-exec-CUZifp6Z.mjs";
4
4
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
- import { ambientEnv, buildCacheRoot } from "@stim-cli/core";
7
+ import { ambientEnv, buildCacheRoot, compiledHelper, configDir } from "@stim-cli/core";
8
8
  import { REMOTE_DEVICE_BACKENDS, SETTINGS, SETTING_GROUPS, TUNNEL_MODES, WEB_VIEWPORTS, acceptsShape, coerceSettingText, expectedShape, isLayeredSetting, settingDefinition, settingValueError } from "@stim-cli/core/state";
9
9
  import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
10
+ import { fileURLToPath } from "node:url";
10
11
  import { existsSync as existsSync$1, lstatSync as lstatSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, statSync as statSync$1 } from "fs";
11
12
  import { chmod, lstat, mkdir, readdir, rm, utimes } from "node:fs/promises";
12
13
  //#region src/temporary.ts
@@ -107,6 +108,51 @@ async function removeTemporaryEntryAsync(path, signal) {
107
108
  signal?.throwIfAborted();
108
109
  }
109
110
  //#endregion
111
+ //#region src/clonefile.ts
112
+ const SOURCE = "stim-clonefile.c";
113
+ const COMPILE_TIMEOUT_MS = 12e4;
114
+ const PROBE_TIMEOUT_MS = 5e3;
115
+ function helperSource() {
116
+ const here = fileURLToPath(import.meta.url);
117
+ if (basename(dirname(here)) === "src") return fileURLToPath(new URL(`../helper/${SOURCE}`, import.meta.url));
118
+ return join(dirname(here), SOURCE);
119
+ }
120
+ /** The `stim-clonefile` helper, compiled on first use, or null off macOS, without the Command Line Tools, or when the compile fails. */
121
+ async function clonefileHelper(operation) {
122
+ if (process.platform !== "darwin") return null;
123
+ const exec = getExecutor();
124
+ if (!await runOperationFileQuiet(operation, exec, "xcode-select", ["-p"], {
125
+ timeoutMs: PROBE_TIMEOUT_MS,
126
+ cwd: operation.cwd
127
+ })) return null;
128
+ const source = helperSource();
129
+ try {
130
+ return await compiledHelper({
131
+ dir: join(configDir(), "helpers"),
132
+ name: "stim-clonefile",
133
+ inputs: [source],
134
+ version: "",
135
+ prune: false,
136
+ compile: async (output) => {
137
+ await runOperationFile(operation, exec, "xcrun", [
138
+ "clang",
139
+ "-O2",
140
+ "-o",
141
+ output,
142
+ source
143
+ ], {
144
+ timeoutMs: COMPILE_TIMEOUT_MS,
145
+ cwd: operation.cwd
146
+ });
147
+ }
148
+ });
149
+ } catch (error) {
150
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
151
+ operation.signal.throwIfAborted();
152
+ return null;
153
+ }
154
+ }
155
+ //#endregion
110
156
  //#region src/workspace/worktree.ts
111
157
  const CARRY_SKIP_BASENAMES = /* @__PURE__ */ new Set([
112
158
  ".DerivedData",
@@ -121,7 +167,7 @@ function isCarrySkipped(rel) {
121
167
  function nativePath(path) {
122
168
  return sep$1 === "/" ? path : path.replaceAll("/", sep$1);
123
169
  }
124
- async function gitReadAsync(dir, args, { removal, env, signal, declareFor, untrimmed }) {
170
+ async function gitReadAsync(dir, args, { removal, timeoutThrows, env, signal, declareFor, untrimmed }) {
125
171
  signal ??= getExecutor().reportSignal?.();
126
172
  try {
127
173
  return await gitAsync(dir, args, {
@@ -135,7 +181,7 @@ async function gitReadAsync(dir, args, { removal, env, signal, declareFor, untri
135
181
  } catch (error) {
136
182
  if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
137
183
  signal?.throwIfAborted();
138
- if (removal && isTimeoutError(error)) throw error;
184
+ if ((removal || timeoutThrows) && isTimeoutError(error)) throw error;
139
185
  return null;
140
186
  }
141
187
  }
@@ -213,7 +259,8 @@ async function warmWorktreePaths(cwd, operation) {
213
259
  const options = {
214
260
  env: operation?.env,
215
261
  signal: operation?.signal,
216
- declareFor: operation
262
+ declareFor: operation,
263
+ timeoutThrows: true
217
264
  };
218
265
  const currentRoot = await repoRootAsync(cwd, options);
219
266
  if (!currentRoot) throw new Error("Not a git repository.");
@@ -334,7 +381,8 @@ function missingDestinationReason(target, rel) {
334
381
  }
335
382
  return lstatSync$1(join$1(target, rel), { throwIfNoEntry: false }) ? "exists" : null;
336
383
  }
337
- async function removeCopiedExclusionsAsync(path, rel, signal) {
384
+ const SWEEP_CONCURRENCY = 32;
385
+ async function removeExclusionsIn({ path, rel }, signal) {
338
386
  signal.throwIfAborted();
339
387
  const entries = await readdir(path, { withFileTypes: true });
340
388
  const excluded = entries.filter((entry) => isCarrySkipped(`${rel}/${entry.name}`));
@@ -351,10 +399,48 @@ async function removeCopiedExclusionsAsync(path, rel, signal) {
351
399
  await chmod(path, stat.mode);
352
400
  }
353
401
  }
402
+ const children = [];
354
403
  for (const entry of entries) {
355
404
  const child = `${rel}/${entry.name}`;
356
- if (entry.isDirectory() && !isCarrySkipped(child)) await removeCopiedExclusionsAsync(join$1(path, entry.name), child, signal);
405
+ if (entry.isDirectory() && !isCarrySkipped(child)) children.push({
406
+ path: join$1(path, entry.name),
407
+ rel: child
408
+ });
357
409
  }
410
+ return children;
411
+ }
412
+ async function removeCopiedExclusionsAsync(path, rel, signal) {
413
+ let active = 0;
414
+ const waiting = [];
415
+ const acquire = async () => {
416
+ if (active < SWEEP_CONCURRENCY) active += 1;
417
+ else await new Promise((wake) => waiting.push(wake));
418
+ };
419
+ const release = () => {
420
+ const next = waiting.shift();
421
+ if (next) next();
422
+ else active -= 1;
423
+ };
424
+ let failure;
425
+ const visit = async (directory) => {
426
+ await acquire();
427
+ let children;
428
+ try {
429
+ if (failure) return;
430
+ children = await removeExclusionsIn(directory, signal);
431
+ } catch (error) {
432
+ failure ??= { error };
433
+ return;
434
+ } finally {
435
+ release();
436
+ }
437
+ await Promise.all(children.map(visit));
438
+ };
439
+ await visit({
440
+ path,
441
+ rel
442
+ });
443
+ if (failure) throw failure.error;
358
444
  }
359
445
  async function cloneIgnoredEntries({ root, target, patterns, operation }) {
360
446
  const options = {
@@ -377,6 +463,8 @@ async function cloneIgnoredEntries({ root, target, patterns, operation }) {
377
463
  const skipped = [];
378
464
  const failed = [];
379
465
  let cloned = true;
466
+ let helper;
467
+ const clonefile = () => helper ??= clonefileHelper(operation);
380
468
  for (const rel of entries) {
381
469
  operation.signal.throwIfAborted();
382
470
  const from = join$1(root, rel);
@@ -393,16 +481,23 @@ async function cloneIgnoredEntries({ root, target, patterns, operation }) {
393
481
  }
394
482
  await mkdir(dirname$1(to), { recursive: true });
395
483
  copying = true;
396
- try {
397
- await getExecutor().runFileAsync("cp", [
398
- "-Rc",
399
- from,
400
- to
401
- ], options);
402
- } catch (cause) {
403
- if (cause instanceof WorkspaceProcessCleanupError || cause instanceof Error && "reportCleanup" in cause) throw cause;
404
- operation.signal.throwIfAborted();
405
- await removeTemporaryEntryAsync(to);
484
+ const copiedWith = async (file, args) => {
485
+ try {
486
+ await getExecutor().runFileAsync(file, args, options);
487
+ return true;
488
+ } catch (cause) {
489
+ if (cause instanceof WorkspaceProcessCleanupError || cause instanceof Error && "reportCleanup" in cause) throw cause;
490
+ operation.signal.throwIfAborted();
491
+ await removeTemporaryEntryAsync(to);
492
+ return false;
493
+ }
494
+ };
495
+ const directoryClone = (await lstat(from)).isDirectory() ? await clonefile() : null;
496
+ if (!(directoryClone && await copiedWith(directoryClone, [from, to])) && !await copiedWith("cp", [
497
+ "-Rc",
498
+ from,
499
+ to
500
+ ])) {
406
501
  await getExecutor().runFileAsync("cp", [
407
502
  "-R",
408
503
  from,
@@ -1,4 +1,4 @@
1
- import { a as layerFile, c as settingEntry, i as SettingsRefusal, n as readScope, o as readSettingsContext, r as requireSetting, s as readSettingsPayload, t as mutateSetting } from "./settings-write-Chuy7Cte.mjs";
1
+ import { a as layerFile, c as settingEntry, i as SettingsRefusal, n as readScope, o as readSettingsContext, r as requireSetting, s as readSettingsPayload, t as mutateSetting } from "./settings-write--gj6MACV.mjs";
2
2
  import { SETTING_SCOPES } from "@stim-cli/core/state";
3
3
  import chalk from "chalk";
4
4
  //#region src/commands/settings.ts
@@ -1,10 +1,10 @@
1
- import { i as withWorkspaceProcessLock } from "./exec-DEoAhcze.mjs";
2
- import { A as writeConfigSettingAsync, O as withConfigLockAsync$1, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-DlrsduwG.mjs";
3
- import { a as killOnAbort, i as gitPath } from "./operation-exec-BW0em61G.mjs";
4
- import { it as unknownSettingKeys, z as readCommittedSettings } from "./settings-C7O8A6ZI.mjs";
5
- import { r as findCommandWorkspaceAsync, t as NO_PROJECT_REFUSAL } from "./project-DhCtQWWU.mjs";
6
- import { i as stimDesktopInstalledAsync, n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-D5OGpYZM.mjs";
7
- import { n as recordingLayers, t as deleteTurnedOffRecordings } from "./recordings-CQmfzAh7.mjs";
1
+ import { u as withWorkspaceProcessLock } from "./exec-VFRpu1iA.mjs";
2
+ import { A as writeConfigSettingAsync, O as withConfigLockAsync$1, _ as loadConfig$1, f as getConfigPath$1, h as getRepoSettings, m as getProjectSettings } from "./config-BZhXY15H.mjs";
3
+ import { a as killOnAbort, i as gitPath } from "./operation-exec-CUZifp6Z.mjs";
4
+ import { it as unknownSettingKeys, z as readCommittedSettings } from "./settings-RKwY33wv.mjs";
5
+ import { r as findCommandWorkspaceAsync, t as NO_PROJECT_REFUSAL } from "./project-Dz-hWkb-.mjs";
6
+ import { i as stimDesktopInstalledAsync, n as settingDefault, r as stimDesktopInstalled } from "./stim-desktop-DS0mzR8U.mjs";
7
+ import { n as recordingLayers, t as deleteTurnedOffRecordings } from "./recordings-CdPwYwvs.mjs";
8
8
  import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { configDir } from "@stim-cli/core";
@@ -2361,6 +2361,19 @@
2361
2361
  ]
2362
2362
  }
2363
2363
  },
2364
+ "easMaxDurationMinutes": {
2365
+ "description": "Maximum minutes of each EAS Simulator session Stim creates, after which EAS stops it and stops billing; stim stop and gc --delete end a session sooner. EAS rejects a value above the account plan's session limit",
2366
+ "type": "integer",
2367
+ "minimum": 1,
2368
+ "default": 30,
2369
+ "x-stim": {
2370
+ "key": "remote.easMaxDurationMinutes",
2371
+ "kind": "number",
2372
+ "scopes": [
2373
+ "machine"
2374
+ ]
2375
+ }
2376
+ },
2364
2377
  "build": {
2365
2378
  "description": "Build placement: auto follows remote.buildMode within enabled automatic pool members; local always builds here; a name in remote.machines requires that remote machine without fallback. The ios, android and macos --remote-build flag overrides this setting and its environment override",
2366
2379
  "type": "string",
@@ -1,5 +1,5 @@
1
- import { g as process_identity_exports, m as operationState, p as isTermination, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
2
- import { d as getConfigDir } from "./config-DlrsduwG.mjs";
1
+ import { D as ownership_claim_exports, S as process_identity_exports, b as operationState, t as getExecutor, y as isTermination } from "./exec-VFRpu1iA.mjs";
2
+ import { d as getConfigDir } from "./config-BZhXY15H.mjs";
3
3
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
4
  import { join } from "node:path";
5
5
  //#region src/engine/simslim.ts
@@ -1,9 +1,9 @@
1
- import { G as projectDeviceSlots, U as parseDeviceSlotKey, p as getProject } from "./config-DlrsduwG.mjs";
2
- import { r as readWorkspaceState$1 } from "./workspace-state-U08dEzhG.mjs";
3
- import { gn as deviceShellArg } from "./budget-3ivAK99e.mjs";
4
- import { y as fileLeaseIo } from "./activity-BpWUPgUm.mjs";
5
- import { O as getAvdNameForSerialAsync, j as listAdbDevicesAsync } from "./android-C7sVmyI9.mjs";
6
- import { f as listAllIosSimsAsync } from "./ios-hcBpnEGk.mjs";
1
+ import { G as projectDeviceSlots, U as parseDeviceSlotKey, p as getProject } from "./config-BZhXY15H.mjs";
2
+ import { r as readWorkspaceState$1 } from "./workspace-state-CeBpIZgr.mjs";
3
+ import { O as getAvdNameForSerialAsync, j as listAdbDevicesAsync } from "./android-882m4hxX.mjs";
4
+ import { vn as deviceShellArg } from "./budget-dPGuhNai.mjs";
5
+ import { y as fileLeaseIo } from "./activity-Dq3ZcdD8.mjs";
6
+ import { f as listAllIosSimsAsync } from "./ios-CVUFFZ6C.mjs";
7
7
  import { hostedNativeRecords } from "@stim-cli/core/state";
8
8
  //#region src/engine/slot-launch.ts
9
9
  function launchSlotScope(root, slot = "default") {
@@ -1,6 +1,5 @@
1
- import { h as withDrainingCancellation, t as getExecutor } from "./exec-DEoAhcze.mjs";
2
- import { f as throwIfCancelled } from "./operation-exec-BW0em61G.mjs";
3
- import { d as runCancellationSignal } from "./native-run-Dv88i0aU.mjs";
1
+ import { c as throwIfCancelled, t as getExecutor, x as withDrainingCancellation } from "./exec-VFRpu1iA.mjs";
2
+ import { d as runCancellationSignal } from "./native-run-BmH7U8pr.mjs";
4
3
  import { existsSync, lstatSync, promises, readdirSync, realpathSync, statSync } from "node:fs";
5
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
5
  import { validMacosResourceDestination } from "@stim-cli/core";
@@ -1,18 +1,17 @@
1
- import { a as workspaceProcessLockError, g as process_identity_exports, h as withDrainingCancellation, i as withWorkspaceProcessLock, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
1
+ import { D as ownership_claim_exports, S as process_identity_exports, a as waitForGroupExit, c as throwIfCancelled, d as workspaceProcessLockError, l as WorkspaceProcessCleanupError, o as cancellableSleep, r as groupExitBudgetMs, t as getExecutor, u as withWorkspaceProcessLock, x as withDrainingCancellation } from "./exec-VFRpu1iA.mjs";
2
2
  import { l as workspaceAgentDeviceDir, m as workspaceLogsDir$1, n as ensureWorkspaceStorage, s as supervisorLogFile } from "./paths-DQtTgsBf.mjs";
3
- import { E as upsertProject, O as withConfigLockAsync$1, p as getProject } from "./config-DlrsduwG.mjs";
4
- import { _ as stepTimer, l as phaseLine } from "./command-output-YKGWivpG.mjs";
5
- import { f as throwIfCancelled, u as cancellableSleep } from "./operation-exec-BW0em61G.mjs";
6
- import { i as recordWorkspaceUse, l as withWorkspaceStateLockAsync, n as clearWorkspaceStateKeys, r as readWorkspaceState$1, s as updateWorkspaceStateAsync } from "./workspace-state-U08dEzhG.mjs";
7
- import { A as metroIdleStopMinutesSetting, G as resolveCacheProviderConfig, J as resolveProjectSettingsAsync, K as resolveIosProjectDir, M as metroTunnelSettingError, O as metroCommandSetting, P as ngrokUrlSetting, R as publicUrlSetting, U as remoteIosSetting, V as remoteAndroidSetting, W as resolveAndroidLayout, a as SETTING_SHAPE_REMEDY, ct as detectProviders, et as settingShapeErrors, h as deviceIdleShutdownMinutesSetting, it as unknownSettingKeys, j as metroPortSetting, k as metroCommandSettingError, lt as planMetroReach, m as defaultAndroidLayout, p as cacheProviderSettingError, rt as tunnelModeSetting, st as PUBLIC_METRO_ENV } from "./settings-C7O8A6ZI.mjs";
8
- import { E as MODE_EXPO, M as readMetroTunnel, O as clearManagedMetroTunnel, S as readAppJson, T as MODE_COMMAND, c as projectProblem, i as findProjectRoot$1, t as NO_PROJECT_REFUSAL, v as detectIsExpo, w as MODE_BARE, x as readAppConfigText } from "./project-DhCtQWWU.mjs";
3
+ import { E as upsertProject, O as withConfigLockAsync$1, p as getProject, x as releaseMetroPort } from "./config-BZhXY15H.mjs";
4
+ import { _ as stepTimer, l as phaseLine } from "./command-output-DxC0k2rk.mjs";
5
+ import { i as recordWorkspaceUse, l as withWorkspaceStateLockAsync, n as clearWorkspaceStateKeys, r as readWorkspaceState$1, s as updateWorkspaceStateAsync } from "./workspace-state-CeBpIZgr.mjs";
6
+ import { A as metroIdleStopMinutesSetting, G as resolveCacheProviderConfig, J as resolveProjectSettingsAsync, K as resolveIosProjectDir, M as metroTunnelSettingError, O as metroCommandSetting, P as ngrokUrlSetting, R as publicUrlSetting, U as remoteIosSetting, V as remoteAndroidSetting, W as resolveAndroidLayout, a as SETTING_SHAPE_REMEDY, ct as detectProviders, et as settingShapeErrors, h as deviceIdleShutdownMinutesSetting, it as unknownSettingKeys, j as metroPortSetting, k as metroCommandSettingError, lt as planMetroReach, m as defaultAndroidLayout, p as cacheProviderSettingError, rt as tunnelModeSetting, st as PUBLIC_METRO_ENV } from "./settings-RKwY33wv.mjs";
7
+ import { E as MODE_EXPO, M as readMetroTunnel, O as clearManagedMetroTunnel, S as readAppJson, T as MODE_COMMAND, c as projectProblem, i as findProjectRoot$1, t as NO_PROJECT_REFUSAL, v as detectIsExpo, w as MODE_BARE, x as readAppConfigText } from "./project-Dz-hWkb-.mjs";
8
+ import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-DS0mzR8U.mjs";
9
+ import { _ as PORT_INSPECTION_REMEDY, a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, f as signalProcessTree, g as reserveMetroPort, h as isMetroRunning, m as createPortProbe, u as processCwds, v as PortInspectionError } from "./metro-Bu8LBfzS.mjs";
9
10
  import { i as levelRank } from "./ndjson-CgE_o3Dl.mjs";
10
- import { _ as PORT_INSPECTION_REMEDY, a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, f as signalProcessTree, g as reserveMetroPort, h as isMetroRunning, u as processCwds, v as PortInspectionError } from "./metro-DiXtA3Ty.mjs";
11
- import { In as runProcessOperation, Kn as readProjectConfig, Pn as exitAfterFlush, Zn as remoteCliInvocation, p as stopOwnedMetroForReset, t as budgetGate } from "./budget-3ivAK99e.mjs";
12
- import { _ as stopTunnel, g as startTunnelSequence, h as resolveSupervisorTarget, v as terminateChild, x as withManagedTunnelLock, y as withManagedRemoteWorktreeLock } from "./workspaces-e41xsjKr.mjs";
13
- import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-D5OGpYZM.mjs";
11
+ import { $n as remoteCliInvocation, In as exitAfterFlush, Jn as readProjectConfig, Rn as runProcessOperation, p as stopOwnedMetroForReset, t as budgetGate } from "./budget-dPGuhNai.mjs";
12
+ import { S as withManagedTunnelLock, _ as startTunnelSequence, b as withManagedRemoteWorktreeLock, g as resolveSupervisorTarget, v as stopTunnel, y as terminateChild } from "./workspaces-CSV2Orf4.mjs";
14
13
  import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
15
- import { n as windowsLauncherArgs } from "./detached-entry-BwNEtIrP.mjs";
14
+ import { n as windowsLauncherArgs } from "./detached-entry-KmieOKgN.mjs";
16
15
  import { appendFileSync, closeSync, mkdirSync, openSync, readFileSync } from "node:fs";
17
16
  import { dirname } from "node:path";
18
17
  import { ambientContext, ambientEnv } from "@stim-cli/core";
@@ -244,12 +243,11 @@ const DEFAULT_START_DEPS = {
244
243
  if (child.pid && process.platform !== "win32") try {
245
244
  signalProcessTree(child.pid, "SIGTERM", { group: true });
246
245
  } catch {}
247
- return terminateChild(child, {
246
+ return await terminateChild(child, {
248
247
  alreadyExited: false,
249
248
  timeoutMs: 1e3,
250
- isAlive: pidExists,
251
- waitForGroup: process.platform !== "win32"
252
- });
249
+ isAlive: pidExists
250
+ }) && (process.platform === "win32" || !child.pid || await waitForGroupExit(child.pid, groupExitBudgetMs()));
253
251
  },
254
252
  withWorktreeLock: withManagedRemoteWorktreeLock,
255
253
  withTunnelLock: withManagedTunnelLock,
@@ -372,7 +370,19 @@ var StartRefusal = class extends Error {
372
370
  this.refusal = refusal;
373
371
  }
374
372
  };
375
- async function startDevServer({ root, settings, settingsContext, waitSeconds = DEFAULT_WAIT_SECONDS, remote: remoteFlag = false, resetCache = false, out, note }, overrides = {}) {
373
+ var MetroPortRace = class extends StartRefusal {
374
+ port;
375
+ constructor(refusal, port) {
376
+ super(refusal);
377
+ this.port = port;
378
+ }
379
+ };
380
+ const PORT_RACE_ATTEMPTS = 3;
381
+ function portBindRace(lines, port) {
382
+ const bind = new RegExp(`EADDRINUSE\\b.*:${port}\\b`);
383
+ return lines.some((line) => bind.test(line));
384
+ }
385
+ async function startDevServer({ root, settings, settingsContext, waitSeconds = DEFAULT_WAIT_SECONDS, remote: remoteFlag = false, resetCache = false, hostedPorts, out, note }, overrides = {}) {
376
386
  const d = {
377
387
  ...DEFAULT_START_DEPS,
378
388
  ...overrides
@@ -459,7 +469,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
459
469
  const pid = child?.pid;
460
470
  if (pid) {
461
471
  const alive = () => d.platform === "win32" ? !identity || (0, process_identity_exports.inspectProcessIdentity)(identity) !== "gone" : (0, ownership_claim_exports.processGroupAlive)(pid);
462
- const deadline = Date.now() + 1e3;
472
+ const deadline = Date.now() + groupExitBudgetMs();
463
473
  while (alive() && Date.now() < deadline) await cancellableSleep(25);
464
474
  if (alive()) throw new WorkspaceProcessCleanupError(claims, `Could not confirm cleanup of Expo config process ${pid}.`, "Inspect and stop the Expo config process and its helpers before retrying.");
465
475
  }
@@ -482,7 +492,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
482
492
  env: ambientEnv(),
483
493
  signal: operationSignal
484
494
  });
485
- reclaimed = budget.reclaimed;
495
+ reclaimed = [...reclaimed, ...budget.reclaimed];
486
496
  if (budget.refusal) return fail(budget.refusal);
487
497
  };
488
498
  if (resetCache) {
@@ -506,7 +516,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
506
516
  }
507
517
  const logsDir = workspaceLogsDir$1(root);
508
518
  const logFile = supervisorLogFile(root);
509
- const port = await resolvePort(root, settings, note, fail);
519
+ const port = await resolvePort(root, settings, note, fail, hostedPorts);
510
520
  let publicOrigin = remote ? publicUrl : null;
511
521
  let resolution = await resolveStartupMetro(port, root);
512
522
  let supervisor = liveSupervisor({
@@ -750,7 +760,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
750
760
  throw new WorkspaceProcessCleanupError(claims, `Supervisor pid ${child.pid} published ownership that cannot be reconciled with the workspace registry.`, "Inspect the supervisor and its dev server, then stop them with their original tooling before retrying.");
751
761
  }
752
762
  const same = spawnedIdentity && (0, process_identity_exports.inspectProcessIdentity)(spawnedIdentity) === "same";
753
- const supervisorCleanup = childExit !== null && !(0, ownership_claim_exports.processGroupAlive)(child.pid) || Boolean(same && supervisorSignalable(child) && await d.terminateSupervisorChild(child));
763
+ const supervisorCleanup = childExit !== null && (same ? !(0, ownership_claim_exports.processGroupAlive)(child.pid) : await waitForGroupExit(child.pid, groupExitBudgetMs())) || Boolean(same && supervisorSignalable(child) && await d.terminateSupervisorChild(child));
754
764
  const publicationRacedCleanup = hasPublishedSupervisor();
755
765
  if (!supervisorCleanup || d.platform !== "win32" && (0, ownership_claim_exports.processGroupAlive)(child.pid) || publicationRacedCleanup) {
756
766
  if (publicationRacedCleanup) for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
@@ -763,6 +773,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
763
773
  throw new WorkspaceProcessCleanupError(claims, `Could not reconcile supervisor ownership: ${cause.message}`, "Inspect and stop the supervisor with its original tooling before retrying.");
764
774
  }
765
775
  };
776
+ let refusal;
766
777
  try {
767
778
  if (remote && !tunnel && !publicUrl && tunnelMode !== "off") {
768
779
  const available = d.providers(tunnelMode);
@@ -1117,7 +1128,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
1117
1128
  const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
1118
1129
  const exitInfo = childExit;
1119
1130
  const how = exitInfo ? exitInfo.signal ? `signal ${exitInfo.signal}` : `code ${exitInfo.code}` : "without being observed";
1120
- return fail(gone ? {
1131
+ const failure = gone ? {
1121
1132
  code: "STIM_SUPERVISOR_EXITED",
1122
1133
  message: `The supervisor exited (${how}) before the dev server came up on port ${port}.`,
1123
1134
  lines: failureEvidence({
@@ -1135,7 +1146,9 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
1135
1146
  sinceTs: attemptStartedTs
1136
1147
  }),
1137
1148
  remedy: "It may still be starting. Run `stim stop` to halt it, or `stim logs` to follow along."
1138
- });
1149
+ };
1150
+ if (gone && !remote && metroPortSetting(settings, ambientEnv()).port === null && portBindRace(failure.lines ?? [], port)) throw new MetroPortRace(failure, port);
1151
+ return fail(failure);
1139
1152
  }
1140
1153
  if (managedTunnelExited()) return failExitedManagedTunnel();
1141
1154
  if (tunnel) {
@@ -1165,13 +1178,36 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
1165
1178
  logsDir,
1166
1179
  alreadyRunning: false
1167
1180
  });
1181
+ } catch (error) {
1182
+ if (error instanceof StartRefusal) refusal = error;
1183
+ throw error;
1168
1184
  } finally {
1169
- await reconcileSpawnedSupervisor();
1185
+ await reconcileSpawnedSupervisor().catch((cleanup) => {
1186
+ if (refusal && cleanup instanceof WorkspaceProcessCleanupError) {
1187
+ cleanup.message = `${refusal.message} ${cleanup.message}`;
1188
+ cleanup.cause = refusal;
1189
+ }
1190
+ throw cleanup;
1191
+ });
1192
+ }
1193
+ };
1194
+ const runStartRetryingPortRace = async (claim, tunnelClaim) => {
1195
+ for (let attempt = 1;; attempt++) try {
1196
+ return await runStart(claim, tunnelClaim);
1197
+ } catch (error) {
1198
+ if (!(error instanceof MetroPortRace)) throw error;
1199
+ if (attempt === PORT_RACE_ATTEMPTS) throw new StartRefusal({
1200
+ ...error.refusal,
1201
+ remedy: `Another process took port ${error.port} after Stim reserved it, ${PORT_RACE_ATTEMPTS} times in a row. Run \`stim start\` again; it reserves a free port.`
1202
+ });
1203
+ await withConfigLockAsync$1(() => releaseMetroPort(root, error.port), operationSignal);
1204
+ note(chalk.yellow(`Port ${error.port} was taken by another process while the dev server started.`));
1205
+ note(chalk.dim("Reserving another port and starting again."));
1170
1206
  }
1171
1207
  };
1172
1208
  const startLocked = async () => {
1173
1209
  try {
1174
- return await d.withTunnelLock(root, (tunnelClaim) => withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", (claim) => runStart(claim, tunnelClaim), {
1210
+ return await d.withTunnelLock(root, (tunnelClaim) => withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", (claim) => runStartRetryingPortRace(claim, tunnelClaim), {
1175
1211
  external: true,
1176
1212
  signal: operationSignal
1177
1213
  }), { signal: operationSignal });
@@ -1220,7 +1256,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
1220
1256
  if (error instanceof WorkspaceProcessCleanupError) return {
1221
1257
  ok: false,
1222
1258
  error: startError(error),
1223
- lines: [],
1259
+ lines: error.cause instanceof StartRefusal ? error.cause.refusal.lines ?? [] : [],
1224
1260
  reclaimed,
1225
1261
  retainedClaimRoots: [...new Set(error.claims.map((claim) => claim.root))]
1226
1262
  };
@@ -1233,11 +1269,11 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
1233
1269
  };
1234
1270
  }
1235
1271
  }
1236
- async function resolvePort(root, settings, note, fail) {
1237
- const result = await resolveWorkspaceMetroPort(root, settings, note, "start");
1272
+ async function resolvePort(root, settings, note, fail, hostedPorts) {
1273
+ const result = await resolveWorkspaceMetroPort(root, settings, note, "start", true, hostedPorts);
1238
1274
  return typeof result === "number" ? result : fail(result);
1239
1275
  }
1240
- async function resolveWorkspaceMetroPort(root, settings, note, command, reserve = true) {
1276
+ async function resolveWorkspaceMetroPort(root, settings, note, command, reserve = true, hostedPorts) {
1241
1277
  const setting = metroPortSetting(settings, ambientEnv());
1242
1278
  if (setting.error) return {
1243
1279
  code: "STIM_BAD_ARG",
@@ -1275,23 +1311,27 @@ async function resolveWorkspaceMetroPort(root, settings, note, command, reserve
1275
1311
  message: `Port ${pinned} is held by something else (${held.notOurs}), and metro.port pins this workspace's Metro to it.`,
1276
1312
  remedy: "Stop the process on that port, or set another metro.port."
1277
1313
  };
1314
+ if (hostedPorts && !held.metro && !await hostedPorts.free(pinned)) return hostedPorts.busy(pinned);
1278
1315
  if (recorded === pinned) return recorded;
1279
1316
  } else if (recorded) {
1280
1317
  const held = await resolveStartupMetro(recorded, root);
1281
- if (!held.notOurs) return recorded;
1282
- const fresh = await reservePort(root, null);
1318
+ const hostBusy = !held.notOurs && hostedPorts && !held.metro && !await hostedPorts.free(recorded) ? hostedPorts.machine : null;
1319
+ if (!held.notOurs && !hostBusy) return recorded;
1320
+ const fresh = await reservePort(root, null, hostedPorts);
1283
1321
  if (typeof fresh === "number" && fresh !== recorded) {
1284
- note(chalk.yellow(`Port ${recorded} is held by something else (${held.notOurs}).`));
1322
+ note(chalk.yellow(hostBusy ? `Port ${recorded} is in use on ${hostBusy}, where an Expo dev client loads its bundle from this workspace's Metro port.` : `Port ${recorded} is held by something else (${held.notOurs}).`));
1285
1323
  note(chalk.dim(`Reserved port ${fresh} for this project instead.`));
1286
1324
  }
1287
1325
  return fresh;
1288
1326
  }
1289
1327
  if (!reserve && pinned !== null) return pinned;
1290
- return reservePort(root, pinned);
1328
+ return reservePort(root, pinned, hostedPorts);
1291
1329
  }
1292
- async function reservePort(root, pinned) {
1330
+ async function reservePort(root, pinned, hostedPorts) {
1331
+ const local = hostedPorts && createPortProbe();
1332
+ const isFree = local && (async (port) => await local(port) && await hostedPorts.free(port));
1293
1333
  try {
1294
- return await reserveMetroPort(root, void 0, void 0, pinned, ambientContext()?.signal);
1334
+ return await reserveMetroPort(root, void 0, isFree, pinned, ambientContext()?.signal);
1295
1335
  } catch (error) {
1296
1336
  throwIfCancelled(ambientContext()?.signal, "Metro startup");
1297
1337
  if (error instanceof PortInspectionError) return {
@@ -1,2 +1,2 @@
1
- import { a as registerStart, c as startDevServer, d as supervisorSignalable, f as tailLines, i as readLogTail, l as startFacts, n as liveSupervisor, o as resolveWorkspaceMetroPort, p as wantsExpoOwnTunnel, r as parseWait, s as startCommand, t as failureEvidence, u as supervisorEntry } from "./start-CU8mCPC7.mjs";
1
+ import { a as registerStart, c as startDevServer, d as supervisorSignalable, f as tailLines, i as readLogTail, l as startFacts, n as liveSupervisor, o as resolveWorkspaceMetroPort, p as wantsExpoOwnTunnel, r as parseWait, s as startCommand, t as failureEvidence, u as supervisorEntry } from "./start-BKGrEhzG.mjs";
2
2
  export { startCommand as default, failureEvidence, liveSupervisor, parseWait, readLogTail, registerStart, resolveWorkspaceMetroPort, startDevServer, startFacts, supervisorEntry, supervisorSignalable, tailLines, wantsExpoOwnTunnel };
@@ -1,8 +1,8 @@
1
- import { g as process_identity_exports, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
1
+ import { D as ownership_claim_exports, S as process_identity_exports, l as WorkspaceProcessCleanupError, t as getExecutor } from "./exec-VFRpu1iA.mjs";
2
2
  import { d as workspaceDir, m as workspaceLogsDir$1 } from "./paths-DQtTgsBf.mjs";
3
- import { t as runOperationFile } from "./operation-exec-BW0em61G.mjs";
4
- import { d as writeWorkspaceState, o as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-U08dEzhG.mjs";
5
- import { c as pidExists } from "./metro-DiXtA3Ty.mjs";
3
+ import { t as runOperationFile } from "./operation-exec-CUZifp6Z.mjs";
4
+ import { d as writeWorkspaceState, o as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-CeBpIZgr.mjs";
5
+ import { c as pidExists } from "./metro-Bu8LBfzS.mjs";
6
6
  import { readFileSync, readlinkSync, rmSync } from "node:fs";
7
7
  import { hostname } from "node:os";
8
8
  import { join } from "node:path";
@@ -1,6 +1,6 @@
1
- import { g as process_identity_exports } from "./exec-DEoAhcze.mjs";
1
+ import { S as process_identity_exports } from "./exec-VFRpu1iA.mjs";
2
2
  import { d as workspaceDir, m as workspaceLogsDir$1 } from "./paths-DQtTgsBf.mjs";
3
- import { o as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-U08dEzhG.mjs";
3
+ import { o as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-CeBpIZgr.mjs";
4
4
  import { existsSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { readMacosRecord } from "@stim-cli/core/state";
@@ -1,8 +1,8 @@
1
- import { P as formatBytes } from "./config-DlrsduwG.mjs";
2
- import { o as formatLongDuration } from "./command-output-YKGWivpG.mjs";
3
- import { i as findProjectRoot$1 } from "./project-DhCtQWWU.mjs";
4
- import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-CUlLsbRt.mjs";
5
- import { t as readProjectStats } from "./stats-report-BzaPrRm6.mjs";
1
+ import { P as formatBytes } from "./config-BZhXY15H.mjs";
2
+ import { o as formatLongDuration } from "./command-output-DxC0k2rk.mjs";
3
+ import { i as findProjectRoot$1 } from "./project-Dz-hWkb-.mjs";
4
+ import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-CccomeE0.mjs";
5
+ import { t as readProjectStats } from "./stats-report-n2U5plhk.mjs";
6
6
  import { archivedUsage, readArchives } from "@stim-cli/core/state";
7
7
  import chalk from "chalk";
8
8
  //#region src/commands/stats.ts
@@ -1,4 +1,4 @@
1
- import { i as gitPath } from "./operation-exec-BW0em61G.mjs";
1
+ import { i as gitPath } from "./operation-exec-CUZifp6Z.mjs";
2
2
  import { readStatsReport, statsProjectKey } from "@stim-cli/core/state";
3
3
  //#region src/engine/stats-report.ts
4
4
  /** The stats report for the project at `root`, keyed like the run recorders; Git runs with `env` and dies when `signal` aborts. */