stim 1.19.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 (172) hide show
  1. package/README.md +20 -3
  2. package/dist/{activity-BSw0x84R.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-Bjxu_VmX.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-BdQC6zs1.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-DE5g_5gv.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-C87geAg-.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-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
  12. package/dist/api.d.mts +98 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-CFI4ptUL.mjs → app-install-0LgaQRT7.mjs} +9 -8
  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/{browser-web-CDhqQxhv.mjs → browser-web-CzBmhmgs.mjs} +19 -19
  19. package/dist/{budget-Dz-kyXLv.mjs → budget-BONq9vXY.mjs} +3101 -3062
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
  22. package/dist/build-plan-CP0tckNU.mjs +553 -0
  23. package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  24. package/dist/cache-manifest.mjs +1 -1
  25. package/dist/{manifest-CX0d1IkZ.mjs → ccache-CjHQ5F8g.mjs} +13 -19
  26. package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  27. package/dist/{cli-C9Zvgbdc.mjs → cli-B2R8wH1F.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-CuUo8Nvg.mjs → client-BLy1Pw2V.mjs} +89 -307
  30. package/dist/collector-run.d.mts +4 -0
  31. package/dist/collector-run.mjs +7 -7
  32. package/dist/{command-output-Bt8ygHE7.mjs → command-output-BjPN99ZB.mjs} +2 -2
  33. package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
  34. package/dist/{created-devices-D0s26Hx5.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  35. package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  36. package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  37. package/dist/{dev-client-CDY_zAmD.mjs → dev-client-ClATaJoH.mjs} +2 -2
  38. package/dist/{device-BHw0WlHl.mjs → device-B7lsC0K1.mjs} +8 -8
  39. package/dist/device-DVCwAFlX.mjs +727 -0
  40. package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-Ca_ke1LE.mjs} +13 -14
  41. package/dist/device-host-worker.mjs +38 -18
  42. package/dist/device-ios-CRUqPxKw.mjs +413 -0
  43. package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  44. package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  45. package/dist/{device-pool-CP4rAsHx.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  46. package/dist/{device-remote-B5GiUifk.mjs → device-remote-Bmg42063.mjs} +17 -68
  47. package/dist/doctor-C2MSuMMg.mjs +515 -0
  48. package/dist/{doctor-PRJH94v6.mjs → doctor-DPAqhVCK.mjs} +333 -744
  49. package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-CB4NnVZm.mjs} +13 -12
  50. package/dist/{simslim-D9LkJxIO.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  51. package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  52. package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  53. package/dist/errors-DzkMYh8Q.mjs +205 -0
  54. package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
  55. package/dist/{gc-CyHMcKcn.mjs → gc-PWp34zGb.mjs} +21 -20
  56. package/dist/{device-ios-B2ruohE-.mjs → gradle-C2OEKCCI.mjs} +21 -650
  57. package/dist/{guide-dRaQJJk9.mjs → guide-Dai9lphv.mjs} +504 -117
  58. package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  59. package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client-Io01s5By.mjs} +9 -3
  60. package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  61. package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  62. package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  63. package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-BdAQi21M.mjs} +17 -14
  64. package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  65. package/dist/{ios-DYUr9LGc.mjs → ios-BZ9dg2Fr.mjs} +8 -7
  66. package/dist/{ios-BWbHFZVz.mjs → ios-CXLIUxXJ.mjs} +180 -24
  67. package/dist/ios-CZuv6gRv.mjs +1112 -0
  68. package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
  69. package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
  70. package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BreZOpM0.mjs} +3 -3
  71. package/dist/ios-project-B9drt_aY.mjs +197 -0
  72. package/dist/{ios-state-By0XuLTQ.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  73. package/dist/js-swap-C8icNYIY.mjs +432 -0
  74. package/dist/launch-DxH0THLP.mjs +745 -0
  75. package/dist/{react-native-ios-CSpIxexZ.mjs → launch-YrbqaIFj.mjs} +59 -612
  76. package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  77. package/dist/{logs-Bao1HOMy.mjs → logs-DHaRNfg7.mjs} +11 -11
  78. package/dist/macos-C0iljhsN.mjs +2 -0
  79. package/dist/{macos-ciyBdTY5.mjs → macos-V_3Ui7g4.mjs} +42 -50
  80. package/dist/macos-run.mjs +1 -1
  81. package/dist/maintenance-run.mjs +13 -10
  82. package/dist/{metro-DI2AjPF_.mjs → metro-D_j9sN-B.mjs} +18 -5
  83. package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  84. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  85. package/dist/{named-ports-B4iWH4E-.mjs → named-ports-C2lvoPsb.mjs} +3 -3
  86. package/dist/native-android-lHzaew7r.mjs +226 -0
  87. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  88. package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  89. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  90. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  91. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  92. package/dist/offload-worker.d.mts +41 -7
  93. package/dist/offload-worker.mjs +409 -74
  94. package/dist/{ownership-EB0ySdGK.mjs → ownership-DXzMMSx6.mjs} +2 -2
  95. package/dist/{placement-log-Dxe34rJm.mjs → placement-log-reFbjqEr.mjs} +1 -1
  96. package/dist/plan-BDk2AeY_.mjs +260 -0
  97. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  98. package/dist/{ports-ByPc0_97.mjs → ports-jzvAJXXL.mjs} +3 -3
  99. package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  100. package/dist/{preview-BbSottmO.mjs → preview-BaBJ72Al.mjs} +50 -32
  101. package/dist/project-B8_duAzW.mjs +1067 -0
  102. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  103. package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  104. package/dist/pull-requests.mjs +1 -1
  105. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  106. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  107. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  108. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  109. package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
  110. package/dist/{reload-rAPdqeRR.mjs → reload-D34GkY78.mjs} +17 -13
  111. package/dist/remote-BU9UyhCJ.mjs +192 -0
  112. package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  113. package/dist/result-BDoRk1k3.mjs +186 -0
  114. package/dist/{run-lAkle1-c.mjs → run-qvff78hv.mjs} +3 -3
  115. package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
  116. package/dist/server-command-7XIKlWtO.mjs +69 -0
  117. package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  118. package/dist/server-expo-DV43vep4.mjs +2 -0
  119. package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
  120. package/dist/{settings-2am7TSxp.mjs → settings-CT3E71-i.mjs} +6 -6
  121. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  122. package/dist/settings.schema.json +84 -0
  123. package/dist/simslim-ChMfe8rg.mjs +149 -0
  124. package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  125. package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
  126. package/dist/start-CbQCSnXQ.mjs +2 -0
  127. package/dist/{start-DtxthMfI.mjs → start-CdvcTaXr.mjs} +97 -68
  128. package/dist/{state-Tp4uImDS.mjs → state-Boo0IMJq.mjs} +1 -1
  129. package/dist/{state-D2KtnALG.mjs → state-DI84A-uu.mjs} +29 -10
  130. package/dist/{stats-8EJ5ivFT.mjs → stats-BIIDrjA8.mjs} +5 -5
  131. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  132. package/dist/{status-BOyp0CUl.mjs → status-DARMkc9q.mjs} +81 -293
  133. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  134. package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  135. package/dist/{stop-DRipAg-e.mjs → stop-CCZS-C9m.mjs} +2 -2
  136. package/dist/{stop-D0UIpZuh.mjs → stop-WQPQHLuy.mjs} +5 -5
  137. package/dist/{stop-zDlknFoz.mjs → stop-iNemHsJC.mjs} +21 -20
  138. package/dist/supervisor-run.d.mts +7 -2
  139. package/dist/supervisor-run.mjs +32 -20
  140. package/dist/{support-DsNaFg6D.mjs → support-CnE_SIjt.mjs} +17 -7
  141. package/dist/{support-CNqYkV0y.mjs → support-DCco5fut.mjs} +61 -8
  142. package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-afeNUExW.mjs} +5 -5
  143. package/dist/tailnet-CoJUb2tB.mjs +265 -0
  144. package/dist/toolchain-BIupIro1.mjs +2 -0
  145. package/dist/{toolchain-C3xSoyUl.mjs → toolchain-DqyDXs30.mjs} +206 -53
  146. package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
  147. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  148. package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-C3ASD6z5.mjs} +6 -6
  149. package/dist/{watchman-BL1IdOtq.mjs → watchman-CQl51YZu.mjs} +6 -6
  150. package/dist/{web-DbbrPUn0.mjs → web-B3eldHFH.mjs} +9 -13
  151. package/dist/web-BLXpOe6b.mjs +2 -0
  152. package/dist/web-run.mjs +10 -10
  153. package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  154. package/dist/{workspaces-Djb2gfi9.mjs → workspaces-D2xSHBDY.mjs} +1260 -1258
  155. package/dist/{worktree-BL4M1rhB.mjs → worktree-BxDqaTev.mjs} +84 -48
  156. package/dist/worktree-q7_OZNY0.mjs +2 -0
  157. package/package.json +8 -6
  158. package/shim/native-android.gradle +101 -0
  159. package/dist/android-BXc6BmEi.mjs +0 -3732
  160. package/dist/android-BvV1gGdN.mjs +0 -4
  161. package/dist/ios-ITYno9rO.mjs +0 -2241
  162. package/dist/macos-nIOATJmR.mjs +0 -2
  163. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  164. package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
  165. package/dist/plan-placement-BVY59qa8.mjs +0 -290
  166. package/dist/project-B5eQ1USs.mjs +0 -612
  167. package/dist/react-native-ios-BMh2i25I.mjs +0 -2
  168. package/dist/server-expo-DGsdUGX2.mjs +0 -2
  169. package/dist/start-D8NbxNBb.mjs +0 -2
  170. package/dist/trigger-CoalsMTV.mjs +0 -2
  171. package/dist/web-DFpFeZu5.mjs +0 -2
  172. package/dist/worktree-DV-A0LNL.mjs +0 -2
@@ -0,0 +1,745 @@
1
+ import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
2
+ import { C as saveConfig, D as withConfigLock, E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, _ as loadConfig, j as formatBytes, w as setDevice, z as deviceSlotPlatforms } from "./config-D0LhTY3S.mjs";
3
+ import { j as providerUploadOutcome } from "./device-lease-DHOBXGgS.mjs";
4
+ import { w as workspaceLogsDir } from "./workspace-state-CSIFuuDa.mjs";
5
+ import { _ as stepTimer, c as launchErrorReport, i as formatDuration, l as phaseLine, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-BjPN99ZB.mjs";
6
+ import { I as workspaceDevServerMode } from "./project-B8_duAzW.mjs";
7
+ import { a as workspaceLinks } from "./stim-desktop-8GQX2OV8.mjs";
8
+ import { $ as trimAndroidCaches, i as androidDataFreeBytes, q as resetAdoptedAvd, u as androidToolCwd } from "./android-BsYNR81W.mjs";
9
+ import { d as signalProcessTree, s as pidExists } from "./metro-D_j9sN-B.mjs";
10
+ import { v as teardownOwnedAvd } from "./workspaces-D2xSHBDY.mjs";
11
+ import { o as exitAfterFlush } from "./remote-cache-Bsts6f6Y.mjs";
12
+ import { a as VERIFY_TIMEOUT_MS, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-BzRdbuO1.mjs";
13
+ import { S as restartedAppNote, c as androidDevClientUrl, g as installConflictKind, s as androidAppProcess, t as ADB_INSTALL_TIMEOUT_MS } from "./app-install-0LgaQRT7.mjs";
14
+ import { i as launchOutcomeRecord } from "./native-runtime-BRmWL7ru.mjs";
15
+ import { a as PLATFORM, b as noDeviceDiagnostic, h as displayPath, n as LAUNCH_FAILED, r as NO_DEVICE, t as INSTALL_FAILED } from "./support-CnE_SIjt.mjs";
16
+ import { c as lostRefusal, s as lostLine, t as DEBUG_VERIFY_STEP_MS } from "./device-lease-run-CvDeHTkG.mjs";
17
+ import { a as reportAndroidResult, i as persistLastBuild, n as finishAndroidUpload } from "./result-BDoRk1k3.mjs";
18
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-B9hn96wd.mjs";
19
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-DXzMMSx6.mjs";
20
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Du7I_GuY.mjs";
21
+ import { mkdirSync, openSync, statSync } from "node:fs";
22
+ import { basename, join } from "node:path";
23
+ import chalk from "chalk";
24
+ //#region src/commands/android/collector.ts
25
+ function collectorEntry() {
26
+ return spawnEntry("collector-run");
27
+ }
28
+ function collectorLogFile(root, slot = "default") {
29
+ return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
30
+ }
31
+ const COLLECTOR_EXIT_WAIT_MS = 2e3;
32
+ const COLLECTOR_POLL_MS = 25;
33
+ function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
34
+ const record = (collectors ?? readCollectors(root))?.[deviceSlotKey(platform, slot)];
35
+ const pid = Number(record?.pid);
36
+ if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null;
37
+ const ownership = verify({
38
+ pid,
39
+ platform: deviceSlotKey(platform, slot),
40
+ root,
41
+ isAlive,
42
+ expected: record
43
+ });
44
+ if (ownership.status === "gone") return null;
45
+ if (ownership.status === "unverified") {
46
+ note(chalk.dim(`Previous ${platform} log collector (pid ${pid}): ${ownership.reason}, so it was not signalled -- starting a replacement anyway`));
47
+ return null;
48
+ }
49
+ try {
50
+ kill(pid, "SIGTERM");
51
+ return pid;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ async function startCollector({ root, slot = "default", serial, packageName, spawn, kill, alive = pidExists, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), out }) {
57
+ const previousPid = killPreviousCollector(root, {
58
+ slot,
59
+ kill,
60
+ isAlive: alive,
61
+ verify,
62
+ note: (line) => out(phaseLine("logs", line))
63
+ });
64
+ if (previousPid) {
65
+ const deadline = Date.now() + waitMs;
66
+ while (Date.now() < deadline && alive(previousPid)) await sleep(COLLECTOR_POLL_MS);
67
+ }
68
+ let stdio = "ignore";
69
+ try {
70
+ mkdirSync(workspaceLogsDir(root), { recursive: true });
71
+ const fd = openSync(collectorLogFile(root, slot), "a");
72
+ stdio = [
73
+ "ignore",
74
+ fd,
75
+ fd
76
+ ];
77
+ } catch {}
78
+ try {
79
+ const child = spawn(process.execPath, [
80
+ collectorEntry(),
81
+ "--platform",
82
+ PLATFORM,
83
+ "--root",
84
+ root,
85
+ ...slot === "default" ? [] : ["--slot", slot],
86
+ "--serial",
87
+ serial,
88
+ "--package",
89
+ packageName
90
+ ], {
91
+ cwd: androidToolCwd() ?? root,
92
+ detached: true,
93
+ stdio,
94
+ env: process.env
95
+ });
96
+ child?.unref?.();
97
+ return child?.pid ?? null;
98
+ } catch (err) {
99
+ out(phaseLine("logs", chalk.yellow(`could not start the device log collector: ${err?.message || err}`)));
100
+ return null;
101
+ }
102
+ }
103
+ //#endregion
104
+ //#region src/commands/android/launch.ts
105
+ function androidLaunchOutcomeRecord(kind, args) {
106
+ if (kind !== "process") return launchOutcomeRecord({
107
+ ...args,
108
+ release: kind === "embedded-js"
109
+ });
110
+ const unverified = args.launchState === LAUNCH_UNVERIFIED;
111
+ return {
112
+ src: "build",
113
+ level: unverified ? "warn" : "info",
114
+ event: unverified ? "launch_unverified" : "launch_verified",
115
+ msg: unverified ? `${args.bundleId} could not be verified as running` : `${args.bundleId} is running its native process`
116
+ };
117
+ }
118
+ function androidProcessRuntime(prepare, kind = "process") {
119
+ return {
120
+ kind,
121
+ prepare,
122
+ scheme: () => void 0,
123
+ verificationWaitMs: RELEASE_VERIFY_WAIT_MS,
124
+ launch: async (_prepared, { serial, androidPackage, remoteDevice, launchRelease }) => remoteDevice ? remoteDevice.launch({
125
+ serial,
126
+ packageName: androidPackage,
127
+ metroPort: null
128
+ }) : launchRelease({
129
+ serial,
130
+ packageName: androidPackage
131
+ }),
132
+ verify: async (_prepared, context) => verifyAndroidProcessRun({
133
+ ...context,
134
+ embeddedJavaScript: kind === "embedded-js"
135
+ })
136
+ };
137
+ }
138
+ function androidMetroRuntime(prepare) {
139
+ return {
140
+ kind: "metro",
141
+ prepare,
142
+ scheme: (root, apkPath, resolve) => resolve(root, apkPath),
143
+ verificationWaitMs: DEBUG_VERIFY_STEP_MS,
144
+ launch: async ({ metroPort }, { serial, androidPackage, scheme, physical, launch }) => launch({
145
+ serial,
146
+ packageName: androidPackage,
147
+ metroPort: metroPort ?? 8081,
148
+ devClientScheme: scheme,
149
+ physical
150
+ }),
151
+ verify: async ({ metroPort }, context) => verifyAndroidMetroRun({
152
+ ...context,
153
+ metroPort
154
+ })
155
+ };
156
+ }
157
+ async function verifyAndroidProcessRun({ root, slot, remoteDevice, verifyReleaseLaunched, serial, androidPackage, variant, logsDir, launchedAt, phase, embeddedJavaScript }) {
158
+ const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
159
+ root,
160
+ slot,
161
+ platform: "android",
162
+ deviceId: serial,
163
+ appId: androidPackage,
164
+ since: launchedAt
165
+ }, logsDir);
166
+ if (remoteDevice) {
167
+ phase("verify", chalk.yellow("UNVERIFIED: remote adapter launch accepted; process verification is unavailable"));
168
+ return { state: LAUNCH_UNVERIFIED };
169
+ }
170
+ const processCheck = await verifyReleaseLaunched({
171
+ serial,
172
+ packageName: androidPackage
173
+ });
174
+ const crashes = readNativeCrashes();
175
+ if (processCheck?.verified && !crashes.length) {
176
+ phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${variant}: no bundle fetch to observe)`);
177
+ return { state: true };
178
+ }
179
+ if (processCheck?.reason === "probe-failed" && !crashes.length) {
180
+ phase("verify", chalk.yellow("UNVERIFIED: the app process check failed"));
181
+ return { state: LAUNCH_UNVERIFIED };
182
+ }
183
+ for (const line of launchErrorPreview(crashes, root)) phase("launch", chalk.red(line));
184
+ if (!crashes.length) phase("logs", "Process missing; no attributable native crash report captured. Read `stim logs --source device` for available output.");
185
+ phase("verify", chalk.yellow(crashes.length ? "FATAL: the app reported a native crash" : `FATAL: no ${androidPackage} process on ${serial} ${formatDuration(processCheck?.waitedMs ?? 0)} after launch`));
186
+ phase("", chalk.yellow(`${embeddedJavaScript ? "A release process" : "The app process"} exited before readiness. Run \`stim logs --errors\` for captured crash reports, or \`stim logs --source device\` for the full device output.`));
187
+ return { state: LAUNCH_FATAL };
188
+ }
189
+ async function verifyAndroidMetroRun({ root, slot, remoteDevice, verifyLaunched, serial, androidPackage, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase, enterActivity }) {
190
+ const runCommand = nativeRunCommand("android", slot, {
191
+ physical,
192
+ deviceId: serial
193
+ });
194
+ const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
195
+ root,
196
+ slot,
197
+ platform: "android",
198
+ deviceId: serial,
199
+ appId: androidPackage,
200
+ since: launchedAt
201
+ }, logsDir);
202
+ const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
203
+ const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
204
+ if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
205
+ const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
206
+ const verification = metroCheck ? await verifyLaunched({
207
+ timeoutMs,
208
+ requireBundleResponse: true,
209
+ slot: launchSlotScope(root, slot),
210
+ platformShared: siblings.length > 0,
211
+ onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
212
+ onActivity: enterActivity,
213
+ logsDir,
214
+ since: launchedAt,
215
+ metroPort,
216
+ platform: "android",
217
+ mode: workspaceDevServerMode(root, isExpo),
218
+ readNativeCrashes,
219
+ processAlive: () => {
220
+ const pid = androidAppProcess(serial, androidPackage);
221
+ return pid === void 0 ? null : pid !== null;
222
+ }
223
+ }) : {
224
+ verified: false,
225
+ skipped: true
226
+ };
227
+ const nativeCrashes = verification.errors?.some((record) => record.event === "native_crash") ? [] : readNativeCrashes();
228
+ if (nativeCrashes.length) verification.fatal = true;
229
+ verification.errors = await errorDiagnostics([...verification.errors ?? [], ...nativeCrashes], {
230
+ root,
231
+ logsDir,
232
+ port: metroPort,
233
+ allowRequest: true
234
+ });
235
+ if (verification.readiness) phase("readiness", appReadinessMessage(verification.readiness, verification.waitedMs ?? 0));
236
+ if (verification?.fatal) {
237
+ const nativeFatal = verification.errors?.some((record) => record.event === "native_crash");
238
+ const deliveryFailed = verification.record?.event === "bundle_response_failed";
239
+ const reason = nativeFatal ? "the app reported a native crash" : verification.processAlive === false ? "the app process exited" : deliveryFailed ? "Metro bundle delivery failed" : "Metro could not build the bundle";
240
+ phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
241
+ for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
242
+ if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) phase("logs", "No attributable native crash report captured yet. Run `stim logs --errors` again for delayed reports, or `stim logs --source device` for available output.");
243
+ if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then run \`${runCommand}\` again; it force-stops a crashed process that remains alive behind the system crash dialog. Metro reload cannot recover it.`));
244
+ else if (verification.processAlive === true && metroPort !== null) {
245
+ const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
246
+ phase("remedy", chalk.yellow(`The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
247
+ }
248
+ return { state: LAUNCH_FATAL };
249
+ }
250
+ if (verification?.verified) {
251
+ phase("verify", `bundle loaded` + (verification.processAlive === true ? ", process alive" : "") + (verification.readiness ? "" : ", stable for 3s -- the first screen may still be rendering") + ` (${formatDuration(verification.waitedMs ?? 0)} total)`);
252
+ const report = launchErrorReport(verification.errors ?? [], root);
253
+ if (report.summary) phase("launch", chalk.dim(report.summary));
254
+ for (const line of report.lines) phase("launch", chalk.yellow(line));
255
+ if (report.lines.length > 0 && verification.processAlive === true && metroPort !== null) {
256
+ const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
257
+ phase("remedy", chalk.yellow(`The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
258
+ }
259
+ return {
260
+ state: true,
261
+ warning: report.lines.length > 0 ? "app errors detected; inspect the error above" : verification.readiness === "timed-out" || verification.readiness === "error" ? "app readiness not confirmed; inspect the UI and logs" : void 0
262
+ };
263
+ }
264
+ if (verification?.skipped) {
265
+ phase("verify", "skipped (--no-metro-check): the launch is reported as unverified");
266
+ return { state: LAUNCH_UNVERIFIED };
267
+ }
268
+ if (verification?.requested) {
269
+ phase("verify", `BUNDLING: the app asked port ${metroPort} for its bundle; build, delivery, or JavaScript loading was still pending after ${formatDuration(verification.waitedMs ?? 0)} (a cold bundle on a large graph outlasts this window)`);
270
+ phase("", chalk.dim("Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute."));
271
+ return { state: LAUNCH_BUNDLING };
272
+ }
273
+ if (verification?.unattributed) {
274
+ const [headline, ...lines] = unattributedLaunchLines({
275
+ platform: "android",
276
+ metroPort,
277
+ slot,
278
+ siblings
279
+ });
280
+ phase("verify", chalk.yellow(headline));
281
+ for (const line of lines) phase("", chalk.yellow(line));
282
+ return {
283
+ state: LAUNCH_UNVERIFIED,
284
+ unattributed: true
285
+ };
286
+ }
287
+ phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
288
+ for (const line of unverifiedLaunchLines({
289
+ platform: PLATFORM,
290
+ metroPort: metroPort ?? 8081,
291
+ waitedMs: verification?.waitedMs,
292
+ bundleId: androidPackage,
293
+ serial,
294
+ component,
295
+ devClientUrl: scheme ? androidDevClientUrl(scheme, metroPort ?? 8081, physical) : null,
296
+ devClient: Boolean(scheme),
297
+ mode: workspaceDevServerMode(root, isExpo)
298
+ })) phase("", chalk.yellow(line));
299
+ return { state: LAUNCH_UNVERIFIED };
300
+ }
301
+ const ADOPTED_INSTALL_HEADROOM_BYTES = 536870912;
302
+ const INSUFFICIENT_STORAGE = /INSTALL_FAILED_INSUFFICIENT_STORAGE/;
303
+ async function wipeAdoptedAvd(room, free) {
304
+ room.phase("device", chalk.yellow(`${room.avdName} has ${free === null ? "too little space" : `${formatBytes(free)} free`} on /data after removing other apps and trimming caches; wiping its user data`));
305
+ const expectedRecord = deviceSlotPlatforms(loadConfig()?.projects?.[room.root], room.slot)?.android;
306
+ if (!expectedRecord?.adoptionPending || expectedRecord.avdName !== room.avdName) throw new Error("the emulator assignment changed before the wipe; its data was kept");
307
+ const wiped = room.wipeDevice(room.avdName, {
308
+ wipe: true,
309
+ owner: {
310
+ projectPath: room.root,
311
+ slot: room.slot,
312
+ expectedRecord
313
+ },
314
+ workspace: room.root
315
+ });
316
+ if (wiped.status !== "torn-down") throw new Error(`could not wipe it: ${wiped.reason ?? wiped.status}`);
317
+ const rebooted = await room.rebootDevice();
318
+ if (!rebooted.ok || !rebooted.serial) throw new Error(`it did not boot after its user data was wiped: ${rebooted.reason ?? "no serial"}`);
319
+ if (room.resolveAvdSerial(room.avdName).serial !== rebooted.serial) throw new Error(`${rebooted.serial} is not running ${room.avdName} after the wipe`);
320
+ room.phase("device", `${room.avdName} (${rebooted.serial}) booted with wiped user data`);
321
+ return rebooted.serial;
322
+ }
323
+ async function installOnAdoptedAvd(room, serial, installApk) {
324
+ const neededBytes = 2 * statSync(room.apkPath).size + ADOPTED_INSTALL_HEADROOM_BYTES;
325
+ const roomy = (free) => free === null || free >= neededBytes;
326
+ const outOfSpace = (result) => Boolean(result.failed) && INSUFFICIENT_STORAGE.test(result.reason ?? "");
327
+ let target = serial;
328
+ let wiped = false;
329
+ if (!roomy(room.dataFreeBytes(target))) {
330
+ room.trimCaches(target, neededBytes);
331
+ const free = room.dataFreeBytes(target);
332
+ if (roomy(free)) room.phase("install", `trimmed app caches on ${room.avdName} to make room for the APK`);
333
+ else {
334
+ target = await wipeAdoptedAvd(room, free);
335
+ wiped = true;
336
+ }
337
+ }
338
+ let installed = installApk(target);
339
+ if (wiped || !outOfSpace(installed)) return {
340
+ serial: target,
341
+ installed
342
+ };
343
+ room.trimCaches(target, neededBytes);
344
+ installed = installApk(target);
345
+ if (!outOfSpace(installed)) return {
346
+ serial: target,
347
+ installed
348
+ };
349
+ target = await wipeAdoptedAvd(room, room.dataFreeBytes(target));
350
+ return {
351
+ serial: target,
352
+ installed: installApk(target)
353
+ };
354
+ }
355
+ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
356
+ if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
357
+ const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
358
+ if (!resolved.serial) throw new Error(`Could not verify a running owned AVD ${device.avdName} before installation.`);
359
+ if (resolved.serial !== serial) {
360
+ phase("device", `${device.avdName} changed serial (${serial} -> ${resolved.serial}); checking readiness before installation`);
361
+ const ready = await waitForDeviceBoot(resolved.serial, 6e4, { commandTimeoutMs: 5e3 });
362
+ if (!ready.ok) throw new Error(`Emulator ${resolved.serial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`);
363
+ if (resolveAvdSerial(device.avdName, { timeoutMs: 5e3 }).serial !== resolved.serial) throw new Error(`Could not verify AVD ${device.avdName} still runs on ${resolved.serial} after checking readiness.`);
364
+ phase("device", `reopen agent-device on ${resolved.serial}`);
365
+ }
366
+ const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
367
+ withConfigLock(() => {
368
+ const current = deviceSlotPlatforms(loadConfig()?.projects?.[root], slot)?.android;
369
+ if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
370
+ if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
371
+ ...current,
372
+ consolePort
373
+ }, slot);
374
+ });
375
+ return resolved.serial;
376
+ }
377
+ async function finishAndroidRun({ runtime, runtimePreparation, lease, releaseLease, root, slot, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, bootPending, apkPath, androidPackage: initialPackage, androidPackageProblem, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, readProjectPackage, packageRemedy, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, recordBuild, now, out, emit, recordRun, reclaimed, devServer, enterPhase, enterActivity, rebootDevice, dataFreeBytes = androidDataFreeBytes, trimCaches = trimAndroidCaches, wipeDevice = teardownOwnedAvd }) {
378
+ let androidPackage = initialPackage;
379
+ let leaseWarned = false;
380
+ const raiseLeaseFor = (boundMs, beforeInstall) => {
381
+ const step = lease?.raise(boundMs);
382
+ if (!step || step.ok) return null;
383
+ if (beforeInstall) {
384
+ const refusal = lostRefusal(step.holder, step.expiresAt, now());
385
+ return fail(refusal.code, refusal.message, refusal.remedy, { lease: refusal.lease });
386
+ }
387
+ if (!leaseWarned) {
388
+ leaseWarned = true;
389
+ phase("lease", chalk.yellow(lostLine(step.holder, step.expiresAt, now())));
390
+ }
391
+ return null;
392
+ };
393
+ enterPhase("device");
394
+ if (bootPending()) enterActivity("booting");
395
+ const booted = await bootPromise;
396
+ enterActivity(null);
397
+ const runCommand = nativeRunCommand("android", slot, {
398
+ physical,
399
+ deviceId: booted.serial
400
+ });
401
+ if (booted.failed) {
402
+ const diag = diagnoseBootFailure(booted, emuLog, runCommand, physical);
403
+ return fail(diag.code, diag.message, diag.remedy, {
404
+ lines: diag.lines,
405
+ logPath: diag.logPath ? displayPath(root, diag.logPath) : null
406
+ });
407
+ }
408
+ let serial;
409
+ try {
410
+ serial = await resolveInstallSerial({
411
+ root,
412
+ slot,
413
+ device,
414
+ physical,
415
+ remoteDevice,
416
+ serial: booted.serial,
417
+ resolveAvdSerial,
418
+ waitForDeviceBoot,
419
+ phase
420
+ });
421
+ } catch (error) {
422
+ return fail(NO_DEVICE, error instanceof Error ? error.message : String(error), `Run \`stim status\` to inspect the owned AVD, then retry \`${runCommand}\`; the APK was not installed.`);
423
+ }
424
+ phase("device", physical ? `${device.deviceName || serial} (${serial}) connected, not owned by Stim` : `${device.avdName || serial} (${serial}) ${device.adopted || device.adoptionPending ? "adopted" : "booted"} ${bootDuration()}`);
425
+ const packageFromApk = readApkPackage(apkPath);
426
+ if (packageFromApk && androidPackage && packageFromApk !== androidPackage) phase("install", chalk.dim(`applicationId ${packageFromApk} (from the APK; project files say ${androidPackage})`));
427
+ androidPackage = packageFromApk || androidPackage || readProjectPackage();
428
+ if (!androidPackage && androidPackageProblem) return fail(LAUNCH_FAILED, androidPackageProblem, packageRemedy, { lastBuildStatus: true });
429
+ const adopting = !physical && !remoteDevice && Boolean(device.adoptionPending);
430
+ if (adopting) {
431
+ if (!androidPackage || !device.avdName) return fail(LAUNCH_FAILED, "Cannot clean an adopted emulator without its app package and AVD name.", "Retry once the APK applicationId can be read.");
432
+ try {
433
+ await resetAdoptedAvd(device.avdName, serial, androidPackage);
434
+ } catch (error) {
435
+ return fail(LAUNCH_FAILED, `Could not clean adopted emulator ${device.avdName}: ${String(error?.message || error)}`, "Fix the device cleanup error and retry; adoption remains pending and the app was not launched.");
436
+ }
437
+ }
438
+ const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
439
+ if (lostBeforeInstall) return lostBeforeInstall;
440
+ enterPhase("install");
441
+ let installTimer = stepTimer(now);
442
+ const installApk = (target) => {
443
+ installTimer = stepTimer(now);
444
+ return install({
445
+ serial: target,
446
+ apkPath,
447
+ packageName: androidPackage,
448
+ allowUninstall: release || adopting
449
+ });
450
+ };
451
+ let installed;
452
+ if (adopting && device.avdName && apkPath) {
453
+ const room = {
454
+ root,
455
+ slot,
456
+ avdName: device.avdName,
457
+ apkPath,
458
+ phase,
459
+ rebootDevice,
460
+ dataFreeBytes,
461
+ trimCaches,
462
+ wipeDevice,
463
+ resolveAvdSerial
464
+ };
465
+ try {
466
+ ({serial, installed} = await installOnAdoptedAvd(room, serial, installApk));
467
+ } catch (error) {
468
+ return fail(INSTALL_FAILED, `Could not make room on adopted emulator ${device.avdName}: ${String(error?.message || error)}`, `Run \`${runCommand}\` again; adoption remains pending, so it retries the cleanup.`);
469
+ }
470
+ } else installed = installApk(serial);
471
+ if (installed.failed) {
472
+ const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
473
+ reason: installed.reason,
474
+ packageName: androidPackage,
475
+ serial,
476
+ useBuildCache
477
+ });
478
+ return fail(installed.code || "STIM_INSTALL_FAILED", installed.reason, installRemedy, { lastBuildStatus: true });
479
+ }
480
+ if (adopting) try {
481
+ withConfigLock(() => {
482
+ const config = loadConfig();
483
+ const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
484
+ if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
485
+ delete current.adoptionPending;
486
+ saveConfig(config);
487
+ });
488
+ } catch (error) {
489
+ return fail(LAUNCH_FAILED, `Could not finish adopting emulator ${device.avdName}: ${String(error?.message || error)}`, "Retry to reconcile the emulator assignment; the app was not launched.");
490
+ }
491
+ const installSkipped = Boolean(installed.skipped);
492
+ phase("install", installSkipped ? `unchanged (${serial} already has this build) ${installTimer()}` : `${basename(apkPath)} -> ${serial} ${installTimer()}`);
493
+ if (installed.note) {
494
+ phase("install", chalk.yellow(installed.note));
495
+ writer.write({
496
+ src: "build",
497
+ level: "warn",
498
+ event: "install_uninstalled_first",
499
+ msg: installed.note
500
+ });
501
+ }
502
+ if (androidPackage) upsertProject(root, { androidPackage });
503
+ record.bundleId = androidPackage;
504
+ if (!androidPackage) return fail(LAUNCH_FAILED, "Could not determine this app's Android package name, so there is nothing to launch.", packageRemedy, { lastBuildStatus: true });
505
+ if (physical) raiseLeaseFor(0, false);
506
+ const scheme = runtime.scheme(root, apkPath, resolveDevClientScheme);
507
+ enterPhase("launch");
508
+ const launchTimer = stepTimer(now);
509
+ const launchedAt = now();
510
+ writer.write({
511
+ src: "build",
512
+ level: "info",
513
+ marker: true,
514
+ event: "launch_attempt",
515
+ ts: launchedAt,
516
+ platform: "android",
517
+ appId: androidPackage,
518
+ deviceId: serial,
519
+ remote: Boolean(remoteDevice),
520
+ physical,
521
+ msg: `launching ${androidPackage} on ${serial}`
522
+ });
523
+ const launched = await runtime.launch(runtimePreparation, {
524
+ serial,
525
+ androidPackage,
526
+ scheme,
527
+ physical,
528
+ remoteDevice,
529
+ launch,
530
+ launchRelease
531
+ });
532
+ if (launched.failed) {
533
+ printNativeCrashReport({
534
+ root,
535
+ slot,
536
+ platform: "android",
537
+ deviceId: serial,
538
+ appId: androidPackage,
539
+ since: launchedAt
540
+ }, logsDir, (line) => phase("launch", chalk.red(line)), Boolean(remoteDevice));
541
+ return fail(launched.code || "STIM_LAUNCH_FAILED", launched.reason, launchRemedy(remoteDevice, serial, androidPackage), { lastBuildStatus: true });
542
+ }
543
+ writer.write({
544
+ src: "build",
545
+ level: "info",
546
+ event: "app_launched",
547
+ msg: (runtime.kind === "process" ? `launched ${androidPackage} on ${serial} (${variant ?? "debug"}, native process, no Metro)` : runtime.kind === "embedded-js" ? `launched ${androidPackage} on ${serial} (${variant}, embedded JS bundle, no Metro)` : `launched ${androidPackage} on ${serial} against Metro port ${metroPort}`) + restartedAppNote(launched.restartedPid, "; ")
548
+ });
549
+ phase("launch", `${androidPackage}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
550
+ if (!physical && !remoteDevice) try {
551
+ writeLaunch(root, "android", {
552
+ appId: androidPackage,
553
+ deviceId: serial,
554
+ metroPort,
555
+ release,
556
+ ...runtime.kind === "process" ? { runtime: "process" } : {},
557
+ launchedAt: new Date(launchedAt).toISOString()
558
+ });
559
+ } catch (error) {
560
+ out(phaseLine("state", chalk.yellow(`could not record Android launch: ${error?.message || error}`)));
561
+ }
562
+ if (runtime.kind === "metro") reportMetroRoute({
563
+ launched,
564
+ remote: Boolean(remoteDevice),
565
+ metroPort,
566
+ androidPackage,
567
+ serial,
568
+ phase,
569
+ writer
570
+ });
571
+ if (launched.devClientNote) {
572
+ phase("metro", chalk.yellow(launched.devClientNote));
573
+ writer.write({
574
+ src: "build",
575
+ level: "warn",
576
+ event: "dev_client_link_failed",
577
+ msg: launched.devClientNote
578
+ });
579
+ }
580
+ const uploadWasAbandoned = await finishAndroidUpload(uploadPending, remote, phase);
581
+ const providerOutcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
582
+ if (providerOutcome) phase("cache", providerOutcome.warn ? chalk.yellow(providerOutcome.line) : providerOutcome.line);
583
+ persistLastBuild({
584
+ recordBuild,
585
+ root,
586
+ record,
587
+ startedAt,
588
+ durationMs: now() - started,
589
+ status: "ok",
590
+ out
591
+ });
592
+ const remoteRelease = Boolean(remoteDevice && release);
593
+ if (remoteDevice) {
594
+ killPreviousCollector(root, {
595
+ slot,
596
+ kill,
597
+ isAlive: pidAlive,
598
+ verify: verifyCollector,
599
+ note: (line) => out(phaseLine("logs", line))
600
+ });
601
+ writer.write({
602
+ src: "build",
603
+ level: "info",
604
+ event: "collector_skipped",
605
+ msg: `remote session ${serial}: device logs come from agent-device/EAS, so no adb logcat collector is attached`
606
+ });
607
+ } else {
608
+ if (physical) raiseLeaseFor(0, false);
609
+ const collectorPid = await startCollector({
610
+ root,
611
+ slot,
612
+ serial,
613
+ packageName: androidPackage,
614
+ spawn,
615
+ kill,
616
+ alive: pidAlive,
617
+ verify: verifyCollector,
618
+ out
619
+ });
620
+ phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
621
+ }
622
+ if (physical) raiseLeaseFor(runtime.verificationWaitMs, false);
623
+ const { state: launchState, warning: launchWarning, unattributed } = await runtime.verify(runtimePreparation, {
624
+ root,
625
+ slot,
626
+ remoteDevice: Boolean(remoteDevice),
627
+ verifyReleaseLaunched,
628
+ verifyLaunched,
629
+ serial,
630
+ androidPackage,
631
+ variant,
632
+ metroCheck,
633
+ logsDir,
634
+ launchedAt,
635
+ metroPort,
636
+ isExpo,
637
+ physical,
638
+ device,
639
+ scheme,
640
+ component: launched.component ?? null,
641
+ phase,
642
+ enterActivity
643
+ });
644
+ if (launchState === "fatal") return fail(LAUNCH_FAILED, "The app failed its launch readiness check.", `Read the launch error above or run \`stim logs --errors\`. The full timeline is in ${logsDir}.`, {
645
+ lastBuildStatus: true,
646
+ logPath: displayPath(root, logsDir)
647
+ });
648
+ writer.write(androidLaunchOutcomeRecord(runtime.kind, {
649
+ launchState,
650
+ bundleId: androidPackage,
651
+ configuration: variant,
652
+ metroPort,
653
+ unattributed
654
+ }));
655
+ const leaseFacts = lease?.facts() ?? null;
656
+ releaseLease();
657
+ const facts = reportAndroidResult({
658
+ root,
659
+ slot,
660
+ json,
661
+ useBuildCache,
662
+ variant,
663
+ release,
664
+ runtimeKind: runtime.kind,
665
+ metroCheck,
666
+ metroPort,
667
+ logsDir: remoteRelease ? null : logsDir,
668
+ serial,
669
+ apkPath,
670
+ androidPackage,
671
+ installSkipped,
672
+ record,
673
+ waitedForBuild,
674
+ remote,
675
+ providerName,
676
+ launchState,
677
+ launchWarning,
678
+ launched,
679
+ ccache,
680
+ durationMs: now() - started,
681
+ writer,
682
+ emit,
683
+ lease: physical ? leaseFacts : void 0,
684
+ recordRun,
685
+ reclaimed,
686
+ devServer,
687
+ links: workspaceLinks(root, {
688
+ platform: "android",
689
+ slot
690
+ })
691
+ });
692
+ if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
693
+ return {
694
+ ok: true,
695
+ facts
696
+ };
697
+ }
698
+ function localInstallRemedy({ reason, packageName, serial, useBuildCache }) {
699
+ const conflict = installConflictKind(reason);
700
+ if (!conflict) return `Check that ${serial} is still connected (\`adb devices\`) and has room for the APK.`;
701
+ const rerun = useBuildCache ? " Then run this command again; it installs the APK from cache without building it again." : " Then run this command again.";
702
+ return `${packageName} is already installed on ${serial} ` + (conflict === "signature" ? "with a different signer" : "at a higher versionCode") + `. Uninstall it first (\`adb -s ${serial} uninstall ${packageName}\`); its data goes with it.` + rerun;
703
+ }
704
+ function launchRemedy(remoteDevice, serial, packageName) {
705
+ return remoteDevice?.failureRemedy() ?? `Check the app installed correctly (\`adb -s ${serial} shell pm list packages ${packageName}\`).`;
706
+ }
707
+ function reportMetroRoute({ launched, remote, metroPort, androidPackage, serial, phase, writer }) {
708
+ if (remote) {
709
+ phase("metro", `public origin ${launched.jsLocation}`);
710
+ return;
711
+ }
712
+ const reversedSummary = (launched.reversed ?? []).join(", ") || `tcp:${metroPort}->tcp:${metroPort}`;
713
+ if (launched.debugHttpHost) {
714
+ phase("metro", `debug_http_host ${launched.debugHttpHost} + adb reverse ${reversedSummary}`);
715
+ return;
716
+ }
717
+ phase("metro", chalk.yellow(`adb reverse ${reversedSummary}; ${launched.debugHttpHostNote || "debug_http_host not written"}`));
718
+ writer.write({
719
+ src: "build",
720
+ level: "warn",
721
+ event: "debug_http_host_failed",
722
+ msg: `debug_http_host was not written for ${androidPackage} on ${serial}: ${launched.debugHttpHostNote || "unknown reason"}`
723
+ });
724
+ }
725
+ function diagnoseBootFailure(booted, logFile, runCommand, physical) {
726
+ if (booted.code) return {
727
+ code: booted.code,
728
+ message: booted.reason ?? "The emulator did not boot.",
729
+ remedy: booted.remedy ?? null,
730
+ lines: [],
731
+ logPath: null
732
+ };
733
+ const diag = noDeviceDiagnostic({
734
+ reason: booted.reason ?? "The emulator did not boot.",
735
+ logFile,
736
+ remedy: booted.remedy ?? `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
737
+ localEmulator: !physical
738
+ });
739
+ return {
740
+ code: NO_DEVICE,
741
+ ...diag
742
+ };
743
+ }
744
+ //#endregion
745
+ export { killPreviousCollector as a, collectorLogFile as i, androidProcessRuntime as n, startCollector as o, finishAndroidRun as r, androidMetroRuntime as t };