stim 1.21.0 → 1.22.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 (184) hide show
  1. package/README.md +60 -7
  2. package/dist/{acquire-artifact-CD4lbiWE.mjs → acquire-artifact-Co2k4f7O.mjs} +21 -3
  3. package/dist/{act-DHAw2A2S.mjs → act-DsuqEkiM.mjs} +298 -164
  4. package/dist/{activity-BzVfHSrY.mjs → activity-CMLmzp7o.mjs} +33 -12
  5. package/dist/{agent-device-usage-output-zh1vhbLp.mjs → agent-device-usage-output-DUFaTMdd.mjs} +7 -7
  6. package/dist/{android-BLgbNqox.mjs → android-ByRMUoiJ.mjs} +66 -52
  7. package/dist/{android-DUYkNF4u.mjs → android-C7sVmyI9.mjs} +160 -59
  8. package/dist/{android-CV_GCELZ.mjs → android-DGkNqa_c.mjs} +5 -5
  9. package/dist/{android-BEJ8Jpl7.mjs → android-DR5lZ2Az.mjs} +2 -2
  10. package/dist/android-cas-compiler.mjs +1 -1
  11. package/dist/{android-cas-Ct5b1xCK.mjs → android-cas-ngferVq2.mjs} +3 -3
  12. package/dist/{client-B7QVOlCr.d.mts → android-project-DVWyCbWU.d.mts} +145 -7
  13. package/dist/api.d.mts +132 -118
  14. package/dist/api.mjs +239 -42
  15. package/dist/{artifact-B8T3S5gi.mjs → artifact-8EY8SAPw.mjs} +40 -35
  16. package/dist/{artifact-DAuZ-FNU.mjs → artifact-DJPy518Y.mjs} +69 -40
  17. package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
  18. package/dist/{browser-web-QQzVjz3O.mjs → browser-web-Bcv99PBM.mjs} +187 -59
  19. package/dist/{budget-Bs6SxxzO.mjs → budget-Cvwn54qV.mjs} +830 -699
  20. package/dist/{build-CoPzQRNw.mjs → build-CabC3G23.mjs} +34 -27
  21. package/dist/{build-cache-DmyR9Zzc.mjs → build-cache-Dv7RN200.mjs} +4 -4
  22. package/dist/{build-plan-CaFskB7_.mjs → build-plan-DUC8Ka9y.mjs} +74 -14
  23. package/dist/{cache-manifest-DkkqguoK.mjs → cache-manifest-DSeQjt-Q.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{ccache-BJRZ9pG3.mjs → ccache-Btwc36-I.mjs} +6 -6
  26. package/dist/{cdp-CWmIUMbw.mjs → cdp-3fwICjzi.mjs} +1 -1
  27. package/dist/{chrome-B6tfCta1.mjs → chrome-Bbw689-6.mjs} +1 -1
  28. package/dist/{cli-0DYf8QhQ.mjs → cli-CHZa9Lvk.mjs} +20 -20
  29. package/dist/cli.mjs +1 -1
  30. package/dist/{client-UXD70ZbM.mjs → client-OyRoEuSr.mjs} +218 -47
  31. package/dist/collector-run.d.mts +1 -1
  32. package/dist/collector-run.mjs +6 -6
  33. package/dist/{command-output-B1UrjZHL.mjs → command-output-gwpm28fu.mjs} +1 -1
  34. package/dist/{config-CYcBF478.mjs → config-DlrsduwG.mjs} +17 -11
  35. package/dist/{created-devices-CqeBlrEn.mjs → created-devices-x9rN6bhs.mjs} +13 -3
  36. package/dist/{dependency-state-BbKQW83h.mjs → dependency-state-1I9KZT1Y.mjs} +2 -2
  37. package/dist/{detached-entry-DFRyG1_o.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
  38. package/dist/{dev-client-UOeESKm6.mjs → dev-client-BSDR5h3F.mjs} +2 -2
  39. package/dist/{device-ExJq8Ei7.mjs → device-B-L3Fbxy.mjs} +71 -14
  40. package/dist/{device-DCxH7_By.mjs → device-DklWZLqb.mjs} +16 -16
  41. package/dist/{device-capacity-CMlqEcvw.mjs → device-capacity-DK5_EwRS.mjs} +9 -10
  42. package/dist/device-host-worker.mjs +25 -32
  43. package/dist/{device-ios-DRUeKaFV.mjs → device-ios-DoB6KFCO.mjs} +39 -16
  44. package/dist/{device-lease-run-C8raGFoP.mjs → device-lease-run-xJPQ_QQG.mjs} +5 -5
  45. package/dist/{device-pool-DMRpVX9a.mjs → device-pool-BMkdsefq.mjs} +3 -3
  46. package/dist/{doctor-DTvaDOda.mjs → doctor-Bo7BXLux.mjs} +24 -37
  47. package/dist/{doctor-Dhqebm0q.mjs → doctor-D5_EzwfA.mjs} +19 -16
  48. package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
  49. package/dist/doctor-fingerprint.mjs +1 -1
  50. package/dist/{doctor-BLHCYxzi.mjs → doctor-thjWAV0z.mjs} +1 -1
  51. package/dist/{doctor-watchman-Cv1F10Kj.mjs → doctor-watchman-N2wDotwQ.mjs} +5 -5
  52. package/dist/{eas-build-BomfRnFT.mjs → eas-build-CwknaQYj.mjs} +4 -4
  53. package/dist/{eas-fallback-DJwZpy0c.mjs → eas-fallback-CSgAulr5.mjs} +27 -18
  54. package/dist/{eas-session-ledger-op6A54yi.mjs → eas-session-ledger-DlY_serQ.mjs} +8 -7
  55. package/dist/{error-diagnostics-Bf0rZvG9.mjs → error-diagnostics-DBWO2rxE.mjs} +10 -10
  56. package/dist/{exec-_UbtCQcG.mjs → exec-DEoAhcze.mjs} +155 -47
  57. package/dist/{exec-H20xSg2T.d.mts → exec-DnYc4HGH.d.mts} +17 -4
  58. package/dist/{gc-_-dCXdyL.mjs → gc-CXxN-4SE.mjs} +587 -313
  59. package/dist/gc-DnNDQyaP.mjs +3 -0
  60. package/dist/{guide-Yqwt2bd6.mjs → guide-Br0LEfKy.mjs} +239 -43
  61. package/dist/{hosted-android-Bw-honsd.mjs → hosted-android-B-9f-jkd.mjs} +6 -6
  62. package/dist/{hosted-client-DB_gBPfY.mjs → hosted-client-DZAPrcro.mjs} +4 -4
  63. package/dist/{hosted-ios-CUvOf4Vr.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
  64. package/dist/{hosted-logs-DL_P5AdN.mjs → hosted-logs-FIRx9X2f.mjs} +5 -5
  65. package/dist/{hosted-macos-QI141yKk.mjs → hosted-macos-_awYSpfv.mjs} +21 -12
  66. package/dist/{hosted-native-Ksyjgzq-.mjs → hosted-native-BdzE668-.mjs} +27 -14
  67. package/dist/{idle-BVeRGroT.d.mts → idle-C4cZ757K.d.mts} +3 -3
  68. package/dist/{idle-shutdown-DaKmZi0z.mjs → idle-shutdown-DDg3SdvY.mjs} +7 -7
  69. package/dist/ios-CDvlmUs7.d.mts +24 -0
  70. package/dist/{ios-CQ68hG9s.mjs → ios-Ds1clbIt.mjs} +8 -8
  71. package/dist/{ios-device-CwFMehUS.mjs → ios-device--h_-8ENa.mjs} +3 -3
  72. package/dist/{ios-device-CXGP1bsD.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
  73. package/dist/{ios-CpByZy12.mjs → ios-hcBpnEGk.mjs} +81 -34
  74. package/dist/{ios-ubFsBzwq.mjs → ios-pm4nL6By.mjs} +59 -50
  75. package/dist/{ios-project-WuwwBdvW.mjs → ios-project-CZhKODF3.mjs} +5 -5
  76. package/dist/{ios-signing-DTTIChGs.mjs → ios-signing-BtAffm0A.mjs} +2 -2
  77. package/dist/{ios-state-DvFX-K_l.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
  78. package/dist/{ios-qNJt4dFm.mjs → ios-xtlRHaeB.mjs} +1 -1
  79. package/dist/{js-swap-Cc1dttps.mjs → js-swap-Bwhh4S7V.mjs} +7 -29
  80. package/dist/{launch-Zq2mOSDy.mjs → launch-BjxemNpK.mjs} +40 -39
  81. package/dist/{launch-CD1yVc7U.mjs → launch-CCzXlc8r.mjs} +16 -16
  82. package/dist/{logs-BS00Anxf.mjs → logs-BfwwadsZ.mjs} +151 -24
  83. package/dist/{logs-CMm05HxN.mjs → logs-jTS_lHSo.mjs} +10 -17
  84. package/dist/{macos-B65--pPd.mjs → macos-DVlSSyUg.mjs} +1 -1
  85. package/dist/{macos-C-0qJ6zq.mjs → macos-NfV2D7dO.mjs} +143 -46
  86. package/dist/macos-run.mjs +1 -1
  87. package/dist/maintenance-run.mjs +8 -8
  88. package/dist/{metro-DEXpIhHC.mjs → metro-DiXtA3Ty.mjs} +59 -37
  89. package/dist/{metro-gateway-BMJNlj3E.mjs → metro-gateway-CKZqKqx7.mjs} +120 -22
  90. package/dist/{named-ports-BWTzt6mu.mjs → named-ports-DkEzOfzO.mjs} +40 -33
  91. package/dist/{native-android-JACyjTZs.mjs → native-android-CqgnOgsT.mjs} +43 -242
  92. package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
  93. package/dist/{native-gradle-inputs-Bep9JcSF.mjs → native-gradle-inputs-Dnh84Zbd.mjs} +3 -129
  94. package/dist/{native-run-DY_RLx3C.mjs → native-run-NwUuxbkg.mjs} +4 -5
  95. package/dist/{native-runtime-Dg1Q7tfF.mjs → native-runtime-B7KIafpM.mjs} +8 -7
  96. package/dist/native-source-Y0yLhFyQ.mjs +133 -0
  97. package/dist/{native-xcode-inputs-CB2iQeWq.mjs → native-xcode-inputs-D_VOfFL7.mjs} +2 -3
  98. package/dist/{native-xcode-ios-CXX39F7o.mjs → native-xcode-ios-BYDOHWLN.mjs} +27 -32
  99. package/dist/offload-worker.d.mts +65 -2
  100. package/dist/offload-worker.mjs +305 -73
  101. package/dist/operation-exec-BW0em61G.mjs +171 -0
  102. package/dist/{ownership-DEmM5vhh.mjs → ownership-C5fo5Edb.mjs} +3 -3
  103. package/dist/{page-CPaHeYqj.mjs → page-DBL2PiUU.mjs} +8 -4
  104. package/dist/{placement-log-BqGbTa61.mjs → placement-log-BnGgJRRZ.mjs} +1 -1
  105. package/dist/{plan-placement-Bwj-IjY1.mjs → plan-placement-DIsMtN-d.mjs} +12 -8
  106. package/dist/{ports-D-MmCTdv.mjs → ports-Cy1RkmuQ.mjs} +3 -3
  107. package/dist/{prebuild-uD_ipCxF.mjs → prebuild-D4JoEAEs.mjs} +49 -14
  108. package/dist/{project-BZNjmW_8.mjs → project-wM_XFTvE.mjs} +16 -16
  109. package/dist/{pull-request-Lfetn8Kr.mjs → pull-request-W1IuLXDt.mjs} +102 -20
  110. package/dist/{pull-request-zjCSAryH.d.mts → pull-request-eODyIqZF.d.mts} +6 -2
  111. package/dist/pull-requests.d.mts +2 -2
  112. package/dist/pull-requests.mjs +2 -2
  113. package/dist/{react-native-android-DuzH0cn3.mjs → react-native-android-6OZ7MSM7.mjs} +71 -48
  114. package/dist/{react-native-build-rhk4smwQ.mjs → react-native-build-Dvd8hC21.mjs} +8 -8
  115. package/dist/{react-native-doctor-CQpuIMso.mjs → react-native-doctor-DsoSF1Y9.mjs} +7 -7
  116. package/dist/{react-native-ios-Bhq1rJeV.mjs → react-native-ios-4u1Rn-GR.mjs} +40 -26
  117. package/dist/{recordings-DYteQSu4.mjs → recordings-CQmfzAh7.mjs} +1 -1
  118. package/dist/registration-BcRgF6Ox.mjs +51 -0
  119. package/dist/{reload-CmTRFJaX.mjs → reload-BzkYabB-.mjs} +16 -15
  120. package/dist/{reload-DzjSl5P_.mjs → reload-D7AVlb8N.mjs} +1 -1
  121. package/dist/{remote-ByYkXdGD.mjs → remote-DjGDmlTZ.mjs} +18 -15
  122. package/dist/{result-DNiS-tTP.mjs → result-D2Ykz_iD.mjs} +24 -23
  123. package/dist/{run-D23HmJMs.mjs → run-DidHghD1.mjs} +3 -2
  124. package/dist/{selection-CSl9i5qU.mjs → selection-DSMbWEX2.mjs} +6 -5
  125. package/dist/{server-bare-CxTrvDg8.mjs → server-bare-CafzuyFB.mjs} +2 -2
  126. package/dist/{server-command-CtzloH3v.mjs → server-command-C6QjeupS.mjs} +5 -5
  127. package/dist/{server-expo-DT_F_uVI.mjs → server-expo-CrjB29pq.mjs} +7 -5
  128. package/dist/server-expo-D58c9L0M.mjs +2 -0
  129. package/dist/{settings-CsJDEXgK.mjs → settings-C7O8A6ZI.mjs} +223 -243
  130. package/dist/{settings-T1gtc3bf.mjs → settings-DpR-1Wnn.mjs} +1 -1
  131. package/dist/{settings-write-HyVZ8eQ8.mjs → settings-write-CzD60rOD.mjs} +7 -6
  132. package/dist/settings.schema.json +13 -0
  133. package/dist/{simslim-Cgk17SXV.mjs → simslim-BNckWpXi.mjs} +2 -2
  134. package/dist/{slot-launch-DF8VfT64.mjs → slot-launch-CbfmNgM6.mjs} +6 -6
  135. package/dist/{stage-BIS0ydsV.mjs → stage-Bps5oius.mjs} +21 -4
  136. package/dist/{start-RChynRDp.mjs → start-Br-oxqMD.mjs} +1 -1
  137. package/dist/{start-7lpH7hmN.mjs → start-EojN4WFz.mjs} +622 -393
  138. package/dist/{state-C4ZtUisW.mjs → state--smjFSTL.mjs} +22 -7
  139. package/dist/{state-XMziexv2.mjs → state-08I3hw3_.mjs} +2 -2
  140. package/dist/{stats-53T7OTb9.mjs → stats-D3GerlFW.mjs} +5 -5
  141. package/dist/{stats-report-BFfMpWWF.mjs → stats-report-BzaPrRm6.mjs} +1 -1
  142. package/dist/{status-DqEUSSxs.mjs → status-BRROykC8.mjs} +2 -2
  143. package/dist/{status-CSi6tlRq.mjs → status-EvrK72L2.mjs} +1 -1
  144. package/dist/{status-BL7jVyAB.mjs → status-WBDajew2.mjs} +43 -29
  145. package/dist/{status-watch-GDCfqpTE.mjs → status-watch-Bg_u-pwR.mjs} +48 -16
  146. package/dist/status-worker.mjs +1 -1
  147. package/dist/{stim-desktop-C12we7lz.mjs → stim-desktop-D5OGpYZM.mjs} +2 -2
  148. package/dist/{stop-Bk_1qiV6.mjs → stop-DTZ9uTw3.mjs} +2 -2
  149. package/dist/{stop-D7h6jAXQ.mjs → stop-Dky10AEJ.mjs} +31 -22
  150. package/dist/{stop-CPzlM1L-.mjs → stop-Mv6W9FAk.mjs} +15 -16
  151. package/dist/supervisor-run.d.mts +3 -3
  152. package/dist/supervisor-run.mjs +28 -36
  153. package/dist/{support-Dlu37g6H.mjs → support-CNioewsd.mjs} +10 -10
  154. package/dist/{support-67vraa1Q.mjs → support-yUiOjnVY.mjs} +8 -8
  155. package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
  156. package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
  157. package/dist/toolchain-BY8X8HXx.mjs +2 -0
  158. package/dist/{toolchain-Hp9sCpK4.mjs → toolchain-DpJMHq0n.mjs} +9 -6
  159. package/dist/{trigger-BAahxUPq.mjs → trigger-Cbyc41d_.mjs} +3 -3
  160. package/dist/trigger-yb_nFzPF.mjs +2 -0
  161. package/dist/{warm-progress-BHX5r0Ei.mjs → warm-progress-CA3TgT3-.mjs} +29 -16
  162. package/dist/web-CZ07gd77.mjs +2 -0
  163. package/dist/{web-CJmIj6AI.mjs → web-DKx6yoFA.mjs} +11 -10
  164. package/dist/web-run.mjs +11 -9
  165. package/dist/windows-report.mjs +1 -1
  166. package/dist/{workspace-state-Cjuigeeu.mjs → workspace-state-U08dEzhG.mjs} +3 -3
  167. package/dist/{workspaces-BccMFmGQ.mjs → workspaces-CpANmBlq.mjs} +594 -266
  168. package/dist/{worktree-DQ6Zi2Mv.mjs → worktree-CHecZ4Oq.mjs} +850 -551
  169. package/dist/worktree-ZqZVjD9K.mjs +2 -0
  170. package/dist/{xcode-Zlx3qjEs.mjs → xcode-BXn9-xYx.mjs} +5 -5
  171. package/package.json +7 -7
  172. package/shim/android-audio/Main.java +24 -0
  173. package/shim/android-audio/README.md +17 -0
  174. package/shim/android-audio.jar +0 -0
  175. package/dist/gc-C-WwL0yi.mjs +0 -3
  176. package/dist/git-C3iTsJaZ.mjs +0 -73
  177. package/dist/server-expo-C-90EhcG.mjs +0 -2
  178. package/dist/settings-DkdmlelE.d.mts +0 -15
  179. package/dist/swiftpm-macos-p8WZbxKU.mjs +0 -130
  180. package/dist/toolchain-DhKwp9iI.mjs +0 -2
  181. package/dist/trigger-Bf3uDcpv.mjs +0 -2
  182. package/dist/web-DknQETPs.mjs +0 -2
  183. package/dist/workspace-process-lock-1_1OoVW5.mjs +0 -57
  184. package/dist/worktree-CK2MpJW5.mjs +0 -2
@@ -1,19 +1,19 @@
1
- import { p as process_identity_exports, t as getExecutor } from "./exec-_UbtCQcG.mjs";
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";
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, p as getProject } from "./config-CYcBF478.mjs";
4
- import { _ as stepTimer, l as phaseLine } from "./command-output-B1UrjZHL.mjs";
5
- import { d as writeWorkspaceState, i as recordWorkspaceUse, n as clearWorkspaceStateKeys, r as readWorkspaceState$1 } from "./workspace-state-Cjuigeeu.mjs";
6
- import { A as metroIdleStopMinutesSetting, G as resolveCacheProviderConfig, J as resolveProjectSettingsAsync, K as resolveIosProjectDir, M as metroTunnelSettingError, O as metroCommandSetting, P as ngrokUrlSetting, Qt as cancellableSleep, R as publicUrlSetting, U as remoteIosSetting, V as remoteAndroidSetting, W as resolveAndroidLayout, a as SETTING_SHAPE_REMEDY, ct as detectProviders, en as throwIfCancelled, 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-CsJDEXgK.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-BZNjmW_8.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-gwpm28fu.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-wM_XFTvE.mjs";
8
9
  import { i as levelRank } from "./ndjson-CgE_o3Dl.mjs";
9
- import { _ as reserveMetroPort, d as processCwds, f as resolveProjectMetro, g as isMetroRunning, l as pidExists, o as listeningPidsAsync, p as signalProcessTree, v as PORT_INSPECTION_REMEDY, y as PortInspectionError } from "./metro-DEXpIhHC.mjs";
10
- import { Kn as readProjectConfig, p as stopOwnedMetroForReset, t as budgetGate } from "./budget-Bs6SxxzO.mjs";
11
- import { _ as terminateChild, b as withManagedTunnelLock, g as stopTunnel, h as startTunnelSequence, m as resolveSupervisorTarget, v as withManagedRemoteWorktreeLock } from "./workspaces-BccMFmGQ.mjs";
12
- import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-C12we7lz.mjs";
13
- import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-1_1OoVW5.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-Cvwn54qV.mjs";
12
+ import { _ as stopTunnel, g as startTunnelSequence, h as resolveSupervisorTarget, v as terminateChild, x as withManagedTunnelLock, y as withManagedRemoteWorktreeLock } from "./workspaces-CpANmBlq.mjs";
13
+ import { a as workspaceLinkLine, o as workspaceLinks } from "./stim-desktop-D5OGpYZM.mjs";
14
14
  import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
15
- import { n as windowsLauncherArgs } from "./detached-entry-DFRyG1_o.mjs";
16
- import { appendFileSync, mkdirSync, openSync, readFileSync } from "node:fs";
15
+ import { n as windowsLauncherArgs } from "./detached-entry-BwNEtIrP.mjs";
16
+ import { appendFileSync, closeSync, mkdirSync, openSync, readFileSync } from "node:fs";
17
17
  import { dirname } from "node:path";
18
18
  import { ambientContext, ambientEnv } from "@stim-cli/core";
19
19
  import { IDLE_STOP_KEY, queryLogs } from "@stim-cli/core/state";
@@ -42,8 +42,8 @@ function configString(config, platform, key) {
42
42
  function literalString(text, key) {
43
43
  return text?.match(new RegExp(`${key}\\s*:\\s*["']([^"']+)["']`))?.[1] ?? null;
44
44
  }
45
- async function idsFromConfig(projectRoot) {
46
- const read = await readProjectConfig(projectRoot);
45
+ async function idsFromConfig(projectRoot, options) {
46
+ const read = await readProjectConfig(projectRoot, options);
47
47
  if (!read.unavailable) return {
48
48
  bundleId: configString(read.config, "ios", "bundleIdentifier"),
49
49
  androidPackage: configString(read.config, "android", "package")
@@ -55,8 +55,8 @@ async function idsFromConfig(projectRoot) {
55
55
  androidPackage: configString(appJson, "android", "package") ?? literalString(text, "package")
56
56
  };
57
57
  }
58
- async function detectAppIds(projectRoot, iosDir = join$1(projectRoot, "ios"), androidModuleDir = defaultAndroidLayout(projectRoot).moduleDir) {
59
- const ids = await idsFromConfig(projectRoot);
58
+ async function detectAppIds(projectRoot, iosDir = join$1(projectRoot, "ios"), androidModuleDir = defaultAndroidLayout(projectRoot).moduleDir, config = {}) {
59
+ const ids = await idsFromConfig(projectRoot, config);
60
60
  const problems = [];
61
61
  const accept = androidPackageFilter(problems);
62
62
  return {
@@ -127,8 +127,8 @@ async function resolveStartupMetro(port, root) {
127
127
  const signal = ambientContext()?.signal;
128
128
  throwIfCancelled(signal, "Metro startup");
129
129
  const result = await resolveProjectMetro(port, root, {
130
- pidsOf: listeningPidsAsync,
131
- cwdOf: async (pid) => (await processCwds([pid])).get(pid) ?? null,
130
+ pidsOf: (candidate) => listeningPidsAsync(candidate, process.platform, signal),
131
+ cwdOf: async (pid) => (await processCwds([pid], signal)).get(pid) ?? null,
132
132
  probe: (candidate) => isMetroRunning(candidate, signal)
133
133
  });
134
134
  throwIfCancelled(signal, "Metro startup");
@@ -227,20 +227,33 @@ function providersOnPath(mode) {
227
227
  const DEFAULT_START_DEPS = {
228
228
  providers: providersOnPath,
229
229
  startTunnelSequence,
230
- isTunnelAlive: pidExists,
231
- writeTunnelRecord: writeWorkspaceState,
230
+ isTunnelAlive: (pid, processToken) => processToken === void 0 ? pidExists(pid) : (0, process_identity_exports.inspectProcessIdentity)({
231
+ pid,
232
+ processToken
233
+ }) === "same",
234
+ writeTunnelRecord: (root, patch) => updateWorkspaceStateAsync(root, (state) => ({
235
+ ...state,
236
+ ...patch
237
+ })),
232
238
  stopTunnel,
233
- writeSupervisorRecord: writeWorkspaceState,
234
- terminateSupervisorChild: (child) => terminateChild(child, {
235
- alreadyExited: false,
236
- timeoutMs: 1e3,
237
- now: Date.now,
238
- sleep: cancellableSleep,
239
- isAlive: pidExists
240
- }),
239
+ writeSupervisorRecord: (root, patch) => updateWorkspaceStateAsync(root, (state) => ({
240
+ ...state,
241
+ ...patch
242
+ })),
243
+ terminateSupervisorChild: async (child) => {
244
+ if (child.pid && process.platform !== "win32") try {
245
+ signalProcessTree(child.pid, "SIGTERM", { group: true });
246
+ } catch {}
247
+ return terminateChild(child, {
248
+ alreadyExited: false,
249
+ timeoutMs: 1e3,
250
+ isAlive: pidExists,
251
+ waitForGroup: process.platform !== "win32"
252
+ });
253
+ },
241
254
  withWorktreeLock: withManagedRemoteWorktreeLock,
242
255
  withTunnelLock: withManagedTunnelLock,
243
- clearTunnelRecord: clearManagedMetroTunnel,
256
+ clearTunnelRecord: (root, record) => withWorkspaceStateLockAsync(root, () => clearManagedMetroTunnel(root, record)),
244
257
  budgetGate,
245
258
  platform: process.platform
246
259
  };
@@ -259,7 +272,7 @@ function registerStart(program, overrides = {}) {
259
272
  else console.log(line);
260
273
  };
261
274
  const note = writeNote;
262
- const fail = ({ code, message, remedy = null, lines = [], reclaimed = [] }) => {
275
+ const fail = ({ code, message, remedy = null, lines = [], reclaimed = [], exitCode = 1 }) => {
263
276
  note(chalk.red(message));
264
277
  for (const line of lines) note(chalk.dim(` ${line}`));
265
278
  if (remedy) note(chalk.dim(remedy));
@@ -272,7 +285,7 @@ function registerStart(program, overrides = {}) {
272
285
  }),
273
286
  ...reclaimed.length ? { reclaimed } : {}
274
287
  }));
275
- process.exit(1);
288
+ process.exit(exitCode);
276
289
  };
277
290
  const wait = parseWait(opts.wait);
278
291
  if (wait.error) return fail({
@@ -310,20 +323,36 @@ function registerStart(program, overrides = {}) {
310
323
  for (const key of unknownSettingKeys(settings)) note(chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`));
311
324
  const cacheProviderError = cacheProviderSettingError(settings);
312
325
  if (cacheProviderError) note(chalk.yellow(`Warning: ${cacheProviderError} No cache provider is used.`));
313
- const result = await startDevServer({
314
- root,
315
- settings,
316
- settingsContext,
317
- waitSeconds,
318
- remote: Boolean(opts.remote),
319
- resetCache: Boolean(opts.resetCache),
320
- out,
321
- note
322
- }, d);
326
+ const { value: result, interrupted } = await runProcessOperation(root, (context) => withDrainingCancellation(context, context.signal, async () => {
327
+ try {
328
+ return await startDevServer({
329
+ root,
330
+ settings,
331
+ settingsContext,
332
+ waitSeconds,
333
+ remote: Boolean(opts.remote),
334
+ resetCache: Boolean(opts.resetCache),
335
+ out,
336
+ note
337
+ }, d);
338
+ } catch (error) {
339
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "STIM_CANCELLED") throw error;
340
+ return {
341
+ ok: false,
342
+ error: startError({
343
+ code: "STIM_CANCELLED",
344
+ message: error.message
345
+ }),
346
+ lines: [],
347
+ reclaimed: []
348
+ };
349
+ }
350
+ }));
323
351
  if (!result.ok) return fail({
324
352
  ...result.error,
325
353
  lines: result.lines,
326
- reclaimed: result.reclaimed
354
+ reclaimed: result.reclaimed,
355
+ exitCode: interrupted ?? 1
327
356
  });
328
357
  report({
329
358
  json,
@@ -333,6 +362,7 @@ function registerStart(program, overrides = {}) {
333
362
  reclaimed: result.reclaimed,
334
363
  links: await workspaceLinks(root)
335
364
  });
365
+ if (interrupted !== null) exitAfterFlush(interrupted);
336
366
  });
337
367
  }
338
368
  var StartRefusal = class extends Error {
@@ -347,6 +377,8 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
347
377
  ...DEFAULT_START_DEPS,
348
378
  ...overrides
349
379
  };
380
+ const operationSignal = ambientContext()?.signal;
381
+ throwIfCancelled(operationSignal, "Metro startup");
350
382
  const waitTimer = stepTimer();
351
383
  let reclaimed = [];
352
384
  const fail = (refusal) => {
@@ -392,18 +424,63 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
392
424
  });
393
425
  if (tunnel) note(chalk.dim("note requesting an Expo tunnel for this workspace's dev server"));
394
426
  const managedRemote = remote && !tunnel && !publicUrl && tunnelMode !== "off";
395
- const runStart = async () => {
396
- const appIds = await detectAppIds(root, resolveIosProjectDir(settings, root).dir, resolveAndroidLayout(settings, root, settingsContext.repoRoot ?? root).moduleDir);
397
- upsertProject(root, {
398
- bundleId: appIds.bundleId ?? void 0,
399
- androidPackage: appIds.androidPackage ?? void 0,
400
- isExpo
427
+ const runStart = async (claim, tunnelClaim) => {
428
+ const claims = [claim, tunnelClaim];
429
+ const appIds = await detectAppIds(root, resolveIosProjectDir(settings, root).dir, resolveAndroidLayout(settings, root, settingsContext.repoRoot ?? root).moduleDir, {
430
+ context: ambientContext(),
431
+ run: async (file, args, options) => {
432
+ for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
433
+ let child;
434
+ let identity;
435
+ let output = "";
436
+ let failure;
437
+ try {
438
+ const invocation = remoteCliInvocation(file, args, "expo");
439
+ output = await getExecutor().runFileAsync(invocation.file, invocation.args, {
440
+ ...options,
441
+ cwd: root,
442
+ containProcessTree: true,
443
+ signal: operationSignal ?? new AbortController().signal,
444
+ onSpawn: (spawned) => {
445
+ child = spawned;
446
+ if (!spawned.pid) return;
447
+ const processToken = (0, process_identity_exports.captureProcessToken)(spawned.pid);
448
+ if (!processToken) throw new Error("Cannot record the Expo config process identity.");
449
+ identity = {
450
+ pid: spawned.pid,
451
+ processToken
452
+ };
453
+ for (const held of claims) (0, ownership_claim_exports.setClaimChild)(held, identity);
454
+ }
455
+ });
456
+ } catch (error) {
457
+ failure = { error };
458
+ }
459
+ const pid = child?.pid;
460
+ if (pid) {
461
+ 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;
463
+ while (alive() && Date.now() < deadline) await cancellableSleep(25);
464
+ 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
+ }
466
+ for (const held of claims) (0, ownership_claim_exports.clearClaimChild)(held);
467
+ if (failure) throw failure.error;
468
+ return output;
469
+ }
401
470
  });
471
+ await withConfigLockAsync$1(() => {
472
+ upsertProject(root, {
473
+ bundleId: appIds.bundleId ?? void 0,
474
+ androidPackage: appIds.androidPackage ?? void 0,
475
+ isExpo
476
+ });
477
+ }, operationSignal);
402
478
  const gateBudget = async () => {
403
479
  const budget = await d.budgetGate({
404
480
  root,
405
481
  note,
406
- env: ambientEnv()
482
+ env: ambientEnv(),
483
+ signal: operationSignal
407
484
  });
408
485
  reclaimed = budget.reclaimed;
409
486
  if (budget.refusal) return fail(budget.refusal);
@@ -416,7 +493,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
416
493
  if (typeof pinCheck !== "number") return fail(pinCheck);
417
494
  }
418
495
  try {
419
- await stopOwnedMetroForReset(root);
496
+ await stopOwnedMetroForReset(root, operationSignal);
420
497
  } catch (error) {
421
498
  const failure = error;
422
499
  return fail({
@@ -449,21 +526,44 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
449
526
  });
450
527
  if (!resetCache && !resolution.metro && !supervisor) await gateBudget();
451
528
  let managedTunnel = null;
452
- let spawnedChild = null;
529
+ const spawned = { child: null };
453
530
  let spawnedTs = null;
454
531
  let childExit = null;
532
+ let spawnedIdentity = null;
533
+ let unknownWindowsSpawn = false;
534
+ let windowsLauncher = null;
535
+ let tunnelPending = null;
536
+ const handedOffSupervisor = () => {
537
+ if (!spawnedIdentity) return null;
538
+ const target = resolveSupervisorTarget({
539
+ state: readWorkspaceState$1(root)?.supervisor,
540
+ record: getProject(root)?.supervisor,
541
+ reservedPort: port
542
+ });
543
+ return target.status === "ours" && typeof target.pid === "number" && target.pid === spawnedIdentity.pid && target.processToken === spawnedIdentity.processToken && target.port === port ? {
544
+ pid: target.pid,
545
+ port,
546
+ mode: target.mode ?? null,
547
+ startedAt: target.startedAt ?? null
548
+ } : null;
549
+ };
455
550
  const spawnDirect = (supervisorArgs, childEnv) => {
456
551
  const fd = openSync(logFile, "a");
457
- const child = getExecutor().spawn(process.execPath, supervisorArgs, {
458
- cwd: root,
459
- detached: true,
460
- stdio: [
461
- "ignore",
462
- fd,
463
- fd
464
- ],
465
- env: childEnv
466
- });
552
+ let child;
553
+ try {
554
+ child = getExecutor().spawn(process.execPath, supervisorArgs, {
555
+ cwd: root,
556
+ detached: true,
557
+ stdio: [
558
+ "ignore",
559
+ fd,
560
+ fd
561
+ ],
562
+ env: childEnv
563
+ });
564
+ } finally {
565
+ closeSync(fd);
566
+ }
467
567
  child.unref?.();
468
568
  child.on?.("exit", (code, signal) => {
469
569
  childExit = {
@@ -480,8 +580,8 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
480
580
  });
481
581
  return child;
482
582
  };
483
- const recordedSupervisor = async (since) => {
484
- const deadline = Date.now() + RECORDED_SUPERVISOR_WAIT_MS;
583
+ const recordedSupervisor = async (since, waitMs = RECORDED_SUPERVISOR_WAIT_MS) => {
584
+ const deadline = Date.now() + waitMs;
485
585
  while (Date.now() < deadline) {
486
586
  const record = readWorkspaceState$1(root)?.supervisor;
487
587
  if (record?.pid && record.port === port && Date.parse(String(record.startedAt)) >= since && pidExists(record.pid)) return {
@@ -514,21 +614,48 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
514
614
  },
515
615
  windowsHide: true
516
616
  });
617
+ const launch = {
618
+ child: shell,
619
+ since,
620
+ closed: false
621
+ };
622
+ windowsLauncher = launch;
623
+ unknownWindowsSpawn = shell.pid !== void 0;
517
624
  const stderr = [];
518
625
  shell.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
519
- const exit = await new Promise((resolve) => {
520
- shell.on?.("close", (code, signal) => resolve({
521
- code,
522
- signal
523
- }));
524
- shell.on?.("error", (error) => resolve({
626
+ const closed = new Promise((resolve) => {
627
+ shell.once("close", (code, signal) => {
628
+ launch.closed = true;
629
+ resolve({
630
+ code,
631
+ signal
632
+ });
633
+ });
634
+ shell.once("error", (error) => resolve({
525
635
  code: null,
526
636
  signal: null,
527
637
  error
528
638
  }));
529
639
  });
640
+ const waiting = new AbortController();
641
+ const signal = operationSignal ? AbortSignal.any([operationSignal, waiting.signal]) : waiting.signal;
642
+ let exit;
643
+ try {
644
+ exit = await Promise.race([closed, cancellableSleep(Math.min(RECORDED_SUPERVISOR_WAIT_MS, waitSeconds * 1e3), {
645
+ signal,
646
+ what: "Windows supervisor launcher"
647
+ }).then(() => fail({
648
+ code: "STIM_METRO_TIMEOUT",
649
+ message: "The Windows supervisor launcher did not finish before the startup deadline.",
650
+ remedy: "Inspect the launcher and supervisor before retrying."
651
+ }))]);
652
+ } finally {
653
+ waiting.abort();
654
+ }
530
655
  const launcherFailed = exit.code !== 0 || exit.error;
656
+ unknownWindowsSpawn = !launcherFailed;
531
657
  const recorded = launcherFailed ? null : await recordedSupervisor(since);
658
+ if (recorded) unknownWindowsSpawn = false;
532
659
  if (recorded === null) {
533
660
  const reason = launcherFailed ? `the supervisor launcher exited (${exit.error ? exit.error.message : `code ${exit.code}`})` : `the supervisor did not record itself within ${RECORDED_SUPERVISOR_WAIT_MS / 1e3}s`;
534
661
  appendFileSync(logFile, `Stim start: ${reason}.\n${stderr.join("")}`);
@@ -538,6 +665,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
538
665
  ...exit.error ? { error: exit.error } : {}
539
666
  };
540
667
  }
668
+ windowsLauncher = null;
541
669
  return recordedSupervisorProcess(recorded);
542
670
  };
543
671
  const spawnSupervisor = async (origin) => {
@@ -564,404 +692,497 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
564
692
  } : {}
565
693
  };
566
694
  childEnv[CACHE_PROVIDER_ENV] = cacheProviderEnv(cacheProvider);
695
+ for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
567
696
  const child = d.platform === "win32" ? await spawnThroughWindowsShell(supervisorArgs, childEnv) : spawnDirect(supervisorArgs, childEnv);
697
+ spawned.child = child;
698
+ if (child.pid) {
699
+ const processToken = child.recordedIdentity?.processToken ?? (0, process_identity_exports.captureProcessToken)(child.pid);
700
+ if (processToken) {
701
+ spawnedIdentity = {
702
+ pid: child.pid,
703
+ processToken
704
+ };
705
+ for (const held of claims) (0, ownership_claim_exports.setClaimChild)(held, spawnedIdentity);
706
+ }
707
+ }
568
708
  out(chalk.dim(phaseLine("metro", `starting on port ${port} (${serverMode}, supervisor pid ${child.pid})`)));
569
- spawnedChild = child;
570
709
  return child;
571
710
  };
572
711
  const waitForSupervisorHandoff = async (child) => {
573
712
  const deadline = Date.now() + 5e3;
574
713
  while (Date.now() < deadline) {
575
- const found = liveSupervisor({
576
- state: readWorkspaceState$1(root),
577
- project: getProject(root),
578
- port
579
- });
580
- if (found?.pid === child.pid) return found;
714
+ const found = handedOffSupervisor();
715
+ if (found) return found;
581
716
  if (childExit !== null || (child.pid ? !pidExists(child.pid) : true)) return null;
582
717
  await cancellableSleep(25);
583
718
  }
584
719
  return null;
585
720
  };
586
- if (remote && !tunnel && !publicUrl && tunnelMode !== "off") {
587
- const available = d.providers(tunnelMode);
588
- const plan = planMetroReach({
589
- mode: tunnelMode,
590
- metroPort: port,
591
- publicUrl,
592
- isExpo: expoServer,
593
- available
594
- });
595
- if ("failed" in plan) return fail({
596
- code: "STIM_REMOTE_METRO_UNREACHABLE",
597
- message: plan.failed,
598
- remedy: plan.remedy
599
- });
600
- if ("start" in plan) {
601
- const candidates = tunnelMode === "auto" ? available.filter((provider) => provider !== "tailscale") : [plan.start];
602
- const expectedStableUrl = ngrokUrlSetting(settings);
603
- let acquisition;
604
- try {
605
- acquisition = await d.withTunnelLock(root, async () => {
606
- let tracking;
607
- let startedCleanup = null;
608
- const recorded = readMetroTunnel(root);
609
- if (recorded?.kind === "managed" && d.isTunnelAlive(recorded.pid)) {
610
- if (!recorded.processToken) return { failed: {
611
- code: "STIM_REMOTE_START_REQUIRED",
612
- message: "The recorded managed Metro tunnel has no process identity token.",
613
- remedy: "Inspect the process, stop it with the provider tooling, remove the stale metroTunnel state, and retry."
614
- } };
615
- if (!(recorded.port === port && candidates.includes(recorded.provider) && (!expectedStableUrl || normalizeManagedTunnelUrl(recorded.url) === expectedStableUrl))) return { failed: {
616
- code: "STIM_REMOTE_START_REQUIRED",
617
- message: `A different managed Metro tunnel is already running for this workspace.`,
618
- remedy: "Run `stim stop`, then `stim start --remote`."
619
- } };
620
- tracking = {
621
- record: {
622
- provider: recorded.provider,
623
- pid: recorded.pid,
624
- url: normalizeManagedTunnelUrl(recorded.url),
625
- port: recorded.port,
626
- startedAt: recorded.startedAt,
627
- processToken: recorded.processToken,
628
- logFile: recorded.logFile
629
- },
630
- startedHere: false
631
- };
632
- } else {
721
+ const reconcileSpawnedSupervisor = async () => {
722
+ try {
723
+ const launch = windowsLauncher;
724
+ if (launch) {
725
+ if (!await terminateChild(launch.child, {
726
+ alreadyExited: launch.closed || launch.child.exitCode !== null || launch.child.signalCode !== null,
727
+ timeoutMs: 1e3,
728
+ isAlive: pidExists
729
+ })) throw new WorkspaceProcessCleanupError(claims, `Could not confirm cleanup of Windows supervisor launcher pid ${launch.child.pid}.`, "Inspect and stop the launcher and its supervisor with their original tooling before retrying.");
730
+ if (unknownWindowsSpawn) {
731
+ const recorded = await recordedSupervisor(launch.since, 1e3);
732
+ if (recorded) {
733
+ spawned.child = recordedSupervisorProcess(recorded);
734
+ spawnedIdentity = recorded;
735
+ for (const held of claims) (0, ownership_claim_exports.setClaimChild)(held, recorded);
736
+ unknownWindowsSpawn = false;
737
+ }
738
+ }
739
+ windowsLauncher = null;
740
+ }
741
+ const child = spawned.child;
742
+ if (tunnelPending) throw tunnelPending;
743
+ if (child?.pid && !handedOffSupervisor()) {
744
+ const hasPublishedSupervisor = () => {
745
+ const identity = spawnedIdentity;
746
+ return identity !== null && [readWorkspaceState$1(root)?.supervisor, getProject(root)?.supervisor].some((record) => record?.pid === identity.pid && record?.processToken === identity.processToken);
747
+ };
748
+ if (hasPublishedSupervisor()) {
749
+ for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
750
+ 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
+ }
752
+ 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));
754
+ const publicationRacedCleanup = hasPublishedSupervisor();
755
+ if (!supervisorCleanup || d.platform !== "win32" && (0, ownership_claim_exports.processGroupAlive)(child.pid) || publicationRacedCleanup) {
756
+ if (publicationRacedCleanup) for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
757
+ throw new WorkspaceProcessCleanupError(claims, `Could not confirm cleanup of supervisor pid ${child.pid}.`, `Stop supervisor pid ${child.pid} with its original tooling before retrying.`);
758
+ }
759
+ for (const held of claims) (0, ownership_claim_exports.clearClaimChild)(held);
760
+ } else if (unknownWindowsSpawn) throw new WorkspaceProcessCleanupError(claims, "The Windows launcher started a supervisor but its identity was not recorded.", "Inspect and stop it with its original tooling before retrying.");
761
+ } catch (cause) {
762
+ if (cause instanceof WorkspaceProcessCleanupError) throw cause;
763
+ throw new WorkspaceProcessCleanupError(claims, `Could not reconcile supervisor ownership: ${cause.message}`, "Inspect and stop the supervisor with its original tooling before retrying.");
764
+ }
765
+ };
766
+ try {
767
+ if (remote && !tunnel && !publicUrl && tunnelMode !== "off") {
768
+ const available = d.providers(tunnelMode);
769
+ const plan = planMetroReach({
770
+ mode: tunnelMode,
771
+ metroPort: port,
772
+ publicUrl,
773
+ isExpo: expoServer,
774
+ available
775
+ });
776
+ if ("failed" in plan) return fail({
777
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
778
+ message: plan.failed,
779
+ remedy: plan.remedy
780
+ });
781
+ if ("start" in plan) {
782
+ const candidates = tunnelMode === "auto" ? available.filter((provider) => provider !== "tailscale") : [plan.start];
783
+ const expectedStableUrl = ngrokUrlSetting(settings);
784
+ let acquisition;
785
+ try {
786
+ acquisition = await (async () => {
787
+ let tracking;
788
+ let startedCleanup = null;
789
+ const recorded = readMetroTunnel(root);
790
+ if (recorded?.kind === "managed" && d.isTunnelAlive(recorded.pid)) {
791
+ if (!recorded.processToken) return { failed: {
792
+ code: "STIM_REMOTE_START_REQUIRED",
793
+ message: "The recorded managed Metro tunnel has no process identity token.",
794
+ remedy: "Inspect the process, stop it with the provider tooling, remove the stale metroTunnel state, and retry."
795
+ } };
796
+ if (!d.isTunnelAlive(recorded.pid, recorded.processToken)) return { failed: {
797
+ code: "STIM_REMOTE_START_REQUIRED",
798
+ message: `The recorded ${recorded.provider} tunnel pid ${recorded.pid} no longer has its recorded process identity.`,
799
+ remedy: "Run `stim stop`, resolve any ownership refusal, then retry `stim start --remote`."
800
+ } };
801
+ if (!(recorded.port === port && candidates.includes(recorded.provider) && (!expectedStableUrl || normalizeManagedTunnelUrl(recorded.url) === expectedStableUrl))) return { failed: {
802
+ code: "STIM_REMOTE_START_REQUIRED",
803
+ message: `A different managed Metro tunnel is already running for this workspace.`,
804
+ remedy: "Run `stim stop`, then `stim start --remote`."
805
+ } };
806
+ tracking = {
807
+ record: {
808
+ provider: recorded.provider,
809
+ pid: recorded.pid,
810
+ url: normalizeManagedTunnelUrl(recorded.url),
811
+ port: recorded.port,
812
+ startedAt: recorded.startedAt,
813
+ processToken: recorded.processToken,
814
+ logFile: recorded.logFile
815
+ },
816
+ startedHere: false
817
+ };
818
+ } else {
819
+ const currentResolution = await resolveStartupMetro(port, root);
820
+ const currentSupervisor = liveSupervisor({
821
+ state: readWorkspaceState$1(root),
822
+ project: getProject(root),
823
+ port
824
+ });
825
+ if (currentResolution.metro || currentSupervisor) return { failed: {
826
+ code: "STIM_REMOTE_START_REQUIRED",
827
+ message: `The dev server on port ${port} is local-only and cannot gain a managed tunnel while it is running.`,
828
+ remedy: "Run `stim stop`, then `stim start --remote`."
829
+ } };
830
+ throwIfCancelled(operationSignal, "Metro tunnel startup");
831
+ for (const held of claims) (0, ownership_claim_exports.markClaimChildPending)(held);
832
+ tunnelPending = new WorkspaceProcessCleanupError(claims, "A new managed Metro tunnel could not be recorded or confirmed stopped.", "Inspect and stop the tunnel with its provider tooling before retrying.");
833
+ const started = await d.startTunnelSequence({
834
+ claims,
835
+ signal: operationSignal,
836
+ providers: candidates,
837
+ port,
838
+ ngrokUrl: expectedStableUrl,
839
+ requireReachable: false
840
+ });
841
+ if ("failed" in started) {
842
+ if (started.cleanupFailed) {
843
+ tunnelPending = new WorkspaceProcessCleanupError(claims, started.reason, "Stop the tunnel with its provider tooling before retrying.");
844
+ throw tunnelPending;
845
+ }
846
+ for (const held of claims) (0, ownership_claim_exports.clearClaimChild)(held);
847
+ tunnelPending = null;
848
+ throwIfCancelled(operationSignal, "Metro tunnel startup");
849
+ return { failed: {
850
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
851
+ message: `Could not start a managed Metro tunnel for port ${port}.`,
852
+ remedy: started.reason
853
+ } };
854
+ }
855
+ for (const held of claims) (0, ownership_claim_exports.setClaimChild)(held, {
856
+ pid: started.pid,
857
+ processToken: started.processToken
858
+ });
859
+ const record = {
860
+ provider: started.provider,
861
+ pid: started.pid,
862
+ url: normalizeManagedTunnelUrl(started.url),
863
+ port,
864
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
865
+ processToken: started.processToken,
866
+ logFile: started.logFile
867
+ };
868
+ startedCleanup = started.cleanup;
869
+ try {
870
+ await d.writeTunnelRecord(root, { metroTunnel: {
871
+ kind: "managed",
872
+ ...record
873
+ } });
874
+ const stored = readMetroTunnel(root);
875
+ if (stored?.kind !== "managed" || stored.pid !== record.pid || stored.processToken !== record.processToken || stored.port !== record.port || stored.provider !== record.provider || stored.url !== record.url) throw new Error("The workspace did not retain this tunnel ownership record.");
876
+ for (const held of claims) (0, ownership_claim_exports.clearClaimChild)(held);
877
+ tunnelPending = null;
878
+ } catch (err) {
879
+ const recordError = err instanceof Error ? err.message : String(err);
880
+ const stopped = await started.cleanup();
881
+ if (stopped.status === "stopped") {
882
+ for (const held of claims) (0, ownership_claim_exports.clearClaimChild)(held);
883
+ tunnelPending = null;
884
+ } else tunnelPending = new WorkspaceProcessCleanupError(claims, `Could not record the managed Metro tunnel: ${recordError}`, `Cleanup failed. Unmanaged pid ${record.pid} may still be running: ${stopped.reason ?? "unknown error"}`);
885
+ return { failed: {
886
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
887
+ message: `Could not record the managed Metro tunnel: ${recordError}`,
888
+ remedy: stopped.status === "failed" ? `Cleanup failed. Unmanaged pid ${record.pid} may still be running: ${stopped.reason ?? "unknown error"}` : "The tunnel process was stopped. Fix the workspace write error, then retry `stim start --remote`."
889
+ } };
890
+ }
891
+ tracking = {
892
+ record,
893
+ startedHere: true
894
+ };
895
+ }
633
896
  const currentResolution = await resolveStartupMetro(port, root);
634
897
  const currentSupervisor = liveSupervisor({
635
898
  state: readWorkspaceState$1(root),
636
899
  project: getProject(root),
637
900
  port
638
901
  });
639
- if (currentResolution.metro || currentSupervisor) return { failed: {
640
- code: "STIM_REMOTE_START_REQUIRED",
641
- message: `The dev server on port ${port} is local-only and cannot gain a managed tunnel while it is running.`,
642
- remedy: "Run `stim stop`, then `stim start --remote`."
643
- } };
644
- const started = await d.startTunnelSequence({
645
- providers: candidates,
646
- port,
647
- ngrokUrl: expectedStableUrl,
648
- requireReachable: false
649
- });
650
- if ("failed" in started) return { failed: {
651
- code: "STIM_REMOTE_METRO_UNREACHABLE",
652
- message: `Could not start a managed Metro tunnel for port ${port}.`,
653
- remedy: started.reason
654
- } };
655
- const record = {
656
- provider: started.provider,
657
- pid: started.pid,
658
- url: normalizeManagedTunnelUrl(started.url),
659
- port,
660
- startedAt: (/* @__PURE__ */ new Date()).toISOString(),
661
- processToken: started.processToken,
662
- logFile: started.logFile
663
- };
664
- startedCleanup = started.cleanup;
665
- try {
666
- d.writeTunnelRecord(root, { metroTunnel: {
667
- kind: "managed",
668
- ...record
669
- } });
670
- } catch (err) {
671
- const stopped = await started.cleanup();
902
+ if (currentResolution.metro || currentSupervisor) {
903
+ if (tracking.startedHere && startedCleanup) {
904
+ const stopped = await startedCleanup();
905
+ if (stopped.status === "failed") return { failed: {
906
+ code: "STIM_REMOTE_START_REQUIRED",
907
+ message: `The dev server on port ${port} started before the managed tunnel was ready.`,
908
+ remedy: `Tunnel cleanup failed for pid ${tracking.record.pid}: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
909
+ } };
910
+ await d.clearTunnelRecord(root, tracking.record);
911
+ return { failed: {
912
+ code: "STIM_REMOTE_START_REQUIRED",
913
+ message: `The dev server on port ${port} started before the managed tunnel was ready.`,
914
+ remedy: "Run `stim stop`, then `stim start --remote`."
915
+ } };
916
+ }
917
+ return {
918
+ origin: tracking.record.url,
919
+ tunnel: tracking
920
+ };
921
+ }
922
+ if (!d.isTunnelAlive(tracking.record.pid, tracking.record.processToken)) {
923
+ if (tracking.startedHere && startedCleanup) {
924
+ const stopped = await startedCleanup();
925
+ if (stopped.status === "failed") return { failed: {
926
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
927
+ message: `The managed ${tracking.record.provider} tunnel is no longer verified running before supervisor startup.`,
928
+ remedy: `Cleanup failed. Unmanaged pid ${tracking.record.pid} may still be running: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
929
+ } };
930
+ }
931
+ await d.clearTunnelRecord(root, tracking.record);
672
932
  return { failed: {
673
933
  code: "STIM_REMOTE_METRO_UNREACHABLE",
674
- message: `Could not record the managed Metro tunnel: ${err?.message || err}`,
675
- remedy: stopped.status === "failed" ? `Cleanup failed. Unmanaged pid ${record.pid} may still be running: ${stopped.reason ?? "unknown error"}` : "The tunnel process was stopped. Fix the workspace write error, then retry `stim start --remote`."
934
+ message: `The managed ${tracking.record.provider} tunnel is no longer verified running before supervisor startup.`,
935
+ remedy: "Retry `stim start --remote`."
676
936
  } };
677
937
  }
678
- tracking = {
679
- record,
680
- startedHere: true
681
- };
682
- }
683
- const currentResolution = await resolveStartupMetro(port, root);
684
- const currentSupervisor = liveSupervisor({
685
- state: readWorkspaceState$1(root),
686
- project: getProject(root),
687
- port
688
- });
689
- if (currentResolution.metro || currentSupervisor) {
690
- if (tracking.startedHere && startedCleanup) {
691
- const stopped = await startedCleanup();
692
- if (stopped.status === "failed") return { failed: {
693
- code: "STIM_REMOTE_START_REQUIRED",
694
- message: `The dev server on port ${port} started before the managed tunnel was ready.`,
695
- remedy: `Tunnel cleanup failed for pid ${tracking.record.pid}: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
696
- } };
697
- d.clearTunnelRecord(root, tracking.record);
938
+ let child;
939
+ try {
940
+ child = await spawnSupervisor(tracking.record.url);
941
+ } catch (err) {
698
942
  return { failed: {
699
- code: "STIM_REMOTE_START_REQUIRED",
700
- message: `The dev server on port ${port} started before the managed tunnel was ready.`,
701
- remedy: "Run `stim stop`, then `stim start --remote`."
943
+ code: "STIM_SUPERVISOR_EXITED",
944
+ message: `Could not spawn the dev server supervisor: ${err?.message || err}`,
945
+ remedy: "Fix the spawn error, then retry `stim start --remote`."
702
946
  } };
703
947
  }
704
- return {
705
- origin: tracking.record.url,
706
- tunnel: tracking
948
+ const handoffRecord = {
949
+ pid: child.pid,
950
+ processToken: child.recordedIdentity ? child.recordedIdentity.processToken ?? null : child.pid ? (0, process_identity_exports.captureProcessToken)(child.pid) : null,
951
+ port,
952
+ mode: serverMode,
953
+ startedAt: new Date(spawnedTs ?? Date.now()).toISOString()
707
954
  };
708
- }
709
- if (!d.isTunnelAlive(tracking.record.pid)) {
710
- if (tracking.startedHere && startedCleanup) {
711
- const stopped = await startedCleanup();
712
- if (stopped.status === "failed") return { failed: {
713
- code: "STIM_REMOTE_METRO_UNREACHABLE",
714
- message: `The managed ${tracking.record.provider} tunnel exited before the supervisor started.`,
715
- remedy: `Cleanup failed. Unmanaged pid ${tracking.record.pid} may still be running: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
716
- } };
717
- }
718
- d.clearTunnelRecord(root, tracking.record);
719
- return { failed: {
720
- code: "STIM_REMOTE_METRO_UNREACHABLE",
721
- message: `The managed ${tracking.record.provider} tunnel exited before the supervisor started.`,
722
- remedy: "Retry `stim start --remote`."
723
- } };
724
- }
725
- let child;
726
- try {
727
- child = await spawnSupervisor(tracking.record.url);
728
- } catch (err) {
729
- return { failed: {
730
- code: "STIM_SUPERVISOR_EXITED",
731
- message: `Could not spawn the dev server supervisor: ${err?.message || err}`,
732
- remedy: "Fix the spawn error, then retry `stim start --remote`."
733
- } };
734
- }
735
- const handoffRecord = {
736
- pid: child.pid,
737
- processToken: child.recordedIdentity ? child.recordedIdentity.processToken ?? null : child.pid ? (0, process_identity_exports.captureProcessToken)(child.pid) : null,
738
- port,
739
- mode: serverMode,
740
- startedAt: new Date(spawnedTs ?? Date.now()).toISOString()
741
- };
742
- try {
743
- d.writeSupervisorRecord(root, { supervisor: handoffRecord });
744
- } catch {
745
- if (!await waitForSupervisorHandoff(child)) {
746
- const stopped = supervisorSignalable(child) && await d.terminateSupervisorChild(child);
747
- return { failed: {
955
+ try {
956
+ await d.writeSupervisorRecord(root, { supervisor: handoffRecord });
957
+ } catch {
958
+ if (!await waitForSupervisorHandoff(child)) return { failed: {
748
959
  code: "STIM_SUPERVISOR_EXITED",
749
960
  message: `Could not record supervisor pid ${child.pid ?? "unknown"} before releasing the managed start lock.`,
750
- remedy: stopped ? "The supervisor process was stopped. Retry `stim start --remote`." : `Cleanup failed. Unmanaged supervisor pid ${child.pid ?? "unknown"} may still be running. Stop it with \`stim stop\`${child.pid === void 0 ? "" : `, or \`${d.platform === "win32" ? `taskkill /PID ${child.pid} /T /F` : `kill ${child.pid}`}\``} before retrying.`
961
+ remedy: "The supervisor process was stopped. Retry `stim start --remote`."
751
962
  } };
752
963
  }
753
- }
754
- return {
755
- origin: tracking.record.url,
756
- tunnel: tracking
757
- };
964
+ return {
965
+ origin: tracking.record.url,
966
+ tunnel: tracking
967
+ };
968
+ })();
969
+ } catch (err) {
970
+ if (err instanceof WorkspaceProcessCleanupError) throw err;
971
+ throwIfCancelled(operationSignal, "Metro startup");
972
+ if (err instanceof Error && "code" in err && err.code === "STIM_CANCELLED") throw err;
973
+ return fail({
974
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
975
+ message: `Could not start the managed Metro tunnel: ${err?.message || err}`,
976
+ remedy: "Retry `stim start --remote` after the other start command finishes."
977
+ });
978
+ }
979
+ if ("failed" in acquisition) return fail(acquisition.failed);
980
+ publicOrigin = acquisition.origin;
981
+ managedTunnel = acquisition.tunnel;
982
+ if (managedTunnel.record.provider === "tailscale") note(phaseLine("lan", `tailnet-only Metro: ${publicOrigin}`));
983
+ resolution = await resolveStartupMetro(port, root);
984
+ supervisor = liveSupervisor({
985
+ state: readWorkspaceState$1(root),
986
+ project: getProject(root),
987
+ port
758
988
  });
759
- } catch (err) {
760
- if (err instanceof Error && "code" in err && err.code === "STIM_CANCELLED") throw err;
761
- return fail({
762
- code: "STIM_REMOTE_METRO_UNREACHABLE",
763
- message: `Could not acquire the managed Metro tunnel lock: ${err?.message || err}`,
764
- remedy: "Retry `stim start --remote` after the other start command finishes."
989
+ }
990
+ }
991
+ const managedTunnelExited = () => managedTunnel !== null && !d.isTunnelAlive(managedTunnel.record.pid, managedTunnel.record.processToken);
992
+ const failExitedManagedTunnel = async () => {
993
+ const tracked = managedTunnel;
994
+ const record = tracked.record;
995
+ const stopped = tracked.startedHere ? await d.stopTunnel(record) : { status: "missing" };
996
+ if (stopped.status !== "failed") await d.clearTunnelRecord(root, record);
997
+ return fail({
998
+ code: "STIM_REMOTE_METRO_UNREACHABLE",
999
+ message: `The managed ${record.provider} tunnel is no longer verified running before the dev server became ready.`,
1000
+ remedy: stopped.status === "failed" ? `The tunnel cleanup also failed: ${stopped.reason ?? "unknown error"}. Run \`stim stop\`, then retry.` : "Run `stim stop`, then `stim start --remote`."
1001
+ });
1002
+ };
1003
+ const requireExpoTunnel = () => {
1004
+ if (tunnel && (!supervisor || readMetroTunnel(root)?.kind !== "expo")) fail({
1005
+ code: "STIM_REMOTE_START_REQUIRED",
1006
+ message: `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.`,
1007
+ remedy: "Run `stim stop`, then `stim start --remote`."
1008
+ });
1009
+ };
1010
+ if (!spawned.child && resolution.metro) {
1011
+ if (managedTunnelExited()) return failExitedManagedTunnel();
1012
+ if (tunnel && supervisor) {
1013
+ if (!await waitForExpoTunnel({
1014
+ root,
1015
+ seconds: waitSeconds,
1016
+ aborted: () => !liveSupervisor({
1017
+ state: readWorkspaceState$1(root),
1018
+ project: getProject(root),
1019
+ port
1020
+ })
1021
+ })) {
1022
+ const stillLive = liveSupervisor({
1023
+ state: readWorkspaceState$1(root),
1024
+ project: getProject(root),
1025
+ port
1026
+ });
1027
+ return fail({
1028
+ code: "STIM_REMOTE_START_REQUIRED",
1029
+ message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
1030
+ remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
1031
+ });
1032
+ }
1033
+ supervisor = liveSupervisor({
1034
+ state: readWorkspaceState$1(root),
1035
+ project: getProject(root),
1036
+ port
765
1037
  });
766
1038
  }
767
- if ("failed" in acquisition) return fail(acquisition.failed);
768
- publicOrigin = acquisition.origin;
769
- managedTunnel = acquisition.tunnel;
770
- if (managedTunnel.record.provider === "tailscale") note(phaseLine("lan", `tailnet-only Metro: ${publicOrigin}`));
771
- resolution = await resolveStartupMetro(port, root);
772
- supervisor = liveSupervisor({
773
- state: readWorkspaceState$1(root),
774
- project: getProject(root),
775
- port
1039
+ requireExpoTunnel();
1040
+ if (!supervisor) {
1041
+ note(chalk.dim(`A dev server for this project already answers on port ${port}, started outside Stim.`));
1042
+ note(chalk.dim("Leaving it alone: Stim will not start a second bundler over a working one."));
1043
+ }
1044
+ if (managedTunnelExited()) return failExitedManagedTunnel();
1045
+ await withWorkspaceStateLockAsync(root, () => clearWorkspaceStateKeys(root, [IDLE_STOP_KEY]), operationSignal);
1046
+ return startFacts({
1047
+ root,
1048
+ port,
1049
+ supervisor,
1050
+ logsDir,
1051
+ alreadyRunning: true
776
1052
  });
777
1053
  }
778
- }
779
- const managedTunnelExited = () => managedTunnel !== null && !d.isTunnelAlive(managedTunnel.record.pid);
780
- const failExitedManagedTunnel = async () => {
781
- const tracked = managedTunnel;
782
- const record = tracked.record;
783
- const stopped = tracked.startedHere ? await d.stopTunnel(record) : { status: "missing" };
784
- if (stopped.status !== "failed") d.clearTunnelRecord(root, record);
785
- return fail({
786
- code: "STIM_REMOTE_METRO_UNREACHABLE",
787
- message: `The managed ${record.provider} tunnel exited before the dev server became ready.`,
788
- remedy: stopped.status === "failed" ? `The tunnel cleanup also failed: ${stopped.reason ?? "unknown error"}. Run \`stim stop\`, then retry.` : "Run `stim stop`, then `stim start --remote`."
789
- });
790
- };
791
- const requireExpoTunnel = () => {
792
- if (tunnel && (!supervisor || readMetroTunnel(root)?.kind !== "expo")) fail({
793
- code: "STIM_REMOTE_START_REQUIRED",
794
- message: `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.`,
795
- remedy: "Run `stim stop`, then `stim start --remote`."
796
- });
797
- };
798
- if (!spawnedChild && resolution.metro) {
799
- if (managedTunnelExited()) return failExitedManagedTunnel();
800
- if (tunnel && supervisor) {
801
- if (!await waitForExpoTunnel({
1054
+ if (!spawned.child && supervisor) {
1055
+ note(chalk.dim(`Supervisor pid ${supervisor.pid} is already running for this workspace; waiting for it to answer on port ${port}...`));
1056
+ if (!await waitForMetro({
802
1057
  root,
1058
+ port,
803
1059
  seconds: waitSeconds,
804
- aborted: () => !liveSupervisor({
805
- state: readWorkspaceState$1(root),
806
- project: getProject(root),
807
- port
808
- })
1060
+ aborted: managedTunnel ? managedTunnelExited : void 0
809
1061
  })) {
810
- const stillLive = liveSupervisor({
811
- state: readWorkspaceState$1(root),
812
- project: getProject(root),
813
- port
814
- });
1062
+ if (managedTunnelExited()) return failExitedManagedTunnel();
815
1063
  return fail({
816
- code: "STIM_REMOTE_START_REQUIRED",
817
- message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
818
- remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
1064
+ code: "STIM_METRO_TIMEOUT",
1065
+ message: `Supervisor pid ${supervisor.pid} did not serve port ${port} within ${waitSeconds}s.`,
1066
+ lines: logTailLines(logFile),
1067
+ remedy: "Run `stim stop` to halt it, then `stim start` again."
819
1068
  });
820
1069
  }
1070
+ if (tunnel) {
1071
+ if (!await waitForExpoTunnel({
1072
+ root,
1073
+ seconds: waitSeconds,
1074
+ aborted: () => !liveSupervisor({
1075
+ state: readWorkspaceState$1(root),
1076
+ project: getProject(root),
1077
+ port
1078
+ })
1079
+ })) {
1080
+ const stillLive = liveSupervisor({
1081
+ state: readWorkspaceState$1(root),
1082
+ project: getProject(root),
1083
+ port
1084
+ });
1085
+ return fail({
1086
+ code: "STIM_REMOTE_START_REQUIRED",
1087
+ message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
1088
+ remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
1089
+ });
1090
+ }
1091
+ }
821
1092
  supervisor = liveSupervisor({
822
1093
  state: readWorkspaceState$1(root),
823
1094
  project: getProject(root),
824
1095
  port
1096
+ }) || (tunnel ? null : supervisor);
1097
+ requireExpoTunnel();
1098
+ if (managedTunnelExited()) return failExitedManagedTunnel();
1099
+ return startFacts({
1100
+ root,
1101
+ port,
1102
+ supervisor,
1103
+ logsDir,
1104
+ alreadyRunning: true
825
1105
  });
826
1106
  }
827
- requireExpoTunnel();
828
- if (!supervisor) {
829
- note(chalk.dim(`A dev server for this project already answers on port ${port}, started outside Stim.`));
830
- note(chalk.dim("Leaving it alone: Stim will not start a second bundler over a working one."));
831
- }
832
1107
  if (managedTunnelExited()) return failExitedManagedTunnel();
833
- clearWorkspaceStateKeys(root, [IDLE_STOP_KEY]);
834
- return startFacts({
835
- root,
836
- port,
837
- supervisor,
838
- logsDir,
839
- alreadyRunning: true
840
- });
841
- }
842
- if (!spawnedChild && supervisor) {
843
- note(chalk.dim(`Supervisor pid ${supervisor.pid} is already running for this workspace; waiting for it to answer on port ${port}...`));
1108
+ const child = spawned.child ?? await spawnSupervisor(publicOrigin);
1109
+ const attemptStartedTs = spawnedTs ?? Date.now();
844
1110
  if (!await waitForMetro({
845
1111
  root,
846
1112
  port,
847
1113
  seconds: waitSeconds,
848
- aborted: managedTunnel ? managedTunnelExited : void 0
1114
+ aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false) || managedTunnelExited()
849
1115
  })) {
850
1116
  if (managedTunnelExited()) return failExitedManagedTunnel();
851
- return fail({
1117
+ const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
1118
+ const exitInfo = childExit;
1119
+ const how = exitInfo ? exitInfo.signal ? `signal ${exitInfo.signal}` : `code ${exitInfo.code}` : "without being observed";
1120
+ return fail(gone ? {
1121
+ code: "STIM_SUPERVISOR_EXITED",
1122
+ message: `The supervisor exited (${how}) before the dev server came up on port ${port}.`,
1123
+ lines: failureEvidence({
1124
+ logFile,
1125
+ logsDir,
1126
+ sinceTs: attemptStartedTs
1127
+ }),
1128
+ remedy: "Fix the error above and run `stim start` again; `stim logs --errors` has the full records."
1129
+ } : {
852
1130
  code: "STIM_METRO_TIMEOUT",
853
- message: `Supervisor pid ${supervisor.pid} did not serve port ${port} within ${waitSeconds}s.`,
854
- lines: logTailLines(logFile),
855
- remedy: "Run `stim stop` to halt it, then `stim start` again."
1131
+ message: `The dev server did not answer on port ${port} within ${waitSeconds}s.`,
1132
+ lines: failureEvidence({
1133
+ logFile,
1134
+ logsDir,
1135
+ sinceTs: attemptStartedTs
1136
+ }),
1137
+ remedy: "It may still be starting. Run `stim stop` to halt it, or `stim logs` to follow along."
856
1138
  });
857
1139
  }
1140
+ if (managedTunnelExited()) return failExitedManagedTunnel();
858
1141
  if (tunnel) {
859
1142
  if (!await waitForExpoTunnel({
860
1143
  root,
861
1144
  seconds: waitSeconds,
862
- aborted: () => !liveSupervisor({
863
- state: readWorkspaceState$1(root),
864
- project: getProject(root),
865
- port
866
- })
1145
+ aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false)
867
1146
  })) {
868
- const stillLive = liveSupervisor({
869
- state: readWorkspaceState$1(root),
870
- project: getProject(root),
871
- port
872
- });
1147
+ const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
873
1148
  return fail({
874
- code: "STIM_REMOTE_START_REQUIRED",
875
- message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
876
- remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
1149
+ code: gone ? "STIM_SUPERVISOR_EXITED" : "STIM_METRO_TIMEOUT",
1150
+ message: gone ? `The supervisor exited before the Expo tunnel became ready on port ${port}.` : `Expo did not report a tunnel URL within ${waitSeconds}s.`,
1151
+ remedy: "Run `stim stop`, then `stim start --remote`."
877
1152
  });
878
1153
  }
879
1154
  }
880
- supervisor = liveSupervisor({
881
- state: readWorkspaceState$1(root),
882
- project: getProject(root),
883
- port
884
- }) || (tunnel ? null : supervisor);
885
- requireExpoTunnel();
886
- if (managedTunnelExited()) return failExitedManagedTunnel();
1155
+ supervisor = await waitForSupervisorHandoff(child);
1156
+ if (!supervisor) return fail({
1157
+ code: "STIM_SUPERVISOR_EXITED",
1158
+ message: "The dev server answered before its supervisor ownership could be verified.",
1159
+ remedy: "Retry `stim start` after the supervisor exits or records its ownership."
1160
+ });
887
1161
  return startFacts({
888
1162
  root,
889
1163
  port,
890
1164
  supervisor,
891
1165
  logsDir,
892
- alreadyRunning: true
1166
+ alreadyRunning: false
893
1167
  });
1168
+ } finally {
1169
+ await reconcileSpawnedSupervisor();
894
1170
  }
895
- if (managedTunnelExited()) return failExitedManagedTunnel();
896
- const child = spawnedChild ?? await spawnSupervisor(publicOrigin);
897
- const attemptStartedTs = spawnedTs ?? Date.now();
898
- if (!await waitForMetro({
899
- root,
900
- port,
901
- seconds: waitSeconds,
902
- aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false) || managedTunnelExited()
903
- })) {
904
- if (managedTunnelExited()) return failExitedManagedTunnel();
905
- const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
906
- const exitInfo = childExit;
907
- const how = exitInfo ? exitInfo.signal ? `signal ${exitInfo.signal}` : `code ${exitInfo.code}` : "without being observed";
908
- return fail(gone ? {
909
- code: "STIM_SUPERVISOR_EXITED",
910
- message: `The supervisor exited (${how}) before the dev server came up on port ${port}.`,
911
- lines: failureEvidence({
912
- logFile,
913
- logsDir,
914
- sinceTs: attemptStartedTs
915
- }),
916
- remedy: "Fix the error above and run `stim start` again; `stim logs --errors` has the full records."
917
- } : {
918
- code: "STIM_METRO_TIMEOUT",
919
- message: `The dev server did not answer on port ${port} within ${waitSeconds}s.`,
920
- lines: failureEvidence({
921
- logFile,
922
- logsDir,
923
- sinceTs: attemptStartedTs
924
- }),
925
- remedy: "It may still be starting. Run `stim stop` to halt it, or `stim logs` to follow along."
926
- });
927
- }
928
- if (managedTunnelExited()) return failExitedManagedTunnel();
929
- if (tunnel) {
930
- if (!await waitForExpoTunnel({
931
- root,
932
- seconds: waitSeconds,
933
- aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false)
934
- })) {
935
- const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
936
- return fail({
937
- code: gone ? "STIM_SUPERVISOR_EXITED" : "STIM_METRO_TIMEOUT",
938
- message: gone ? `The supervisor exited before the Expo tunnel became ready on port ${port}.` : `Expo did not report a tunnel URL within ${waitSeconds}s.`,
939
- remedy: "Run `stim stop`, then `stim start --remote`."
940
- });
941
- }
942
- }
943
- supervisor = liveSupervisor({
944
- state: readWorkspaceState$1(root),
945
- project: getProject(root),
946
- port
947
- }) || {
948
- pid: child.pid,
949
- port,
950
- mode: null,
951
- startedAt: null
952
- };
953
- return startFacts({
954
- root,
955
- port,
956
- supervisor,
957
- logsDir,
958
- alreadyRunning: false
959
- });
960
1171
  };
961
1172
  const startLocked = async () => {
962
1173
  try {
963
- return await withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", runStart, { external: true });
1174
+ return await d.withTunnelLock(root, (tunnelClaim) => withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", (claim) => runStart(claim, tunnelClaim), {
1175
+ external: true,
1176
+ signal: operationSignal
1177
+ }), { signal: operationSignal });
964
1178
  } catch (error) {
1179
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof StartRefusal) throw error;
1180
+ throwIfCancelled(operationSignal, "Metro startup");
1181
+ if (workspaceProcessLockError(error) === "refused") return fail({
1182
+ code: "STIM_WORKTREE_REMOVAL_IN_PROGRESS",
1183
+ message: `The workspace at ${root} is being removed.`,
1184
+ remedy: "Retry `stim start` after workspace removal finishes."
1185
+ });
965
1186
  if (workspaceProcessLockError(error) !== "timeout") throw error;
966
1187
  return fail({
967
1188
  code: "STIM_METRO_TIMEOUT",
@@ -972,7 +1193,7 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
972
1193
  };
973
1194
  if (!managedRemote) return startLocked();
974
1195
  try {
975
- return await d.withWorktreeLock(worktreeRoot, startLocked);
1196
+ return await d.withWorktreeLock(worktreeRoot, startLocked, { signal: operationSignal });
976
1197
  } catch (err) {
977
1198
  const lockError = workspaceProcessLockError(err);
978
1199
  if (lockError === "refused") return fail({
@@ -996,6 +1217,13 @@ async function startDevServer({ root, settings, settingsContext, waitSeconds = D
996
1217
  reclaimed
997
1218
  };
998
1219
  } catch (error) {
1220
+ if (error instanceof WorkspaceProcessCleanupError) return {
1221
+ ok: false,
1222
+ error: startError(error),
1223
+ lines: [],
1224
+ reclaimed,
1225
+ retainedClaimRoots: [...new Set(error.claims.map((claim) => claim.root))]
1226
+ };
999
1227
  if (!(error instanceof StartRefusal)) throw error;
1000
1228
  return {
1001
1229
  ok: false,
@@ -1063,8 +1291,9 @@ async function resolveWorkspaceMetroPort(root, settings, note, command, reserve
1063
1291
  }
1064
1292
  async function reservePort(root, pinned) {
1065
1293
  try {
1066
- return await reserveMetroPort(root, void 0, void 0, pinned);
1294
+ return await reserveMetroPort(root, void 0, void 0, pinned, ambientContext()?.signal);
1067
1295
  } catch (error) {
1296
+ throwIfCancelled(ambientContext()?.signal, "Metro startup");
1068
1297
  if (error instanceof PortInspectionError) return {
1069
1298
  code: error.code,
1070
1299
  message: error.reason,