stim 1.21.0 → 1.23.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-ybouwJSR.mjs} +21 -3
  3. package/dist/{act-DHAw2A2S.mjs → act-DG54PWtJ.mjs} +298 -164
  4. package/dist/{activity-BzVfHSrY.mjs → activity-B5RtpFda.mjs} +33 -12
  5. package/dist/{agent-device-usage-output-zh1vhbLp.mjs → agent-device-usage-output-UJ5gC80l.mjs} +7 -7
  6. package/dist/{android-BLgbNqox.mjs → android-BoUJKbfz.mjs} +66 -52
  7. package/dist/{android-DUYkNF4u.mjs → android-C7sVmyI9.mjs} +160 -59
  8. package/dist/{android-CV_GCELZ.mjs → android-CHH4KLOR.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-DAuZ-FNU.mjs → artifact-5RteZem7.mjs} +69 -40
  16. package/dist/{artifact-B8T3S5gi.mjs → artifact-Dwdo7k1E.mjs} +40 -35
  17. package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
  18. package/dist/{browser-web-QQzVjz3O.mjs → browser-web-CeEVlGug.mjs} +187 -59
  19. package/dist/{budget-Bs6SxxzO.mjs → budget-DnPX3DXj.mjs} +830 -699
  20. package/dist/{build-CoPzQRNw.mjs → build-CqufZFqy.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-C8CIvRyi.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-RcAy92gH.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-D-J9fWGV.mjs} +20 -20
  29. package/dist/cli.mjs +1 -1
  30. package/dist/{client-UXD70ZbM.mjs → client-yyso1bwi.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-Db8BqzxH.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-BnYSDiKa.mjs} +2 -2
  39. package/dist/{device-DCxH7_By.mjs → device-BvzPUBs3.mjs} +16 -16
  40. package/dist/{device-ExJq8Ei7.mjs → device-CCkWmkAA.mjs} +71 -14
  41. package/dist/{device-capacity-CMlqEcvw.mjs → device-capacity-CjaSdrhg.mjs} +9 -10
  42. package/dist/device-host-worker.mjs +25 -32
  43. package/dist/{device-ios-DRUeKaFV.mjs → device-ios-uOiEETX2.mjs} +39 -16
  44. package/dist/{device-lease-run-C8raGFoP.mjs → device-lease-run-BgwPHc6u.mjs} +5 -5
  45. package/dist/{device-pool-DMRpVX9a.mjs → device-pool-C-ptmZTa.mjs} +3 -3
  46. package/dist/{doctor-DTvaDOda.mjs → doctor-8sr2tFOL.mjs} +24 -37
  47. package/dist/{doctor-Dhqebm0q.mjs → doctor-D2SN3Ltx.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-pxxvipYC.mjs} +1 -1
  51. package/dist/{doctor-watchman-Cv1F10Kj.mjs → doctor-watchman-sy_RNhFt.mjs} +5 -5
  52. package/dist/{eas-build-BomfRnFT.mjs → eas-build-CyjU5K7E.mjs} +4 -4
  53. package/dist/{eas-fallback-DJwZpy0c.mjs → eas-fallback-DV3Wytf1.mjs} +27 -18
  54. package/dist/{eas-session-ledger-op6A54yi.mjs → eas-session-ledger-BzzsX97d.mjs} +8 -7
  55. package/dist/{error-diagnostics-Bf0rZvG9.mjs → error-diagnostics-C6sVoeqO.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-CDoogxAI.mjs} +587 -313
  59. package/dist/gc-DLbzvTP2.mjs +3 -0
  60. package/dist/{guide-Yqwt2bd6.mjs → guide-BC37GcDG.mjs} +288 -59
  61. package/dist/{hosted-android-Bw-honsd.mjs → hosted-android-CkclQig_.mjs} +6 -6
  62. package/dist/{hosted-client-DB_gBPfY.mjs → hosted-client-ByuMMO5s.mjs} +4 -4
  63. package/dist/{hosted-ios-CUvOf4Vr.mjs → hosted-ios-DtYHeuYR.mjs} +1 -1
  64. package/dist/{hosted-logs-DL_P5AdN.mjs → hosted-logs-Br84qWy1.mjs} +5 -5
  65. package/dist/{hosted-macos-QI141yKk.mjs → hosted-macos-DdtYajZt.mjs} +21 -12
  66. package/dist/{hosted-native-Ksyjgzq-.mjs → hosted-native-UPI9zdZ4.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-DO7oIRlc.mjs} +7 -7
  69. package/dist/ios-CDvlmUs7.d.mts +24 -0
  70. package/dist/{ios-ubFsBzwq.mjs → ios-DZZbNYlY.mjs} +59 -50
  71. package/dist/{ios-device-CXGP1bsD.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
  72. package/dist/{ios-device-CwFMehUS.mjs → ios-device-JC3uWM4K.mjs} +3 -3
  73. package/dist/{ios-CpByZy12.mjs → ios-hcBpnEGk.mjs} +81 -34
  74. package/dist/{ios-project-WuwwBdvW.mjs → ios-project-CVyNwjAF.mjs} +5 -5
  75. package/dist/{ios-signing-DTTIChGs.mjs → ios-signing-BtAffm0A.mjs} +2 -2
  76. package/dist/{ios-state-DvFX-K_l.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
  77. package/dist/{ios-CQ68hG9s.mjs → ios-vbUOqGXu.mjs} +8 -8
  78. package/dist/{ios-qNJt4dFm.mjs → ios-xtlRHaeB.mjs} +1 -1
  79. package/dist/{js-swap-Cc1dttps.mjs → js-swap-C3QxUFJR.mjs} +7 -29
  80. package/dist/{launch-CD1yVc7U.mjs → launch-Bz7JtaC4.mjs} +16 -16
  81. package/dist/{launch-Zq2mOSDy.mjs → launch-CHY3p-yz.mjs} +40 -39
  82. package/dist/{logs-CMm05HxN.mjs → logs-CtNeNDuq.mjs} +10 -17
  83. package/dist/{logs-BS00Anxf.mjs → logs-mxqsWpJs.mjs} +151 -24
  84. package/dist/{macos-B65--pPd.mjs → macos-inNFbBWc.mjs} +1 -1
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/{macos-C-0qJ6zq.mjs → macos-u0BoHKnO.mjs} +143 -46
  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-CdCSylTn.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-BUTu8T7A.mjs} +4 -5
  95. package/dist/{native-runtime-Dg1Q7tfF.mjs → native-runtime-CRSOZxKY.mjs} +8 -7
  96. package/dist/native-source-Y0yLhFyQ.mjs +133 -0
  97. package/dist/{native-xcode-inputs-CB2iQeWq.mjs → native-xcode-inputs-G6rkkhxu.mjs} +2 -3
  98. package/dist/{native-xcode-ios-CXX39F7o.mjs → native-xcode-ios-_os2PlCD.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-BU42oOyT.mjs} +12 -8
  106. package/dist/{ports-D-MmCTdv.mjs → ports-BSmQKzW7.mjs} +3 -3
  107. package/dist/{prebuild-uD_ipCxF.mjs → prebuild-BFm63bQ0.mjs} +63 -16
  108. package/dist/{project-BZNjmW_8.mjs → project-CTLNHCfE.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-CFgUroxW.mjs} +73 -53
  114. package/dist/{react-native-build-rhk4smwQ.mjs → react-native-build-CmJkTljQ.mjs} +8 -8
  115. package/dist/{react-native-doctor-CQpuIMso.mjs → react-native-doctor-LTF-Xt8l.mjs} +7 -7
  116. package/dist/{react-native-ios-Bhq1rJeV.mjs → react-native-ios-ChBF1MkB.mjs} +43 -29
  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-DF9hqUUB.mjs} +16 -15
  120. package/dist/{reload-DzjSl5P_.mjs → reload-RBi1nj6Z.mjs} +1 -1
  121. package/dist/{remote-ByYkXdGD.mjs → remote-BmvMCcJw.mjs} +18 -15
  122. package/dist/{result-DNiS-tTP.mjs → result-Cx5CRLvS.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-BTiGPEjS.mjs} +5 -5
  127. package/dist/server-expo-C-Buqvn_.mjs +2 -0
  128. package/dist/{server-expo-DT_F_uVI.mjs → server-expo-D9lEz4aq.mjs} +7 -5
  129. package/dist/{settings-CsJDEXgK.mjs → settings-C7O8A6ZI.mjs} +223 -243
  130. package/dist/{settings-T1gtc3bf.mjs → settings-eKF_vJJs.mjs} +1 -1
  131. package/dist/{settings-write-HyVZ8eQ8.mjs → settings-write-79mQRr9C.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-D7M2nE4b.mjs} +6 -6
  135. package/dist/{stage-BIS0ydsV.mjs → stage-DKNM4ZPd.mjs} +21 -4
  136. package/dist/{start-RChynRDp.mjs → start-CRfN1kWV.mjs} +1 -1
  137. package/dist/{start-7lpH7hmN.mjs → start-bjfiScgz.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-DioA6cBU.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-Eqx_20WC.mjs} +1 -1
  144. package/dist/{status-BL7jVyAB.mjs → status-kzbeWk4x.mjs} +43 -29
  145. package/dist/{status-watch-GDCfqpTE.mjs → status-watch-BIqqbJ6q.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-B18d8KDw.mjs} +2 -2
  149. package/dist/{stop-CPzlM1L-.mjs → stop-B2uLQfoM.mjs} +15 -16
  150. package/dist/{stop-D7h6jAXQ.mjs → stop-DzLJc7Ei.mjs} +31 -22
  151. package/dist/supervisor-run.d.mts +3 -3
  152. package/dist/supervisor-run.mjs +28 -36
  153. package/dist/{support-Dlu37g6H.mjs → support-BTpZ6RAJ.mjs} +15 -11
  154. package/dist/{support-67vraa1Q.mjs → support-DTBV56gy.mjs} +8 -8
  155. package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
  156. package/dist/swiftpm-macos-r_WSKlXr.mjs +532 -0
  157. package/dist/{toolchain-Hp9sCpK4.mjs → toolchain-CWeGJgCc.mjs} +9 -6
  158. package/dist/toolchain-GVgnf8d1.mjs +2 -0
  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-Cduk0NSE.mjs +2 -0
  163. package/dist/{web-CJmIj6AI.mjs → web-CwREhjD-.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-BxzYlo3P.mjs} +594 -266
  168. package/dist/{worktree-DQ6Zi2Mv.mjs → worktree-8lqn_lBp.mjs} +850 -551
  169. package/dist/worktree-BxfVxG_b.mjs +2 -0
  170. package/dist/{xcode-Zlx3qjEs.mjs → xcode-iQnROM5o.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,20 +1,20 @@
1
- import { c as stopDeclaringSpawnsOn, f as ownership_claim_exports, i as declareSpawnsOn, p as process_identity_exports, t as getExecutor } from "./exec-_UbtCQcG.mjs";
1
+ import { a as workspaceProcessLockError, c as declareSpawnsOn, f as stopDeclaringSpawnsOn, g as process_identity_exports, i as withWorkspaceProcessLock, o as workspaceProcessLockPath, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
2
2
  import { d as workspaceDir, h as workspaceName$1, l as workspaceAgentDeviceDir } from "./paths-DQtTgsBf.mjs";
3
- import { D as withConfigLock$1, F as isOnMountedVolume, I as listMountedVolumes, K as projectDeviceSlots, O as withConfigLockAsync$1, P as formatBytes, R as measuredDirectorySizeAsync, U as deviceSlotPlatforms, _ as loadConfig$1, d as getConfigDir, g as isPathPrefix, j as acquireAvdClaim, p as getProject } from "./config-CYcBF478.mjs";
3
+ import { F as isOnMountedVolume, G as projectDeviceSlots, H as deviceSlotPlatforms, I as listMountedVolumes, L as measuredDirectorySizeAsync, O as withConfigLockAsync$1, P as formatBytes, _ as loadConfig$1, d as getConfigDir, g as isPathPrefix, j as acquireAvdClaim, p as getProject } from "./config-DlrsduwG.mjs";
4
4
  import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
5
- import { a as formatElapsed, l as phaseLine } from "./command-output-B1UrjZHL.mjs";
6
- import { l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-Cjuigeeu.mjs";
7
- import { It as repoRootAsync, Qt as cancellableSleep, en as throwIfCancelled, ut as resolveTailscaleBinary } from "./settings-CsJDEXgK.mjs";
8
- import { a as macosRuntimeClaim, n as macosDir } from "./state-XMziexv2.mjs";
9
- import { l as pidExists, m as signalProcessTreeAsync, p as signalProcessTree } from "./metro-DEXpIhHC.mjs";
10
- import { _ as chromeProcessStateAsync, c as webClaimRoot, i as clearWebRecord, o as readWebRecord, p as webProfileDir, t as CDP_PORT_LABEL, u as webFacts, v as liveProfileHolder, y as removeSingletonFiles } from "./state-C4ZtUisW.mjs";
5
+ import { a as formatElapsed, l as phaseLine } from "./command-output-Db8BqzxH.mjs";
6
+ import { f as throwIfCancelled, t as runOperationFile, u as cancellableSleep } from "./operation-exec-BW0em61G.mjs";
7
+ import { l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-U08dEzhG.mjs";
8
+ import { Nt as repoRootAsync, ut as resolveTailscaleBinary } from "./settings-C7O8A6ZI.mjs";
9
+ import { a as macosRuntimeClaim, n as macosDir } from "./state-08I3hw3_.mjs";
10
+ import { c as pidExists, f as signalProcessTree, p as signalProcessTreeAsync } from "./metro-DiXtA3Ty.mjs";
11
+ import { _ as chromeProcessStateAsync, c as webClaimRoot, i as clearWebRecord, o as readWebRecord, p as webProfileDir, t as CDP_PORT_LABEL, u as webFacts, v as liveProfileHolder, y as removeSingletonFiles } from "./state--smjFSTL.mjs";
11
12
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
12
- import { N as listBuildLocks, d as readAgentDeviceRecords, t as agentDeviceDaemon } from "./activity-BzVfHSrY.mjs";
13
- import { n as readCreatedDevices$1, r as recordCreatedDevice, t as forgetCreatedDevice } from "./created-devices-CqeBlrEn.mjs";
14
- import { A as listAdbDevicesAsync, B as ownedAvdMatchesConfiguration, F as listOrphanedAvdDirectories, Q as resolveOwnedAvdSerialAsync, W as ownedAvdSystemImage, _ as avdStorageRoots, _t as removeParkedAfter, at as wipeAvdUserData, et as shutdownAndroidEmulator, f as assertOwnedAvdStopped, ft as eraseParkedAfter, gt as readParked, h as avdPathExists, mt as parkSim, rt as waitForAndroidEmulatorShutdown, st as isStimOwnedAvd, x as deleteAvd, z as ownedAvdDirectory } from "./android-DUYkNF4u.mjs";
15
- import { E as resolveOwnedIosSim, O as shutdownIosSim, S as renameIosSim, c as eraseIosSim, f as listAllIosSimsAsync, o as deleteIosSim, p as listIosDeviceTypesAsync, s as deleteParkedIosSim, v as parkedSimName, y as parseRuntimeVersion } from "./ios-CpByZy12.mjs";
16
- import { n as workspaceProcessLockError, r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-1_1OoVW5.mjs";
17
- import { r as releaseBrowserPort } from "./named-ports-BWTzt6mu.mjs";
13
+ import { N as listBuildLocks, d as readAgentDeviceRecords, t as agentDeviceDaemon } from "./activity-B5RtpFda.mjs";
14
+ import { i as recordCreatedDevice, r as readCreatedDevices$1, t as forgetCreatedDevice } from "./created-devices-x9rN6bhs.mjs";
15
+ import { H as ownedAvdMatchesConfiguration, K as ownedAvdSystemImage, L as listOrphanedAvdDirectories, S as deleteAvd, V as ownedAvdDirectory, at as waitForAndroidEmulatorShutdown, et as resolveOwnedAvdSerialAsync, g as avdPathExists, gt as parkSim, j as listAdbDevicesAsync, lt as isStimOwnedAvd, mt as eraseParkedAfter, nt as shutdownAndroidEmulator, p as assertOwnedAvdStoppedAsync, st as wipeAvdUserData, v as avdStorageRoots, vt as readParked, yt as removeParkedAfter } from "./android-C7sVmyI9.mjs";
16
+ import { E as resolveOwnedIosSim, O as shutdownIosSim, S as renameIosSim, c as eraseIosSim, f as listAllIosSimsAsync, o as deleteIosSim, p as listIosDeviceTypesAsync, s as deleteParkedIosSim, v as parkedSimName, y as parseRuntimeVersion } from "./ios-hcBpnEGk.mjs";
17
+ import { r as releaseBrowserPort } from "./named-ports-DkEzOfzO.mjs";
18
18
  import { createHash, randomUUID } from "node:crypto";
19
19
  import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
20
20
  import { isDeepStrictEqual } from "node:util";
@@ -24,26 +24,35 @@ import { ambientContext, ambientEnv, ambientWrite } from "@stim-cli/core";
24
24
  import { buildSlotsDir, hostedNativeRecords, isJsonObject, macosAppState, parseHostedNativePlacement, readJsonFile, readMacosRecord, requireAutomaticMachine, tryAcquireBuildSlotClaim, unreadableHostedNative } from "@stim-cli/core/state";
25
25
  import { basename as basename$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1 } from "path";
26
26
  import chalk from "chalk";
27
- import { existsSync as existsSync$1, lstatSync as lstatSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
27
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
28
+ import { lstat, readFile, readdir, rm, stat, unlink } from "node:fs/promises";
28
29
  import { resolve4 } from "node:dns/promises";
29
30
  import { request } from "node:https";
30
31
  //#region src/commands/gc/results.ts
31
- const results = [];
32
- function recordGcResult(kind, status, label, { id = null, bytes = null, detail = null } = {}) {
33
- results.push({
34
- kind,
35
- status,
36
- label,
37
- id,
38
- bytes,
39
- detail
40
- });
41
- }
42
- function takeGcResults() {
43
- return results.splice(0);
32
+ function createGcResults() {
33
+ const results = [];
34
+ return {
35
+ record(kind, status, label, { id = null, bytes = null, detail = null } = {}) {
36
+ results.push({
37
+ kind,
38
+ status,
39
+ label,
40
+ id,
41
+ bytes,
42
+ detail
43
+ });
44
+ },
45
+ take() {
46
+ return results.splice(0);
47
+ }
48
+ };
44
49
  }
50
+ const detachedResults = createGcResults();
51
+ const recordGcResult = detachedResults.record;
52
+ const takeGcResults = detachedResults.take;
45
53
  const GC_OUTPUT = {
46
54
  log: (line) => console.log(line),
55
+ error: (line) => console.error(line),
47
56
  record: recordGcResult,
48
57
  fail: () => {
49
58
  process.exitCode = 1;
@@ -205,12 +214,18 @@ function parseDu(output) {
205
214
  }
206
215
  return sizes;
207
216
  }
208
- async function measureDu(args) {
217
+ async function measureDu(args, operation) {
209
218
  let output = "";
210
219
  let complete = true;
211
220
  try {
212
- output = await getExecutor().runFileAsync("du", args, { timeoutMs: 2e4 });
221
+ output = await runOperationFile(operation, getExecutor(), "du", args, {
222
+ timeoutMs: 2e4,
223
+ cwd: operation?.cwd,
224
+ signal: operation?.signal
225
+ });
213
226
  } catch (error) {
227
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
228
+ operation?.signal.throwIfAborted();
214
229
  complete = false;
215
230
  const failure = error;
216
231
  if (failure.status === 1 && !failure.code && typeof failure.stdout === "string") output = failure.stdout;
@@ -349,9 +364,10 @@ function explainFailure(error) {
349
364
  function report(message) {
350
365
  ambientWrite("stderr", `${phaseLine("device", message)}\n`);
351
366
  }
352
- async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot) {
367
+ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot, operation) {
353
368
  const exec = getExecutor();
354
369
  try {
370
+ operation?.signal.throwIfAborted();
355
371
  if (!exec.findExecutable("agent-device")) return;
356
372
  const root = workspace === void 0 ? void 0 : canonical(workspace);
357
373
  if (root === null) return;
@@ -368,7 +384,7 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
368
384
  const remaining = deadline - Date.now();
369
385
  if (remaining <= 0) throw new Error("agent-device cleanup timed out");
370
386
  try {
371
- return await exec.runFileAsync("agent-device", [
387
+ return await runOperationFile(operation, exec, "agent-device", [
372
388
  ...args,
373
389
  ...dir ? ["--state-dir", dir] : [],
374
390
  "--json",
@@ -379,6 +395,8 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
379
395
  killSignal: "SIGKILL"
380
396
  });
381
397
  } catch (error) {
398
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
399
+ operation?.signal.throwIfAborted();
382
400
  throw explainFailure(error);
383
401
  }
384
402
  };
@@ -406,27 +424,35 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
406
424
  ]))?.success !== true) throw new Error("agent-device did not confirm session close");
407
425
  report(`closed agent-device session ${session.name} on ${session.id}`);
408
426
  } catch (error) {
427
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
428
+ operation?.signal.throwIfAborted();
409
429
  report(`could not close agent-device session ${session.name} on ${session.id}: ${error.message}`);
410
430
  }
411
431
  } catch (error) {
432
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
433
+ operation?.signal.throwIfAborted();
412
434
  report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
413
435
  }
414
436
  }
415
437
  } catch (error) {
438
+ if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
439
+ operation?.signal.throwIfAborted();
416
440
  report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
417
441
  }
418
442
  }
419
443
  //#endregion
420
444
  //#region src/devices/teardown.ts
421
- async function teardownParkedAvd(avdName) {
445
+ async function teardownParkedAvd(avdName, { operation } = {}) {
422
446
  try {
447
+ operation?.signal.throwIfAborted();
423
448
  return await removeParkedAfter("android", avdName, async () => {
424
449
  const result = await teardownOwnedAvd(avdName, {
425
450
  del: true,
426
- owner: { pool: true }
451
+ owner: { pool: true },
452
+ operation
427
453
  });
428
454
  if (result.status !== "torn-down" && result.status !== "missing") throw new Error(result.reason);
429
- }) ? {
455
+ }, operation) ? {
430
456
  status: "torn-down",
431
457
  label: avdName
432
458
  } : {
@@ -435,21 +461,24 @@ async function teardownParkedAvd(avdName) {
435
461
  reason: "emulator is no longer parked"
436
462
  };
437
463
  } catch (error) {
464
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
465
+ operation?.signal.throwIfAborted();
438
466
  return {
439
467
  status: "failed",
440
468
  reason: String(error?.message || error)
441
469
  };
442
470
  }
443
471
  }
444
- async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSim } = {}) {
472
+ async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSim, operation } = {}) {
445
473
  try {
474
+ operation?.signal.throwIfAborted();
446
475
  const removed = await removeParkedAfter("ios", udid, async () => {
447
476
  await closeOwnedDeviceSessions({
448
477
  platform: "ios",
449
478
  id: udid
450
- }, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim));
451
- await deleteSim(udid);
452
- });
479
+ }, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), void 0, void 0, operation);
480
+ await deleteSim(udid, operation);
481
+ }, operation);
453
482
  if (!removed) return {
454
483
  status: "skipped",
455
484
  kind: "not-parked",
@@ -460,27 +489,34 @@ async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSi
460
489
  label: label ?? removed.name
461
490
  };
462
491
  } catch (error) {
492
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
493
+ operation?.signal.throwIfAborted();
463
494
  return {
464
495
  status: "failed",
465
496
  reason: String(error?.message || error)
466
497
  };
467
498
  }
468
499
  }
469
- async function eraseParkedIosSim(udid, { label } = {}) {
500
+ async function eraseParkedIosSim(udid, { label, operation } = {}) {
470
501
  try {
502
+ operation?.signal.throwIfAborted();
471
503
  const erased = await eraseParkedAfter("ios", udid, async (_record, eraseStarted) => {
472
504
  await closeOwnedDeviceSessions({
473
505
  platform: "ios",
474
506
  id: udid
475
- }, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim));
476
- const resolved = resolveOwnedIosSim(udid, await listAllIosSimsAsync());
507
+ }, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), void 0, void 0, operation);
508
+ const resolved = resolveOwnedIosSim(udid, await listAllIosSimsAsync({
509
+ signal: operation?.signal,
510
+ operation
511
+ }));
477
512
  if (resolved.missing) throw new Error(`simulator ${udid} is not on this machine`);
478
513
  if (resolved.notOwned) throw new Error(`simulator ${udid} is now named ${JSON.stringify(resolved.notOwned)}`);
479
514
  const state = resolved.sim.state;
480
515
  if (state !== "Shutdown") throw new Error(`simulator ${udid} is ${state}, not Shutdown; it was kept`);
516
+ operation?.signal.throwIfAborted();
481
517
  eraseStarted();
482
- await eraseIosSim(udid);
483
- });
518
+ await eraseIosSim(udid, operation);
519
+ }, operation);
484
520
  if (!erased) return {
485
521
  status: "skipped",
486
522
  kind: "not-parked",
@@ -491,23 +527,31 @@ async function eraseParkedIosSim(udid, { label } = {}) {
491
527
  label: label ?? erased.name
492
528
  };
493
529
  } catch (error) {
530
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
531
+ operation?.signal.throwIfAborted();
494
532
  return {
495
533
  status: "failed",
496
534
  reason: String(error?.message || error)
497
535
  };
498
536
  }
499
537
  }
500
- async function eraseParkedAvd(avdName) {
538
+ async function eraseParkedAvd(avdName, { operation } = {}) {
501
539
  try {
540
+ operation?.signal.throwIfAborted();
502
541
  return await eraseParkedAfter("android", avdName, async (_record, eraseStarted) => {
503
- if ((await resolveOwnedAvdSerialAsync(avdName, { timeoutMs: AVD_INVENTORY_TIMEOUT_MS })).serial) throw new Error(`AVD ${avdName} is running; it was kept`);
542
+ if ((await resolveOwnedAvdSerialAsync(avdName, {
543
+ timeoutMs: AVD_INVENTORY_TIMEOUT_MS,
544
+ operation,
545
+ signal: operation?.signal
546
+ })).serial) throw new Error(`AVD ${avdName} is running; it was kept`);
504
547
  const result = await teardownOwnedAvd(avdName, {
505
548
  wipe: true,
506
549
  owner: { pool: true },
507
- onWipeStart: eraseStarted
550
+ onWipeStart: eraseStarted,
551
+ operation
508
552
  });
509
553
  if (result.status !== "torn-down") throw new Error(result.reason ?? `AVD ${avdName} is ${result.status}`);
510
- }) ? {
554
+ }, operation) ? {
511
555
  status: "torn-down",
512
556
  label: avdName
513
557
  } : {
@@ -516,6 +560,8 @@ async function eraseParkedAvd(avdName) {
516
560
  reason: "emulator is no longer parked"
517
561
  };
518
562
  } catch (error) {
563
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
564
+ operation?.signal.throwIfAborted();
519
565
  return {
520
566
  status: "failed",
521
567
  reason: String(error?.message || error)
@@ -525,9 +571,13 @@ async function eraseParkedAvd(avdName) {
525
571
  const IOS_SHUTDOWN_SETTLE_MS = 15e3;
526
572
  const ADB_RELEASE_WAIT_MS = 3e3;
527
573
  const AVD_INVENTORY_TIMEOUT_MS = 15e3;
528
- async function repoRootAbove(workspace) {
574
+ async function repoRootAbove(workspace, operation) {
529
575
  if (workspace === void 0 || !getExecutor().findExecutable("agent-device")) return void 0;
530
- const root = await repoRootAsync(workspace);
576
+ const root = await repoRootAsync(workspace, {
577
+ declareFor: operation,
578
+ env: operation?.env,
579
+ signal: operation?.signal
580
+ });
531
581
  try {
532
582
  return root && realpathSync(root) !== realpathSync(workspace) ? root : void 0;
533
583
  } catch {
@@ -536,37 +586,49 @@ async function repoRootAbove(workspace) {
536
586
  }
537
587
  const IOS_SHUTDOWN_POLL_MS = 250;
538
588
  const IOS_SHUTDOWN_ATTEMPTS = 2;
539
- async function resolveOwnedIosSimNow(udid) {
540
- return resolveOwnedIosSim(udid, await listAllIosSimsAsync());
589
+ async function resolveOwnedIosSimNow(udid, operation) {
590
+ operation?.signal.throwIfAborted();
591
+ const sims = await listAllIosSimsAsync({
592
+ operation,
593
+ signal: operation?.signal
594
+ });
595
+ operation?.signal.throwIfAborted();
596
+ return resolveOwnedIosSim(udid, sims);
541
597
  }
542
- async function settleIosSimShutdown(udid, { now = Date.now, sleep = sleepAsync }) {
543
- let resolved = await resolveOwnedIosSimNow(udid);
598
+ async function settleIosSimShutdown(udid, { now = Date.now, sleep }, operation) {
599
+ let resolved = await resolveOwnedIosSimNow(udid, operation);
544
600
  for (let attempt = 1;; attempt++) {
545
601
  const deadline = now() + IOS_SHUTDOWN_SETTLE_MS;
546
602
  while (resolved.sim && resolved.sim.state !== "Shutdown" && now() < deadline) {
547
- await sleep(IOS_SHUTDOWN_POLL_MS);
548
- resolved = await resolveOwnedIosSimNow(udid);
603
+ await (sleep ? sleep(IOS_SHUTDOWN_POLL_MS) : cancellableSleep(IOS_SHUTDOWN_POLL_MS, {
604
+ signal: operation?.signal,
605
+ what: "simulator shutdown"
606
+ }));
607
+ resolved = await resolveOwnedIosSimNow(udid, operation);
549
608
  }
550
609
  if (!resolved.sim || resolved.sim.state === "Shutdown" || attempt === IOS_SHUTDOWN_ATTEMPTS) return resolved;
551
- await shutdownIosSim(udid);
552
- resolved = await resolveOwnedIosSimNow(udid);
610
+ await shutdownIosSim(udid, operation);
611
+ resolved = await resolveOwnedIosSimNow(udid, operation);
553
612
  }
554
613
  }
555
614
  function iosShutdownFailureReason(udid, state) {
556
615
  return `simulator ${udid} is still ${state} after ${IOS_SHUTDOWN_ATTEMPTS} shutdown attempts and ${IOS_SHUTDOWN_ATTEMPTS * IOS_SHUTDOWN_SETTLE_MS / 1e3}s of waiting`;
557
616
  }
558
- async function parkOwnedIosSim(settled, udid, park) {
617
+ async function parkOwnedIosSim(settled, udid, park, operation) {
559
618
  if (settled.missing) throw new Error(`simulator ${udid} disappeared after shutdown`);
560
619
  if (settled.notOwned) throw new Error(`simulator ${udid} is now named ${JSON.stringify(settled.notOwned)}`);
561
620
  const sim = settled.sim;
562
621
  if (sim.state !== "Shutdown") throw new Error(iosShutdownFailureReason(udid, sim.state));
563
- const model = (await listIosDeviceTypesAsync()).find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null;
622
+ const model = (await listIosDeviceTypesAsync({
623
+ operation,
624
+ signal: operation?.signal
625
+ })).find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null;
564
626
  const runtime = parseRuntimeVersion(sim.runtime);
565
627
  const name = parkedSimName(sim.udid, {
566
628
  model,
567
629
  runtime
568
630
  });
569
- await renameIosSim(sim.udid, name);
631
+ await renameIosSim(sim.udid, name, operation);
570
632
  const record = {
571
633
  udid: sim.udid,
572
634
  name,
@@ -588,10 +650,10 @@ async function parkOwnedIosSim(settled, udid, park) {
588
650
  })
589
651
  };
590
652
  }
591
- async function teardownOwnedIosSim(udid, { del = false, label, park, workspace, shutdownClock = {} } = {}) {
653
+ async function teardownOwnedIosSim(udid, { del = false, label, park, workspace, shutdownClock = {}, operation } = {}) {
592
654
  let parkFallback;
593
655
  try {
594
- const resolved = await resolveOwnedIosSimNow(udid);
656
+ const resolved = await resolveOwnedIosSimNow(udid, operation);
595
657
  if (resolved.notOwned) return {
596
658
  status: "skipped",
597
659
  kind: "not-owned",
@@ -601,22 +663,25 @@ async function teardownOwnedIosSim(udid, { del = false, label, park, workspace,
601
663
  await closeOwnedDeviceSessions({
602
664
  platform: "ios",
603
665
  id: udid
604
- }, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim), workspace);
605
- const current = await resolveOwnedIosSimNow(udid);
666
+ }, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), workspace, void 0, operation);
667
+ const current = await resolveOwnedIosSimNow(udid, operation);
606
668
  if (current.missing) return { status: "missing" };
607
669
  if (current.notOwned) return {
608
670
  status: "skipped",
609
671
  kind: "not-owned",
610
672
  reason: "simulator ownership changed before shutdown"
611
673
  };
612
- await shutdownIosSim(udid);
674
+ await shutdownIosSim(udid, operation);
613
675
  const sim = resolved.sim;
614
676
  if (del && park && park.max > 0) try {
615
- const { record, evicted } = await parkOwnedIosSim(await settleIosSimShutdown(udid, shutdownClock), udid, park);
677
+ const { record, evicted } = await parkOwnedIosSim(await settleIosSimShutdown(udid, shutdownClock, operation), udid, park, operation);
616
678
  const removed = [];
617
679
  const failures = [];
618
680
  for (const entry of evicted) {
619
- const result = await teardownParkedIosSim(entry.udid, { label: entry.name });
681
+ const result = await teardownParkedIosSim(entry.udid, {
682
+ label: entry.name,
683
+ operation
684
+ });
620
685
  if (result.status === "torn-down") removed.push({
621
686
  udid: entry.udid,
622
687
  name: entry.name
@@ -634,32 +699,36 @@ async function teardownOwnedIosSim(udid, { del = false, label, park, workspace,
634
699
  ...failures.length ? { evictionFailures: failures } : {}
635
700
  };
636
701
  } catch (e) {
702
+ if (e instanceof WorkspaceProcessCleanupError) throw e;
703
+ operation?.signal.throwIfAborted();
637
704
  parkFallback = String(e?.message || e);
638
705
  }
639
706
  if (del) {
640
- await deleteIosSim(udid);
707
+ await deleteIosSim(udid, operation);
641
708
  return {
642
709
  status: "torn-down",
643
710
  label: label ?? sim.name ?? udid,
644
711
  ...parkFallback ? { parkFallback } : {}
645
712
  };
646
713
  }
647
- const settled = await settleIosSimShutdown(udid, shutdownClock);
714
+ const settled = await settleIosSimShutdown(udid, shutdownClock, operation);
648
715
  if (settled.sim && settled.sim.state !== "Shutdown") return {
649
716
  status: "failed",
650
717
  label: label ?? sim.name ?? udid,
651
718
  reason: iosShutdownFailureReason(udid, settled.sim.state)
652
719
  };
653
- const root = sim.state === "Shutdown" ? void 0 : await repoRootAbove(workspace);
720
+ const root = sim.state === "Shutdown" ? void 0 : await repoRootAbove(workspace, operation);
654
721
  if (root && settled.sim) await closeOwnedDeviceSessions({
655
722
  platform: "ios",
656
723
  id: udid
657
- }, async () => (await resolveOwnedIosSimNow(udid)).sim?.state === "Shutdown", workspace, root);
724
+ }, async () => (await resolveOwnedIosSimNow(udid, operation)).sim?.state === "Shutdown", workspace, root, operation);
658
725
  return {
659
726
  status: "torn-down",
660
727
  label: label ?? sim.name ?? udid
661
728
  };
662
729
  } catch (e) {
730
+ if (e instanceof WorkspaceProcessCleanupError) throw e;
731
+ operation?.signal.throwIfAborted();
663
732
  return {
664
733
  status: "failed",
665
734
  reason: String(e?.message || e),
@@ -677,11 +746,12 @@ function assertAvdReferences(avdName, owner) {
677
746
  }
678
747
  if (readParked("android", { config }).some((entry) => entry.name.toLowerCase() === avdName.toLowerCase()) && !(owner && "pool" in owner)) throw new Error(`AVD ${avdName} is in the emulator pool; its data was kept.`);
679
748
  }
680
- function removeOrphanedAvdDirectory(candidate, owner) {
749
+ async function removeOrphanedAvdDirectory(candidate, owner, operation) {
750
+ operation?.signal.throwIfAborted();
681
751
  const surveyed = listOrphanedAvdDirectories(candidate.name).find((entry) => entry.directory === candidate.directory);
682
752
  if (!surveyed || surveyed.name !== candidate.name || surveyed.dev !== candidate.dev || surveyed.ino !== candidate.ino) throw new Error(`AVD data at ${candidate.directory} changed or is registered; it was kept.`);
683
- assertOwnedAvdStopped(candidate.name, { resolveDirectory: () => candidate.directory });
684
- const detached = withConfigLock$1(() => {
753
+ await assertOwnedAvdStoppedAsync(candidate.name, operation, () => candidate.directory);
754
+ const detached = await withConfigLockAsync$1(() => {
685
755
  const current = lstatSync(candidate.directory);
686
756
  if (!current.isDirectory() || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`AVD data at ${candidate.directory} changed or is registered; it was kept.`);
687
757
  assertAvdReferences(candidate.name, owner);
@@ -696,17 +766,19 @@ function removeOrphanedAvdDirectory(candidate, owner) {
696
766
  }
697
767
  forgetCreatedDevice("android", candidate.name);
698
768
  return destination;
699
- });
769
+ }, operation?.signal);
700
770
  const current = lstatSync(detached);
701
771
  if (!current.isDirectory() || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`Detached AVD data changed at ${detached}; it was kept.`);
772
+ operation?.signal.throwIfAborted();
702
773
  try {
703
- rmSync(detached, { recursive: true });
774
+ await rm(detached, { recursive: true });
704
775
  } catch (error) {
705
776
  throw new Error(`Could not remove AVD data at ${detached}; retry gc --delete: ${error.message}`, { cause: error });
706
777
  }
707
778
  forgetCreatedDevice("android", basename(detached, ".avd"));
708
779
  }
709
- function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
780
+ async function teardownUnregisteredAvd(avdName, del, owner, surveyed, operation) {
781
+ operation?.signal.throwIfAborted();
710
782
  const candidates = surveyed ? [surveyed] : listOrphanedAvdDirectories(avdName);
711
783
  if (candidates.length === 0) {
712
784
  if (avdStorageRoots().some((root) => avdPathExists(join(root, `${avdName}.ini`)) || avdPathExists(join(root, `${avdName}.avd`)))) throw new Error(`AVD ${avdName} is not listed but its storage remains; it was kept.`);
@@ -718,7 +790,7 @@ function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
718
790
  };
719
791
  for (const candidate of candidates) {
720
792
  if (candidate.name !== avdName) throw new Error(`AVD data does not match ${avdName}; it was kept.`);
721
- removeOrphanedAvdDirectory(candidate, owner);
793
+ await removeOrphanedAvdDirectory(candidate, owner, operation);
722
794
  }
723
795
  return {
724
796
  status: "torn-down",
@@ -727,37 +799,65 @@ function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
727
799
  }
728
800
  async function teardownOwnedAvd(avdName, options = {}) {
729
801
  let claim;
802
+ let retained = false;
803
+ const operation = options.operation;
730
804
  try {
805
+ operation?.signal.throwIfAborted();
731
806
  if (!isStimOwnedAvd(avdName)) return {
732
807
  status: "skipped",
733
808
  kind: "not-owned",
734
809
  reason: `AVD ${avdName} is not Stim-owned: Stim has no record of creating it`
735
810
  };
736
811
  claim = acquireAvdClaim(avdName);
812
+ if (operation) declareSpawnsOn(operation, claim);
737
813
  const result = await teardownClaimedAvd(avdName, claim, options);
814
+ operation?.signal.throwIfAborted();
738
815
  if (result.status === "torn-down" || result.status === "missing") options.onRemoved?.();
739
816
  return result;
740
817
  } catch (error) {
818
+ if (claim && (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error)) {
819
+ retained = true;
820
+ throw Object.assign(new WorkspaceProcessCleanupError([.../* @__PURE__ */ new Set([claim, ...error instanceof WorkspaceProcessCleanupError ? error.claims : []])], error.message, "Verify the AVD operation and its descendants have stopped."), { reportCleanup: true });
821
+ }
822
+ operation?.signal.throwIfAborted();
741
823
  return {
742
824
  status: "failed",
743
825
  reason: String(error?.message || error)
744
826
  };
745
827
  } finally {
746
- (0, ownership_claim_exports.releaseClaim)(claim);
828
+ if (operation) stopDeclaringSpawnsOn(operation, claim);
829
+ if (!retained) (0, ownership_claim_exports.releaseClaim)(claim);
747
830
  }
748
831
  }
749
- async function serialFree(serial, timeoutMs = AVD_INVENTORY_TIMEOUT_MS) {
750
- const adb = await listAdbDevicesAsync({ timeoutMs });
832
+ async function serialFree(serial, timeoutMs = AVD_INVENTORY_TIMEOUT_MS, operation) {
833
+ operation?.signal.throwIfAborted();
834
+ const adb = await listAdbDevicesAsync({
835
+ timeoutMs,
836
+ operation,
837
+ signal: operation?.signal
838
+ });
839
+ operation?.signal.throwIfAborted();
751
840
  return ![
752
841
  ...adb.emulators,
753
842
  ...adb.physical,
754
843
  ...adb.unhealthy
755
844
  ].some((entry) => entry.serial === serial);
756
845
  }
757
- async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, park, owner, workspace, orphanedDirectory, onlyIfMissing = false, onWipeStart, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = assertOwnedAvdStopped, resolveAvd = (name) => resolveOwnedAvdSerialAsync(name, { timeoutMs: AVD_INVENTORY_TIMEOUT_MS }) }) {
846
+ async function teardownClaimedAvd(avdName, claim, { operation, del = false, wipe = false, park, owner, workspace, orphanedDirectory, onlyIfMissing = false, onWipeStart, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = (name) => assertOwnedAvdStoppedAsync(name, operation), resolveAvd: resolve = (name) => resolveOwnedAvdSerialAsync(name, {
847
+ timeoutMs: AVD_INVENTORY_TIMEOUT_MS,
848
+ operation,
849
+ signal: operation?.signal
850
+ }) }) {
758
851
  let parkFallback;
852
+ const resolveAvd = async (name) => {
853
+ operation?.signal.throwIfAborted();
854
+ const result = await resolve(name);
855
+ operation?.signal.throwIfAborted();
856
+ return result;
857
+ };
759
858
  try {
760
- if (del || wipe) await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner));
859
+ operation?.signal.throwIfAborted();
860
+ if (del || wipe) await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
761
861
  const resolved = await resolveAvd(avdName);
762
862
  if (resolved.notOwned) return {
763
863
  status: "skipped",
@@ -766,7 +866,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
766
866
  };
767
867
  if (resolved.missing) {
768
868
  if (wipe) throw new Error(`AVD ${avdName} is not registered; its data was kept.`);
769
- return teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory);
869
+ return await teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory, operation);
770
870
  }
771
871
  if (onlyIfMissing) return {
772
872
  status: "skipped",
@@ -779,7 +879,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
779
879
  const serial = resolved.serial;
780
880
  const stillStopped = async (sessionSerial) => {
781
881
  const current = await resolveAvd(avdName);
782
- return !current.notOwned && !current.missing && !current.serial && await serialFree(sessionSerial);
882
+ return !current.notOwned && !current.missing && !current.serial && await serialFree(sessionSerial, AVD_INVENTORY_TIMEOUT_MS, operation);
783
883
  };
784
884
  if (serial) {
785
885
  const stillOwned = async () => {
@@ -790,44 +890,48 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
790
890
  platform: "android",
791
891
  id: serial,
792
892
  avdName
793
- }, stillOwned, workspace);
893
+ }, stillOwned, workspace, void 0, operation);
794
894
  if (!await stillOwned()) throw new Error(`Owned AVD ${avdName} changed before shutdown; it was kept.`);
795
895
  await waitForShutdown(avdName, async (timeoutMs) => {
796
- (0, ownership_claim_exports.markClaimChildPending)(claim);
896
+ if (!operation) (0, ownership_claim_exports.markClaimChildPending)(claim);
797
897
  try {
798
- await shutdownAndroidEmulator(serial, timeoutMs);
898
+ await shutdownAndroidEmulator(serial, timeoutMs, operation);
799
899
  } finally {
800
- (0, ownership_claim_exports.clearClaimChild)(claim);
900
+ if (!operation) (0, ownership_claim_exports.clearClaimChild)(claim);
801
901
  }
802
- });
803
- const root = await repoRootAbove(workspace);
902
+ }, { operation });
903
+ const root = await repoRootAbove(workspace, operation);
804
904
  if (root) {
805
905
  const deadline = Date.now() + ADB_RELEASE_WAIT_MS;
806
- while (!await serialFree(serial, Math.max(1, deadline - Date.now())) && Date.now() < deadline) await sleepAsync(250);
906
+ while (!await serialFree(serial, Math.max(1, deadline - Date.now()), operation) && Date.now() < deadline) await cancellableSleep(250, {
907
+ signal: operation?.signal,
908
+ what: "emulator release"
909
+ });
807
910
  await closeOwnedDeviceSessions({
808
911
  platform: "android",
809
912
  id: serial,
810
913
  avdName
811
- }, stillStopped, workspace, root);
914
+ }, stillStopped, workspace, root, operation);
812
915
  }
813
916
  } else {
814
917
  await closeOwnedDeviceSessions({
815
918
  platform: "android",
816
919
  id: null,
817
920
  avdName
818
- }, stillStopped, workspace);
819
- await waitForShutdown(avdName, null);
921
+ }, stillStopped, workspace, void 0, operation);
922
+ await waitForShutdown(avdName, null, { operation });
820
923
  }
821
924
  if (wipe && !del) {
822
925
  const current = await resolveAvd(avdName);
823
926
  if (current.notOwned || current.missing) throw new Error(`AVD ${avdName} changed before its data was wiped.`);
824
927
  if (current.serial) throw new Error(`Owned AVD ${avdName} started again before its data was wiped.`);
825
- assertStopped(avdName);
826
- await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner));
928
+ await assertStopped(avdName);
929
+ await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
930
+ operation?.signal.throwIfAborted();
827
931
  const directory = ownedAvdDirectory(avdName);
828
932
  if (!directory) throw new Error(`The data directory of AVD ${avdName} could not be resolved.`);
829
933
  onWipeStart?.();
830
- wipeAvdUserData(directory);
934
+ await wipeAvdUserData(directory, operation?.signal);
831
935
  }
832
936
  if (del) {
833
937
  const current = await resolveAvd(avdName);
@@ -836,10 +940,11 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
836
940
  kind: "not-owned",
837
941
  reason: `AVD ${avdName} is not Stim-owned: Stim has no record of creating it`
838
942
  };
839
- if (current.missing) return teardownUnregisteredAvd(avdName, del, owner);
943
+ if (current.missing) return await teardownUnregisteredAvd(avdName, del, owner, void 0, operation);
840
944
  if (current.serial) throw new Error(`Owned AVD ${avdName} started again before deletion.`);
841
- assertStopped(avdName);
842
- await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner));
945
+ await assertStopped(avdName);
946
+ await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
947
+ operation?.signal.throwIfAborted();
843
948
  if (park && park.max > 0) try {
844
949
  const systemImage = ownedAvdSystemImage(avdName);
845
950
  if (!systemImage || !park.configuration || !ownedAvdMatchesConfiguration(avdName, park.configuration)) throw new Error("the AVD has no verified creation configuration");
@@ -861,7 +966,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
861
966
  const removed = [];
862
967
  const failures = [];
863
968
  for (const entry of evicted) {
864
- const result = await teardownParkedAvd(entry.name);
969
+ const result = await teardownParkedAvd(entry.name, { operation });
865
970
  if (result.status === "torn-down") removed.push({
866
971
  udid: entry.name,
867
972
  name: entry.name,
@@ -881,16 +986,18 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
881
986
  ...failures.length ? { evictionFailures: failures } : {}
882
987
  };
883
988
  } catch (error) {
989
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
990
+ operation?.signal.throwIfAborted();
884
991
  parkFallback = String(error?.message || error);
885
992
  }
886
993
  const directory = ownedAvdDirectory(avdName);
887
994
  const paths = avdStorageRoots().map((root) => join(root, `${avdName}.ini`));
888
995
  if (directory) paths.push(directory);
889
- (0, ownership_claim_exports.markClaimChildPending)(claim);
996
+ if (!operation) (0, ownership_claim_exports.markClaimChildPending)(claim);
890
997
  try {
891
- await deleteAvd(avdName);
998
+ await deleteAvd(avdName, operation);
892
999
  } finally {
893
- (0, ownership_claim_exports.clearClaimChild)(claim);
1000
+ if (!operation) (0, ownership_claim_exports.clearClaimChild)(claim);
894
1001
  }
895
1002
  const remaining = paths.filter(avdPathExists);
896
1003
  if (remaining.length) throw new Error(`AVD ${avdName} was only partially deleted; data remains at ${remaining.join(", ")}.`);
@@ -902,6 +1009,8 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
902
1009
  ...parkFallback ? { parkFallback } : {}
903
1010
  };
904
1011
  } catch (e) {
1012
+ if (e instanceof WorkspaceProcessCleanupError) throw e;
1013
+ operation?.signal.throwIfAborted();
905
1014
  return {
906
1015
  status: "failed",
907
1016
  reason: String(e?.message || e)
@@ -910,20 +1019,29 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
910
1019
  }
911
1020
  const BROWSER_EXIT_WAIT_MS = 15e3;
912
1021
  const CHROME_KILL_WAIT_MS = 5e3;
913
- async function stopOwnedChrome(record, profile) {
1022
+ async function stopOwnedChrome(record, profile, operation) {
914
1023
  for (const signal of ["SIGTERM", "SIGKILL"]) {
915
- const state = await chromeProcessStateAsync(record, profile);
1024
+ operation?.signal.throwIfAborted();
1025
+ const state = await chromeProcessStateAsync(record, profile, operation);
916
1026
  if (state === "gone") return true;
917
1027
  if (state === "unknown") return false;
918
1028
  try {
919
- await signalProcessTreeAsync(record.pid, signal, { group: true });
920
- } catch {}
1029
+ await signalProcessTreeAsync(record.pid, signal, {
1030
+ group: true,
1031
+ operation
1032
+ });
1033
+ } catch (error) {
1034
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1035
+ operation?.signal.throwIfAborted();
1036
+ }
921
1037
  const deadline = Date.now() + CHROME_KILL_WAIT_MS;
922
- while (await chromeProcessStateAsync(record, profile) !== "gone" && Date.now() < deadline) await sleepAsync(50);
1038
+ while (await chromeProcessStateAsync(record, profile, operation) !== "gone" && Date.now() < deadline) await cancellableSleep(50, {
1039
+ signal: operation?.signal,
1040
+ what: "Chrome shutdown"
1041
+ });
923
1042
  }
924
- return await chromeProcessStateAsync(record, profile) === "gone";
1043
+ return await chromeProcessStateAsync(record, profile, operation) === "gone";
925
1044
  }
926
- const sleepAsync = (ms) => new Promise((done) => setTimeout(done, ms));
927
1045
  /**
928
1046
  * Closes the workspace's Stim-owned Chrome and its supervisor, after verifying both process identities. With
929
1047
  * `deleteProfile`, it also deletes the profile directory, only when the created-devices ledger lists it.
@@ -932,9 +1050,13 @@ async function teardownOwnedBrowser(root, options = {}) {
932
1050
  try {
933
1051
  return await withWorkspaceProcessLock(workspaceDir(root), "web", () => teardownBrowserHeld(root, options), {
934
1052
  external: true,
935
- ownerPurpose: "browser teardown"
1053
+ ownerPurpose: "browser teardown",
1054
+ signal: options.operation?.signal,
1055
+ declareSpawnsFor: options.operation
936
1056
  });
937
1057
  } catch (error) {
1058
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1059
+ options.operation?.signal.throwIfAborted();
938
1060
  return {
939
1061
  status: "failed",
940
1062
  reason: String(error?.message || error)
@@ -942,8 +1064,10 @@ async function teardownOwnedBrowser(root, options = {}) {
942
1064
  }
943
1065
  }
944
1066
  /** {@link teardownOwnedBrowser} for a caller that already holds the {@link BROWSER_LOCK} lock. */
945
- async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
1067
+ async function teardownBrowserHeld(root, { deleteProfile = false, operation } = {}) {
1068
+ const signal = operation?.signal;
946
1069
  try {
1070
+ signal?.throwIfAborted();
947
1071
  const record = readWebRecord(root);
948
1072
  const profile = record?.profile ?? webProfileDir(root);
949
1073
  if (record) {
@@ -958,12 +1082,12 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
958
1082
  try {
959
1083
  process.kill(record.pid, "SIGTERM");
960
1084
  } catch {}
961
- if (!await (0, process_identity_exports.waitForProcessExit)(record, BROWSER_EXIT_WAIT_MS)) return {
1085
+ if (!await (0, process_identity_exports.waitForProcessExit)(record, BROWSER_EXIT_WAIT_MS, signal)) return {
962
1086
  status: "failed",
963
1087
  reason: `browser supervisor pid ${record.pid} did not exit`
964
1088
  };
965
1089
  }
966
- if (record.chromeProcess && !await stopOwnedChrome(record.chromeProcess, profile)) return {
1090
+ if (record.chromeProcess && !await stopOwnedChrome(record.chromeProcess, profile, operation)) return {
967
1091
  status: "failed",
968
1092
  reason: `Chrome pid ${record.chromeProcess.pid} did not exit`
969
1093
  };
@@ -983,8 +1107,8 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
983
1107
  kind: "not-verified",
984
1108
  reason: claims.status === "held" ? `the browser supervisor claim is still held${claims.holder ? ` by pid ${claims.holder.owner.pid}` : ""}` : `the browser supervisor claim could not be cleared (${claims.reason}); remove it with ${(0, ownership_claim_exports.claimRemoveCommand)(webClaimRoot(root))}`
985
1109
  };
986
- await withWorkspaceStateLockAsync(root, () => clearWebRecord(root, record));
987
- await releaseBrowserPort(root, record.cdpPort);
1110
+ await withWorkspaceStateLockAsync(root, () => clearWebRecord(root, record), signal);
1111
+ await releaseBrowserPort(root, record.cdpPort, signal);
988
1112
  } else {
989
1113
  const holder = liveProfileHolder(profile);
990
1114
  if (holder !== null) return {
@@ -993,8 +1117,9 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
993
1117
  reason: `pid ${holder} holds ${profile} but no browser supervisor is recorded; it was left running`
994
1118
  };
995
1119
  const port = getProject(root)?.ports?.[CDP_PORT_LABEL];
996
- if (typeof port === "number") await releaseBrowserPort(root, port);
1120
+ if (typeof port === "number") await releaseBrowserPort(root, port, signal);
997
1121
  }
1122
+ signal?.throwIfAborted();
998
1123
  if (existsSync(profile)) removeSingletonFiles(profile);
999
1124
  if (deleteProfile && existsSync(profile)) {
1000
1125
  if (!readCreatedDevices$1().web.has(profile)) return {
@@ -1002,10 +1127,11 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
1002
1127
  kind: "not-owned",
1003
1128
  reason: `${profile} is not in Stim's ledger; it was kept`
1004
1129
  };
1005
- rmSync(profile, {
1130
+ await rm(profile, {
1006
1131
  recursive: true,
1007
1132
  force: true
1008
1133
  });
1134
+ signal?.throwIfAborted();
1009
1135
  forgetCreatedDevice("web", profile);
1010
1136
  }
1011
1137
  return record ? {
@@ -1013,6 +1139,8 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
1013
1139
  label: `Chrome (${profile})`
1014
1140
  } : { status: "missing" };
1015
1141
  } catch (error) {
1142
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1143
+ signal?.throwIfAborted();
1016
1144
  return {
1017
1145
  status: "failed",
1018
1146
  reason: String(error?.message || error)
@@ -1055,6 +1183,21 @@ function candidatePids(dirs) {
1055
1183
  function bundleInstancePids(root) {
1056
1184
  return candidatePids(bundleDirs(root));
1057
1185
  }
1186
+ async function bundleInstancePidsAsync(root, signal, operation) {
1187
+ signal?.throwIfAborted();
1188
+ const dirs = bundleDirs(root);
1189
+ if (!dirs.length) return [];
1190
+ return bundleCandidates(await runOperationFile(operation, getExecutor(), "ps", [
1191
+ "-axww",
1192
+ "-o",
1193
+ "pid=,comm="
1194
+ ], {
1195
+ timeoutMs: 1e4,
1196
+ killSignal: "SIGKILL",
1197
+ signal,
1198
+ cwd: operation?.cwd
1199
+ }), dirs).filter((pid) => pid !== process.pid);
1200
+ }
1058
1201
  function unverified(pid, why) {
1059
1202
  return Object.assign(/* @__PURE__ */ new Error(`Cannot verify macOS app pid ${pid}: ${why}; no signal was sent.`), { code: "STIM_MACOS_OWNER_UNVERIFIED" });
1060
1203
  }
@@ -1078,24 +1221,27 @@ function failedBundleListing(record, error) {
1078
1221
  return open;
1079
1222
  }
1080
1223
  /** Verifies owned bundle instances without blocking on process and executable probes. */
1081
- async function runningBundleInstancesAsync(root) {
1224
+ async function runningBundleInstancesAsync(root, operation) {
1225
+ operation?.signal.throwIfAborted();
1082
1226
  const dirs = bundleDirs(root);
1083
1227
  if (!dirs.length) return [];
1084
- const listing = await getExecutor().runFileAsync("ps", [
1228
+ const listing = await runOperationFile(operation, getExecutor(), "ps", [
1085
1229
  "-axww",
1086
1230
  "-o",
1087
1231
  "pid=,comm="
1088
1232
  ], {
1233
+ cwd: operation?.cwd,
1089
1234
  timeoutMs: 1e4,
1090
1235
  killSignal: "SIGKILL"
1091
1236
  });
1092
1237
  const instances = [];
1093
1238
  for (const pid of bundleCandidates(listing, dirs).filter((candidate) => candidate !== process.pid)) {
1239
+ operation?.signal.throwIfAborted();
1094
1240
  const record = bundleRecord(pid);
1095
1241
  if (!record) continue;
1096
1242
  let open;
1097
1243
  try {
1098
- open = await getExecutor().runFileAsync("lsof", [
1244
+ open = await runOperationFile(operation, getExecutor(), "lsof", [
1099
1245
  "-a",
1100
1246
  "-p",
1101
1247
  String(pid),
@@ -1103,10 +1249,13 @@ async function runningBundleInstancesAsync(root) {
1103
1249
  "txt",
1104
1250
  "-Fn"
1105
1251
  ], {
1252
+ cwd: operation?.cwd,
1106
1253
  timeoutMs: 1e4,
1107
1254
  killSignal: "SIGKILL"
1108
1255
  });
1109
1256
  } catch (error) {
1257
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1258
+ operation?.signal.throwIfAborted();
1110
1259
  const partial = failedBundleListing(record, error);
1111
1260
  if (partial === null) continue;
1112
1261
  open = partial;
@@ -1230,7 +1379,7 @@ async function defaultProbeReachable(url, signal) {
1230
1379
  const status = await probePublicHttp(url, signal);
1231
1380
  return status !== null && status > 0;
1232
1381
  }
1233
- function waitForUrl(child, parseLine, timeoutMs) {
1382
+ function waitForUrl(child, parseLine, timeoutMs, signal) {
1234
1383
  return new Promise((resolve) => {
1235
1384
  let settled = false;
1236
1385
  let lastLine = null;
@@ -1247,12 +1396,14 @@ function waitForUrl(child, parseLine, timeoutMs) {
1247
1396
  child.stderr?.removeListener("data", onErr);
1248
1397
  child.removeListener("error", onError);
1249
1398
  child.removeListener("exit", onExit);
1399
+ signal?.removeEventListener("abort", onAbort);
1250
1400
  resolve({
1251
1401
  url,
1252
1402
  exited,
1253
1403
  lastLine
1254
1404
  });
1255
1405
  };
1406
+ const onAbort = () => finish(null);
1256
1407
  const onLine = (line) => {
1257
1408
  if (settled) return;
1258
1409
  if (line.trim()) lastLine = line.trim();
@@ -1276,9 +1427,11 @@ function waitForUrl(child, parseLine, timeoutMs) {
1276
1427
  child.on("error", onError);
1277
1428
  child.on("exit", onExit);
1278
1429
  timer = setTimeout(() => finish(null), timeoutMs);
1430
+ signal?.addEventListener("abort", onAbort, { once: true });
1431
+ if (signal?.aborted) onAbort();
1279
1432
  });
1280
1433
  }
1281
- function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
1434
+ function waitForFileUrl(child, logFile, parseLine, timeoutMs, signal) {
1282
1435
  return new Promise((resolve) => {
1283
1436
  let settled = false;
1284
1437
  let watcher = null;
@@ -1290,11 +1443,13 @@ function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
1290
1443
  clearTimeout(timer);
1291
1444
  child.removeListener("error", onError);
1292
1445
  child.removeListener("exit", onExit);
1446
+ signal?.removeEventListener("abort", onAbort);
1293
1447
  resolve({
1294
1448
  url,
1295
1449
  exited
1296
1450
  });
1297
1451
  };
1452
+ const onAbort = () => finish(null);
1298
1453
  const probe = () => {
1299
1454
  try {
1300
1455
  for (const line of readFileSync(logFile, "utf-8").split(/\r?\n/)) {
@@ -1319,7 +1474,9 @@ function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
1319
1474
  } catch {}
1320
1475
  const interval = setInterval(probe, Math.min(1e3, timeoutMs));
1321
1476
  const timer = setTimeout(() => finish(null), timeoutMs);
1322
- probe();
1477
+ signal?.addEventListener("abort", onAbort, { once: true });
1478
+ if (signal?.aborted) onAbort();
1479
+ else probe();
1323
1480
  });
1324
1481
  }
1325
1482
  function createTunnelLogFile(provider) {
@@ -1342,17 +1499,25 @@ function removeRecordedTunnelLogFile(path) {
1342
1499
  if (!resolved.startsWith(`${dir}${sep}`) || resolve(join(dir, basename(resolved))) !== resolved) return;
1343
1500
  removeTunnelLogFile(resolved);
1344
1501
  }
1345
- async function waitUntilReachable({ url, now, sleep, probe, timeoutMs }) {
1502
+ async function waitUntilReachable({ url, now, sleep, probe, timeoutMs, signal }) {
1346
1503
  const deadline = now() + timeoutMs;
1347
1504
  for (;;) {
1348
- if (await probe(url, new AbortController().signal).catch(() => false)) return true;
1505
+ throwIfCancelled(signal, "Metro tunnel startup");
1506
+ const controller = new AbortController();
1507
+ const ok = await probe(url, signal ? AbortSignal.any([signal, controller.signal]) : controller.signal).catch(() => false);
1508
+ throwIfCancelled(signal, "Metro tunnel startup");
1509
+ if (ok) return true;
1349
1510
  if (now() >= deadline) return false;
1350
- await sleep(REACHABLE_POLL_MS);
1511
+ if (sleep === defaultSleep) await cancellableSleep(REACHABLE_POLL_MS, {
1512
+ signal,
1513
+ what: "Metro tunnel startup"
1514
+ });
1515
+ else await sleep(REACHABLE_POLL_MS);
1351
1516
  }
1352
1517
  }
1353
1518
  const CLEANUP_TIMEOUT_MS = 1e3;
1354
- const CLEANUP_POLL_MS = 25;
1355
- async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
1519
+ async function startTunnel({ claims = [], signal, provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
1520
+ throwIfCancelled(signal, "Metro tunnel startup");
1356
1521
  const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
1357
1522
  const outputFile = provider === "tailscale" ? null : logFile || (spawnFn ? null : createTunnelLogFile(provider));
1358
1523
  let child;
@@ -1360,6 +1525,7 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
1360
1525
  const binary = provider === "tailscale" ? resolveTailscaleBinary((bin) => getExecutor().findExecutable(bin)) : provider;
1361
1526
  if (!binary) throw new Error("Install Tailscale and sign in to your tailnet.");
1362
1527
  const { bin, args } = tunnelArgv(provider, port, ngrokUrl, outputFile, binary);
1528
+ for (const claim of claims) (0, ownership_claim_exports.markClaimChildPending)(claim);
1363
1529
  child = spawn(bin, args, {
1364
1530
  stdio: outputFile ? [
1365
1531
  "ignore",
@@ -1373,6 +1539,7 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
1373
1539
  detached: true
1374
1540
  });
1375
1541
  } catch (err) {
1542
+ for (const claim of claims) (0, ownership_claim_exports.clearClaimChild)(claim);
1376
1543
  removeTunnelLogFile(outputFile);
1377
1544
  return {
1378
1545
  failed: true,
@@ -1380,33 +1547,40 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
1380
1547
  };
1381
1548
  }
1382
1549
  let childExited = false;
1550
+ let childIdentity = null;
1383
1551
  child.once("exit", () => {
1384
1552
  childExited = true;
1385
1553
  });
1386
1554
  const cleanup = async (alreadyExited = childExited) => {
1387
- const stopped = await terminateChild(child, {
1555
+ if (claims.length > 0 && childIdentity && process.platform !== "win32" && (0, process_identity_exports.inspectProcessIdentity)(childIdentity) === "same") try {
1556
+ signalProcessTree(childIdentity.pid, "SIGTERM", { group: true });
1557
+ } catch {}
1558
+ const drained = await terminateChild(child, {
1388
1559
  alreadyExited,
1389
1560
  timeoutMs: cleanupTimeoutMs,
1390
- now,
1391
- sleep,
1392
- isAlive: isChildAlive
1393
- });
1394
- if (stopped) removeTunnelLogFile(outputFile);
1395
- return stopped ? { status: "stopped" } : {
1561
+ isAlive: isChildAlive,
1562
+ waitForGroup: claims.length > 0 && process.platform !== "win32"
1563
+ }) && (claims.length === 0 || process.platform === "win32" || !child.pid || !(0, ownership_claim_exports.processGroupAlive)(child.pid));
1564
+ if (drained) {
1565
+ for (const claim of claims) (0, ownership_claim_exports.clearClaimChild)(claim);
1566
+ removeTunnelLogFile(outputFile);
1567
+ }
1568
+ return drained ? { status: "stopped" } : {
1396
1569
  status: "failed",
1397
1570
  reason: `Sent SIGKILL but could not confirm that pid ${child.pid ?? "unknown"} exited.`
1398
1571
  };
1399
1572
  };
1400
1573
  const failAfterCleanup = async (reason, alreadyExited = childExited) => {
1401
1574
  const stopped = await cleanup(alreadyExited);
1402
- return stopped.status === "stopped" ? {
1403
- failed: true,
1404
- reason
1405
- } : {
1575
+ if (stopped.status === "failed") return {
1406
1576
  failed: true,
1407
1577
  reason: `${reason} ${stopped.reason}`,
1408
1578
  cleanupFailed: true
1409
1579
  };
1580
+ return {
1581
+ failed: true,
1582
+ reason: signal?.aborted ? "Metro tunnel startup was cancelled." : reason
1583
+ };
1410
1584
  };
1411
1585
  const pid = child.pid;
1412
1586
  if (!pid) return failAfterCleanup(`${provider} started but reported no pid.`);
@@ -1419,18 +1593,35 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
1419
1593
  };
1420
1594
  const initialProcessToken = captureChildIdentity();
1421
1595
  if (!initialProcessToken) return failAfterCleanup(`${provider} started but its process identity token could not be read.`);
1422
- const { url, exited, lastLine } = outputFile ? await waitForFileUrl(child, outputFile, parserFor(provider), urlTimeoutMs) : await waitForUrl(child, parserFor(provider), urlTimeoutMs);
1596
+ childIdentity = {
1597
+ pid,
1598
+ processToken: initialProcessToken
1599
+ };
1600
+ if (claims.length > 0) try {
1601
+ for (const claim of claims) (0, ownership_claim_exports.setClaimChild)(claim, childIdentity);
1602
+ } catch (error) {
1603
+ return failAfterCleanup(`Could not record ${provider} process ownership: ${describe(error)}`);
1604
+ }
1605
+ const { url, exited, lastLine } = outputFile ? await waitForFileUrl(child, outputFile, parserFor(provider), urlTimeoutMs, signal) : await waitForUrl(child, parserFor(provider), urlTimeoutMs, signal);
1423
1606
  if (!url) return failAfterCleanup(exited ? provider === "tailscale" && lastLine ? `${provider} exited before printing a tunnel URL: ${lastLine}` : `${provider} exited before printing a tunnel URL.` : `${provider} did not print a tunnel URL within ${urlTimeoutMs}ms.`, exited || childExited);
1424
1607
  if (!outputFile) resumeChildPipes(child);
1425
1608
  if (requireReachable) {
1426
- if (!await waitUntilReachable({
1427
- url,
1428
- now,
1429
- sleep,
1430
- probe: probeReachable,
1431
- timeoutMs: reachableTimeoutMs
1432
- })) return failAfterCleanup(`${url} did not become reachable within ${reachableTimeoutMs}ms; ${provider} may still be registering, or the network path is blocked.`);
1609
+ let reachable;
1610
+ try {
1611
+ reachable = await waitUntilReachable({
1612
+ url,
1613
+ now,
1614
+ sleep,
1615
+ probe: probeReachable,
1616
+ timeoutMs: reachableTimeoutMs,
1617
+ signal
1618
+ });
1619
+ } catch (cause) {
1620
+ return failAfterCleanup(`Could not establish ${provider} tunnel reachability: ${describe(cause)}`);
1621
+ }
1622
+ if (!reachable) return failAfterCleanup(`${url} did not become reachable within ${reachableTimeoutMs}ms; ${provider} may still be registering, or the network path is blocked.`);
1433
1623
  }
1624
+ if (signal?.aborted) return failAfterCleanup("Metro tunnel startup was cancelled.");
1434
1625
  const finalProcessToken = captureChildIdentity();
1435
1626
  if (!finalProcessToken || finalProcessToken !== initialProcessToken) return failAfterCleanup(`${provider} process identity changed before its tunnel could be recorded.`);
1436
1627
  if (!outputFile) unrefChildPipes(child);
@@ -1444,10 +1635,16 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
1444
1635
  cleanup: () => cleanup()
1445
1636
  };
1446
1637
  }
1447
- async function startTunnelSequence({ providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
1638
+ async function startTunnelSequence({ claims = [], signal, providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
1448
1639
  const failures = [];
1449
1640
  for (const provider of providers) {
1641
+ if (signal?.aborted) return {
1642
+ failed: true,
1643
+ reason: "Metro tunnel startup was cancelled."
1644
+ };
1450
1645
  const result = await start({
1646
+ claims,
1647
+ signal,
1451
1648
  provider,
1452
1649
  port,
1453
1650
  ngrokUrl: provider === "ngrok" ? ngrokUrl : null,
@@ -1460,7 +1657,8 @@ async function startTunnelSequence({ providers, port, ngrokUrl = null, requireRe
1460
1657
  failures.push(`${provider}: ${result.reason}`);
1461
1658
  if (result.cleanupFailed) return {
1462
1659
  failed: true,
1463
- reason: failures.join(" ")
1660
+ reason: failures.join(" "),
1661
+ cleanupFailed: true
1464
1662
  };
1465
1663
  }
1466
1664
  return {
@@ -1480,50 +1678,53 @@ function resumeChildPipes(child) {
1480
1678
  child.stdout?.resume?.();
1481
1679
  child.stderr?.resume?.();
1482
1680
  }
1483
- async function signalAndWaitForExit(child, signal, { timeoutMs, now, sleep, isAlive, platform }) {
1484
- let exited = false;
1485
- const onExit = () => {
1486
- exited = true;
1487
- };
1488
- child.once("exit", onExit);
1489
- try {
1490
- if (platform === "win32" && child.pid) signalProcessTree(child.pid, signal, { platform });
1491
- child.kill(signal);
1492
- } catch (err) {
1493
- child.removeListener("exit", onExit);
1494
- return isEsrch(err);
1495
- }
1496
- const hasExited = () => exited;
1497
- const deadline = now() + timeoutMs;
1498
- while (!hasExited() && now() < deadline) await sleep(CLEANUP_POLL_MS);
1499
- if (!hasExited() && child.pid && !isAlive(child.pid)) exited = true;
1500
- child.removeListener("exit", onExit);
1501
- return hasExited();
1502
- }
1503
- async function terminateChild(child, { alreadyExited, timeoutMs, now, sleep, isAlive, platform = process.platform }) {
1504
- if (alreadyExited) {
1505
- closeChildPipes(child);
1506
- return true;
1507
- }
1508
- if (await signalAndWaitForExit(child, "SIGTERM", {
1509
- timeoutMs,
1510
- now,
1511
- sleep,
1512
- isAlive,
1513
- platform
1514
- })) {
1515
- closeChildPipes(child);
1516
- return true;
1517
- }
1518
- const killed = await signalAndWaitForExit(child, "SIGKILL", {
1681
+ function signalAndWaitForExit(child, signal, { timeoutMs, isAlive, platform }) {
1682
+ return new Promise((resolve) => {
1683
+ let settled = false;
1684
+ let timer;
1685
+ const onExit = () => finish(true);
1686
+ const onError = (error) => finish(isEsrch(error));
1687
+ const finish = (exited) => {
1688
+ if (settled) return;
1689
+ settled = true;
1690
+ clearTimeout(timer);
1691
+ child.removeListener("exit", onExit);
1692
+ child.removeListener("error", onError);
1693
+ resolve(exited);
1694
+ };
1695
+ child.once("exit", onExit);
1696
+ child.once("error", onError);
1697
+ try {
1698
+ if (platform === "win32" && child.pid) signalProcessTree(child.pid, signal, { platform });
1699
+ child.kill(signal);
1700
+ } catch (error) {
1701
+ finish(isEsrch(error));
1702
+ }
1703
+ if (!settled) timer = setTimeout(() => finish(Boolean(child.pid && !isAlive(child.pid))), timeoutMs);
1704
+ });
1705
+ }
1706
+ async function terminateChild(child, { alreadyExited, timeoutMs, isAlive, platform = process.platform, waitForGroup = false }) {
1707
+ let stopped = alreadyExited;
1708
+ let deadline = Date.now() + timeoutMs;
1709
+ if (!stopped) stopped = await signalAndWaitForExit(child, "SIGTERM", {
1519
1710
  timeoutMs,
1520
- now,
1521
- sleep,
1522
1711
  isAlive,
1523
1712
  platform
1524
1713
  });
1714
+ if (!stopped) {
1715
+ deadline = Date.now() + timeoutMs;
1716
+ stopped = await signalAndWaitForExit(child, "SIGKILL", {
1717
+ timeoutMs,
1718
+ isAlive,
1719
+ platform
1720
+ });
1721
+ }
1722
+ if (stopped && waitForGroup && child.pid) {
1723
+ while ((0, ownership_claim_exports.processGroupAlive)(child.pid) && Date.now() < deadline) await cancellableSleep(25);
1724
+ stopped = !(0, ownership_claim_exports.processGroupAlive)(child.pid);
1725
+ }
1525
1726
  closeChildPipes(child);
1526
- return killed;
1727
+ return stopped;
1527
1728
  }
1528
1729
  function describe(err) {
1529
1730
  return err?.message || String(err);
@@ -1533,7 +1734,14 @@ const STOP_POLL_MS = 100;
1533
1734
  function isEsrch(err) {
1534
1735
  return err?.code === "ESRCH";
1535
1736
  }
1536
- async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, kill = (pid) => signalProcessTreeAsync(pid, "SIGTERM", { platform }), now = Date.now, sleep = defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
1737
+ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, operation, kill = (pid) => signalProcessTreeAsync(pid, "SIGTERM", {
1738
+ platform,
1739
+ operation
1740
+ }), now = Date.now, sleep = operation ? (ms) => cancellableSleep(ms, {
1741
+ signal: operation.signal,
1742
+ what: "tunnel stop"
1743
+ }) : defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
1744
+ operation?.signal.throwIfAborted();
1537
1745
  const missing = () => {
1538
1746
  removeRecordedTunnelLogFile(record?.logFile);
1539
1747
  return { status: "missing" };
@@ -1553,6 +1761,8 @@ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = proce
1553
1761
  try {
1554
1762
  await kill(pid);
1555
1763
  } catch (err) {
1764
+ if (err instanceof WorkspaceProcessCleanupError) throw err;
1765
+ operation?.signal.throwIfAborted();
1556
1766
  return isEsrch(err) ? missing() : {
1557
1767
  status: "failed",
1558
1768
  reason: describe(err)
@@ -1564,7 +1774,11 @@ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = proce
1564
1774
  const current = inspectIdentity(record);
1565
1775
  return current === "gone" || current === "different";
1566
1776
  };
1567
- while (now() < deadline && !exited()) await sleep(STOP_POLL_MS);
1777
+ while (now() < deadline && !exited()) {
1778
+ operation?.signal.throwIfAborted();
1779
+ await sleep(STOP_POLL_MS);
1780
+ }
1781
+ operation?.signal.throwIfAborted();
1568
1782
  if (!exited()) return {
1569
1783
  status: "failed",
1570
1784
  reason: `pid ${pid} did not exit within ${timeoutMs}ms.`
@@ -1638,7 +1852,7 @@ function numberOrNull(v) {
1638
1852
  //#region src/workspace/in-use.ts
1639
1853
  const NATIVE_RUN = "native-run";
1640
1854
  const NATIVE_RUN_HELD = "a stim ios, android or stop run holds its native-run.lock";
1641
- function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted = true, nativeRun = true } = {}) {
1855
+ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted = true, nativeRun = true } = {}, bundleInstances = bundleInstancePids) {
1642
1856
  const reasons = [];
1643
1857
  if (supervisor) {
1644
1858
  const project = getProject(root);
@@ -1654,7 +1868,7 @@ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted =
1654
1868
  else if (macos?.state === "running" || macos?.state === "orphaned" || macos?.build.state === "running") reasons.push("its macOS app or build is running");
1655
1869
  else if (macos?.state === "unverified" || !macos && readWorkspaceState$1(root)?.macos !== void 0) reasons.push("its macOS process owner cannot be verified");
1656
1870
  else try {
1657
- const pids = bundleInstancePids(root);
1871
+ const pids = bundleInstances(root);
1658
1872
  if (pids.length) reasons.push(`its macOS app is running (pid ${pids.join(", ")})`);
1659
1873
  } catch (error) {
1660
1874
  reasons.push(`its running macOS apps cannot be listed: ${error.message}`);
@@ -1683,20 +1897,44 @@ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted =
1683
1897
  if (managedLocks) reasons.push(...managedLockHolders(root));
1684
1898
  return reasons;
1685
1899
  }
1686
- function emptyWorkspaceDir(dir) {
1687
- if (lstatSync(dir).isSymbolicLink()) {
1688
- unlinkSync(dir);
1689
- return;
1900
+ async function workspaceInUseAsync(root, checks = {}, signal, operation) {
1901
+ let instances;
1902
+ try {
1903
+ instances = checks.supervisor === false ? [] : await bundleInstancePidsAsync(root, signal, operation);
1904
+ } catch (error) {
1905
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1906
+ signal?.throwIfAborted();
1907
+ instances = error instanceof Error ? error : new Error(String(error));
1690
1908
  }
1691
- for (const name of readdirSync(dir)) if (name !== `${NATIVE_RUN}.lock`) rmSync(join(dir, name), {
1692
- recursive: true,
1693
- force: true
1909
+ signal?.throwIfAborted();
1910
+ return workspaceInUse(root, checks, () => {
1911
+ if (instances instanceof Error) throw instances;
1912
+ return instances;
1694
1913
  });
1695
1914
  }
1696
- async function withIdleWorkspace(root, fn, { purpose, ...checks }) {
1915
+ async function emptyWorkspaceDir(dir, signal) {
1916
+ signal?.throwIfAborted();
1917
+ const stat = await lstat(dir);
1918
+ signal?.throwIfAborted();
1919
+ if (stat.isSymbolicLink()) {
1920
+ await unlink(dir);
1921
+ return;
1922
+ }
1923
+ for (const name of await readdir(dir)) {
1924
+ signal?.throwIfAborted();
1925
+ if (name !== `${NATIVE_RUN}.lock`) await rm(join(dir, name), {
1926
+ recursive: true,
1927
+ force: true
1928
+ });
1929
+ }
1930
+ }
1931
+ async function withIdleWorkspace(root, fn, { purpose, operation, signal = operation?.signal, ...checks }) {
1697
1932
  try {
1698
1933
  return await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN, async () => {
1699
- const reasons = workspaceInUse(root, {
1934
+ const reasons = signal ? await workspaceInUseAsync(root, {
1935
+ ...checks,
1936
+ nativeRun: false
1937
+ }, signal, operation) : workspaceInUse(root, {
1700
1938
  ...checks,
1701
1939
  nativeRun: false
1702
1940
  });
@@ -1711,9 +1949,13 @@ async function withIdleWorkspace(root, fn, { purpose, ...checks }) {
1711
1949
  }, {
1712
1950
  external: true,
1713
1951
  waitMs: 0,
1714
- ownerPurpose: purpose
1952
+ ownerPurpose: purpose,
1953
+ signal,
1954
+ declareSpawnsFor: operation
1715
1955
  });
1716
1956
  } catch (error) {
1957
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
1958
+ signal?.throwIfAborted();
1717
1959
  if (workspaceProcessLockError(error)) return {
1718
1960
  ran: false,
1719
1961
  reasons: [NATIVE_RUN_HELD]
@@ -1744,11 +1986,17 @@ function readRecordedRoot(dir, name) {
1744
1986
  try {
1745
1987
  parsed = readJsonFile(join$1(dir, "workspace.json"));
1746
1988
  } catch (error) {
1747
- return {
1748
- projectRoot: null,
1749
- problem: error?.code === "ENOENT" ? "it has no workspace.json" : "its workspace.json does not parse"
1750
- };
1989
+ return unreadableRecordedRoot(error);
1751
1990
  }
1991
+ return recordedRoot(parsed, name);
1992
+ }
1993
+ function unreadableRecordedRoot(error) {
1994
+ return {
1995
+ projectRoot: null,
1996
+ problem: error?.code === "ENOENT" ? "it has no workspace.json" : "its workspace.json does not parse"
1997
+ };
1998
+ }
1999
+ function recordedRoot(parsed, name) {
1752
2000
  const projectRoot = isJsonObject(parsed) ? parsed.projectRoot : void 0;
1753
2001
  if (typeof projectRoot !== "string" || !isAbsolute$1(projectRoot)) return {
1754
2002
  projectRoot: null,
@@ -1786,6 +2034,42 @@ function listWorkspaceDirs() {
1786
2034
  }
1787
2035
  return entries;
1788
2036
  }
2037
+ async function listWorkspaceDirsAsync(signal) {
2038
+ signal?.throwIfAborted();
2039
+ const root = workspacesRoot();
2040
+ let names;
2041
+ try {
2042
+ names = (await readdir(root)).toSorted();
2043
+ } catch {
2044
+ signal?.throwIfAborted();
2045
+ return [];
2046
+ }
2047
+ const entries = [];
2048
+ for (const name of names) {
2049
+ signal?.throwIfAborted();
2050
+ const dir = join$1(root, name);
2051
+ try {
2052
+ if (!(await stat(dir)).isDirectory()) continue;
2053
+ } catch {
2054
+ signal?.throwIfAborted();
2055
+ continue;
2056
+ }
2057
+ signal?.throwIfAborted();
2058
+ let recorded;
2059
+ try {
2060
+ recorded = recordedRoot(JSON.parse(await readFile(join$1(dir, "workspace.json"), "utf8")), name);
2061
+ } catch (error) {
2062
+ signal?.throwIfAborted();
2063
+ recorded = unreadableRecordedRoot(error);
2064
+ }
2065
+ signal?.throwIfAborted();
2066
+ entries.push({
2067
+ dir,
2068
+ ...recorded
2069
+ });
2070
+ }
2071
+ return entries;
2072
+ }
1789
2073
  function classifyWorkspaceDirs(entries, { registryKeys, exists, isMounted, inUse }) {
1790
2074
  const orphaned = [];
1791
2075
  const skipped = [];
@@ -1841,28 +2125,47 @@ function rootPresence(path) {
1841
2125
  return code === "ENOENT" || code === "ENOTDIR" ? false : null;
1842
2126
  }
1843
2127
  }
1844
- function collectOrphanedWorkspaces(registryKeys, mountedVolumes, { measure = true } = {}) {
1845
- const classified = classifyWorkspaceDirs(listWorkspaceDirs(), {
1846
- registryKeys,
1847
- exists: rootPresence,
1848
- isMounted: (path) => isOnMountedVolume(path, mountedVolumes),
1849
- inUse: (root) => workspaceInUse(root)
1850
- });
1851
- if (!measure) return classified;
1852
- return (async () => {
1853
- const orphaned = [];
1854
- for (const entry of classified.orphaned) {
1855
- const bytes = await measuredDirectorySizeAsync(entry.dir, { timeoutMs: SIZE_TIMEOUT_MS });
1856
- orphaned.push({
1857
- ...entry,
1858
- bytes
2128
+ async function collectOrphanedWorkspaces(registryKeys, mountedVolumes, { measure = true, operation } = {}) {
2129
+ const classified = {
2130
+ orphaned: [],
2131
+ skipped: []
2132
+ };
2133
+ for (const entry of await listWorkspaceDirsAsync(operation?.signal)) {
2134
+ operation?.signal.throwIfAborted();
2135
+ const current = classifyWorkspaceDirs([entry], {
2136
+ registryKeys,
2137
+ exists: rootPresence,
2138
+ isMounted: (path) => isOnMountedVolume(path, mountedVolumes),
2139
+ inUse: () => []
2140
+ });
2141
+ classified.skipped.push(...current.skipped);
2142
+ for (const orphan of current.orphaned) {
2143
+ const reasons = operation ? await workspaceInUseAsync(orphan.projectRoot, {}, operation.signal, operation) : workspaceInUse(orphan.projectRoot);
2144
+ if (reasons.length) classified.skipped.push({
2145
+ dir: orphan.dir,
2146
+ reason: `its project root ${orphan.projectRoot} is gone, but ${reasons.join("; ")}`
1859
2147
  });
2148
+ else classified.orphaned.push(orphan);
1860
2149
  }
1861
- return {
1862
- orphaned,
1863
- skipped: classified.skipped
1864
- };
1865
- })();
2150
+ }
2151
+ if (!measure) return classified;
2152
+ const orphaned = [];
2153
+ for (const entry of classified.orphaned) {
2154
+ operation?.signal.throwIfAborted();
2155
+ const bytes = await measuredDirectorySizeAsync(entry.dir, {
2156
+ timeoutMs: SIZE_TIMEOUT_MS,
2157
+ operation,
2158
+ signal: operation?.signal
2159
+ });
2160
+ orphaned.push({
2161
+ ...entry,
2162
+ bytes
2163
+ });
2164
+ }
2165
+ return {
2166
+ orphaned,
2167
+ skipped: classified.skipped
2168
+ };
1866
2169
  }
1867
2170
  const REBUILD_COST = "The next build of an unchanged app installs from the shared build cache; after a native change the compilation cache speeds the rebuild, but on React Native 0.86 Swift does not use it (explicit modules are off), so that build recompiles Swift. The next stim macos in a cleared workspace is a full Swift build.";
1868
2171
  const WORKSPACE_OUTPUT_DIRS = [
@@ -1897,47 +2200,53 @@ function outputPaths(dir) {
1897
2200
  } catch {}
1898
2201
  return paths;
1899
2202
  }
1900
- async function outputBytes(paths, timeoutMs) {
2203
+ async function outputBytes(paths, timeoutMs, signal, operation) {
1901
2204
  let total = 0;
1902
2205
  for (const path of paths) {
1903
- const size = await measuredDirectorySizeAsync(path, { timeoutMs });
2206
+ const size = await measuredDirectorySizeAsync(path, {
2207
+ timeoutMs,
2208
+ signal,
2209
+ operation
2210
+ });
1904
2211
  if (size === null) return null;
1905
2212
  total += size;
1906
2213
  }
1907
2214
  return total;
1908
2215
  }
1909
- function collectWorkspaceOutputs({ olderThan, now, exclude = [], measure = true, sizeTimeoutMs = SIZE_TIMEOUT_MS }) {
2216
+ function collectWorkspaceOutputs({ olderThan, now, exclude = [], measure = true, sizeTimeoutMs = SIZE_TIMEOUT_MS, signal, operation }) {
1910
2217
  const mountedVolumes = listMountedVolumes();
1911
- const entries = listWorkspaceDirs().filter((entry) => !exclude.includes(entry.dir)).filter((entry) => entry.projectRoot === null || isOnMountedVolume(entry.projectRoot, mountedVolumes)).map((entry) => Object.assign({}, entry, { paths: outputPaths(entry.dir) })).filter((entry) => entry.paths.length > 0);
2218
+ const outputEntries = (entries) => entries.filter((entry) => !exclude.includes(entry.dir)).filter((entry) => entry.projectRoot === null || isOnMountedVolume(entry.projectRoot, mountedVolumes)).map((entry) => Object.assign({}, entry, { paths: outputPaths(entry.dir) })).filter((entry) => entry.paths.length > 0);
1912
2219
  const report = (sizes) => ({
1913
2220
  root: workspacesRoot(),
1914
- workspaces: planWorkspaceOutputs(sizes.map((entry) => ({
1915
- ...entry,
1916
- lastUsed: entry.projectRoot === null ? NaN : workspaceLastUsed(entry.projectRoot),
1917
- inUse: entry.projectRoot === null ? [] : workspaceInUse(entry.projectRoot)
1918
- })), {
2221
+ workspaces: planWorkspaceOutputs(sizes.map((entry) => Object.assign({}, entry, { lastUsed: entry.projectRoot === null ? NaN : workspaceLastUsed(entry.projectRoot) })), {
1919
2222
  olderThan,
1920
2223
  now
1921
2224
  })
1922
2225
  });
1923
- if (!measure) return report(entries.map(({ paths: _paths, ...entry }) => ({
1924
- ...entry,
1925
- bytes: null
2226
+ if (!measure && !signal) return report(outputEntries(listWorkspaceDirs()).map(({ paths: _paths, ...entry }) => Object.assign({}, entry, {
2227
+ bytes: null,
2228
+ inUse: entry.projectRoot === null ? [] : workspaceInUse(entry.projectRoot)
1926
2229
  })));
1927
2230
  return (async () => {
1928
2231
  const sizes = [];
1929
- for (const { paths, ...entry } of entries) sizes.push({
1930
- ...entry,
1931
- bytes: await outputBytes(paths, sizeTimeoutMs)
1932
- });
2232
+ const entries = outputEntries(await listWorkspaceDirsAsync(signal));
2233
+ for (const { paths, ...entry } of entries) {
2234
+ signal?.throwIfAborted();
2235
+ sizes.push({
2236
+ ...entry,
2237
+ bytes: measure ? await outputBytes(paths, sizeTimeoutMs, signal, operation) : null,
2238
+ inUse: entry.projectRoot === null ? [] : signal ? await workspaceInUseAsync(entry.projectRoot, {}, signal, operation) : workspaceInUse(entry.projectRoot)
2239
+ });
2240
+ }
1933
2241
  return report(sizes);
1934
2242
  })();
1935
2243
  }
1936
- async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, { log, record } = GC_OUTPUT) {
2244
+ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now(), operation, signal = operation?.signal }, { log, record } = GC_OUTPUT) {
1937
2245
  let failures = 0;
1938
2246
  let cleared = 0;
1939
2247
  const clearedRoots = [];
1940
2248
  for (const entry of report.workspaces) {
2249
+ signal?.throwIfAborted();
1941
2250
  const root = entry.projectRoot;
1942
2251
  if (!entry.willClear || root === null) {
1943
2252
  if (entry.keptReason) {
@@ -1951,7 +2260,7 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, {
1951
2260
  }
1952
2261
  let run;
1953
2262
  try {
1954
- run = await withIdleWorkspace(root, () => {
2263
+ run = await withIdleWorkspace(root, async () => {
1955
2264
  const [again] = planWorkspaceOutputs([{
1956
2265
  ...entry,
1957
2266
  problem: null,
@@ -1962,13 +2271,23 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, {
1962
2271
  now
1963
2272
  });
1964
2273
  if (again?.keptReason) return again.keptReason;
1965
- for (const path of outputPaths(entry.dir)) rmSync$1(path, {
1966
- recursive: true,
1967
- force: true
1968
- });
2274
+ for (const path of outputPaths(entry.dir)) {
2275
+ signal?.throwIfAborted();
2276
+ await rm(path, {
2277
+ recursive: true,
2278
+ force: true
2279
+ });
2280
+ }
2281
+ signal?.throwIfAborted();
1969
2282
  return null;
1970
- }, { purpose: "gc" });
2283
+ }, {
2284
+ purpose: "gc",
2285
+ signal,
2286
+ operation
2287
+ });
1971
2288
  } catch (error) {
2289
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
2290
+ signal?.throwIfAborted();
1972
2291
  failures++;
1973
2292
  log(chalk.red(`Could not clear the build outputs of ${root}: ${error?.message || String(error)}`));
1974
2293
  record("workspaceOutputs", "failed", root, {
@@ -2001,47 +2320,56 @@ function stillOrphaned({ dir, projectRoot }) {
2001
2320
  if (readRecordedRoot(dir, basename$1(dir)).projectRoot !== projectRoot || rootPresence(projectRoot) !== false || !isOnMountedVolume(projectRoot)) return false;
2002
2321
  return !Object.keys(loadConfig$1()?.projects ?? {}).some((key) => isPathPrefix(projectRoot, key));
2003
2322
  }
2004
- async function deleteOrphanedWorkspaces(orphaned) {
2323
+ async function deleteOrphanedWorkspaces(orphaned, output = GC_OUTPUT, operation) {
2005
2324
  let failures = 0;
2006
2325
  for (const entry of orphaned) {
2326
+ operation?.signal.throwIfAborted();
2007
2327
  if (!existsSync$1(entry.dir)) continue;
2008
2328
  let run;
2009
2329
  try {
2010
2330
  run = await withIdleWorkspace(entry.projectRoot, async () => {
2011
2331
  if (!stillOrphaned(entry)) return false;
2012
- const browser = await teardownOwnedBrowser(entry.projectRoot, { deleteProfile: true });
2332
+ const browser = await teardownOwnedBrowser(entry.projectRoot, {
2333
+ deleteProfile: true,
2334
+ ...operation ? { operation } : {}
2335
+ });
2013
2336
  if (browser.status === "failed" || browser.status === "skipped") throw new Error(`its owned Chrome profile was kept: ${browser.reason}`);
2014
- emptyWorkspaceDir(entry.dir);
2337
+ await emptyWorkspaceDir(entry.dir, operation?.signal);
2015
2338
  return true;
2016
- }, { purpose: "gc" });
2339
+ }, {
2340
+ purpose: "gc",
2341
+ operation
2342
+ });
2017
2343
  } catch (error) {
2344
+ if (error instanceof WorkspaceProcessCleanupError) throw error;
2345
+ operation?.signal.throwIfAborted();
2018
2346
  failures++;
2019
- console.log(chalk.red(`Could not remove ${entry.dir}: ${error?.message || error}`));
2020
- recordGcResult("workspaceDirectory", "failed", entry.dir, {
2347
+ output.log(chalk.red(`Could not remove ${entry.dir}: ${error?.message || error}`));
2348
+ output.record("workspaceDirectory", "failed", entry.dir, {
2021
2349
  bytes: entry.bytes ?? null,
2022
2350
  detail: error?.message || String(error)
2023
2351
  });
2024
2352
  continue;
2025
2353
  }
2026
2354
  if (!run.ran) {
2027
- console.log(chalk.yellow(`Kept ${entry.dir}: ${run.reasons.join("; ")}`));
2028
- recordGcResult("workspaceDirectory", "kept", entry.dir, {
2355
+ output.log(chalk.yellow(`Kept ${entry.dir}: ${run.reasons.join("; ")}`));
2356
+ output.record("workspaceDirectory", "kept", entry.dir, {
2029
2357
  bytes: entry.bytes ?? null,
2030
2358
  detail: run.reasons.join("; ")
2031
2359
  });
2032
2360
  continue;
2033
2361
  }
2034
2362
  if (!run.value) {
2035
- console.log(chalk.yellow(`Kept ${entry.dir}: it can no longer be confirmed orphaned.`));
2036
- recordGcResult("workspaceDirectory", "kept", entry.dir, {
2363
+ output.log(chalk.yellow(`Kept ${entry.dir}: it can no longer be confirmed orphaned.`));
2364
+ output.record("workspaceDirectory", "kept", entry.dir, {
2037
2365
  bytes: entry.bytes ?? null,
2038
2366
  detail: "it can no longer be confirmed orphaned"
2039
2367
  });
2040
2368
  continue;
2041
2369
  }
2042
2370
  const size = entry.bytes === void 0 ? "" : ` (${sizeText(entry.bytes)})`;
2043
- console.log(chalk.green(`Removed the orphaned workspace directory ${entry.dir}${size}`));
2044
- recordGcResult("workspaceDirectory", "done", entry.dir, { bytes: entry.bytes ?? null });
2371
+ output.log(chalk.green(`Removed the orphaned workspace directory ${entry.dir}${size}`));
2372
+ output.record("workspaceDirectory", "done", entry.dir, { bytes: entry.bytes ?? null });
2045
2373
  }
2046
2374
  return failures;
2047
2375
  }
@@ -2049,4 +2377,4 @@ function sizeText(bytes) {
2049
2377
  return bytes === null ? "size unknown" : formatBytes(bytes);
2050
2378
  }
2051
2379
  //#endregion
2052
- export { teardownParkedIosSim as A, takeGcResults as B, eraseParkedAvd as C, teardownOwnedBrowser as D, teardownOwnedAvd as E, acquireBuildSlot as F, listBuildSlots as I, releaseBuildSlot as L, canonical$1 as M, measureDu as N, teardownOwnedIosSim as O, writeUsageCache as P, GC_OUTPUT as R, runningBundleInstancesAsync as S, teardownBrowserHeld as T, terminateChild as _, collectWorkspaceOutputs as a, withManagedTunnelLock as b, listWorkspaceDirs as c, emptyWorkspaceDir as d, withIdleWorkspace as f, stopTunnel as g, startTunnelSequence as h, collectOrphanedWorkspaces as i, probePublicHttp as j, teardownParkedAvd as k, rootPresence as l, resolveSupervisorTarget as m, WORKSPACE_OUTPUT_DIRS as n, deleteOrphanedWorkspaces as o, workspaceInUse as p, clearWorkspaceOutputs as r, isInsideWorkspaces as s, REBUILD_COST as t, sizeText as u, withManagedRemoteWorktreeLock as v, eraseParkedIosSim as w, withManagedTunnelRemovalLock as x, withManagedRemoteWorktreeRemovalLock as y, recordGcResult as z };
2380
+ export { teardownParkedAvd as A, createGcResults as B, runningBundleInstancesAsync as C, teardownOwnedAvd as D, teardownBrowserHeld as E, writeUsageCache as F, acquireBuildSlot as I, listBuildSlots as L, probePublicHttp as M, canonical$1 as N, teardownOwnedBrowser as O, measureDu as P, releaseBuildSlot as R, withManagedTunnelRemovalLock as S, eraseParkedIosSim as T, takeGcResults as V, stopTunnel as _, collectWorkspaceOutputs as a, withManagedRemoteWorktreeRemovalLock as b, listWorkspaceDirs as c, emptyWorkspaceDir as d, withIdleWorkspace as f, startTunnelSequence as g, resolveSupervisorTarget as h, collectOrphanedWorkspaces as i, teardownParkedIosSim as j, teardownOwnedIosSim as k, rootPresence as l, workspaceInUseAsync as m, WORKSPACE_OUTPUT_DIRS as n, deleteOrphanedWorkspaces as o, workspaceInUse as p, clearWorkspaceOutputs as r, isInsideWorkspaces as s, REBUILD_COST as t, sizeText as u, terminateChild as v, eraseParkedAvd as w, withManagedTunnelLock as x, withManagedRemoteWorktreeLock as y, GC_OUTPUT as z };