stim 1.18.0 → 1.20.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 (178) hide show
  1. package/README.md +21 -4
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
  12. package/dist/api.d.mts +104 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
  19. package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/build-plan-CP0tckNU.mjs +553 -0
  22. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/ccache-CjHQ5F8g.mjs +142 -0
  25. package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  26. package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
  27. package/dist/cli.mjs +1 -1
  28. package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
  29. package/dist/collector-run.d.mts +10 -0
  30. package/dist/collector-run.mjs +7 -7
  31. package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
  32. package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
  33. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  34. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  35. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  36. package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
  37. package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
  38. package/dist/device-DVCwAFlX.mjs +727 -0
  39. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
  40. package/dist/device-host-worker.mjs +38 -18
  41. package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
  42. package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  43. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  44. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  45. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
  46. package/dist/doctor-C2MSuMMg.mjs +515 -0
  47. package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
  48. package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
  49. package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  50. package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  51. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  52. package/dist/errors-DzkMYh8Q.mjs +205 -0
  53. package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
  54. package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
  55. package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
  56. package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
  57. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  58. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
  59. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  60. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  61. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  62. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
  63. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  64. package/dist/ios-BZ9dg2Fr.mjs +9 -0
  65. package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
  66. package/dist/ios-CZuv6gRv.mjs +1112 -0
  67. package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
  68. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
  69. package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
  70. package/dist/ios-project-B9drt_aY.mjs +197 -0
  71. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  72. package/dist/js-swap-C8icNYIY.mjs +432 -0
  73. package/dist/launch-DxH0THLP.mjs +745 -0
  74. package/dist/launch-YrbqaIFj.mjs +1530 -0
  75. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  76. package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
  77. package/dist/macos-C0iljhsN.mjs +2 -0
  78. package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
  79. package/dist/macos-run.mjs +1 -1
  80. package/dist/maintenance-run.mjs +13 -10
  81. package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
  82. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  83. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  84. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
  85. package/dist/native-android-lHzaew7r.mjs +226 -0
  86. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  87. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  88. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  89. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  90. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  91. package/dist/offload-worker.d.mts +12 -6
  92. package/dist/offload-worker.mjs +409 -73
  93. package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
  94. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
  95. package/dist/plan-BDk2AeY_.mjs +260 -0
  96. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  97. package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
  98. package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  99. package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
  100. package/dist/project-B8_duAzW.mjs +1067 -0
  101. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  102. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  103. package/dist/pull-requests.mjs +1 -1
  104. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  105. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  106. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  107. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  108. package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
  109. package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
  110. package/dist/remote-BU9UyhCJ.mjs +192 -0
  111. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  112. package/dist/result-BDoRk1k3.mjs +186 -0
  113. package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
  114. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
  115. package/dist/server-command-7XIKlWtO.mjs +69 -0
  116. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  117. package/dist/server-expo-DV43vep4.mjs +2 -0
  118. package/dist/settings-Bbztm4oV.mjs +1503 -0
  119. package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
  120. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  121. package/dist/settings.schema.json +156 -6
  122. package/dist/simslim-ChMfe8rg.mjs +149 -0
  123. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  124. package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
  125. package/dist/start-CbQCSnXQ.mjs +2 -0
  126. package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
  127. package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
  128. package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
  129. package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
  130. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  131. package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
  132. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  133. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  134. package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
  135. package/dist/stop-WQPQHLuy.mjs +72 -0
  136. package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
  137. package/dist/supervisor-run.d.mts +7 -2
  138. package/dist/supervisor-run.mjs +32 -20
  139. package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
  140. package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
  141. package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
  142. package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
  143. package/dist/toolchain-BIupIro1.mjs +2 -0
  144. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
  145. package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
  146. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  147. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
  148. package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
  149. package/dist/web-B3eldHFH.mjs +123 -0
  150. package/dist/web-BLXpOe6b.mjs +2 -0
  151. package/dist/web-run.mjs +10 -10
  152. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  153. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
  154. package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
  155. package/dist/worktree-q7_OZNY0.mjs +2 -0
  156. package/package.json +8 -6
  157. package/shim/bundle-response.cjs +2 -1
  158. package/shim/bundle-response.d.cts +1 -1
  159. package/shim/expo-metro-config.cjs +1 -1
  160. package/shim/native-android.gradle +101 -0
  161. package/dist/android-BKA16DTf.mjs +0 -4
  162. package/dist/android-BvwuMUJ9.mjs +0 -3572
  163. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  164. package/dist/ios-CPdkoKaa.mjs +0 -3953
  165. package/dist/ios-DNpdygiv.mjs +0 -7
  166. package/dist/macos-Ck7OBMNY.mjs +0 -2
  167. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  168. package/dist/plan-placement-CtxkCO54.mjs +0 -1859
  169. package/dist/project-C3ChRLZy.mjs +0 -212
  170. package/dist/projects-DD7dBTCj.mjs +0 -92
  171. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  172. package/dist/settings-CQyMElEP.mjs +0 -701
  173. package/dist/start-NTc1Ifqs.mjs +0 -2
  174. package/dist/stop-BGhKMOuH.mjs +0 -48
  175. package/dist/trigger-DWAq9hkw.mjs +0 -2
  176. package/dist/web-B6_bFDn7.mjs +0 -2
  177. package/dist/worktree-BJfnU6r5.mjs +0 -2
  178. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -0,0 +1,727 @@
1
+ import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
3
+ import { D as withConfigLock, H as projectDeviceSlots, _ as loadConfig, a as clearDevice, b as releaseAndroidConsolePort, i as clearAndroidBootPending, k as acquireAvdClaim, t as allConsolePortsAndSerials, w as setDevice, z as deviceSlotPlatforms } from "./config-D0LhTY3S.mjs";
4
+ import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting } from "./settings-Bbztm4oV.mjs";
5
+ import { l as phaseLine } from "./command-output-BjPN99ZB.mjs";
6
+ import { o as ownedDeviceLabel } from "./project-B8_duAzW.mjs";
7
+ import { A as listAvds, B as ownedAvdSystemImage, E as getAvdNameForSerial, F as ownedAvdDeviceProfile, J as resolveOwnedAvdSerial, L as ownedAvdMatchesConfiguration, O as listAdbDevices, P as nextConsolePort, R as ownedAvdName, W as pickDefaultSystemImage, b as createOwnedAvd, dt as parkedMaxSetting, f as assertOwnedAvdStopped, ft as readParked, g as avdPoolConfiguration, j as listInstalledSystemImages, lt as isLegacyDeletionClaim, n as OWNED_AVD_CONFIG_DEFAULTS, ot as adoptParked, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, v as bootAndroidEmulator, y as configureNewOwnedAvd } from "./android-BsYNR81W.mjs";
8
+ import { s as pidExists } from "./metro-D_j9sN-B.mjs";
9
+ import { v as teardownOwnedAvd, x as teardownParkedAvd } from "./workspaces-D2xSHBDY.mjs";
10
+ import { F as memoryCulpritAdvice, N as memoryCulpritsUnder, P as readHostMemoryPressure, d as listAllIosSims } from "./ios-CXLIUxXJ.mjs";
11
+ import { t as clearDeviceIdleShutdown } from "./idle-shutdown-DdMqoSUB.mjs";
12
+ import { i as countLiveOwnedDevices, n as DeviceAdmissionRefusal, p as withDeviceBootAdmission } from "./device-capacity-Ca_ke1LE.mjs";
13
+ import { t as reconcileSimSlim } from "./simslim-ChMfe8rg.mjs";
14
+ import { i as ensureOwnedIosDevice, r as ensureIosBooted } from "./device-ios-CRUqPxKw.mjs";
15
+ import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
16
+ import { captureProcessIdentity } from "@stim-cli/core/process-identity";
17
+ import { randomUUID } from "node:crypto";
18
+ import chalk from "chalk";
19
+ import { isDeepStrictEqual } from "node:util";
20
+ import { request } from "node:http";
21
+ //#region src/engine/metro-warmup.ts
22
+ const WARMUP_TIMEOUT_MS = 6e4;
23
+ async function requestLocal(url, platform, prefetch) {
24
+ return new Promise((resolve) => {
25
+ const req = request(url, { headers: {
26
+ accept: "application/json",
27
+ "expo-platform": platform,
28
+ ...prefetch ? { "x-stim-metro-warmup": "1" } : {}
29
+ } });
30
+ const timer = setTimeout(() => req.destroy(), WARMUP_TIMEOUT_MS);
31
+ timer.unref();
32
+ req.on("socket", (socket) => socket.unref());
33
+ req.on("error", () => resolve(null));
34
+ req.on("close", () => {
35
+ clearTimeout(timer);
36
+ resolve(null);
37
+ });
38
+ req.on("response", (res) => {
39
+ let body = "";
40
+ res.setEncoding("utf8");
41
+ res.on("data", (chunk) => {
42
+ if (!prefetch) body += chunk;
43
+ });
44
+ res.on("error", () => resolve(null));
45
+ res.on("end", () => resolve(res.statusCode === 200 ? body : null));
46
+ });
47
+ req.end();
48
+ });
49
+ }
50
+ async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
51
+ try {
52
+ const origin = `http://127.0.0.1:${port}`;
53
+ if (await requestLocal(new URL("/_stim/metro-warmup", origin), platform, false) !== "ready") return;
54
+ const url = new URL("/index.bundle", origin);
55
+ url.search = new URLSearchParams({
56
+ platform,
57
+ dev: "true",
58
+ lazy: "true",
59
+ minify: "false",
60
+ ...platform === "ios" ? { inlineSourceMap: "false" } : {},
61
+ modulesOnly: "false",
62
+ runModule: "true",
63
+ excludeSource: "true",
64
+ sourcePaths: "url-server",
65
+ ...appId ? { app: appId } : {}
66
+ }).toString();
67
+ if (!bundleUrl && isExpo) {
68
+ const body = await requestLocal(new URL(origin), platform, false);
69
+ if (body === null) return;
70
+ const manifest = JSON.parse(body);
71
+ bundleUrl = manifest.launchAsset?.url ?? manifest.bundleUrl;
72
+ if (typeof bundleUrl !== "string") return;
73
+ }
74
+ if (bundleUrl) {
75
+ const bundle = new URL(bundleUrl, origin);
76
+ url.pathname = bundle.pathname;
77
+ url.search = bundle.search;
78
+ }
79
+ await requestLocal(url, platform, true);
80
+ } catch {}
81
+ }
82
+ //#endregion
83
+ //#region src/engine/android-avd-setup.ts
84
+ var AvdBootError = class extends Error {
85
+ remedy;
86
+ constructor(message, remedy, cause) {
87
+ super(message, { cause });
88
+ this.remedy = remedy;
89
+ }
90
+ };
91
+ var AvdRecoveryError = class extends AvdBootError {};
92
+ function findOtherOwner(avdName, projectPath, selectedSlot = "default") {
93
+ for (const [path, project] of Object.entries(loadConfig()?.projects ?? {})) if (projectDeviceSlots(project).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
94
+ return null;
95
+ }
96
+ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, systemImage, deviceProfile, configuration, configure, teardown = teardownOwnedAvd }) {
97
+ const { claim, reservation, previous, avdName } = withConfigLock(() => {
98
+ const previous = deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
99
+ if (previous?.avdName && previous.avdName !== previousAvdName) throw new Error(`Another Stim run assigned AVD ${previous.avdName} to this workspace. Retry to use it.`);
100
+ if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherOwner(ownedAvdName(label), projectPath, slot)) label = `${label}-${randomUUID().slice(0, 8)}`;
101
+ const avdName = ownedAvdName(label);
102
+ const claim = acquireAvdClaim(avdName);
103
+ const reservation = {
104
+ avdName,
105
+ owned: true,
106
+ deviceName: avdName,
107
+ setupIncomplete: true,
108
+ poolConfiguration: configuration
109
+ };
110
+ try {
111
+ setDevice(projectPath, "android", reservation, slot);
112
+ return {
113
+ claim,
114
+ reservation,
115
+ previous,
116
+ avdName
117
+ };
118
+ } catch (error) {
119
+ releaseClaim(claim);
120
+ throw error;
121
+ }
122
+ });
123
+ const currentRecord = () => deviceSlotPlatforms(loadConfig()?.projects?.[projectPath], slot)?.android;
124
+ const assertReservation = () => {
125
+ if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`);
126
+ const owner = findOtherOwner(avdName, projectPath, slot);
127
+ if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`);
128
+ const current = currentRecord();
129
+ if (!isDeepStrictEqual(current, reservation)) {
130
+ const state = current?.setupIncomplete ? "has incomplete setup" : "was registered or removed";
131
+ throw new Error(`AVD ${current?.avdName ?? avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`);
132
+ }
133
+ };
134
+ let prepared;
135
+ let child;
136
+ const creationRunning = () => child?.pid !== void 0 && processGroupAlive(child.pid);
137
+ let configurationFailure;
138
+ try {
139
+ try {
140
+ const creation = await createOwnedAvd(label, {
141
+ systemImage,
142
+ deviceProfile,
143
+ spawn: (...args) => {
144
+ markClaimChildPending(claim);
145
+ child = getExecutor().spawn(...args);
146
+ if (child.pid !== void 0) {
147
+ const captured = captureProcessIdentity(child.pid);
148
+ if (captured.ok) try {
149
+ setClaimChild(claim, {
150
+ pid: child.pid,
151
+ processToken: captured.token
152
+ });
153
+ } catch {}
154
+ }
155
+ return child;
156
+ }
157
+ }).then((result) => ({ result }), (error) => ({ error }));
158
+ if (creationRunning()) throw new AvdRecoveryError(`AVD ${avdName} creation left its process group running (pid ${child.pid}); its reservation remains incomplete.`, `Wait for process group ${child.pid} to finish before retrying stim android. If its identity could not be recorded, inspect the group and remove the claim only after it stops: ${claimRemoveCommand(claim.path)}`);
159
+ clearClaimChild(claim);
160
+ if ("error" in creation) throw creation.error;
161
+ prepared = {
162
+ ...creation.result,
163
+ created: true
164
+ };
165
+ } catch (error) {
166
+ const message = String(error?.message || error);
167
+ if (!message.includes("already exists")) throw error;
168
+ try {
169
+ if (!listAvds().includes(avdName)) throw new AvdRecoveryError(`AVD ${avdName} already exists on disk but is not listed by the emulator. ${message}`, "Run `npx stim gc` to inspect orphaned owned AVDs, then `npx stim gc --delete` to reclaim those safe to delete. Retry `stim android` after cleanup; keep any AVD that GC cannot verify.", error);
170
+ withConfigLock(assertReservation);
171
+ const resolved = resolveOwnedAvdSerial(avdName);
172
+ if (resolved.missing || resolved.notOwned) throw new Error(`AVD ${avdName} could not be verified for recovery. Retry after checking its registration.`, { cause: error });
173
+ if (!resolved.serial) assertOwnedAvdStopped(avdName);
174
+ prepared = {
175
+ avdName,
176
+ created: false,
177
+ systemImage: ownedAvdSystemImage(avdName),
178
+ deviceProfile: ownedAvdDeviceProfile(avdName),
179
+ ...resolved.serial ? {
180
+ serial: resolved.serial,
181
+ consolePort: Number(resolved.serial.replace(/^emulator-/, ""))
182
+ } : {}
183
+ };
184
+ } catch (recoveryError) {
185
+ withConfigLock(() => {
186
+ if (!isDeepStrictEqual(currentRecord(), reservation)) return;
187
+ if (previous) setDevice(projectPath, "android", previous, slot);
188
+ else clearDevice(projectPath, "android", slot, avdName);
189
+ });
190
+ if (recoveryError instanceof AvdRecoveryError) throw recoveryError;
191
+ throw new AvdRecoveryError(`Could not recover owned AVD ${avdName}: ${String(recoveryError?.message || recoveryError)}`, "Inspect `npx stim status` and `adb devices`. Wait for any other Stim run using this AVD to finish, then retry `stim android`. Keep the AVD and its process locks while its state is unverified.", recoveryError);
192
+ }
193
+ }
194
+ if (prepared.created) try {
195
+ configure(avdName);
196
+ } catch (error) {
197
+ configurationFailure = { error };
198
+ }
199
+ if (!configurationFailure) withConfigLock(() => {
200
+ assertReservation();
201
+ setDevice(projectPath, "android", {
202
+ avdName,
203
+ owned: true,
204
+ deviceName: avdName,
205
+ ...prepared.created ? {
206
+ poolConfiguration: configuration,
207
+ bootPending: true
208
+ } : {},
209
+ ...prepared.consolePort ? { consolePort: prepared.consolePort } : {}
210
+ }, slot);
211
+ });
212
+ } finally {
213
+ if (!creationRunning()) releaseClaim(claim);
214
+ else {
215
+ child?.stdout?.destroy();
216
+ child?.stderr?.destroy();
217
+ child?.unref();
218
+ }
219
+ }
220
+ if (configurationFailure) {
221
+ const { error } = configurationFailure;
222
+ const cleanup = teardown(avdName, {
223
+ del: true,
224
+ owner: {
225
+ projectPath,
226
+ slot,
227
+ expectedRecord: reservation
228
+ },
229
+ onRemoved: () => {
230
+ clearDevice(projectPath, "android", slot, avdName);
231
+ }
232
+ });
233
+ const orphan = cleanup.status === "failed" || cleanup.status === "skipped" ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
234
+ throw new Error(`Created owned AVD ${avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
235
+ }
236
+ return prepared;
237
+ }
238
+ //#endregion
239
+ //#region src/engine/device-android.ts
240
+ const ANDROID_FRESH_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 12e4;
241
+ const ANDROID_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 24e4;
242
+ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
243
+ const projectAvdConfig = androidAvdConfigSetting(settings, settingsRoot);
244
+ const avdConfig = {
245
+ ...OWNED_AVD_CONFIG_DEFAULTS,
246
+ ...projectAvdConfig
247
+ };
248
+ const requestedProfile = flags.deviceProfile || settings.android?.deviceProfile || null;
249
+ let replacedProfile = null;
250
+ if (record?.setupIncomplete && record.avdName) {
251
+ const avdName = record.avdName;
252
+ const cleanup = teardownAvd(avdName, {
253
+ del: true,
254
+ owner: {
255
+ projectPath,
256
+ slot,
257
+ expectedRecord: record
258
+ },
259
+ onRemoved: () => {
260
+ clearDevice(projectPath, "android", slot, avdName);
261
+ }
262
+ });
263
+ if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`, "Wait for any active AVD operation to finish. If the refusal names an unresolved claim, inspect its creator and native processes before removing only that claim. Retry `stim android` to reconcile the incomplete device.");
264
+ record = null;
265
+ }
266
+ const requestedImage = flags.systemImageFlag || null;
267
+ const currentImage = requestedImage && record?.owned && record.avdName ? ownedAvdSystemImage(record.avdName) : null;
268
+ if (record?.avdName && requestedImage && currentImage && currentImage !== requestedImage) {
269
+ const avdName = record.avdName;
270
+ if (!record.bootPending) throw new AvdRecoveryError(`this project's emulator ${avdName} uses system image ${currentImage}, but ${requestedImage} was requested. --system-image replaces only an emulator that never finished a boot, and this one has.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
271
+ replacedProfile = ownedAvdDeviceProfile(avdName);
272
+ const cleanup = teardownAvd(avdName, {
273
+ del: true,
274
+ owner: {
275
+ projectPath,
276
+ slot,
277
+ expectedRecord: record
278
+ },
279
+ onRemoved: () => {
280
+ clearDevice(projectPath, "android", slot, avdName);
281
+ }
282
+ });
283
+ if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${avdName} never finished a boot, but could not be deleted to create one on ${requestedImage} (${cleanup.reason || cleanup.status}). Stim kept the device record.`, "Fix the cause, then retry, or pass `--slot <name>` to create the requested emulator beside it.");
284
+ out(phaseLine("device", `deleted ${avdName}, which never finished a boot, to create one on ${requestedImage}`));
285
+ record = null;
286
+ }
287
+ const deviceProfile = requestedProfile ?? replacedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
288
+ const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
289
+ const configurationBeforeDefaults = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), projectAvdConfig, deviceProfile);
290
+ if (record?.avdName) {
291
+ if (record.owned) {
292
+ const resolved = resolveOwnedAvdSerial(record.avdName);
293
+ const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
294
+ if (requestedProfile && currentProfile && currentProfile !== requestedProfile) throw new AvdRecoveryError(`this project's emulator uses device profile ${currentProfile}, but ${requestedProfile} was requested. Stim will not silently boot a different model.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
295
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned -- creating a fresh owned AVD instead of reusing it.`));
296
+ else if (resolved.serial) {
297
+ const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
298
+ if (record.consolePort && record.consolePort !== consolePort) out(phaseLine("device", `${record.avdName} changed serial (emulator-${record.consolePort} -> ${resolved.serial}); reconnecting this run, reopen agent-device on ${resolved.serial}`));
299
+ const updated = {
300
+ ...record,
301
+ avdName: record.avdName,
302
+ consolePort,
303
+ owned: true,
304
+ deviceName: record.deviceName ?? record.avdName
305
+ };
306
+ setDevice(projectPath, "android", updated, slot);
307
+ return {
308
+ ...updated,
309
+ systemImage: ownedAvdSystemImage(record.avdName),
310
+ deviceProfile: currentProfile
311
+ };
312
+ } else if (!resolved.missing) {
313
+ out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
314
+ return {
315
+ ...await bootOwnedAvdOnFreshPort({
316
+ deviceSlotWait: flags.deviceSlotWait,
317
+ avdName: record.avdName,
318
+ metadata: record,
319
+ projectPath,
320
+ slot,
321
+ deviceName: record.deviceName,
322
+ out,
323
+ logFile,
324
+ alive
325
+ }),
326
+ systemImage: ownedAvdSystemImage(record.avdName),
327
+ deviceProfile: currentProfile
328
+ };
329
+ }
330
+ } else if (listAvds().includes(record.avdName)) {
331
+ if (!listAdbDevices().emulators.some((e) => e.consolePort === record.consolePort)) {
332
+ note(chalk.yellow(`Note: assigned AVD ${record.avdName} (emulator-${record.consolePort}) is shut down and is not owned by Stim, so it will not be booted automatically.`));
333
+ note(chalk.dim("Boot it yourself, or run `stim gc --delete` to clear the assignment so Stim can create an owned AVD."));
334
+ }
335
+ return record;
336
+ }
337
+ } else if (record?.serial) {
338
+ note(chalk.yellow(`Note: this project has a stored assignment to physical device ${record.serial}, which Stim no longer keeps.`));
339
+ note(chalk.dim("Creating an owned emulator instead. Pass `--device` to build for a connected device."));
340
+ }
341
+ if (parkedMaxSetting("android").max > 0) {
342
+ const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
343
+ const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && (entry.configuration === configuration || entry.configuration === configurationBeforeDefaults)).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
344
+ for (const parked of candidates) {
345
+ if (isLegacyDeletionClaim(parked.deletionClaim)) continue;
346
+ const resolved = resolveOwnedAvdSerial(parked.name);
347
+ if (resolved.missing) {
348
+ const result = teardownParkedAvd(parked.name);
349
+ if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
350
+ continue;
351
+ }
352
+ if (resolved.notOwned || resolved.serial) continue;
353
+ try {
354
+ assertOwnedAvdStopped(parked.name);
355
+ } catch (error) {
356
+ out(phaseLine("device", `kept parked ${parked.name}: ${String(error?.message || error)}`));
357
+ continue;
358
+ }
359
+ if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, parked.configuration)) {
360
+ const result = teardownParkedAvd(parked.name);
361
+ if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
362
+ continue;
363
+ }
364
+ const adopted = {
365
+ avdName: parked.name,
366
+ deviceName: parked.name,
367
+ owned: true,
368
+ poolConfiguration: parked.configuration,
369
+ adoptionPending: true
370
+ };
371
+ if (!adoptParked({
372
+ platform: "android",
373
+ projectPath,
374
+ slot,
375
+ udid: parked.udid,
376
+ device: adopted
377
+ })) continue;
378
+ if (parked.configuration !== configuration) try {
379
+ configureAvd(parked.name, {
380
+ dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
381
+ avdConfig
382
+ });
383
+ adopted.poolConfiguration = configuration;
384
+ } catch (error) {
385
+ out(phaseLine("device", `${parked.name} keeps its old AVD settings: ${String(error?.message || error)}`));
386
+ }
387
+ return {
388
+ ...await bootOwnedAvdOnFreshPort({
389
+ deviceSlotWait: flags.deviceSlotWait,
390
+ avdName: parked.name,
391
+ projectPath,
392
+ slot,
393
+ metadata: adopted,
394
+ out,
395
+ logFile,
396
+ alive
397
+ }),
398
+ adopted: true,
399
+ systemImage,
400
+ deviceProfile
401
+ };
402
+ }
403
+ }
404
+ const created = await prepareOwnedAvd({
405
+ projectPath,
406
+ slot,
407
+ label,
408
+ previousAvdName: record?.avdName,
409
+ systemImage: flags.systemImage || settings.android?.systemImage || void 0,
410
+ deviceProfile,
411
+ configuration,
412
+ configure: (avdName) => configureAvd(avdName, {
413
+ dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
414
+ avdConfig
415
+ }),
416
+ teardown: teardownAvd
417
+ });
418
+ if (!created.created) {
419
+ out(chalk.dim(phaseLine("device", `recovered ${created.avdName} (unrecorded from a prior run)`)));
420
+ if (created.serial) return {
421
+ ...created,
422
+ owned: true,
423
+ deviceName: created.avdName
424
+ };
425
+ }
426
+ return {
427
+ ...await bootOwnedAvdOnFreshPort({
428
+ deviceSlotWait: flags.deviceSlotWait,
429
+ avdName: created.avdName,
430
+ metadata: created.created ? {
431
+ poolConfiguration: configuration,
432
+ bootPending: true
433
+ } : void 0,
434
+ projectPath,
435
+ slot,
436
+ deviceName: created.avdName,
437
+ out,
438
+ logFile,
439
+ alive
440
+ }),
441
+ created: created.created,
442
+ systemImage: created.systemImage,
443
+ deviceProfile: created.deviceProfile
444
+ };
445
+ }
446
+ function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
447
+ return lock(() => {
448
+ const consolePort = nextConsolePort([...recordedPorts(), ...livePorts]);
449
+ const claim = {
450
+ ...metadata?.poolConfiguration ? { poolConfiguration: metadata.poolConfiguration } : {},
451
+ ...metadata?.adoptionPending ? { adoptionPending: true } : {},
452
+ ...metadata?.bootPending ? { bootPending: true } : {},
453
+ avdName,
454
+ consolePort,
455
+ owned: true,
456
+ deviceName: deviceName ?? avdName
457
+ };
458
+ record(projectPath, "android", claim, slot);
459
+ return claim;
460
+ });
461
+ }
462
+ function liveAndroidConsolePorts() {
463
+ const adbLive = listAdbDevices();
464
+ return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
465
+ }
466
+ async function bootOwnedAvdOnFreshPort({ avdName, deviceSlotWait, metadata, projectPath, slot, deviceName, out, logFile = null, alive = pidExists }) {
467
+ return withDeviceBootAdmission({
468
+ platform: "android",
469
+ key: avdName
470
+ }, async () => {
471
+ const claim = claimAndroidConsolePort({
472
+ projectPath,
473
+ slot,
474
+ avdName,
475
+ deviceName,
476
+ livePorts: liveAndroidConsolePorts(),
477
+ metadata
478
+ });
479
+ const serial = `emulator-${claim.consolePort}`;
480
+ try {
481
+ reportAndroidMemoryPressure(out);
482
+ const pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
483
+ out(chalk.dim(phaseLine("device", `waiting for ${serial} to finish booting`)));
484
+ const result = await waitForAndroidBoot({
485
+ serial,
486
+ timeoutMs: ANDROID_FRESH_BOOT_TIMEOUT_MS,
487
+ pid,
488
+ alive,
489
+ out
490
+ });
491
+ if (result.failed) throw new AvdBootError(result.reason, result.remedy);
492
+ const running = getAvdNameForSerial(serial);
493
+ if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
494
+ clearAndroidBootPending(projectPath, avdName, slot);
495
+ const { bootPending: _booted, ...booted } = claim;
496
+ return {
497
+ ...booted,
498
+ serial,
499
+ setup: true
500
+ };
501
+ } catch (error) {
502
+ releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
503
+ throw error;
504
+ }
505
+ }, {
506
+ ...deviceSlotWait,
507
+ root: projectPath,
508
+ out
509
+ });
510
+ }
511
+ function reportAndroidMemoryPressure(out) {
512
+ const pressure = readHostMemoryPressure();
513
+ if (pressure === "warning" || pressure === "critical") {
514
+ const named = memoryCulpritAdvice(memoryCulpritsUnder(pressure));
515
+ out(chalk.dim(phaseLine("memory", `macOS reports ${pressure} host memory pressure; emulator boot may be slow.${named ? ` ${named}` : ""} Free memory with \`stim stop\` only in workspaces you own; ask before closing other apps or devices.`)));
516
+ }
517
+ }
518
+ function androidBootRemedy(pressure) {
519
+ let count = null;
520
+ try {
521
+ const live = countLiveOwnedDevices({
522
+ sims: () => process.platform === "darwin" ? listAllIosSims({ timeoutMs: 2e3 }) : [],
523
+ adb: () => ({
524
+ ...listAdbDevices({ timeoutMs: 2e3 }),
525
+ unhealthy: []
526
+ }),
527
+ booting: []
528
+ });
529
+ count = typeof live === "number" ? live : null;
530
+ } catch {}
531
+ const named = memoryCulpritAdvice(memoryCulpritsUnder(pressure));
532
+ const observation = pressure === "warning" || pressure === "critical" ? `macOS reports ${pressure} host memory pressure. ${named ? `${named} ` : ""}` : "";
533
+ const devices = count ? `${count} Stim-owned ${count === 1 ? "device is" : "devices are"} running. ` : "";
534
+ if (observation || devices) return `${observation}${devices}Stop an unneeded device with \`stim stop\` only in a workspace you own, then run \`stim android\` again; ask before closing other apps or devices. These observations do not establish the cause of the timeout or an OOM crash.`;
535
+ return "Inspect the emulator log and run `stim doctor` to check JAVA_HOME, ANDROID_HOME, and installed system images, then run `stim android` again.";
536
+ }
537
+ async function waitForAndroidBoot({ serial, timeoutMs, pid = null, alive = pidExists, out }) {
538
+ const aborted = () => pid !== null && !alive(pid);
539
+ const wait = (windowMs) => waitForBoot(serial, windowMs, {
540
+ aborted,
541
+ commandTimeoutMs: 5e3
542
+ });
543
+ let result = await wait(timeoutMs);
544
+ if (result.ok) return {
545
+ ok: true,
546
+ serial
547
+ };
548
+ let pressure = readHostMemoryPressure();
549
+ let elapsedMs = timeoutMs;
550
+ if (!result.exited && pid !== null && !aborted() && (pressure === "warning" || pressure === "critical")) {
551
+ const extensionMs = 24e4;
552
+ out(chalk.dim(phaseLine("memory", `macOS reports ${pressure} host memory pressure; retrying the boot wait once for the same live emulator ${serial} (up to ${extensionMs / 1e3}s more).`)));
553
+ result = await wait(extensionMs);
554
+ if (result.ok) return {
555
+ ok: true,
556
+ serial
557
+ };
558
+ elapsedMs += extensionMs;
559
+ pressure = readHostMemoryPressure();
560
+ }
561
+ const exited = result.exited || aborted();
562
+ return {
563
+ failed: true,
564
+ reason: `${exited ? `The emulator process for ${serial} exited before the device finished booting.` : `Emulator ${serial} did not finish booting within ${Math.round(elapsedMs / 1e3)}s.`} Diagnostic: ${JSON.stringify(result.diagnostic)}`,
565
+ remedy: exited ? "Inspect the emulator log for the process exit, fix what it reports, then run `stim android` again." : androidBootRemedy(pressure)
566
+ };
567
+ }
568
+ async function ensureAndroidBooted({ device, deviceSlotWait, projectPath, slot, timeoutMs, out, logFile = null, alive = pidExists }) {
569
+ if (!device?.avdName || !projectPath) return {
570
+ failed: true,
571
+ reason: "No owned Android emulator is recorded for this project."
572
+ };
573
+ let resolved;
574
+ try {
575
+ resolved = resolveOwnedAvdSerial(device.avdName);
576
+ } catch (e) {
577
+ return {
578
+ failed: true,
579
+ reason: `Could not list AVDs: ${e?.message || e}`
580
+ };
581
+ }
582
+ if (resolved.missing) return {
583
+ failed: true,
584
+ reason: `AVD ${device.avdName} no longer exists. Run \`stim android\` again to create a fresh owned AVD.`
585
+ };
586
+ if (resolved.notOwned) return {
587
+ failed: true,
588
+ reason: `AVD ${device.avdName} is not Stim-owned; refusing to boot it.`
589
+ };
590
+ const avdName = device.avdName;
591
+ const booted = (result) => {
592
+ if (!result.failed) clearAndroidBootPending(projectPath, avdName, slot);
593
+ return result;
594
+ };
595
+ if (resolved.serial) return booted(await waitForAndroidBoot({
596
+ serial: resolved.serial,
597
+ timeoutMs,
598
+ out
599
+ }));
600
+ const freshSerial = `emulator-${device.consolePort}`;
601
+ if (device.owned && device.serial === freshSerial) return booted(await waitForAndroidBoot({
602
+ serial: freshSerial,
603
+ timeoutMs,
604
+ out
605
+ }));
606
+ const boot = async () => {
607
+ const claim = claimAndroidConsolePort({
608
+ projectPath,
609
+ slot,
610
+ avdName,
611
+ deviceName: device.deviceName,
612
+ livePorts: liveAndroidConsolePorts(),
613
+ metadata: device
614
+ });
615
+ const serial = `emulator-${claim.consolePort}`;
616
+ reportAndroidMemoryPressure(out);
617
+ out(chalk.dim(phaseLine("device", `booting ${avdName} as ${serial}`)));
618
+ let pid = null;
619
+ try {
620
+ pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
621
+ } catch (e) {
622
+ releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
623
+ return {
624
+ failed: true,
625
+ reason: `Could not start emulator for AVD ${avdName}: ${e?.message || e}`
626
+ };
627
+ }
628
+ const result = await waitForAndroidBoot({
629
+ serial,
630
+ timeoutMs,
631
+ pid,
632
+ alive,
633
+ out
634
+ });
635
+ if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
636
+ return booted(result);
637
+ };
638
+ try {
639
+ return await withDeviceBootAdmission({
640
+ platform: "android",
641
+ key: avdName
642
+ }, boot, {
643
+ ...deviceSlotWait,
644
+ root: projectPath,
645
+ out
646
+ });
647
+ } catch (error) {
648
+ const refusal = error instanceof DeviceAdmissionRefusal ? error : (0, ownership_claim_exports.claimFailure)(error, "stim android");
649
+ if (!refusal) throw error;
650
+ return {
651
+ failed: true,
652
+ code: refusal.code,
653
+ reason: refusal.message,
654
+ remedy: refusal.remedy
655
+ };
656
+ }
657
+ }
658
+ //#endregion
659
+ //#region src/engine/device.ts
660
+ /**
661
+ * Whether `ensureOwnedDevice` created, adopted or started booting the device, rather than finding it booted.
662
+ * Undefined for a remote device, which those functions do not describe.
663
+ */
664
+ function didSetUpDevice(device, remote) {
665
+ return remote ? void 0 : Boolean(device.setup || device.booting);
666
+ }
667
+ async function ensureOwnedDevice({ platform, project, projectPath, slot, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim, simSnapshot }) {
668
+ clearDeviceIdleShutdown(projectPath, platform, slot);
669
+ if (slot && slot !== "default") label = `${label}-${slot}`;
670
+ const record = deviceSlotPlatforms(project, slot)?.[platform] ?? null;
671
+ if (platform === "ios") return ensureOwnedIosDevice({
672
+ record,
673
+ projectPath,
674
+ slot,
675
+ settingsRoot,
676
+ label,
677
+ settings,
678
+ flags,
679
+ note,
680
+ out,
681
+ reconcileIosSimulator,
682
+ simSnapshot
683
+ });
684
+ return ensureOwnedAndroidDevice({
685
+ record,
686
+ projectPath,
687
+ slot,
688
+ settingsRoot,
689
+ label,
690
+ settings,
691
+ flags,
692
+ note,
693
+ out,
694
+ logFile,
695
+ alive,
696
+ configureAvd,
697
+ teardownAvd
698
+ });
699
+ }
700
+ const BOOT_POLL_MS = 500;
701
+ async function ensureBooted({ platform, device, simulatorApp, timeoutMs, pollMs = BOOT_POLL_MS, out = () => {}, logFile = null, alive = pidExists, projectPath, slot, deviceSlotWait }) {
702
+ if (platform === "ios") return ensureIosBooted({
703
+ device,
704
+ projectPath,
705
+ deviceSlotWait,
706
+ simulatorApp,
707
+ timeoutMs: timeoutMs ?? 6e5,
708
+ pollMs,
709
+ out
710
+ });
711
+ if (platform === "android") return ensureAndroidBooted({
712
+ device,
713
+ deviceSlotWait,
714
+ projectPath,
715
+ slot,
716
+ timeoutMs: timeoutMs ?? ANDROID_BOOT_TIMEOUT_MS,
717
+ out,
718
+ logFile,
719
+ alive
720
+ });
721
+ return {
722
+ failed: true,
723
+ reason: `Unknown platform "${platform}".`
724
+ };
725
+ }
726
+ //#endregion
727
+ export { AvdRecoveryError as a, AvdBootError as i, ensureBooted as n, warmMetro as o, ensureOwnedDevice as r, didSetUpDevice as t };