stim 1.20.2 → 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 (231) hide show
  1. package/README.md +141 -8
  2. package/dist/acquire-artifact-Co2k4f7O.mjs +171 -0
  3. package/dist/act-DsuqEkiM.mjs +2801 -0
  4. package/dist/activity-CMLmzp7o.mjs +1397 -0
  5. package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-DUFaTMdd.mjs} +23 -67
  6. package/dist/{android-DCKZrNvV.mjs → android-ByRMUoiJ.mjs} +157 -93
  7. package/dist/{android-Mz_Inn3w.mjs → android-C7sVmyI9.mjs} +299 -116
  8. package/dist/{android-Dlzxshgp.mjs → android-DGkNqa_c.mjs} +5 -5
  9. package/dist/{android-HqP1_vB_.mjs → android-DR5lZ2Az.mjs} +2 -2
  10. package/dist/android-cas-compiler.mjs +2 -2
  11. package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-ngferVq2.mjs} +7 -6
  12. package/dist/android-project-BCAAmMwz.mjs +12 -0
  13. package/dist/android-project-DVWyCbWU.d.mts +391 -0
  14. package/dist/api.d.mts +810 -64
  15. package/dist/api.mjs +782 -1
  16. package/dist/{artifact-HSwkIWEQ.mjs → artifact-8EY8SAPw.mjs} +240 -213
  17. package/dist/{artifact-D3czlUvT.mjs → artifact-DJPy518Y.mjs} +203 -164
  18. package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
  19. package/dist/{browser-web-CA-F1GQo.mjs → browser-web-Bcv99PBM.mjs} +194 -64
  20. package/dist/budget-Cvwn54qV.mjs +9285 -0
  21. package/dist/{build-DYDbfkk5.mjs → build-CabC3G23.mjs} +153 -98
  22. package/dist/build-cache-Dv7RN200.mjs +602 -0
  23. package/dist/build-plan-DUC8Ka9y.mjs +248 -0
  24. package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DSeQjt-Q.mjs} +2 -2
  25. package/dist/cache-manifest.mjs +1 -1
  26. package/dist/{ccache-zdKiYQ3E.mjs → ccache-Btwc36-I.mjs} +10 -9
  27. package/dist/{cdp-Dq_ypAlJ.mjs → cdp-3fwICjzi.mjs} +18 -5
  28. package/dist/{chrome-BnzZMqsR.mjs → chrome-Bbw689-6.mjs} +7 -4
  29. package/dist/{cli-CLP3diBm.mjs → cli-CHZa9Lvk.mjs} +20 -20
  30. package/dist/cli.mjs +1 -1
  31. package/dist/client-OyRoEuSr.mjs +1577 -0
  32. package/dist/collector-run.d.mts +4 -40
  33. package/dist/collector-run.mjs +9 -8
  34. package/dist/{command-output-BTy8hZ0F.mjs → command-output-gwpm28fu.mjs} +1 -1
  35. package/dist/config-DlrsduwG.mjs +955 -0
  36. package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-x9rN6bhs.mjs} +14 -4
  37. package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-1I9KZT1Y.mjs} +20 -5
  38. package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
  39. package/dist/{dev-client-DAWVIGBu.mjs → dev-client-BSDR5h3F.mjs} +2 -2
  40. package/dist/{device-BECOiufl.mjs → device-B-L3Fbxy.mjs} +91 -34
  41. package/dist/device-DklWZLqb.mjs +1068 -0
  42. package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-DK5_EwRS.mjs} +42 -13
  43. package/dist/device-host-worker.mjs +76 -79
  44. package/dist/{device-ios-COSi6oS1.mjs → device-ios-DoB6KFCO.mjs} +77 -51
  45. package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-xJPQ_QQG.mjs} +14 -26
  46. package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-BMkdsefq.mjs} +5 -5
  47. package/dist/{doctor-kfMBnUw6.mjs → doctor-Bo7BXLux.mjs} +271 -320
  48. package/dist/{doctor-CB8oZKCk.mjs → doctor-D5_EzwfA.mjs} +186 -91
  49. package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
  50. package/dist/doctor-fingerprint.mjs +19 -0
  51. package/dist/doctor-thjWAV0z.mjs +2 -0
  52. package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-N2wDotwQ.mjs} +40 -21
  53. package/dist/{eas-build-JyfqyHfC.mjs → eas-build-CwknaQYj.mjs} +53 -20
  54. package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-CSgAulr5.mjs} +41 -214
  55. package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-DlY_serQ.mjs} +230 -11
  56. package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-DBWO2rxE.mjs} +172 -82
  57. package/dist/exec-DEoAhcze.mjs +858 -0
  58. package/dist/exec-DnYc4HGH.d.mts +78 -0
  59. package/dist/{gc-qWOMdnER.mjs → gc-CXxN-4SE.mjs} +1091 -1029
  60. package/dist/gc-DnNDQyaP.mjs +3 -0
  61. package/dist/{guide-G9cAbVAc.mjs → guide-Br0LEfKy.mjs} +1165 -297
  62. package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-B-9f-jkd.mjs} +10 -8
  63. package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DZAPrcro.mjs} +34 -19
  64. package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
  65. package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-FIRx9X2f.mjs} +77 -43
  66. package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-_awYSpfv.mjs} +35 -22
  67. package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-BdzE668-.mjs} +49 -27
  68. package/dist/idle-C4cZ757K.d.mts +24 -0
  69. package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DDg3SdvY.mjs} +11 -10
  70. package/dist/ios-CDvlmUs7.d.mts +24 -0
  71. package/dist/{ios-BwVCF7oA.mjs → ios-Ds1clbIt.mjs} +8 -8
  72. package/dist/{ios-device-D7pi2Sca.mjs → ios-device--h_-8ENa.mjs} +51 -26
  73. package/dist/{ios-device-B5veSDDQ.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
  74. package/dist/{ios-RkW4pnQf.mjs → ios-hcBpnEGk.mjs} +200 -93
  75. package/dist/{ios-Ce7bc7br.mjs → ios-pm4nL6By.mjs} +160 -108
  76. package/dist/{ios-project-K9hY_Jab.mjs → ios-project-CZhKODF3.mjs} +13 -13
  77. package/dist/ios-signing-BtAffm0A.mjs +489 -0
  78. package/dist/{ios-state-CBLe1xYw.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
  79. package/dist/{ios-nOLJGVdy.mjs → ios-xtlRHaeB.mjs} +1 -1
  80. package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Bwhh4S7V.mjs} +30 -25
  81. package/dist/{launch-C3qJ6Agb.mjs → launch-BjxemNpK.mjs} +85 -543
  82. package/dist/{launch-F4sOqAhB.mjs → launch-CCzXlc8r.mjs} +91 -90
  83. package/dist/logs-BfwwadsZ.mjs +479 -0
  84. package/dist/logs-jTS_lHSo.mjs +248 -0
  85. package/dist/{macos-D7uDvO-z.mjs → macos-DVlSSyUg.mjs} +1 -1
  86. package/dist/macos-NfV2D7dO.mjs +694 -0
  87. package/dist/macos-run.mjs +1 -1
  88. package/dist/maintenance-run.mjs +123 -248
  89. package/dist/{metro-DnCEjCxs.mjs → metro-DiXtA3Ty.mjs} +107 -30
  90. package/dist/metro-gateway-CKZqKqx7.mjs +209 -0
  91. package/dist/{named-ports-4WopRPg-.mjs → named-ports-DkEzOfzO.mjs} +42 -90
  92. package/dist/native-android-CqgnOgsT.mjs +645 -0
  93. package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
  94. package/dist/native-gradle-inputs-Dnh84Zbd.mjs +211 -0
  95. package/dist/native-inputs-TRTTTRxt.mjs +91 -0
  96. package/dist/native-run-NwUuxbkg.mjs +276 -0
  97. package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-B7KIafpM.mjs} +11 -9
  98. package/dist/native-source-Y0yLhFyQ.mjs +133 -0
  99. package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-D_VOfFL7.mjs} +26 -18
  100. package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-BYDOHWLN.mjs} +104 -60
  101. package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
  102. package/dist/offload-worker.d.mts +59 -41
  103. package/dist/offload-worker.mjs +478 -115
  104. package/dist/operation-exec-BW0em61G.mjs +171 -0
  105. package/dist/{ownership-DCtw_2IA.mjs → ownership-C5fo5Edb.mjs} +3 -3
  106. package/dist/{page-DRHy329N.mjs → page-DBL2PiUU.mjs} +12 -5
  107. package/dist/paths-BsJMoPP2.mjs +12 -0
  108. package/dist/paths-DQtTgsBf.mjs +42 -0
  109. package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BnGgJRRZ.mjs} +5 -5
  110. package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-DIsMtN-d.mjs} +15 -11
  111. package/dist/{ports-DdSUicVo.mjs → ports-Cy1RkmuQ.mjs} +3 -3
  112. package/dist/prebuild-D4JoEAEs.mjs +484 -0
  113. package/dist/{project-_phNIJfs.mjs → project-wM_XFTvE.mjs} +65 -37
  114. package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-W1IuLXDt.mjs} +102 -20
  115. package/dist/pull-request-eODyIqZF.d.mts +124 -0
  116. package/dist/pull-requests.d.mts +2 -120
  117. package/dist/pull-requests.mjs +2 -2
  118. package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-6OZ7MSM7.mjs} +121 -72
  119. package/dist/{gradle-DSrXb6lG.mjs → react-native-build-Dvd8hC21.mjs} +292 -15
  120. package/dist/react-native-doctor-DsoSF1Y9.mjs +234 -0
  121. package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-4u1Rn-GR.mjs} +71 -39
  122. package/dist/{recordings-D0d5aQt0.mjs → recordings-CQmfzAh7.mjs} +12 -9
  123. package/dist/registration-BcRgF6Ox.mjs +51 -0
  124. package/dist/{reload-C8JhxjSh.mjs → reload-BzkYabB-.mjs} +94 -47
  125. package/dist/reload-D7AVlb8N.mjs +2 -0
  126. package/dist/{remote-BiR6aYjy.mjs → remote-DjGDmlTZ.mjs} +26 -18
  127. package/dist/result-D2Ykz_iD.mjs +435 -0
  128. package/dist/{run-BtPgpKks.mjs → run-DidHghD1.mjs} +5 -5
  129. package/dist/selection-DSMbWEX2.mjs +620 -0
  130. package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CafzuyFB.mjs} +3 -4
  131. package/dist/{server-command-CTtCkmcQ.mjs → server-command-C6QjeupS.mjs} +14 -7
  132. package/dist/{server-expo-DsguMGUF.mjs → server-expo-CrjB29pq.mjs} +54 -11
  133. package/dist/server-expo-D58c9L0M.mjs +2 -0
  134. package/dist/{settings-B717odTD.mjs → settings-C7O8A6ZI.mjs} +309 -119
  135. package/dist/settings-DpR-1Wnn.mjs +114 -0
  136. package/dist/settings-write-CzD60rOD.mjs +360 -0
  137. package/dist/settings.schema.json +176 -16
  138. package/dist/{simslim-CCr3rE-A.mjs → simslim-BNckWpXi.mjs} +14 -12
  139. package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-CbfmNgM6.mjs} +15 -20
  140. package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
  141. package/dist/stage-Bps5oius.mjs +200 -0
  142. package/dist/{start-TXeMmmO2.mjs → start-Br-oxqMD.mjs} +1 -1
  143. package/dist/start-EojN4WFz.mjs +1364 -0
  144. package/dist/{state-D1Icyjlu.mjs → state--smjFSTL.mjs} +55 -13
  145. package/dist/{state-CG_OFoaA.mjs → state-08I3hw3_.mjs} +3 -2
  146. package/dist/{stats-BoY5sEPU.mjs → stats-D3GerlFW.mjs} +7 -13
  147. package/dist/stats-report-BzaPrRm6.mjs +16 -0
  148. package/dist/{status-BTA7kKiQ.mjs → status-BRROykC8.mjs} +7 -5
  149. package/dist/status-EvrK72L2.mjs +2 -0
  150. package/dist/{status-WgFbkRzj.mjs → status-WBDajew2.mjs} +554 -452
  151. package/dist/{status-watch-C23ILtf9.mjs → status-watch-Bg_u-pwR.mjs} +222 -25
  152. package/dist/status-worker.d.mts +1 -0
  153. package/dist/status-worker.mjs +19 -0
  154. package/dist/stim-desktop-D5OGpYZM.mjs +112 -0
  155. package/dist/{stop-DR6VoDIk.mjs → stop-DTZ9uTw3.mjs} +2 -2
  156. package/dist/{stop-DPEhunlw.mjs → stop-Dky10AEJ.mjs} +33 -23
  157. package/dist/{stop-BNmBMi1F.mjs → stop-Mv6W9FAk.mjs} +137 -98
  158. package/dist/supervisor-run.d.mts +8 -17
  159. package/dist/supervisor-run.mjs +56 -28
  160. package/dist/{support-CwjbFs9H.mjs → support-CNioewsd.mjs} +14 -14
  161. package/dist/{support-DMTqj3Hm.mjs → support-yUiOjnVY.mjs} +20 -13
  162. package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
  163. package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
  164. package/dist/toolchain-BY8X8HXx.mjs +2 -0
  165. package/dist/toolchain-DpJMHq0n.mjs +876 -0
  166. package/dist/{trigger-CigAYjeL.mjs → trigger-Cbyc41d_.mjs} +21 -6
  167. package/dist/trigger-yb_nFzPF.mjs +2 -0
  168. package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-CA3TgT3-.mjs} +263 -156
  169. package/dist/web-CZ07gd77.mjs +2 -0
  170. package/dist/{web-BDBMxDpx.mjs → web-DKx6yoFA.mjs} +20 -14
  171. package/dist/web-run.mjs +13 -12
  172. package/dist/windows-report.d.mts +1 -0
  173. package/dist/windows-report.mjs +18 -0
  174. package/dist/windows-report.ps1 +43 -0
  175. package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-U08dEzhG.mjs} +62 -58
  176. package/dist/{workspaces-C36s2jb9.mjs → workspaces-CpANmBlq.mjs} +1723 -1286
  177. package/dist/{worktree-CgvJYJ-Q.mjs → worktree-CHecZ4Oq.mjs} +1097 -547
  178. package/dist/worktree-ZqZVjD9K.mjs +2 -0
  179. package/dist/xcode-BXn9-xYx.mjs +896 -0
  180. package/package.json +7 -5
  181. package/shim/android-audio/Main.java +24 -0
  182. package/shim/android-audio/README.md +17 -0
  183. package/shim/android-audio.jar +0 -0
  184. package/shim/android-optimizations.gradle +7 -5
  185. package/dist/activity-BSV745Kz.mjs +0 -629
  186. package/dist/api-qvOzWyfO.mjs +0 -154
  187. package/dist/api-run.mjs +0 -294
  188. package/dist/app-install-BIKtMfPq.mjs +0 -896
  189. package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
  190. package/dist/budget-f7516Zk0.mjs +0 -4106
  191. package/dist/build-facts-DcLv2HSd.d.mts +0 -111
  192. package/dist/cancellation-C_22VQTH.mjs +0 -24
  193. package/dist/client-qQAaXSUs.mjs +0 -1771
  194. package/dist/config-BYDxlMY3.mjs +0 -562
  195. package/dist/device-DuiPJQSn.mjs +0 -290
  196. package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
  197. package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
  198. package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
  199. package/dist/exec-DqKo9GKJ.mjs +0 -268
  200. package/dist/git-DDfFzkMm.mjs +0 -45
  201. package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
  202. package/dist/logs-BGrEUaEs.mjs +0 -383
  203. package/dist/macos-Ch5VJmcS.mjs +0 -584
  204. package/dist/metro-gateway-CX9Xlkq2.mjs +0 -103
  205. package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
  206. package/dist/native-android-D5qc3-_v.mjs +0 -226
  207. package/dist/native-gradle-inputs-BsLRrl7u.mjs +0 -349
  208. package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
  209. package/dist/plan-C4TjdPsR.mjs +0 -260
  210. package/dist/prebuild-DYbucPXy.mjs +0 -212
  211. package/dist/preview-78a9kR_-.mjs +0 -1011
  212. package/dist/react-native-build-CtCccJtb.mjs +0 -239
  213. package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
  214. package/dist/remote-cache-OyOyVIS3.mjs +0 -526
  215. package/dist/result-CaJg5Iat.mjs +0 -186
  216. package/dist/server-expo-Bigriskl.mjs +0 -2
  217. package/dist/settings-BORNaaCO.mjs +0 -402
  218. package/dist/settings-vk3mJGSm.d.mts +0 -13
  219. package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
  220. package/dist/stage-KNTQopqv.mjs +0 -157
  221. package/dist/start-BWqzODpb.mjs +0 -1116
  222. package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
  223. package/dist/swiftpm-macos-DRNVJWlb.mjs +0 -119
  224. package/dist/tailnet-Dh-qiYzG.mjs +0 -265
  225. package/dist/toolchain-BFkAcstd.mjs +0 -2
  226. package/dist/toolchain-DxSsNssu.mjs +0 -1657
  227. package/dist/trigger-9_USgmxz.mjs +0 -2
  228. package/dist/web-Dq-i3wzJ.mjs +0 -2
  229. package/dist/worktree-DpLdCHga.mjs +0 -2
  230. package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
  231. package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
@@ -1,1272 +0,0 @@
1
- import { d as getConfigDir } from "./config-BYDxlMY3.mjs";
2
- import { t as getExecutor } from "./exec-DqKo9GKJ.mjs";
3
- import { st as planMetroReach } from "./settings-B717odTD.mjs";
4
- import { E as workspaceStateFile, S as workspaceId, w as workspaceLogsDir$1, x as workspaceDir } from "./workspace-state-DJt_MMvf.mjs";
5
- import { a as formatElapsed } from "./command-output-BTy8hZ0F.mjs";
6
- import { N as readRemoteSession, O as clearRemoteSession, j as readMetroTunnel, o as ownedDeviceLabel } from "./project-_phNIJfs.mjs";
7
- import { s as pidExists } from "./metro-DnCEjCxs.mjs";
8
- import { N as probePublicHttp } from "./workspaces-C36s2jb9.mjs";
9
- import { a as withWorkspaceProcessLock } from "./named-ports-4WopRPg-.mjs";
10
- import { v as nativeRunWaitNotice } from "./tailnet-Dh-qiYzG.mjs";
11
- import { D as sanitizeDeviceLabel } from "./ios-RkW4pnQf.mjs";
12
- import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-B6Q8hZP-.mjs";
13
- import { d as devClientDeepLink, i as INSTALL_ERROR, o as LAUNCH_ERROR } from "./app-install-BIKtMfPq.mjs";
14
- import { a as removeEasSessionClaim, i as recordEasSessionClaim, n as easMachineStateRoot$1, o as compareStimVersions, r as readEasSessionLedger$1, t as assertEasMachineRootWritable } from "./eas-session-ledger-Cm0Gwq2F.mjs";
15
- import { existsSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
16
- import { dirname, join, resolve } from "node:path";
17
- import { readJsonObject } from "@stim-cli/core/state";
18
- //#region src/engine/metro-gate.ts
19
- const REMOTE_METRO_WRONG = "STIM_REMOTE_METRO_WRONG";
20
- const GATE_TIMEOUT_MS = 25e3;
21
- const POLL_MS = 250;
22
- function probeBundleUrl(origin, platform, entryPoint = "index") {
23
- const entry = entryPoint.replace(/^\/+/, "").replace(/\.(?:[cm]?[jt]sx?)$/, "");
24
- return `${origin.replace(/\/+$/, "")}/${entry}.bundle?platform=${platform}&dev=true`;
25
- }
26
- async function gateMetroOrigin({ origin, metroPort, platform, entryPoint = "index", readRecords, isProof, probe = probePublicHttp, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), timeoutMs = GATE_TIMEOUT_MS }) {
27
- const since = now();
28
- const controller = new AbortController();
29
- const bundleUrl = probeBundleUrl(origin, platform, entryPoint);
30
- let status = null;
31
- let pending = false;
32
- let request = Promise.resolve();
33
- const startProbe = () => {
34
- pending = true;
35
- request = (async () => {
36
- try {
37
- status = await probe(bundleUrl, controller.signal);
38
- } catch {
39
- status = null;
40
- } finally {
41
- pending = false;
42
- }
43
- })();
44
- };
45
- startProbe();
46
- try {
47
- const deadline = now() + timeoutMs;
48
- while (now() < deadline) {
49
- for (const record of readRecords()) if (isProof(record, since)) return { ok: true };
50
- if (!pending && status === null) startProbe();
51
- await sleep(POLL_MS);
52
- }
53
- } finally {
54
- controller.abort();
55
- }
56
- await request;
57
- return {
58
- failed: true,
59
- code: REMOTE_METRO_WRONG,
60
- reason: describeMiss(origin, metroPort, status),
61
- remedy: `Check that ${origin} forwards to port ${metroPort} on THIS machine. A tunnel built for a port this workspace no longer holds will answer normally and serve a different project. \`stim start\` prints the port it reserved.`
62
- };
63
- }
64
- function describeMiss(origin, metroPort, status) {
65
- if (status === null) return `${origin} did not answer, so it cannot be this workspace's Metro (port ${metroPort}).`;
66
- if (status >= 500) return `${origin} answered ${status}, so it reached a tunnel but not a dev server (port ${metroPort} may no longer be forwarded).`;
67
- return `${origin} answered ${status}, but the request never reached THIS workspace's Metro on port ${metroPort} -- it is serving a different dev server.`;
68
- }
69
- //#endregion
70
- //#region src/engine/agent-device.ts
71
- const DAEMON_TOKEN_ENV = "AGENT_DEVICE_DAEMON_AUTH_TOKEN";
72
- const PROFILE_FILE = "agent-device.remote.json";
73
- function sessionNameFor(label) {
74
- return `stim-${sanitizeDeviceLabel(label)}`;
75
- }
76
- function remoteProfilePath(root) {
77
- return join(workspaceDir(root), PROFILE_FILE);
78
- }
79
- function remoteProfile({ daemon, platform, label }) {
80
- const scope = sessionNameFor(label);
81
- return {
82
- daemonBaseUrl: daemon.baseUrl,
83
- daemonTransport: "http",
84
- platform,
85
- session: scope,
86
- tenant: scope,
87
- runId: scope,
88
- sessionIsolation: "tenant",
89
- ...platform === "android" ? {
90
- leaseProvider: "proxy",
91
- clientId: scope
92
- } : {}
93
- };
94
- }
95
- function daemonEnv(daemon) {
96
- return { [DAEMON_TOKEN_ENV]: daemon.token };
97
- }
98
- function withProfile(profilePath, args) {
99
- return [
100
- ...args,
101
- "--remote-config",
102
- profilePath
103
- ];
104
- }
105
- function connectArgs(profilePath) {
106
- return withProfile(profilePath, ["connect", "--force"]);
107
- }
108
- function installArgs(profilePath, artifactPath) {
109
- return withProfile(profilePath, ["install", artifactPath]);
110
- }
111
- function openArgs(profilePath, bundleId, url, metro = null) {
112
- const positional = url ? [bundleId, url] : [bundleId];
113
- const hint = metro ? [
114
- "--metro-host",
115
- metro.host,
116
- "--metro-port",
117
- metro.port
118
- ] : [];
119
- return withProfile(profilePath, [
120
- "open",
121
- ...positional,
122
- "--relaunch",
123
- ...hint
124
- ]);
125
- }
126
- function metroHintFrom(origin) {
127
- let url;
128
- try {
129
- url = new URL(origin);
130
- } catch {
131
- return null;
132
- }
133
- const port = url.port || (url.protocol === "https:" ? "443" : url.protocol === "http:" ? "80" : "");
134
- return port ? {
135
- host: url.hostname,
136
- port
137
- } : null;
138
- }
139
- function acceptAlertArgs(profilePath) {
140
- return withProfile(profilePath, ["alert", "accept"]);
141
- }
142
- function disconnectArgs(profilePath) {
143
- return withProfile(profilePath, ["disconnect"]);
144
- }
145
- function closeArgs(profilePath) {
146
- return withProfile(profilePath, ["close"]);
147
- }
148
- const HEALTH_TIMEOUT_MS = 3e3;
149
- function archField(value) {
150
- if (!value || typeof value !== "object") return null;
151
- const arch = value.hostArch;
152
- return arch === "arm64" || arch === "x86_64" ? arch : null;
153
- }
154
- /** agent-device proxies report the daemon machine under `upstream`; a daemon reports itself at the top level. */
155
- function parseHealthHostArch(body) {
156
- if (!body || typeof body !== "object") return null;
157
- return archField(body.upstream) ?? archField(body);
158
- }
159
- async function readRemoteSimulatorArch(daemon, { fetch: fetchImpl = fetch, timeoutMs = HEALTH_TIMEOUT_MS } = {}) {
160
- if (!daemon) return "arm64";
161
- try {
162
- const response = await fetchImpl(`${daemon.baseUrl.replace(/\/+$/, "")}/health`, { signal: AbortSignal.timeout(timeoutMs) });
163
- if (!response.ok) return "arm64";
164
- return parseHealthHostArch(await response.json()) ?? "arm64";
165
- } catch {
166
- return "arm64";
167
- }
168
- }
169
- //#endregion
170
- //#region src/engine/eas-simulator.ts
171
- const OWNED_PREFIX = "stim-";
172
- const LIVE_STATUSES$1 = ["new", "in-progress"];
173
- const LIVE_SESSION_STATUSES = /* @__PURE__ */ new Set(["NEW", "IN_PROGRESS"]);
174
- const TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set(["STOPPED", "ERRORED"]);
175
- const SESSION_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
176
- const MIN_EAS_CLI_SIMULATOR_VERSION = "21.6.0";
177
- const MIN_EAS_CLI_DEVICE_VERSION = "22.2.0";
178
- function easCliUpgradeRemedy(minimum) {
179
- return `Upgrade eas-cli to ${minimum} or later (\`npm install --global eas-cli@latest\`, or the project's eas-cli dependency)`;
180
- }
181
- function easCliSupport(versionOutput, minimum) {
182
- const version = /\beas-cli\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(versionOutput ?? "")?.[1] ?? null;
183
- const order = version ? compareStimVersions(version, minimum) : null;
184
- return {
185
- supported: order !== null && order >= 0,
186
- version
187
- };
188
- }
189
- function ownedSessionName(label) {
190
- const clean = sanitizeDeviceLabel(label);
191
- return `${OWNED_PREFIX}${clean.startsWith(OWNED_PREFIX) ? clean.slice(5) : clean}`;
192
- }
193
- function isOwnedSessionName(name) {
194
- return typeof name === "string" && name.startsWith(OWNED_PREFIX);
195
- }
196
- function createSessionArgs({ label, platform, maxDurationMinutes = null, device = null }) {
197
- const args = [
198
- "sim",
199
- "--platform",
200
- platform,
201
- "--json",
202
- "--non-interactive",
203
- "--out-config-type",
204
- "env",
205
- "--name",
206
- ownedSessionName(label)
207
- ];
208
- if (maxDurationMinutes) args.push("--max-duration-minutes", String(maxDurationMinutes));
209
- if (device) args.push("--device", device);
210
- return args;
211
- }
212
- function getSessionArgs(sessionId) {
213
- return [
214
- "simulator:get",
215
- "--id",
216
- sessionId,
217
- "--json",
218
- "--non-interactive"
219
- ];
220
- }
221
- function stopSessionArgs(sessionId) {
222
- return [
223
- "simulator:stop",
224
- "--id",
225
- sessionId,
226
- "--json",
227
- "--non-interactive"
228
- ];
229
- }
230
- function listOwnedSessionsArgs(after = null) {
231
- const args = [
232
- "simulator:list",
233
- "--name",
234
- OWNED_PREFIX,
235
- ...LIVE_STATUSES$1.flatMap((s) => ["--status", s]),
236
- "--limit",
237
- "100",
238
- "--json",
239
- "--non-interactive"
240
- ];
241
- if (after) args.push("--after", after);
242
- return args;
243
- }
244
- function remoteDaemonFrom(config) {
245
- if (!isRecord(config)) return null;
246
- const typename = str(config["__typename"]);
247
- if (typename && typename !== "AgentDeviceRunSessionRemoteConfig") return null;
248
- const baseUrl = str(config.agentDeviceRemoteSessionUrl);
249
- const token = str(config.agentDeviceRemoteSessionToken);
250
- if (!baseUrl || !token) return null;
251
- const daemon = {
252
- baseUrl,
253
- token
254
- };
255
- const preview = str(config.webPreviewUrl);
256
- if (preview) daemon.webPreviewUrl = preview;
257
- return daemon;
258
- }
259
- function parseJson(stdout) {
260
- try {
261
- return JSON.parse(stdout);
262
- } catch {
263
- return null;
264
- }
265
- }
266
- function isRecord(value) {
267
- return typeof value === "object" && value !== null;
268
- }
269
- function str(value) {
270
- return typeof value === "string" && value.length > 0 ? value : null;
271
- }
272
- function inspectSessionForTeardown(stdout, sessionId) {
273
- const data = parseJson(stdout);
274
- if (!isRecord(data)) return {
275
- action: "refused",
276
- reason: `Session ${sessionId} lookup did not return valid JSON.`
277
- };
278
- const id = str(data.id);
279
- if (!id) return {
280
- action: "refused",
281
- reason: `Session ${sessionId} lookup returned no session id.`
282
- };
283
- if (id !== sessionId) return {
284
- action: "refused",
285
- reason: `Session ${sessionId} lookup returned different session ${id}.`
286
- };
287
- const status = str(data.status);
288
- if (!status) return {
289
- action: "refused",
290
- reason: `Session ${sessionId} lookup returned no status.`
291
- };
292
- const name = str(data.name);
293
- if (!isOwnedSessionName(name)) return {
294
- action: "refused",
295
- reason: `Session ${sessionId} is not owned by Stim (name: ${name ?? "missing"}).`
296
- };
297
- if (TERMINAL_SESSION_STATUSES.has(status)) return {
298
- action: "already-stopped",
299
- name,
300
- status
301
- };
302
- if (!LIVE_SESSION_STATUSES.has(status)) return {
303
- action: "refused",
304
- reason: `Session ${sessionId} has unknown status ${status}.`
305
- };
306
- return {
307
- action: "stop",
308
- name,
309
- status
310
- };
311
- }
312
- function isDefinitiveMissingSessionError(error, sessionId) {
313
- const candidate = error;
314
- const stderr = typeof candidate?.stderr === "string" ? candidate.stderr : "";
315
- const message = typeof candidate?.message === "string" ? candidate.message : "";
316
- const escapedSessionId = sessionId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
317
- const missingSession = new RegExp(`(?:device[\\s-]*run[\\s-]*session|simulator[\\s-]*session)[\\s:#]*\\b${escapedSessionId}\\b[\\s,:]*(?:(?:was\\s+)?not\\s+found|does\\s+not\\s+exist)\\b`, "i");
318
- return `${stderr}\n${message}`.split(/\r?\n/).map((line) => line.replace(/\s+/g, " ").trim()).some((line) => missingSession.test(line));
319
- }
320
- function verifyStoppedSession(stdout, sessionId) {
321
- const stopped = parseStoppedSession(stdout);
322
- if (!stopped) return {
323
- ok: false,
324
- reason: `Stop for session ${sessionId} did not return valid JSON with an id.`
325
- };
326
- if (stopped.id !== sessionId) return {
327
- ok: false,
328
- reason: `Stop for session ${sessionId} returned different session ${stopped.id}.`
329
- };
330
- if (!stopped.status || !TERMINAL_SESSION_STATUSES.has(stopped.status)) return {
331
- ok: false,
332
- reason: `Stop for session ${sessionId} returned unknown status ${stopped.status ?? "missing"}.`
333
- };
334
- return { ok: true };
335
- }
336
- function parseCreatedSession(stdout) {
337
- const data = parseJson(stdout);
338
- if (!isRecord(data)) return null;
339
- const id = str(data.id);
340
- if (!id) return null;
341
- return {
342
- id,
343
- name: str(data.name),
344
- url: str(data.deviceRunSessionUrl),
345
- daemon: remoteDaemonFrom(data.remoteConfig)
346
- };
347
- }
348
- function parseSessionListPage(stdout) {
349
- const data = parseJson(stdout);
350
- if (!isRecord(data) || !Array.isArray(data.sessions)) return {
351
- ok: false,
352
- reason: "EAS session list did not return valid JSON with a sessions array."
353
- };
354
- if (!isRecord(data.pageInfo) || typeof data.pageInfo.hasNextPage !== "boolean") return {
355
- ok: false,
356
- reason: "EAS session list page did not return valid pagination information."
357
- };
358
- const endCursor = data.pageInfo.endCursor;
359
- if (endCursor !== null && typeof endCursor !== "string") return {
360
- ok: false,
361
- reason: "EAS session list returned a malformed pagination cursor."
362
- };
363
- return {
364
- ok: true,
365
- page: {
366
- sessions: data.sessions,
367
- hasNextPage: data.pageInfo.hasNextPage,
368
- endCursor
369
- }
370
- };
371
- }
372
- function findOrphanedOwnedSessions({ sessions, recordedSessionIds, projectScope }) {
373
- const scope = typeof projectScope === "string" ? projectScope.trim() : "";
374
- if (!scope) return {
375
- orphaned: [],
376
- notices: ["EAS session list has no current-project scope."]
377
- };
378
- const recorded = new Set(recordedSessionIds.filter((id) => typeof id === "string" && id.length > 0));
379
- const candidates = /* @__PURE__ */ new Map();
380
- const conflicts = /* @__PURE__ */ new Set();
381
- const blocked = /* @__PURE__ */ new Set();
382
- const notices = [];
383
- for (const raw of sessions) {
384
- if (!isRecord(raw)) {
385
- notices.push("EAS session list contains a malformed entry.");
386
- continue;
387
- }
388
- const id = str(raw.id);
389
- const name = str(raw.name);
390
- if (!id) {
391
- if (isOwnedSessionName(name)) notices.push("EAS session list contains an owned entry with no session id.");
392
- continue;
393
- }
394
- if (!name) {
395
- blocked.add(id);
396
- candidates.delete(id);
397
- notices.push(`EAS session ${id} has no name and cannot be classified as Stim-owned.`);
398
- continue;
399
- }
400
- if (!isOwnedSessionName(name)) continue;
401
- const status = str(raw.status)?.toUpperCase().replace(/-/g, "_") ?? null;
402
- if (!status) {
403
- blocked.add(id);
404
- candidates.delete(id);
405
- notices.push(`Owned EAS session ${id} has no status.`);
406
- continue;
407
- }
408
- if (TERMINAL_SESSION_STATUSES.has(status)) continue;
409
- if (!LIVE_SESSION_STATUSES.has(status)) {
410
- blocked.add(id);
411
- candidates.delete(id);
412
- notices.push(`Owned EAS session ${id} has unknown status ${status}.`);
413
- continue;
414
- }
415
- const platformValue = str(raw.platform);
416
- const normalizedPlatform = platformValue?.toLowerCase() ?? null;
417
- if (!normalizedPlatform || !SESSION_PLATFORMS.has(normalizedPlatform)) {
418
- blocked.add(id);
419
- candidates.delete(id);
420
- notices.push(platformValue ? `Owned EAS session ${id} has unknown platform ${platformValue}.` : `Owned EAS session ${id} has no platform.`);
421
- continue;
422
- }
423
- if (recorded.has(id) || blocked.has(id)) continue;
424
- const candidate = {
425
- id,
426
- name,
427
- status,
428
- platform: normalizedPlatform,
429
- projectScope: scope
430
- };
431
- const previous = candidates.get(id);
432
- if (!previous) {
433
- candidates.set(id, candidate);
434
- continue;
435
- }
436
- if (previous.name !== candidate.name || previous.status !== candidate.status || previous.platform !== candidate.platform) {
437
- conflicts.add(id);
438
- candidates.delete(id);
439
- notices.push(`EAS session list contains conflicting entries for session ${id}.`);
440
- }
441
- }
442
- for (const id of [...conflicts, ...blocked]) candidates.delete(id);
443
- return {
444
- orphaned: [...candidates.values()],
445
- notices
446
- };
447
- }
448
- function parseStoppedSession(stdout) {
449
- const data = parseJson(stdout);
450
- if (!isRecord(data)) return null;
451
- const id = str(data.id);
452
- if (!id) return null;
453
- return {
454
- id,
455
- status: str(data.status)
456
- };
457
- }
458
- //#endregion
459
- //#region src/engine/eas-project-lock.ts
460
- const EAS_PROJECT_LOCK_WAIT_MS = 24e4;
461
- function describeEasProjectLockHolder(holder, now) {
462
- const { purpose, workspace } = holder.details;
463
- const facts = [`pid ${holder.owner.pid}`];
464
- if (typeof workspace === "string") facts.push(`in ${workspace}`);
465
- const started = Date.parse(holder.startedAt);
466
- if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
467
- return `${typeof purpose === "string" ? purpose : "another Stim EAS operation"} (${facts.join(", ")})`;
468
- }
469
- function withEasProjectLock(_root, fn, options = {}) {
470
- const { machineRoot = easMachineStateRoot$1(), ...lockOptions } = options;
471
- assertEasMachineRootWritable(machineRoot);
472
- return withWorkspaceProcessLock(machineRoot, "eas-project", fn, {
473
- waitMs: EAS_PROJECT_LOCK_WAIT_MS,
474
- ...lockOptions,
475
- external: true
476
- });
477
- }
478
- //#endregion
479
- //#region src/engine/device-remote.ts
480
- const REMOTE_SESSION_ERROR = "STIM_NO_REMOTE_SESSION";
481
- const REMOTE_METRO_ERROR = "STIM_REMOTE_METRO_UNREACHABLE";
482
- function resolveMetroOrigin({ metroPort, publicUrl = null, mode = "auto", isExpo = false, available = [] }) {
483
- const plan = planMetroReach({
484
- mode,
485
- metroPort,
486
- publicUrl,
487
- isExpo,
488
- available
489
- });
490
- if ("origin" in plan) return plan;
491
- if ("failed" in plan) return plan;
492
- return {
493
- failed: "Metro has no address the device can reach yet.",
494
- remedy: "This is a Stim bug: the tunnel step did not run before the launch."
495
- };
496
- }
497
- function describe(err) {
498
- const e = err;
499
- const stderr = typeof e?.stderr === "string" ? e.stderr.trim() : "";
500
- if (err?.code === "ETIMEDOUT") return String(e?.message ?? err);
501
- return stderr || String(e?.message ?? err);
502
- }
503
- const PROXY_CREDENTIAL_ENV = ["AGENT_DEVICE_DAEMON_BASE_URL", "AGENT_DEVICE_DAEMON_AUTH_TOKEN"];
504
- function easExecOptions(root) {
505
- return {
506
- cwd: root,
507
- omitEnv: PROXY_CREDENTIAL_ENV
508
- };
509
- }
510
- const EAS_OPERATION_TIMEOUT_MS = 3e4;
511
- const EAS_SESSION_CREATE_TIMEOUT_MS = 6e5;
512
- const AGENT_DEVICE_TIMEOUT_MS = 12e4;
513
- const AGENT_DEVICE_INSTALL_TIMEOUT_MS = 6e5;
514
- function easBoundedExecOptions(root) {
515
- return {
516
- ...easExecOptions(root),
517
- timeoutMs: EAS_OPERATION_TIMEOUT_MS
518
- };
519
- }
520
- function writeProfile(ctx, daemon) {
521
- const path = remoteProfilePath(ctx.root);
522
- mkdirSync(dirname(path), { recursive: true });
523
- const profile = remoteProfile({
524
- daemon,
525
- platform: ctx.platform ?? "ios",
526
- label: ctx.label
527
- });
528
- writeFileSync(path, `${JSON.stringify(profile, null, 2)}\n`);
529
- return path;
530
- }
531
- function remoteDeviceDeps(ctx) {
532
- let session = null;
533
- let createdSession = null;
534
- const easEnv = easExecOptions(ctx.root);
535
- return {
536
- checkCapacity: () => null,
537
- ensureDevice: async () => ({
538
- deviceName: ctx.backend === "proxy" ? "remote device (your daemon)" : "EAS Simulator",
539
- owned: true,
540
- remote: true
541
- }),
542
- ensureBooted: async ({ out = () => {} } = {}) => {
543
- session = null;
544
- createdSession = null;
545
- const note = (message) => {
546
- try {
547
- out(message);
548
- } catch {}
549
- };
550
- let daemon = ctx.existingDaemon ?? null;
551
- let id = null;
552
- let createdHere = null;
553
- if (!daemon) {
554
- const recorded = readRemoteSession(ctx.root);
555
- if (recorded && recorded.platform !== (ctx.platform ?? "ios")) return {
556
- failed: true,
557
- code: "STIM_REMOTE_PLATFORM_MISMATCH",
558
- reason: `Session ${recorded.sessionId} belongs to ${recorded.platform ?? "an unknown platform"}, not ${ctx.platform ?? "ios"}.`,
559
- remedy: "Run `stim stop` for this workspace before selecting a different remote platform."
560
- };
561
- if (recorded) {
562
- const existing = readLiveDaemon(ctx, recorded.sessionId);
563
- if (existing && ctx.deviceType && recorded.deviceType !== ctx.deviceType) return {
564
- failed: true,
565
- code: "STIM_REMOTE_DEVICE_MISMATCH",
566
- reason: `EAS Simulator session ${recorded.sessionId} runs ${recorded.deviceType ? `"${recorded.deviceType}"` : "the model EAS chose"}, not "${ctx.deviceType}". Stim will not reuse a session on a different model.`,
567
- remedy: `Run \`stim stop\` to end that session, then run \`stim ${ctx.platform ?? "ios"} --remote eas --device-type "${ctx.deviceType}"\` again to start one on the requested model.`
568
- };
569
- if (existing) {
570
- note(`Reusing EAS Simulator session ${recorded.sessionId}.`);
571
- daemon = existing;
572
- id = recorded.sessionId;
573
- } else {
574
- note(`Recorded session ${recorded.sessionId} is not usable; verifying it before replacement.`);
575
- const cleanup = teardownRemote(ctx, { sessionId: recorded.sessionId });
576
- if (cleanup.status === "failed" || cleanup.reason) return {
577
- failed: true,
578
- code: "STIM_REMOTE_SESSION_CLEANUP",
579
- reason: cleanup.reason ?? `Could not verify recorded EAS session ${recorded.sessionId}.`,
580
- remedy: "Inspect the recorded session, then run `stim stop` again."
581
- };
582
- clearRemoteSession(ctx.root, recorded.sessionId);
583
- }
584
- }
585
- }
586
- if (!daemon) {
587
- note("Creating an EAS Simulator session (this takes a moment).");
588
- let stdout;
589
- try {
590
- stdout = getExecutor().runFile(ctx.easBin, createSessionArgs({
591
- label: ctx.label,
592
- platform: ctx.platform ?? "ios",
593
- maxDurationMinutes: ctx.maxDurationMinutes ?? null,
594
- device: ctx.deviceType ?? null
595
- }), {
596
- ...easEnv,
597
- timeoutMs: EAS_SESSION_CREATE_TIMEOUT_MS,
598
- killSignal: "SIGKILL"
599
- });
600
- } catch (err) {
601
- if (err?.code === "ETIMEDOUT") {
602
- const partial = parseCreatedSession(String(err.stdout ?? ""));
603
- if (partial) return abandonSession(ctx, partial.id, `eas sim did not finish within ${EAS_SESSION_CREATE_TIMEOUT_MS / 6e4} minutes after creating session ${partial.id}.`);
604
- const name = ownedSessionName(ctx.label);
605
- return {
606
- failed: true,
607
- reason: `eas sim did not finish within ${EAS_SESSION_CREATE_TIMEOUT_MS / 6e4} minutes and the eas process was killed. EAS may still have started session ${name}; it has no Stim ownership claim, so \`stim gc\` will not stop it.`,
608
- remedy: `Run \`eas simulator:list --name ${name}\` and stop any running session with \`eas simulator:stop --id <id>\`, then retry.`
609
- };
610
- }
611
- return {
612
- failed: true,
613
- reason: `eas sim failed: ${describe(err)}`
614
- };
615
- }
616
- const created = parseCreatedSession(stdout);
617
- if (!created) return {
618
- failed: true,
619
- reason: "eas sim returned no session; run it by hand to see what it reported."
620
- };
621
- id = created.id;
622
- createdHere = created.id;
623
- daemon = created.daemon ?? await waitForDaemon(ctx, created.id, note, { sleep: ctx.sleep ?? defaultSleep });
624
- if (!daemon) return abandonSession(ctx, created.id, `Session ${created.id} never published an agent-device endpoint. It may be an appium or argent session rather than an agent-device one.`);
625
- }
626
- let profilePath;
627
- try {
628
- profilePath = writeProfile(ctx, daemon);
629
- } catch (err) {
630
- const reason = `Could not write the remote connection profile: ${describe(err)}`;
631
- if (createdHere) return abandonSession(ctx, createdHere, reason);
632
- return {
633
- failed: true,
634
- reason
635
- };
636
- }
637
- try {
638
- getExecutor().runFile(ctx.agentDeviceBin, closeArgs(profilePath), {
639
- cwd: ctx.root,
640
- env: daemonEnv(daemon),
641
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
642
- killSignal: "SIGKILL"
643
- });
644
- } catch {}
645
- try {
646
- getExecutor().runFile(ctx.agentDeviceBin, connectArgs(profilePath), {
647
- cwd: ctx.root,
648
- env: daemonEnv(daemon),
649
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
650
- killSignal: "SIGKILL"
651
- });
652
- } catch (err) {
653
- if (createdHere) return abandonSession(ctx, createdHere, `agent-device connect failed: ${describe(err)}`);
654
- return {
655
- failed: true,
656
- reason: `agent-device connect failed: ${describe(err)}`
657
- };
658
- }
659
- session = {
660
- id,
661
- daemon,
662
- profilePath
663
- };
664
- createdSession = createdHere;
665
- if (daemon.webPreviewUrl) note(`Watch this device: ${daemon.webPreviewUrl}`);
666
- return {
667
- ok: true,
668
- udid: id ?? daemonHostLabel(daemon.baseUrl)
669
- };
670
- },
671
- installArtifact: (artifactPath) => {
672
- if (!session) return notConnected(INSTALL_ERROR);
673
- try {
674
- getExecutor().runFile(ctx.agentDeviceBin, installArgs(session.profilePath, artifactPath), {
675
- cwd: ctx.root,
676
- env: daemonEnv(session.daemon),
677
- timeoutMs: AGENT_DEVICE_INSTALL_TIMEOUT_MS,
678
- killSignal: "SIGKILL"
679
- });
680
- return {
681
- ok: true,
682
- appPath: artifactPath
683
- };
684
- } catch (err) {
685
- return {
686
- failed: true,
687
- code: INSTALL_ERROR,
688
- reason: `agent-device install failed for ${artifactPath}: ${describe(err)}`
689
- };
690
- }
691
- },
692
- launchApp: ({ appId, metroPort, devClientScheme = null }) => {
693
- if (!session) return notConnected(LAUNCH_ERROR);
694
- if (metroPort === null) try {
695
- getExecutor().runFile(ctx.agentDeviceBin, openArgs(session.profilePath, appId, null, null), {
696
- cwd: ctx.root,
697
- env: daemonEnv(session.daemon),
698
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
699
- killSignal: "SIGKILL"
700
- });
701
- return {
702
- ok: true,
703
- mode: "launch"
704
- };
705
- } catch (err) {
706
- return {
707
- failed: true,
708
- code: LAUNCH_ERROR,
709
- reason: `agent-device open ${appId} failed: ${describe(err)}`
710
- };
711
- }
712
- const origin = resolveMetroOrigin({
713
- metroPort,
714
- publicUrl: ctx.publicMetroUrl ?? null,
715
- mode: ctx.tunnelMode ?? "auto",
716
- isExpo: ctx.isExpo ?? false
717
- });
718
- if ("failed" in origin) return {
719
- failed: true,
720
- code: REMOTE_METRO_ERROR,
721
- reason: `${origin.failed} ${origin.remedy}`
722
- };
723
- const url = devClientScheme ? devClientDeepLink(devClientScheme, origin.origin) : null;
724
- try {
725
- getExecutor().runFile(ctx.agentDeviceBin, openArgs(session.profilePath, appId, url, metroHintFrom(origin.origin)), {
726
- cwd: ctx.root,
727
- env: daemonEnv(session.daemon),
728
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
729
- killSignal: "SIGKILL"
730
- });
731
- if (url) try {
732
- getExecutor().runFile(ctx.agentDeviceBin, acceptAlertArgs(session.profilePath), {
733
- cwd: ctx.root,
734
- env: daemonEnv(session.daemon),
735
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
736
- killSignal: "SIGKILL"
737
- });
738
- } catch {}
739
- return {
740
- ok: true,
741
- mode: url ? "openurl" : "launch",
742
- url: url ?? void 0,
743
- jsLocation: origin.origin
744
- };
745
- } catch (err) {
746
- return {
747
- failed: true,
748
- code: LAUNCH_ERROR,
749
- reason: `agent-device open ${appId} failed: ${describe(err)}`
750
- };
751
- }
752
- },
753
- createdSessionId: () => createdSession,
754
- abandonCreatedSession: () => {
755
- if (!createdSession) return {
756
- ok: true,
757
- sessionId: null
758
- };
759
- const stopped = stopCreatedSession(ctx, createdSession);
760
- if (stopped.ok) {
761
- session = null;
762
- createdSession = null;
763
- }
764
- return stopped;
765
- },
766
- webPreviewUrl: () => session?.daemon.webPreviewUrl ?? null,
767
- failureRemedy: () => ctx.backend === "eas" && session?.id ? `EAS Simulator session ${session.id} is still running and billed until it reaches its time limit or \`stim stop\` ends it. Fix the error above and run this command again to reuse the session, or run \`stim stop\` to end it.` : "Check that the remote daemon is reachable and still has the device, then run this command again."
768
- };
769
- }
770
- async function resolveRemoteContext({ root, backend, platform = "ios", easBin, env = process.env, lookupAgentDevice = defaultLookupAgentDevice, readEasCliVersion = readInstalledEasCliVersion, maxDurationMinutes = null, deviceType = null }) {
771
- const agentDeviceBin = lookupAgentDevice();
772
- if (!agentDeviceBin) return {
773
- failed: "agent-device is not on PATH, and it is what drives a remote device.",
774
- remedy: `Install it (\`npm i -g agent-device\`), then run \`stim ${platform} --remote ${backend}\` again.`
775
- };
776
- const baseUrl = env.AGENT_DEVICE_DAEMON_BASE_URL?.trim();
777
- const token = env.AGENT_DEVICE_DAEMON_AUTH_TOKEN?.trim();
778
- let existingDaemon = null;
779
- if (backend === "proxy") {
780
- if (!baseUrl && !token) return {
781
- failed: "The proxy backend requires AGENT_DEVICE_DAEMON_BASE_URL and AGENT_DEVICE_DAEMON_AUTH_TOKEN.",
782
- remedy: "Export AGENT_DEVICE_DAEMON_BASE_URL and AGENT_DEVICE_DAEMON_AUTH_TOKEN, then run the device command with `--remote proxy` again.",
783
- code: "STIM_REMOTE_PROXY_CONFIG"
784
- };
785
- if (!baseUrl) return {
786
- failed: "The proxy backend requires AGENT_DEVICE_DAEMON_BASE_URL.",
787
- remedy: "Export AGENT_DEVICE_DAEMON_BASE_URL, then run the device command with `--remote proxy` again.",
788
- code: "STIM_REMOTE_PROXY_CONFIG"
789
- };
790
- if (!token) return {
791
- failed: "The proxy backend requires AGENT_DEVICE_DAEMON_AUTH_TOKEN.",
792
- remedy: "Export AGENT_DEVICE_DAEMON_AUTH_TOKEN, then run the device command with `--remote proxy` again.",
793
- code: "STIM_REMOTE_PROXY_CONFIG"
794
- };
795
- existingDaemon = {
796
- baseUrl,
797
- token
798
- };
799
- } else if (!easBin) return {
800
- failed: "The eas backend requires eas-cli.",
801
- remedy: "Install eas-cli, then run the device command with `--remote eas` again.",
802
- code: "STIM_REMOTE_EAS_UNAVAILABLE"
803
- };
804
- else {
805
- const versionOutput = readEasCliVersion(easBin, root);
806
- const { supported, version } = easCliSupport(versionOutput, MIN_EAS_CLI_SIMULATOR_VERSION);
807
- if (!supported) return {
808
- failed: version ? `eas-cli ${version} (${easBin}) has no EAS Simulator commands; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.` : `Could not read an eas-cli version from \`${easBin} --version\`; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.`,
809
- remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_SIMULATOR_VERSION)}, then run the device command with \`--remote eas\` again.`,
810
- code: "STIM_REMOTE_EAS_UNAVAILABLE"
811
- };
812
- if (deviceType && !easCliSupport(versionOutput, "22.2.0").supported) return {
813
- failed: `eas-cli ${version} (${easBin}) cannot choose the EAS Simulator model; --device-type on the eas backend needs eas-cli ${MIN_EAS_CLI_DEVICE_VERSION} or later.`,
814
- remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_DEVICE_VERSION)}, or drop --device-type to let EAS pick the model.`,
815
- code: "STIM_BAD_ARG"
816
- };
817
- }
818
- return { ctx: {
819
- root,
820
- label: `${ownedDeviceLabel(root)}-${workspaceId(root)}`,
821
- backend,
822
- platform,
823
- easBin: easBin ?? "",
824
- agentDeviceBin,
825
- maxDurationMinutes,
826
- deviceType: backend === "eas" ? deviceType : null,
827
- publicMetroUrl: null,
828
- existingDaemon
829
- } };
830
- }
831
- function readInstalledEasCliVersion(easBin, root) {
832
- try {
833
- return getExecutor().runFile(easBin, ["--version"], easBoundedExecOptions(root));
834
- } catch {
835
- return null;
836
- }
837
- }
838
- function binOnPath(bin) {
839
- try {
840
- return Boolean(getExecutor().findExecutable(bin));
841
- } catch {
842
- return false;
843
- }
844
- }
845
- function defaultLookupAgentDevice() {
846
- const found = getExecutor().findExecutable("agent-device");
847
- return (String(found ?? "").split("\n")[0] ?? "").trim() || null;
848
- }
849
- const DAEMON_WAIT_MS = 18e4;
850
- const DAEMON_POLL_MS = 5e3;
851
- async function waitForDaemon(ctx, sessionId, out, { sleep = defaultSleep } = {}) {
852
- const attempts = Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS);
853
- for (let attempt = 0; attempt < attempts; attempt++) {
854
- const daemon = readDaemon(ctx, sessionId);
855
- if (daemon) return daemon;
856
- if (attempt === 0) out(`Waiting for session ${sessionId} to become reachable.`);
857
- await sleep(DAEMON_POLL_MS);
858
- }
859
- return null;
860
- }
861
- function defaultSleep(ms) {
862
- return new Promise((resolve) => setTimeout(resolve, ms));
863
- }
864
- function stopCreatedSession(ctx, sessionId) {
865
- let stopOutput;
866
- try {
867
- stopOutput = getExecutor().runFile(ctx.easBin, stopSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
868
- } catch (err) {
869
- return {
870
- failed: true,
871
- code: "STIM_REMOTE_SESSION_CLEANUP",
872
- reason: `Stim could not stop session ${sessionId} (${describe(err)}). The session bills until its cap.`,
873
- remedy: `Run \`eas simulator:stop --id ${sessionId}\`.`,
874
- sessionId
875
- };
876
- }
877
- const verified = verifyStoppedSession(stopOutput, sessionId);
878
- if (!verified.ok) return {
879
- failed: true,
880
- code: "STIM_REMOTE_SESSION_CLEANUP",
881
- reason: `Stim could not verify that session ${sessionId} stopped (${verified.reason}). The session bills until its cap.`,
882
- remedy: `Run \`eas simulator:stop --id ${sessionId}\`.`,
883
- sessionId
884
- };
885
- return {
886
- ok: true,
887
- sessionId
888
- };
889
- }
890
- function abandonSession(ctx, sessionId, reason) {
891
- const stopped = stopCreatedSession(ctx, sessionId);
892
- if (stopped.ok) return {
893
- failed: true,
894
- reason: `${reason} The session was stopped.`
895
- };
896
- return {
897
- failed: true,
898
- code: stopped.code,
899
- reason: `${reason} ${stopped.reason} ${stopped.remedy}`,
900
- remedy: stopped.remedy
901
- };
902
- }
903
- function withRemoteSessionLock(root, fn, options = {}) {
904
- let canonicalRoot;
905
- try {
906
- canonicalRoot = realpathSync(root);
907
- } catch {
908
- canonicalRoot = resolve(root);
909
- }
910
- return withWorkspaceProcessLock(workspaceDir(canonicalRoot), "remote-session", fn, {
911
- external: true,
912
- waitMs: 24e4,
913
- ...options
914
- });
915
- }
916
- const EAS_START_HOLD_MS = 108e4 + Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS) * 35e3;
917
- async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, deviceType = null, boot, createdSessionId, abandonCreatedSession, webPreviewUrl = () => null, writeState, register = () => {}, withProjectLock = withEasProjectLock, withLock = withRemoteSessionLock, removeEasSessionClaim: removeClaim = removeEasSessionClaim, ledgerRoot = easMachineStateRoot$1(), notice = () => {}, now = Date.now }) {
918
- const waitNotice = nativeRunWaitNotice({
919
- write: notice,
920
- now,
921
- describe: describeEasProjectLockHolder
922
- });
923
- let stuck = null;
924
- let seen = null;
925
- let projectLocked = false;
926
- try {
927
- return await withProjectLock(root, () => {
928
- projectLocked = true;
929
- return withLock(root, async () => {
930
- register();
931
- const booted = await boot();
932
- if (booted.failed) return booted;
933
- const sessionId = createdSessionId();
934
- if (!sessionId) return booted;
935
- try {
936
- recordEasSessionClaim({
937
- sessionId,
938
- name: sessionName,
939
- platform,
940
- workspaceRoot: root,
941
- workspaceHome: getConfigDir(),
942
- stateFile: workspaceStateFile(root)
943
- }, ledgerRoot);
944
- const preview = webPreviewUrl();
945
- writeState(root, { remoteDevice: {
946
- platform,
947
- sessionId,
948
- startedAt,
949
- ...preview ? { webPreviewUrl: preview } : {},
950
- ...deviceType ? { deviceType } : {}
951
- } });
952
- return booted;
953
- } catch (err) {
954
- const cleanup = abandonCreatedSession();
955
- if (cleanup.ok) {
956
- let removalFailure = "the claim store did not remove it";
957
- try {
958
- if (removeClaim(sessionId, ledgerRoot)) return {
959
- failed: true,
960
- code: "STIM_REMOTE_SESSION_STATE",
961
- reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped.`,
962
- remedy: "Fix workspace state storage, then retry the remote command."
963
- };
964
- } catch (error) {
965
- removalFailure = describe(error);
966
- }
967
- return {
968
- failed: true,
969
- code: "STIM_REMOTE_SESSION_CLEANUP",
970
- reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped, but its ownership claim could not be removed: ${removalFailure}.`,
971
- remedy: `The stopped session claim remains under ${ledgerRoot}. Repair that ownership ledger before retrying the remote command.`
972
- };
973
- }
974
- return {
975
- failed: true,
976
- code: cleanup.code ?? "STIM_REMOTE_SESSION_CLEANUP",
977
- reason: `Could not record EAS session ${sessionId}: ${describe(err)}. ${cleanup.reason ?? ""}`.trim(),
978
- remedy: cleanup.remedy ?? `Run \`eas simulator:stop --id ${sessionId}\`.`
979
- };
980
- }
981
- });
982
- }, {
983
- ownerPurpose: "EAS remote start",
984
- machineRoot: ledgerRoot,
985
- waitMs: Number.POSITIVE_INFINITY,
986
- details: {
987
- workspace: root,
988
- platform
989
- },
990
- now,
991
- onHeld: (holder) => {
992
- const at = now();
993
- if (seen?.claimId !== holder.claimId) seen = {
994
- claimId: holder.claimId,
995
- at
996
- };
997
- const claimedAt = Date.parse(holder.startedAt);
998
- const heldMs = at - (Number.isFinite(claimedAt) ? Math.min(claimedAt, seen.at) : seen.at);
999
- if (heldMs > EAS_START_HOLD_MS) {
1000
- stuck = {
1001
- holder,
1002
- heldMs
1003
- };
1004
- const error = /* @__PURE__ */ new Error(`The eas-project lock was held past ${formatElapsed(EAS_START_HOLD_MS)}.`);
1005
- error.code = "STIM_LOCK_TIMEOUT";
1006
- throw error;
1007
- }
1008
- waitNotice(holder, `waiting for ${describeEasProjectLockHolder(holder, at)} to release the EAS project lock; one EAS session starts at a time on this machine`);
1009
- }
1010
- });
1011
- } catch (err) {
1012
- const code = err.code ?? "STIM_NO_REMOTE_SESSION";
1013
- if (stuck && !projectLocked) {
1014
- const { holder, heldMs } = stuck;
1015
- return {
1016
- failed: true,
1017
- code,
1018
- reason: `${describeEasProjectLockHolder(holder, now())} has held the EAS project lock for ${formatElapsed(heldMs)}; an EAS session start holds it for at most ${formatElapsed(EAS_START_HOLD_MS)}.`,
1019
- remedy: `If pid ${holder.owner.pid} is stuck, stop it, then run the remote command again.`
1020
- };
1021
- }
1022
- return {
1023
- failed: true,
1024
- code,
1025
- reason: describe(err),
1026
- remedy: "Retry the remote command after the other workspace operation finishes."
1027
- };
1028
- }
1029
- }
1030
- function daemonHostLabel(baseUrl) {
1031
- try {
1032
- return new URL(baseUrl).host;
1033
- } catch {
1034
- return baseUrl;
1035
- }
1036
- }
1037
- function notConnected(code) {
1038
- return {
1039
- failed: true,
1040
- code,
1041
- reason: "No remote session is connected; the device step did not complete."
1042
- };
1043
- }
1044
- const LIVE_STATUSES = /* @__PURE__ */ new Set(["NEW", "IN_PROGRESS"]);
1045
- function readLiveDaemon(ctx, sessionId) {
1046
- let stdout;
1047
- try {
1048
- stdout = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
1049
- } catch {
1050
- return null;
1051
- }
1052
- try {
1053
- const inspection = inspectSessionForTeardown(stdout, sessionId);
1054
- if (inspection.action !== "stop" || !LIVE_STATUSES.has(inspection.status)) return null;
1055
- return remoteDaemonFrom(JSON.parse(stdout).remoteConfig);
1056
- } catch {
1057
- return null;
1058
- }
1059
- }
1060
- function readDaemon(ctx, sessionId) {
1061
- let stdout;
1062
- try {
1063
- stdout = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
1064
- } catch {
1065
- return null;
1066
- }
1067
- try {
1068
- return remoteDaemonFrom(JSON.parse(stdout).remoteConfig);
1069
- } catch {
1070
- return null;
1071
- }
1072
- }
1073
- function remoteIosDeps(ctx) {
1074
- const shared = {
1075
- ...ctx,
1076
- platform: "ios"
1077
- };
1078
- const core = remoteDeviceDeps(shared);
1079
- return {
1080
- ctx: shared,
1081
- checkDeviceCapacity: core.checkCapacity,
1082
- ensureOwnedDevice: core.ensureDevice,
1083
- ensureBooted: core.ensureBooted,
1084
- installIosApp: ({ appPath }) => core.installArtifact(appPath),
1085
- launchIosApp: ({ bundleId, metroPort, devClientScheme }) => core.launchApp({
1086
- appId: bundleId,
1087
- metroPort,
1088
- devClientScheme
1089
- }),
1090
- createdSessionId: core.createdSessionId,
1091
- abandonCreatedSession: core.abandonCreatedSession,
1092
- webPreviewUrl: core.webPreviewUrl,
1093
- failureRemedy: core.failureRemedy
1094
- };
1095
- }
1096
- function remoteAndroidDeps(ctx) {
1097
- const shared = {
1098
- ...ctx,
1099
- platform: "android"
1100
- };
1101
- const core = remoteDeviceDeps(shared);
1102
- return {
1103
- ctx: shared,
1104
- checkCapacity: core.checkCapacity,
1105
- ensureDevice: core.ensureDevice,
1106
- ensureDeviceBooted: async (opts) => {
1107
- const booted = await core.ensureBooted(opts);
1108
- return booted.ok ? {
1109
- ok: true,
1110
- serial: booted.udid
1111
- } : booted;
1112
- },
1113
- install: ({ apkPath }) => core.installArtifact(apkPath),
1114
- launch: ({ packageName, metroPort, devClientScheme }) => core.launchApp({
1115
- appId: packageName,
1116
- metroPort,
1117
- devClientScheme
1118
- }),
1119
- createdSessionId: core.createdSessionId,
1120
- abandonCreatedSession: core.abandonCreatedSession,
1121
- webPreviewUrl: core.webPreviewUrl,
1122
- failureRemedy: core.failureRemedy
1123
- };
1124
- }
1125
- function endRecordedSession({ root, sessionId, easBin, lookupAgentDevice, ledgerRoot }) {
1126
- const agentDeviceBin = (lookupAgentDevice ?? defaultLookupAgentDevice)();
1127
- if (!easBin) return {
1128
- status: "failed",
1129
- reason: `No eas-cli to stop session ${sessionId} with; it keeps billing until its max duration. Run \`eas simulator:stop --id ${sessionId}\`.`
1130
- };
1131
- return teardownRemote({
1132
- root,
1133
- label: "",
1134
- backend: "eas",
1135
- easBin,
1136
- agentDeviceBin: agentDeviceBin ?? "",
1137
- existingDaemon: null,
1138
- easLedgerRoot: ledgerRoot
1139
- }, { sessionId });
1140
- }
1141
- function teardownRemote(ctx, { sessionId }) {
1142
- const profilePath = remoteProfilePath(ctx.root);
1143
- try {
1144
- getExecutor().runFile(ctx.agentDeviceBin, disconnectArgs(profilePath), {
1145
- cwd: ctx.root,
1146
- timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
1147
- killSignal: "SIGKILL"
1148
- });
1149
- } catch {}
1150
- if (!sessionId) return { status: "torn-down" };
1151
- const ledger = readEasSessionLedger$1(ctx.easLedgerRoot);
1152
- const claimNeedsRemoval = !ledger.safe || ledger.claims.has(sessionId);
1153
- const removeClaim = () => {
1154
- try {
1155
- if ((ctx.removeEasSessionClaim ?? removeEasSessionClaim)(sessionId, ctx.easLedgerRoot) || !claimNeedsRemoval) return void 0;
1156
- return `EAS session ${sessionId} is stopped, but its ownership claim could not be removed. The claim and workspace record were kept for reconciliation. Re-run stop.`;
1157
- } catch (error) {
1158
- return `EAS session ${sessionId} is stopped, but its ownership claim could not be removed: ${describe(error)}. The claim and workspace record were kept for reconciliation. Re-run stop.`;
1159
- }
1160
- };
1161
- let sessionOutput;
1162
- try {
1163
- sessionOutput = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
1164
- } catch (err) {
1165
- if (isDefinitiveMissingSessionError(err, sessionId)) return {
1166
- status: "torn-down",
1167
- reason: removeClaim()
1168
- };
1169
- return {
1170
- status: "failed",
1171
- reason: `Could not verify EAS session ${sessionId}: ${describe(err)}. The session record was kept for retry.`
1172
- };
1173
- }
1174
- const inspection = inspectSessionForTeardown(sessionOutput, sessionId);
1175
- if (inspection.action === "refused") return {
1176
- status: "failed",
1177
- reason: `${inspection.reason} The session record was kept for retry.`
1178
- };
1179
- if (inspection.action === "already-stopped") return {
1180
- status: "torn-down",
1181
- reason: removeClaim()
1182
- };
1183
- let stopOutput;
1184
- try {
1185
- stopOutput = getExecutor().runFile(ctx.easBin, stopSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
1186
- } catch (err) {
1187
- return {
1188
- status: "failed",
1189
- reason: `eas simulator:stop ${sessionId} failed: ${describe(err)}. The session record was kept for retry.`
1190
- };
1191
- }
1192
- const verified = verifyStoppedSession(stopOutput, sessionId);
1193
- if (!verified.ok) return {
1194
- status: "failed",
1195
- reason: `${verified.reason} The session record was kept for retry.`
1196
- };
1197
- return {
1198
- status: "torn-down",
1199
- reason: removeClaim()
1200
- };
1201
- }
1202
- async function ensureMetroReachable({ ctx, metroPort, isExpo, tunnelMode = "auto", publicUrl = null, available = [], env = process.env, readTunnelRecord = readMetroTunnel, isTunnelAlive = pidExists, gateOrigin = gateMetroOrigin }) {
1203
- const { root } = ctx;
1204
- const platform = ctx.platform ?? "ios";
1205
- const publicMetroUrl = env["STIM_METRO_PUBLIC_URL"]?.trim() || publicUrl || null;
1206
- const recorded = readTunnelRecord(root);
1207
- const effectiveAvailable = recorded?.kind === "managed" && isTunnelAlive(recorded.pid) ? [recorded.provider, ...available.filter((provider) => provider !== recorded.provider)] : available;
1208
- const plan = planMetroReach({
1209
- mode: tunnelMode,
1210
- metroPort,
1211
- publicUrl: publicMetroUrl,
1212
- isExpo,
1213
- available: effectiveAvailable
1214
- });
1215
- if ("failed" in plan) return plan;
1216
- let resolvedUrl = null;
1217
- let gate = false;
1218
- if ("origin" in plan) {
1219
- resolvedUrl = plan.origin;
1220
- gate = plan.gate;
1221
- } else if ("expoTunnel" in plan) {
1222
- if (!recorded || recorded.kind !== "expo") return {
1223
- failed: "This workspace's Metro tunnels itself (metro.tunnel is expo, or auto on an Expo project), but no Expo tunnel URL is recorded.",
1224
- remedy: "Run `stim start --remote` so Expo can establish its own tunnel before a remote device connects."
1225
- };
1226
- resolvedUrl = recorded.url;
1227
- gate = true;
1228
- } else {
1229
- const port = Number(metroPort);
1230
- if (plan.start === "tailscale" && ctx.backend === "eas") return {
1231
- failed: "metro.tunnel is \"tailscale\", but an EAS Simulator is not on your tailnet and cannot reach it.",
1232
- remedy: "Use the proxy backend with a device on the same tailnet, or set metro.tunnel to \"auto\" or \"ngrok\".",
1233
- code: REMOTE_METRO_ERROR
1234
- };
1235
- const providerMatches = recorded?.kind === "managed" && recorded.provider === plan.start;
1236
- if (recorded && recorded.kind === "managed" && recorded.port === port && providerMatches && isTunnelAlive(recorded.pid)) resolvedUrl = recorded.url;
1237
- else return {
1238
- failed: `No live managed Metro tunnel is recorded for port ${port}.`,
1239
- remedy: "Run `stim start --remote`, then retry the device command."
1240
- };
1241
- gate = recorded.provider !== "tailscale";
1242
- }
1243
- if (gate && resolvedUrl) {
1244
- const logsDir = workspaceLogsDir$1(root);
1245
- const result = await gateOrigin({
1246
- origin: resolvedUrl,
1247
- metroPort,
1248
- platform,
1249
- entryPoint: bundleEntryPoint(root, isExpo),
1250
- readRecords: () => readMetroRecords(logsDir),
1251
- isProof: isBundleProof
1252
- });
1253
- if (result.failed) return {
1254
- failed: result.reason ?? "Metro gate failed.",
1255
- remedy: result.remedy ?? "",
1256
- code: REMOTE_METRO_WRONG
1257
- };
1258
- }
1259
- ctx.publicMetroUrl = resolvedUrl;
1260
- return { ok: true };
1261
- }
1262
- function bundleEntryPoint(root, isExpo) {
1263
- if (!isExpo) return "index";
1264
- const declared = readJsonObject(resolve(root, "package.json"))?.main;
1265
- if (typeof declared !== "string" || !declared.trim()) return "index";
1266
- const main = declared.trim();
1267
- const local = main.startsWith(".") || /\.[cm]?[jt]sx?$/.test(main) || existsSync(resolve(root, main));
1268
- const normalized = main.replace(/^\.\//, "").replace(/\.(?:[cm]?[jt]sx?)$/, "");
1269
- return local ? normalized : `node_modules/${normalized}`;
1270
- }
1271
- //#endregion
1272
- export { parseSessionListPage as C, gateMetroOrigin as D, readRemoteSimulatorArch as E, ownedSessionName as S, verifyStoppedSession as T, findOrphanedOwnedSessions as _, ensureMetroReachable as a, isDefinitiveMissingSessionError as b, remoteAndroidDeps as c, withRemoteSessionLock as d, withEasProjectLock as f, easCliUpgradeRemedy as g, easCliSupport as h, endRecordedSession as i, remoteIosDeps as l, MIN_EAS_CLI_SIMULATOR_VERSION as m, binOnPath as n, ensureRemoteBootOwned as o, MIN_EAS_CLI_DEVICE_VERSION as p, bundleEntryPoint as r, readInstalledEasCliVersion as s, REMOTE_SESSION_ERROR as t, resolveRemoteContext as u, getSessionArgs as v, stopSessionArgs as w, listOwnedSessionsArgs as x, inspectSessionForTeardown as y };