stim 1.23.0 → 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-ybouwJSR.mjs → acquire-artifact-CEs02U2o.mjs} +2 -2
  3. package/dist/{act-DG54PWtJ.mjs → act-Cyvm8xYc.mjs} +40 -25
  4. package/dist/{activity-B5RtpFda.mjs → activity-Dq3ZcdD8.mjs} +6 -6
  5. package/dist/{agent-device-usage-output-UJ5gC80l.mjs → agent-device-usage-output-CccomeE0.mjs} +5 -5
  6. package/dist/{android-C7sVmyI9.mjs → android-882m4hxX.mjs} +140 -46
  7. package/dist/{android-BoUJKbfz.mjs → android-BZppyPe_.mjs} +66 -46
  8. package/dist/{android-DR5lZ2Az.mjs → android-CX5IcRuf.mjs} +13 -3
  9. package/dist/{android-CHH4KLOR.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 +22 -4
  13. package/dist/api.mjs +32 -31
  14. package/dist/{artifact-5RteZem7.mjs → artifact-BR5VNArK.mjs} +17 -16
  15. package/dist/{artifact-Dwdo7k1E.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-CeEVlGug.mjs → browser-web-ycg9qWB8.mjs} +16 -17
  18. package/dist/{budget-DnPX3DXj.mjs → budget-dPGuhNai.mjs} +101 -40
  19. package/dist/{build-CqufZFqy.mjs → build-CaF574eS.mjs} +31 -28
  20. package/dist/{build-cache-Dv7RN200.mjs → build-cache-HFUQ9eLz.mjs} +152 -19
  21. package/dist/{build-plan-C8CIvRyi.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-RcAy92gH.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-D-J9fWGV.mjs → cli-xqAidP7O.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-yyso1bwi.mjs → client-18-ZowoT.mjs} +254 -28
  30. package/dist/collector-run.mjs +6 -6
  31. package/dist/{command-output-Db8BqzxH.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-BnYSDiKa.mjs → dev-client-w_fok0Ek.mjs} +3 -3
  37. package/dist/{device-BvzPUBs3.mjs → device-4RUugw5X.mjs} +16 -16
  38. package/dist/{device-CCkWmkAA.mjs → device-BktRGdjS.mjs} +40 -44
  39. package/dist/{device-capacity-CjaSdrhg.mjs → device-capacity-Ic-kdmEV.mjs} +20 -16
  40. package/dist/device-host-worker.mjs +17 -17
  41. package/dist/{device-ios-uOiEETX2.mjs → device-ios-DzpSlZyd.mjs} +23 -24
  42. package/dist/{device-lease-run-BgwPHc6u.mjs → device-lease-run-DUtkbqBQ.mjs} +5 -5
  43. package/dist/{device-pool-C-ptmZTa.mjs → device-pool-CgBHk401.mjs} +3 -3
  44. package/dist/{doctor-pxxvipYC.mjs → doctor-BZ6oxNum.mjs} +1 -1
  45. package/dist/{doctor-8sr2tFOL.mjs → doctor-C33AzVfR.mjs} +28 -28
  46. package/dist/{doctor-D2SN3Ltx.mjs → doctor-DZgpqR6J.mjs} +17 -16
  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-sy_RNhFt.mjs → doctor-watchman-B453A231.mjs} +5 -5
  50. package/dist/{eas-build-CyjU5K7E.mjs → eas-build-CxKUNHsb.mjs} +4 -4
  51. package/dist/{eas-fallback-DV3Wytf1.mjs → eas-fallback-LMxk9Kw0.mjs} +14 -14
  52. package/dist/{eas-session-ledger-BzzsX97d.mjs → eas-session-ledger-Ck6YXPAO.mjs} +12 -8
  53. package/dist/{error-diagnostics-C6sVoeqO.mjs → error-diagnostics-BkKCZq4P.mjs} +17 -42
  54. package/dist/{exec-DEoAhcze.mjs → exec-VFRpu1iA.mjs} +116 -28
  55. package/dist/{gc-DLbzvTP2.mjs → gc-DDWEAj0w.mjs} +2 -2
  56. package/dist/{gc-CDoogxAI.mjs → gc-yVKpAOKM.mjs} +205 -28
  57. package/dist/{guide-BC37GcDG.mjs → guide-Bggftwc5.mjs} +252 -73
  58. package/dist/{hosted-android-CkclQig_.mjs → hosted-android-D4dweBof.mjs} +4 -4
  59. package/dist/{hosted-client-ByuMMO5s.mjs → hosted-client-BBTVe3X6.mjs} +4 -4
  60. package/dist/{hosted-ios-DtYHeuYR.mjs → hosted-ios-Sn0djzg8.mjs} +1 -1
  61. package/dist/{hosted-logs-Br84qWy1.mjs → hosted-logs-CYIZ5ubJ.mjs} +11 -6
  62. package/dist/{hosted-macos-DdtYajZt.mjs → hosted-macos-CXjsUNBR.mjs} +7 -7
  63. package/dist/{hosted-native-UPI9zdZ4.mjs → hosted-native-Doxbfzem.mjs} +58 -11
  64. package/dist/{idle-shutdown-DO7oIRlc.mjs → idle-shutdown-BInjLKqm.mjs} +10 -10
  65. package/dist/{ios-DZZbNYlY.mjs → ios-B1FXRCMX.mjs} +71 -44
  66. package/dist/{ios-vbUOqGXu.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-JC3uWM4K.mjs → ios-device-BI38NFFH.mjs} +3 -3
  71. package/dist/{ios-project-CVyNwjAF.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-C3QxUFJR.mjs → js-swap-BvrQvXHY.mjs} +7 -6
  75. package/dist/{launch-CHY3p-yz.mjs → launch-BVlyQw3T.mjs} +51 -49
  76. package/dist/{launch-Bz7JtaC4.mjs → launch-SfzGH_s9.mjs} +48 -29
  77. package/dist/{logs-mxqsWpJs.mjs → logs-DXE32kyb.mjs} +16 -9
  78. package/dist/{logs-CtNeNDuq.mjs → logs-ovlU7m7N.mjs} +8 -6
  79. package/dist/{macos-inNFbBWc.mjs → macos-B3BveDrU.mjs} +1 -1
  80. package/dist/{macos-u0BoHKnO.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-CdCSylTn.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-BUTu8T7A.mjs → native-run-BmH7U8pr.mjs} +6 -6
  89. package/dist/{native-runtime-CRSOZxKY.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-G6rkkhxu.mjs → native-xcode-inputs-irplbQb0.mjs} +2 -2
  92. package/dist/{native-xcode-ios-_os2PlCD.mjs → native-xcode-ios-BY-6hh6M.mjs} +18 -18
  93. package/dist/offload-worker.d.mts +7 -2
  94. package/dist/offload-worker.mjs +104 -43
  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-BU42oOyT.mjs → plan-placement-4axc0Hs7.mjs} +3 -3
  101. package/dist/{ports-BSmQKzW7.mjs → ports-D-E_OiAO.mjs} +3 -3
  102. package/dist/{prebuild-BFm63bQ0.mjs → prebuild-DmVCf5xb.mjs} +14 -9
  103. package/dist/{project-CTLNHCfE.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-CFgUroxW.mjs → react-native-android-DOinxgwB.mjs} +75 -32
  107. package/dist/{react-native-build-CmJkTljQ.mjs → react-native-build-AuCu-aAP.mjs} +22 -19
  108. package/dist/{react-native-doctor-LTF-Xt8l.mjs → react-native-doctor-2ivJUmwZ.mjs} +10 -9
  109. package/dist/{react-native-ios-ChBF1MkB.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-DF9hqUUB.mjs → reload-C1TTVesC.mjs} +144 -42
  113. package/dist/{reload-RBi1nj6Z.mjs → reload-Ck9RJeSq.mjs} +1 -1
  114. package/dist/{remote-BmvMCcJw.mjs → remote-DEZ7AOVq.mjs} +7 -7
  115. package/dist/{result-Cx5CRLvS.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-BTiGPEjS.mjs → server-command-C0pmjNdG.mjs} +6 -6
  120. package/dist/server-expo-CEntXzRw.mjs +2 -0
  121. package/dist/{server-expo-D9lEz4aq.mjs → server-expo-Tt_hLRpy.mjs} +37 -13
  122. package/dist/{settings-C7O8A6ZI.mjs → settings-RKwY33wv.mjs} +114 -19
  123. package/dist/{settings-eKF_vJJs.mjs → settings-kkt0vP4w.mjs} +1 -1
  124. package/dist/{settings-write-79mQRr9C.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-D7M2nE4b.mjs → slot-launch-A1BgotaM.mjs} +6 -6
  128. package/dist/{stage-DKNM4ZPd.mjs → stage-ERTMh88n.mjs} +2 -3
  129. package/dist/{start-bjfiScgz.mjs → start-BKGrEhzG.mjs} +75 -35
  130. package/dist/{start-CRfN1kWV.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-DioA6cBU.mjs → stats-B-ckuiUg.mjs} +5 -5
  134. package/dist/{stats-report-BzaPrRm6.mjs → stats-report-n2U5plhk.mjs} +1 -1
  135. package/dist/{status-kzbeWk4x.mjs → status-DfFp6a76.mjs} +24 -24
  136. package/dist/{status-BRROykC8.mjs → status-Fect2w9R.mjs} +1 -1
  137. package/dist/{status-Eqx_20WC.mjs → status-Pw-807f0.mjs} +1 -1
  138. package/dist/{status-watch-BIqqbJ6q.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-B18d8KDw.mjs → stop-BFu7X_Do.mjs} +2 -2
  143. package/dist/{stop-B2uLQfoM.mjs → stop-CL1kDHds.mjs} +55 -21
  144. package/dist/{stop-DzLJc7Ei.mjs → stop-DhclRwOO.mjs} +5 -5
  145. package/dist/supervisor-run.mjs +16 -16
  146. package/dist/{support-BTpZ6RAJ.mjs → support-B1W-hmC2.mjs} +8 -8
  147. package/dist/{support-DTBV56gy.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-r_WSKlXr.mjs → swiftpm-macos-BVCP5mO4.mjs} +14 -13
  150. package/dist/{toolchain-GVgnf8d1.mjs → toolchain-BjgKdoEf.mjs} +1 -1
  151. package/dist/{toolchain-CWeGJgCc.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-CwREhjD-.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-BxzYlo3P.mjs → workspaces-CSV2Orf4.mjs} +36 -32
  162. package/dist/{worktree-BxfVxG_b.mjs → worktree-DEDUzO_L.mjs} +1 -1
  163. package/dist/{worktree-8lqn_lBp.mjs → worktree-Dw8kfacb.mjs} +324 -99
  164. package/dist/{xcode-iQnROM5o.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-C-Buqvn_.mjs +0 -2
  168. package/dist/trigger-yb_nFzPF.mjs +0 -2
  169. package/dist/web-Cduk0NSE.mjs +0 -2
@@ -1,14 +1,13 @@
1
- import { t as getExecutor } from "./exec-DEoAhcze.mjs";
1
+ import { o as cancellableSleep, t as getExecutor } from "./exec-VFRpu1iA.mjs";
2
2
  import { n as stripAnsi, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
3
- import { u as cancellableSleep } from "./operation-exec-BW0em61G.mjs";
4
- import { q as resolveProjectSettings } from "./settings-C7O8A6ZI.mjs";
5
- import { C as resolvePackageJson } from "./project-CTLNHCfE.mjs";
3
+ import { q as resolveProjectSettings } from "./settings-RKwY33wv.mjs";
4
+ import { C as resolvePackageJson } from "./project-Dz-hWkb-.mjs";
6
5
  import { r as createNdjsonWriter } from "./ndjson-CgE_o3Dl.mjs";
7
6
  import { a as expoMetroStoreEnv, c as registerMetroStore, f as resolveMetroSharedCache, i as expoMetroConfigPath, n as supervisorError, o as metroStoreConfirmedRoot, s as metroStoreRoot } from "./errors-ccaPc4ox.mjs";
8
7
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
9
8
  import { delimiter, dirname, join } from "node:path";
10
9
  import { LOG_ROTATE_BYTES } from "@stim-cli/core";
11
- import { projectNodeBin, satisfiesNodePin } from "@stim-cli/core/toolchain";
10
+ import { meetsNodePinMajor, projectNodeBin, projectNodePin, resolveNodePinSpec, satisfiesNodePin } from "@stim-cli/core/toolchain";
12
11
  //#region src/supervisor/child-server.ts
13
12
  function superviseChildServer({ mode, child, log, toRecord, signal, alive = null, killTimeoutMs, onRecord = null }) {
14
13
  let lastMsg = null;
@@ -86,6 +85,7 @@ function superviseChildServer({ mode, child, log, toRecord, signal, alive = null
86
85
  }
87
86
  //#endregion
88
87
  //#region src/supervisor/project-node.ts
88
+ /** The version of the `node` that `env`'s `PATH` runs. */
89
89
  function pathNodeVersion(env) {
90
90
  const childEnv = {};
91
91
  for (const [key, value] of Object.entries(env)) if (value !== void 0) childEnv[key] = value;
@@ -95,18 +95,20 @@ function pathNodeVersion(env) {
95
95
  });
96
96
  }
97
97
  /**
98
- * `env` with the Node that `root` pins (`.nvmrc`, `.node-version`, `.tool-versions` or Volta) first on `PATH`,
99
- * for the dev server Stim starts through its `#!/usr/bin/env node` script. It changes nothing when the project pins
100
- * no Node, when no installed Node satisfies the pin, or when the `node` already on `PATH` satisfies it.
98
+ * `env` with the Node `projectNodeBin` picks for the pin in `root`, or the nearest directory above it up to `top`
99
+ * (`.nvmrc`, `.node-version`, `.tool-versions` or Volta), first on `PATH`, for the processes Stim starts that run the
100
+ * project's own Node code. It changes nothing when the project pins no Node, when no install fits the pin, or when
101
+ * the `node` already on `PATH` satisfies it: matching the pin, or, when no install matches it, of its major and not
102
+ * older than it.
101
103
  */
102
- function withProjectNode(root, env, { nodeVersion = pathNodeVersion } = {}) {
103
- const pinned = projectNodeBin(root, env);
104
+ function withProjectNode(root, env, { nodeVersion = pathNodeVersion, top = root } = {}) {
105
+ const pinned = projectNodeBin(root, env, void 0, top);
104
106
  if (!pinned) return {
105
107
  env,
106
108
  pinned: null
107
109
  };
108
110
  const current = nodeVersion(env);
109
- if (current && satisfiesNodePin(current, pinned.spec)) return {
111
+ if (current && (satisfiesNodePin(current, pinned.spec) || pinned.match === "major" && meetsNodePinMajor(current, pinned.spec))) return {
110
112
  env,
111
113
  pinned: null
112
114
  };
@@ -118,6 +120,24 @@ function withProjectNode(root, env, { nodeVersion = pathNodeVersion } = {}) {
118
120
  pinned
119
121
  };
120
122
  }
123
+ /**
124
+ * Why the project's own Node code may have failed under `env`: the pin that `root`, or the nearest directory above it
125
+ * up to `top`, sets when neither an install nor the `node` on `PATH` fits it as `withProjectNode` decides, else null.
126
+ */
127
+ function unmetNodePin(root, env, { nodeVersion = pathNodeVersion, top = root } = {}) {
128
+ const pin = projectNodePin(root, void 0, top);
129
+ if (!pin || projectNodeBin(root, env, void 0, top)) return null;
130
+ const spec = resolveNodePinSpec(pin.spec, env);
131
+ const current = nodeVersion(env) ?? "unknown";
132
+ if (!spec) return `Stim cannot resolve ${pin.file} ${pin.spec} through nvm's aliases, so Node ${current} on PATH ran.`;
133
+ if (satisfiesNodePin(current, spec) || meetsNodePinMajor(current, spec)) return null;
134
+ return `Node ${current} on PATH does not satisfy ${pin.file} ${pin.spec}, and no installed Node does.`;
135
+ }
136
+ /** The log line naming the Node `withProjectNode` put first on `PATH` and the pin it stands for. */
137
+ function projectNodeLine(pinned) {
138
+ const line = `Node ${pinned.version} from ${pinned.bin}, for ${pinned.pin} ${pinned.spec}`;
139
+ return pinned.match === "major" ? `${line} (no install has ${pinned.spec}; the newest ${pinned.version.split(".")[0]}.x not older than it)` : line;
140
+ }
121
141
  //#endregion
122
142
  //#region src/supervisor/server-expo.ts
123
143
  function resolveExpoBin(root) {
@@ -253,6 +273,9 @@ function expoProxyEnv(env) {
253
273
  const publicUrl = env.STIM_METRO_PUBLIC_URL?.trim();
254
274
  return publicUrl ? { EXPO_PACKAGER_PROXY_URL: publicUrl.replace(/\/+$/, "") } : {};
255
275
  }
276
+ function expoTelemetryEnv(env) {
277
+ return env.EXPO_NO_TELEMETRY === void 0 ? { EXPO_NO_TELEMETRY: "1" } : {};
278
+ }
256
279
  const WAITING_ON_RE = /^Waiting on (\S+)/;
257
280
  const URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
258
281
  const HTTP_SCHEME_RE = /^https?:\/\//i;
@@ -335,7 +358,7 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
335
358
  src: "metro",
336
359
  level: "info",
337
360
  event: "project_node",
338
- msg: `Metro runs on Node ${node.pinned.version} from ${node.pinned.bin}, which ${node.pinned.pin} pins`
361
+ msg: `Metro runs on ${projectNodeLine(node.pinned)}`
339
362
  });
340
363
  const child = spawn(command, commandArgs, {
341
364
  cwd: root,
@@ -349,6 +372,7 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
349
372
  ...node.env,
350
373
  FORCE_COLOR: "0",
351
374
  ...expoProxyEnv(process.env),
375
+ ...expoTelemetryEnv(process.env),
352
376
  ...storeEnv,
353
377
  ...tunnel ? { EXPO_UNSTABLE_TUNNEL_V2: "1" } : {}
354
378
  }
@@ -372,4 +396,4 @@ async function startExpoServer({ root, port, logsDir, writer = null, spawnFn = n
372
396
  });
373
397
  }
374
398
  //#endregion
375
- export { expoProxyEnv as a, inferLevel as c, parseExpoWaitingOnUrl as d, recordFromLine as f, superviseChildServer as h, expoBinRefusal as i, isBundleMarker as l, withProjectNode as m, expoBinFromPackage as n, expoSdkMajor as o, startExpoServer as p, expoBinPath as r, findBinUpward as s, cleanLine as t, isDevClientNavigationNotice as u };
399
+ export { unmetNodePin as _, expoProxyEnv as a, findBinUpward as c, isDevClientNavigationNotice as d, parseExpoWaitingOnUrl as f, projectNodeLine as g, pathNodeVersion as h, expoBinRefusal as i, inferLevel as l, startExpoServer as m, expoBinFromPackage as n, expoSdkMajor as o, recordFromLine as p, expoBinPath as r, expoTelemetryEnv as s, cleanLine as t, isBundleMarker as u, withProjectNode as v, superviseChildServer as y };
@@ -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-79mQRr9C.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-CTLNHCfE.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-DnPX3DXj.mjs";
4
- import { y as fileLeaseIo } from "./activity-B5RtpFda.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-BUTu8T7A.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-Db8BqzxH.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-CTLNHCfE.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-DnPX3DXj.mjs";
12
- import { _ as stopTunnel, g as startTunnelSequence, h as resolveSupervisorTarget, v as terminateChild, x as withManagedTunnelLock, y as withManagedRemoteWorktreeLock } from "./workspaces-BxzYlo3P.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-bjfiScgz.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 };