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
@@ -1,3732 +0,0 @@
1
- import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
3
- import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
4
- import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
5
- import { _ as stepTimer, c as launchErrorReport, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-Bt8ygHE7.mjs";
6
- import { C as saveConfig, D as withConfigLock$1, E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, V as parseDeviceSlotOption, W as validateDeviceSlot, _ as loadConfig$1, j as formatBytes, p as getProject, u as getConcurrencyLimits, w as setDevice, z as deviceSlotPlatforms } from "./config-DC5jEtd1.mjs";
7
- import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, l as emulatorLogFile, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-CR9X86xJ.mjs";
8
- import { M as writeWorkspaceLaunch, b as MODE_EXPO, c as projectShortcut, p as detectIsExpo, r as findProjectRoot, s as projectProblem, t as NO_PROJECT_REFUSAL, y as MODE_BARE } from "./project-B5eQ1USs.mjs";
9
- import { A as publicUrlSetting, B as settingShapeErrors, C as metroWarmupUrlSetting, Ct as removeTemporaryEntry, Dt as optimizationBuildProfile, Et as compilerCacheFallbackMessage, F as resolveCacheProviderConfig, H as settingsLayers, I as resolveSettings, L as settingFile, M as remoteAndroidSetting, N as remoteEasFallbackSetting, Ot as resolveOptimizations, St as makeTemporaryDirectory, T as parseIosRemote, Tt as artifactCachePolicy, U as tunnelModeSetting, W as unknownSettingKeys, i as SETTING_SHAPE_REMEDY, l as androidDataPartitionSizeGbSettingError, mt as repoRoot, nt as gitCommonDir, o as androidAvdConfigSettingError, q as detectProviders, u as cacheProviderSettingError, x as metroPortSetting, z as settingOriginScope } from "./settings-CxgngUZk.mjs";
10
- import { d as signalProcessTree, s as pidExists, u as resolveProjectMetro } from "./metro-DI2AjPF_.mjs";
11
- import { F as recordFinishedBuild, I as startBuildProgress, L as tapBuildLog, O as NO_BUILD_PROGRESS, R as createRunRecorder, U as recordRunStats, V as recordCapacityRefusal, W as statsProjectKey$1, t as budgetGate, z as readRunEstimates } from "./budget-Dz-kyXLv.mjs";
12
- import { D as teardownOwnedAvd, M as acquireBuildSlot, P as releaseBuildSlot } from "./workspaces-Djb2gfi9.mjs";
13
- import { A as providerDownloadPath, B as captureAssetManifest, D as fingerprintProject, E as fingerprintDiffRecord, F as storedAssetManifest, H as readAssetManifest, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, V as compareAssetManifests, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine, z as assetDiffReason } from "./device-lease-Cu7PtQpZ.mjs";
14
- import { $ as trimAndroidCaches, A as listAvds, B as ownedAvdSystemImage, D as hostSystemImageArch, F as ownedAvdDeviceProfile, G as probeEmulatorSerial, I as ownedAvdDirectory, J as resolveOwnedAvdSerial, K as profileNeedsFoldFeature, O as listAdbDevices, Q as systemImageSupportsFold, T as findBuildTool, U as physicalDeviceModel, W as pickDefaultSystemImage, Y as resolvePhysicalDevice, a as androidDeviceAbi, ft as parkedMaxSetting, i as androidDataFreeBytes, j as listInstalledSystemImages, k as listAvdDeviceProfiles, l as androidSystemImageAbi, q as resetAdoptedAvd, r as androidBuildToolName, rt as configuredAndroidEmulatorApp, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-BdQC6zs1.mjs";
15
- import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-C65jk_q2.mjs";
16
- import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
17
- import { A as resolveBuildPlacement, C as setRemoteLogSink, D as parseBuildMachineOption, E as namedBuildMachine, O as requireLocalBuild, S as withNativeBuildRun, a as offloadBuild, b as runCancellation, c as placementLoad, i as closeOffload, l as remotePhaseText, n as buildPlacementCandidates, p as cancelledFailure, r as chooseBuildMachine, s as offloadPlacement, w as OffloadRefusal, x as runCancellationSignal } from "./client-CuUo8Nvg.mjs";
18
- import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity, t as DEFAULT_DEVICE_SLOT_WAIT_MS } from "./device-capacity-BoN1jQkO.mjs";
19
- import { i as ccacheActivityLine, n as CCACHE_NOT_RUN, r as CCACHE_UNAVAILABLE, s as resolveCcache } from "./manifest-CX0d1IkZ.mjs";
20
- import { E as startBuildHeartbeat, O as tailLines, h as HEARTBEAT_INTERVAL_MS, n as androidToolchain, t as androidRequirements } from "./toolchain-C3xSoyUl.mjs";
21
- import { t as cleanLine } from "./server-expo-n3tUi3Em.mjs";
22
- import { r as hostedMetroSettings } from "./metro-gateway-BxpJLIwh.mjs";
23
- import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-By0XuLTQ.mjs";
24
- import { _ as devicePlacementLine, a as UPDATES_MANIFEST_NAME, d as ensureOwnedDevice, f as AvdBootError, g as automaticDevicePlacement, h as checkEasFallback, i as skippedMissReason, l as didSetUpDevice, m as warmMetro, n as fingerprintErrorMissReason, o as detectEntryFile, p as AvdRecoveryError, s as refreshUpdatesManifestFile, t as explainBuildMiss, u as ensureBooted } from "./miss-reason-CjQCOKqq.mjs";
25
- import { n as devicePlacementRecord, t as buildPlacementRecord } from "./placement-log-Dxe34rJm.mjs";
26
- import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-Bs7fytj7.mjs";
27
- import { S as restartedAppNote, b as launchAndroidReleaseApp, c as androidDevClientUrl, g as installConflictKind, h as installAndroidApp, r as DEFAULT_METRO_PORT, s as androidAppProcess, t as ADB_INSTALL_TIMEOUT_MS, y as launchAndroidApp } from "./app-install-CFI4ptUL.mjs";
28
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-HV_KtPIw.mjs";
29
- import { a as VERIFY_TIMEOUT_MS, d as verifyAndroidReleaseLaunch, f as verifyLaunch, 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-DCveSE6d.mjs";
30
- import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-N3hAEaAL.mjs";
31
- import { C as resolveSystemImage, S as resolveDeviceProfile, T as systemImageRefusal, _ as isReleaseVariant, a as PLATFORM, b as pooledAndroidDevice, d as apkPackage, f as deviceProfileRefusal, g as foldableImageRefusal, i as NO_FINGERPRINT, m as dumpApkManifest, n as LAUNCH_FAILED, o as androidBuildOptions, p as displayPath, r as NO_DEVICE, s as androidDevClientScheme, t as INSTALL_FAILED, v as noDeviceDiagnostic, w as resolveVariant, x as remoteAvdFlagRefusal } from "./support-DsNaFg6D.mjs";
32
- import { d as productFlavorRefusal, f as readProductFlavors, h as formatDiagnostic, o as buildAndroid } from "./device-ios-B2ruohE-.mjs";
33
- import { T as ownedSessionName, a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext } from "./device-remote-B5GiUifk.mjs";
34
- import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-D9LkJxIO.mjs";
35
- import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-DtxthMfI.mjs";
36
- import { a as acquireRunLease, c as lostRefusal, d as runLease, f as waitFlagConflict, l as parseDeviceWait, o as leaseExpiryText, s as lostLine, t as DEBUG_VERIFY_STEP_MS, u as releaseLeaseOnSignal } from "./device-lease-run-D38SnZtt.mjs";
37
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-EB0ySdGK.mjs";
38
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-BGxsbfny.mjs";
39
- import { t as selectFromPool } from "./device-pool-CP4rAsHx.mjs";
40
- import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-BKJ3wJI1.mjs";
41
- import { a as printPlan, i as planPayload, n as planCachedBuild, o as refusePlan, r as planFlagRefusal, s as runArtifactLifecycle, t as planHostedDevice } from "./plan-placement-BVY59qa8.mjs";
42
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics--v5HGOU0.mjs";
43
- import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-C87geAg-.mjs";
44
- import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
45
- import { basename, dirname, isAbsolute, join } from "node:path";
46
- import { buildDiagnostics, hostedAndroidStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
47
- import chalk from "chalk";
48
- import { InvalidArgumentError } from "commander";
49
- import { workspaceId } from "@stim-cli/core";
50
- import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
51
- //#region src/commands/android/remote.ts
52
- async function connectAndroidHosting({ root, slot, machine, release, metroCheck, selectors, read = readHostedAndroid, resolveSerial = resolveOwnedAvdSerial, prepare = prepareHostedAndroid }) {
53
- let recorded;
54
- try {
55
- recorded = read(root, slot)[slot];
56
- } catch (error) {
57
- return { failure: {
58
- code: "STIM_HOSTING_REFUSED",
59
- message: error.message,
60
- remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
61
- } };
62
- }
63
- if (recorded && recorded.machine !== machine) return { failure: {
64
- code: "STIM_BAD_ARG",
65
- message: `This workspace's Android emulator runs on ${recorded.machine}; run stim stop first.`
66
- } };
67
- if (!machine) return { target: null };
68
- if (!release && !metroCheck) return { failure: {
69
- code: "STIM_BAD_ARG",
70
- message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
71
- remedy: "Run stim stop; stim start, then retry without --no-metro-check."
72
- } };
73
- const local = deviceSlotPlatforms(getProject(root), slot)?.android;
74
- if (local?.owned && local.avdName) {
75
- let running = true;
76
- try {
77
- const resolved = resolveSerial(local.avdName, { timeoutMs: 5e3 });
78
- running = Boolean(resolved.serial) || !resolved.notRunning && !resolved.missing;
79
- } catch {}
80
- if (running) return { failure: {
81
- code: "STIM_BAD_ARG",
82
- message: `This workspace's Android emulator for slot ${slot} runs on this Mac; run stim stop first.`
83
- } };
84
- }
85
- try {
86
- return { target: await prepare(machine, selectors, recorded) };
87
- } catch (error) {
88
- return { failure: {
89
- code: error.code ?? "STIM_HOSTING_REFUSED",
90
- message: error.message,
91
- ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
92
- } };
93
- }
94
- }
95
- async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, eas, ...args }) {
96
- const selectors = selected.kind === "hosted" ? {
97
- ...selected.systemImage ? { systemImage: selected.systemImage } : {},
98
- ...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
99
- } : {};
100
- if (selected.kind !== "hosted" || selected.machine !== "auto") {
101
- const connected = await connectAndroidHosting({
102
- ...args,
103
- selectors,
104
- machine: selected.kind === "hosted" ? selected.machine : null
105
- });
106
- if ("failure" in connected) return connected;
107
- return {
108
- target: selected,
109
- hostedTarget: connected.target,
110
- selectors,
111
- budget: connected.target ? {
112
- reclaimed: [],
113
- refusal: null
114
- } : await checkBudget({
115
- root: args.root,
116
- note
117
- })
118
- };
119
- }
120
- const refusal = localSelectors(selected);
121
- if (refusal) return { failure: refusal };
122
- try {
123
- const placed = await automatic({
124
- root: args.root,
125
- slot: args.slot,
126
- platform: "android",
127
- selectors,
128
- buildMachine,
129
- noWait,
130
- ...eas ? { eas } : {}
131
- });
132
- phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
133
- log?.(devicePlacementRecord({
134
- platform: "android",
135
- fromFlag,
136
- easFallback: Boolean(eas),
137
- placed
138
- }));
139
- if (placed.placement.decision === "eas") return {
140
- target: {
141
- kind: "remote",
142
- backend: "eas",
143
- systemImage: selected.systemImage,
144
- deviceProfile: selected.deviceProfile
145
- },
146
- hostedTarget: null,
147
- selectors,
148
- budget: await checkBudget({
149
- root: args.root,
150
- note
151
- }),
152
- devicePlacement: placed.placement
153
- };
154
- if (placed.target && !args.release && !args.metroCheck) return { failure: {
155
- code: "STIM_BAD_ARG",
156
- message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
157
- remedy: "Run stim stop; stim start, then retry without --no-metro-check."
158
- } };
159
- return {
160
- target: placed.target ? {
161
- ...selected,
162
- machine: placed.target.host.machine
163
- } : {
164
- kind: "emulator",
165
- systemImage: selected.systemImage,
166
- deviceProfile: selected.deviceProfile
167
- },
168
- hostedTarget: placed.target,
169
- selectors,
170
- budget: placed.target ? {
171
- reclaimed: [],
172
- refusal: null
173
- } : await checkBudget({
174
- root: args.root,
175
- note
176
- }),
177
- devicePlacement: placed.placement
178
- };
179
- } catch (error) {
180
- return { failure: {
181
- code: error.code ?? "STIM_HOSTING_REFUSED",
182
- message: error.message,
183
- ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
184
- } };
185
- }
186
- }
187
- //#endregion
188
- //#region src/commands/android/result.ts
189
- function androidFacts({ root, slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null, host }) {
190
- return {
191
- platform: PLATFORM,
192
- ...host ? { host } : {},
193
- ...slot && slot !== "default" ? { slot } : {},
194
- serial: serial ?? null,
195
- avdName: avdName ?? null,
196
- deviceName: deviceName ?? avdName ?? null,
197
- systemImage: systemImage ?? null,
198
- deviceProfile: deviceProfile ?? null,
199
- fingerprint: fingerprint ?? null,
200
- cacheKey: cacheKey ?? null,
201
- variant: variant ?? null,
202
- metroPort: metroPort ?? null,
203
- cacheHit: cacheLevel(cacheHit),
204
- cacheSkipped: Boolean(cacheSkipped),
205
- buildMachine,
206
- ...builtOn ? { builtOn } : {},
207
- ...offloadedTo ? { offloadedTo } : {},
208
- ...offloadFallback ? { offloadFallback } : {},
209
- waitedForBuild: waitedForBuild ? {
210
- pid: waitedForBuild.pid ?? null,
211
- ms: waitedForBuild.ms ?? 0
212
- } : null,
213
- appPath: appPath ?? null,
214
- bundleId: bundleId ?? null,
215
- installSkipped: Boolean(installSkipped),
216
- launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
217
- ccache,
218
- debugHttpHost: debugHttpHost ?? null,
219
- debugHttpHostNote: debugHttpHostNote ?? null,
220
- devClientUrl: devClientUrl ?? null,
221
- logs: logs ?? null,
222
- ...root && !host ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
223
- durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
224
- ...lease === void 0 ? {} : { lease },
225
- ...devServer ? { devServer } : {}
226
- };
227
- }
228
- function lastBuildRecord({ devicePlacement, fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
229
- const record = {
230
- platform: PLATFORM,
231
- avdName: avdName ?? null,
232
- deviceName: deviceName ?? avdName ?? null,
233
- fingerprint: fingerprint ?? null,
234
- cacheKey: cacheKey ?? null,
235
- cacheHit: cacheLevel(cacheHit),
236
- cacheSkipped: Boolean(cacheSkipped),
237
- durationMs: Number.isFinite(durationMs) ? durationMs : null,
238
- appPath: appPath ?? null,
239
- bundleId: bundleId ?? null,
240
- startedAt,
241
- status,
242
- configuration: configuration ?? null
243
- };
244
- if (errorCode) record.errorCode = errorCode;
245
- if (devicePlacement) record.devicePlacement = devicePlacement;
246
- record.buildMachine = buildMachine;
247
- if (builtOn) record.builtOn = builtOn;
248
- if (offloadedTo) record.offloadedTo = offloadedTo;
249
- if (offloadFallback) record.offloadFallback = offloadFallback;
250
- if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
251
- const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
252
- if (recorded.length) record.diagnostics = recorded;
253
- return record;
254
- }
255
- async function finishAndroidUpload(uploadPending, remote, phase) {
256
- if (!uploadPending) return false;
257
- const upload = await uploadPending;
258
- if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
259
- else if (upload?.timedOut) {
260
- phase("cache", chalk.yellow(`${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`));
261
- return true;
262
- } else if (upload?.failed) {
263
- const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
264
- code: "logged-out",
265
- reason: upload.failed,
266
- phase: "upload"
267
- }) : null;
268
- phase("cache", chalk.yellow(authNote || `${remote?.name} upload failed: ${upload.failed}`));
269
- }
270
- return false;
271
- }
272
- function reportAndroidResult({ host, root, slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null, links }) {
273
- const offloadedTo = record.offloadedTo ?? null;
274
- recordRun({
275
- failed: false,
276
- cacheHit: cacheLevel(record.cacheHit),
277
- waited: waitedForBuild,
278
- durationMs,
279
- offloadedTo
280
- });
281
- const facts = androidFacts({
282
- host,
283
- root,
284
- slot,
285
- serial,
286
- avdName: record.avdName,
287
- deviceName: record.deviceName,
288
- systemImage: record.systemImage,
289
- deviceProfile: record.deviceProfile,
290
- debugHttpHost: launched.debugHttpHost ?? null,
291
- debugHttpHostNote: launched.debugHttpHostNote ?? null,
292
- devClientUrl: launched.devClientUrl ?? null,
293
- fingerprint: record.fingerprint,
294
- cacheKey: record.cacheKey,
295
- variant,
296
- metroPort,
297
- cacheHit: record.cacheHit,
298
- cacheSkipped: !useBuildCache,
299
- waitedForBuild,
300
- appPath: apkPath,
301
- bundleId: androidPackage,
302
- installSkipped,
303
- launched: launchState,
304
- ccache,
305
- logs: logsDir,
306
- durationMs,
307
- lease,
308
- devServer,
309
- buildMachine: record.buildMachine,
310
- builtOn: record.builtOn ?? void 0,
311
- offloadedTo,
312
- offloadFallback: record.offloadFallback ?? null
313
- });
314
- writer.close();
315
- if (record.devicePlacement) facts.devicePlacement = record.devicePlacement;
316
- if (json) emit(JSON.stringify({
317
- ...facts,
318
- ...links ? { links } : {},
319
- ...reclaimed.length ? { reclaimed } : {}
320
- }));
321
- else {
322
- const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${host ? `${host.device?.name ?? "Android emulator"} on ${host.machine}` : serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo)})`;
323
- const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
324
- const deviceName = record.avdName || record.deviceName || serial;
325
- const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
326
- const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
327
- emit([
328
- outcome,
329
- phaseLine("device", host ? `${deviceName} on ${host.machine}` : `${deviceName} (${serial})`),
330
- phaseLine("app", androidPackage),
331
- phaseLine("metro", metroResult),
332
- phaseLine("cache", cacheResult),
333
- ...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
334
- phaseLine("logs", logsDir || "unavailable (remote device)"),
335
- ...!host ? [phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)] : []
336
- ].join("\n"));
337
- if (links) console.error(chalk.dim(workspaceLinkLine(links)));
338
- }
339
- return facts;
340
- }
341
- function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
342
- if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
343
- if (cacheHit === "local") return "cache hit";
344
- return offloadedTo ? `built on ${offloadedTo}` : "built";
345
- }
346
- function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
347
- const lastBuild = lastBuildRecord({
348
- ...record,
349
- startedAt,
350
- durationMs,
351
- status,
352
- errorCode,
353
- diagnostics
354
- });
355
- try {
356
- recordBuild(root, lastBuild);
357
- } catch (err) {
358
- out(phaseLine("state", chalk.yellow(`could not record lastBuild: ${err?.message || err}`)));
359
- }
360
- return lastBuild;
361
- }
362
- //#endregion
363
- //#region src/commands/android/hosted.ts
364
- async function finishHostedAndroidRun({ target, artifact, root, slot = "default", release, isExpo, metroPort, logsDir, selectors, place, writePlacement, verifyLaunch: verify, writeLaunch, readApkPackage, resolveDevClientScheme, now, started, startedAt, fail, phase, enterPhase, out, recordBuild, ...report }) {
365
- const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
366
- if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
367
- const launchedAt = now();
368
- try {
369
- const run = await place(target, {
370
- root,
371
- slot,
372
- bundle: artifact.apkPath,
373
- handoff: artifact.handoff,
374
- bundleId: packageName,
375
- release,
376
- metroPort,
377
- selectors,
378
- ...!release && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
379
- reserved: (placement) => writePlacement(root, slot, placement),
380
- note: out,
381
- enterPhase
382
- });
383
- writePlacement(root, slot, run.placement);
384
- writeLaunch(root, "android", {
385
- appId: packageName,
386
- deviceId: run.placement.session,
387
- metroPort,
388
- release,
389
- launchedAt: new Date(launchedAt).toISOString()
390
- });
391
- let launched = run.launched;
392
- if (!release) {
393
- const evidence = await verify({
394
- requireBundleResponse: true,
395
- platform: "android",
396
- logsDir: logsDir ?? void 0,
397
- since: launchedAt,
398
- metroPort,
399
- mode: isExpo ? MODE_EXPO : MODE_BARE,
400
- slot: launchSlotScope(root, slot),
401
- platformShared: siblingPlatformSlots(root, "android", slot).length > 0
402
- });
403
- if (evidence.fatal) return fail("STIM_LAUNCH_FAILED", "Metro could not deliver the hosted app bundle.", "Run stim logs --errors and fix the bundle error.");
404
- launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
405
- }
406
- if (launched === "unverified") out(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim android --remote ${target.host.machine}.`);
407
- const host = hostedAndroidStatus(run.placement);
408
- report.record.deviceName = host.device?.name ?? null;
409
- report.record.bundleId = packageName;
410
- persistLastBuild({
411
- recordBuild,
412
- root,
413
- record: report.record,
414
- startedAt,
415
- durationMs: now() - started,
416
- status: "success",
417
- out
418
- });
419
- await finishAndroidUpload(artifact.uploadPending, artifact.remote, phase);
420
- await artifact.providerUpload;
421
- return {
422
- ok: true,
423
- facts: reportAndroidResult({
424
- ...report,
425
- root,
426
- slot,
427
- release,
428
- metroPort,
429
- logsDir,
430
- host,
431
- serial: null,
432
- apkPath: artifact.apkPath,
433
- androidPackage: packageName,
434
- installSkipped: false,
435
- waitedForBuild: artifact.waitedForBuild,
436
- remote: artifact.remote,
437
- providerName: artifact.providerName,
438
- launchState: launched,
439
- launched: {},
440
- ccache: artifact.ccache,
441
- durationMs: now() - started
442
- })
443
- };
444
- } catch (error) {
445
- return fail(error.code ?? "STIM_HOSTING_REFUSED", error.message, `Retry stim android --remote ${target.host.machine}, or run stim stop to reconcile the recorded placement.`);
446
- } finally {
447
- if (artifact.swapDir) rmSync(artifact.swapDir, {
448
- recursive: true,
449
- force: true
450
- });
451
- }
452
- }
453
- //#endregion
454
- //#region src/engine/apk-swap.ts
455
- const ANDROID_BUNDLE_NAME = "index.android.bundle";
456
- const LAST_LINES = 5;
457
- function hermesEnabledFromGradleProperties(text) {
458
- if (typeof text !== "string") return true;
459
- let value = null;
460
- for (const raw of text.split("\n")) {
461
- const line = raw.trim();
462
- if (line === "" || line.startsWith("#") || line.startsWith("!")) continue;
463
- const match = /^hermesEnabled\s*[=:]\s*(.*)$/.exec(line);
464
- if (match) value = match[1].trim();
465
- }
466
- return value === null || value.toLowerCase() !== "false";
467
- }
468
- function readAndroidHermesEnabled(root) {
469
- try {
470
- return hermesEnabledFromGradleProperties(readFileSync(join(root, "android", "gradle.properties"), "utf-8"));
471
- } catch {
472
- return true;
473
- }
474
- }
475
- function hermescBinDir(platform = process.platform) {
476
- if (platform === "darwin") return "osx-bin";
477
- return platform === "win32" ? "win64-bin" : "linux64-bin";
478
- }
479
- function hermescName(platform) {
480
- return platform === "win32" ? "hermesc.exe" : "hermesc";
481
- }
482
- function hermescCandidates(root, { platform = process.platform, reactNativePath = null } = {}) {
483
- const bin = hermescBinDir(platform);
484
- const name = hermescName(platform);
485
- const rn = reactNativePath ?? join(root, "node_modules", "react-native");
486
- return [.../* @__PURE__ */ new Set([
487
- join(dirname(rn), "hermes-compiler", "hermesc", bin, name),
488
- join(root, "node_modules", "hermes-compiler", "hermesc", bin, name),
489
- join(root, "node_modules", "react-native", "sdks", "hermesc", bin, name),
490
- join(root, "node_modules", "react-native", "sdks", "hermes", "build", "bin", name)
491
- ])];
492
- }
493
- function androidHermescPath(root, { exists = existsSync, platform = process.platform, reactNativePath = null } = {}) {
494
- const candidates = hermescCandidates(root, {
495
- platform,
496
- reactNativePath
497
- });
498
- return candidates.find(exists) ?? candidates[candidates.length - 1];
499
- }
500
- function androidHermescArgs({ bundle, out }) {
501
- return [
502
- "-emit-binary",
503
- "-O",
504
- "-w",
505
- "-out",
506
- out,
507
- bundle
508
- ];
509
- }
510
- function androidBundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
511
- if (isExpo) return {
512
- file: "npx",
513
- args: [
514
- "expo",
515
- "export:embed",
516
- "--platform",
517
- "android",
518
- "--dev",
519
- "false",
520
- "--bundle-output",
521
- bundleOutput,
522
- "--assets-dest",
523
- assetsDest
524
- ]
525
- };
526
- return {
527
- file: "npx",
528
- args: [
529
- "react-native",
530
- "bundle",
531
- "--platform",
532
- "android",
533
- "--dev",
534
- "false",
535
- "--entry-file",
536
- entryFile,
537
- "--bundle-output",
538
- bundleOutput,
539
- "--assets-dest",
540
- assetsDest
541
- ]
542
- };
543
- }
544
- function jarPath({ javaHome = process.env.JAVA_HOME } = {}) {
545
- return javaHome ? join(javaHome, "bin", "jar") : "jar";
546
- }
547
- function zipalignArgs({ buildToolsMajor, input, output }) {
548
- return [
549
- ...buildToolsMajor >= 35 ? ["-P", "16"] : ["-p"],
550
- "-f",
551
- "-v",
552
- "4",
553
- input,
554
- output
555
- ];
556
- }
557
- function keystorePassArg(value) {
558
- const text = typeof value === "string" ? value.trim() : "";
559
- if (text === "") return "pass:android";
560
- if (/^(?:pass|file|env):/.test(text) || text === "stdin") return text;
561
- return `pass:${text}`;
562
- }
563
- function resolveKeystore(root, settings) {
564
- const android = settings?.["android"];
565
- const bag = android && typeof android === "object" && !Array.isArray(android) ? android : {};
566
- const configured = bag["keystore"];
567
- return {
568
- path: typeof configured === "string" && configured.trim() !== "" ? isAbsolute(configured.trim()) ? configured.trim() : join(root, configured.trim()) : join(root, "android", "app", "debug.keystore"),
569
- pass: keystorePassArg(bag["keystorePassword"])
570
- };
571
- }
572
- function apksignerArgs({ keystore, apkPath }) {
573
- return [
574
- "sign",
575
- "--ks",
576
- keystore.path,
577
- "--ks-pass",
578
- keystore.pass,
579
- apkPath
580
- ];
581
- }
582
- async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedApkPath, "stim-apk-swap-"), exists = existsSync, hermesEnabled = null, buildTools = null, findTool = findBuildTool, storedAssets = null, readManifest = readAssetManifest, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line), platform = process.platform }) {
583
- const e = exec || getExecutor();
584
- const startedAt = now();
585
- const elapsed = () => now() - startedAt;
586
- let tmp;
587
- const fail = (step, reason, lastLines = []) => {
588
- logWriter?.write?.({
589
- src: "build",
590
- level: "error",
591
- msg: `APK swap failed at ${step}: ${reason}`,
592
- event: "apk_swap"
593
- });
594
- if (tmp) removeTemporaryEntry(tmp);
595
- return {
596
- failed: true,
597
- step,
598
- reason,
599
- lastLines,
600
- durationMs: elapsed()
601
- };
602
- };
603
- let work;
604
- let final;
605
- try {
606
- tmp = mkdtemp();
607
- const base = basename(cachedApkPath);
608
- work = join(tmp, `unaligned-${base}`);
609
- final = join(tmp, base);
610
- copyApk(e, cachedApkPath, work, platform);
611
- } catch (err) {
612
- return fail("copy", `could not copy ${cachedApkPath} aside: ${describe(err)}`);
613
- }
614
- const stage = join(tmp, "stage");
615
- const bundleOutput = join(stage, "assets", ANDROID_BUNDLE_NAME);
616
- const assetsDest = join(stage, "res");
617
- const command = androidBundleCommand({
618
- isExpo,
619
- entryFile: isExpo ? "index.js" : detectEntryFile(root),
620
- bundleOutput,
621
- assetsDest
622
- });
623
- try {
624
- mkdirSync(join(stage, "assets"), { recursive: true });
625
- mkdirSync(assetsDest, { recursive: true });
626
- } catch (err) {
627
- return fail("bundle", `could not create ${stage}: ${describe(err)}`);
628
- }
629
- logWriter?.write?.({
630
- src: "build",
631
- level: "info",
632
- msg: `${command.file} ${command.args.join(" ")}`,
633
- event: "apk_swap"
634
- });
635
- const spawn = spawnFn || ((cmd, args, opts) => e.spawn(cmd, args, opts));
636
- const transcript = [];
637
- const push = (line) => {
638
- const msg = cleanLine(line);
639
- if (msg.trim() === "") return;
640
- transcript.push(msg);
641
- logWriter?.write?.({
642
- src: "build",
643
- level: "debug",
644
- msg,
645
- raw: true,
646
- event: "apk_swap"
647
- });
648
- };
649
- let child;
650
- try {
651
- child = spawn(command.file, command.args, {
652
- cwd: root,
653
- stdio: [
654
- "ignore",
655
- "pipe",
656
- "pipe"
657
- ],
658
- env: {
659
- ...process.env,
660
- FORCE_COLOR: "0"
661
- }
662
- });
663
- } catch (err) {
664
- return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(err)}`);
665
- }
666
- const reader = {
667
- out: createLineReader(push),
668
- err: createLineReader(push)
669
- };
670
- child.stdout?.setEncoding?.("utf-8");
671
- child.stderr?.setEncoding?.("utf-8");
672
- child.stdout?.on("data", (chunk) => reader.out.push(chunk));
673
- child.stderr?.on("data", (chunk) => reader.err.push(chunk));
674
- const stopHeartbeat = startBuildHeartbeat({
675
- intervalMs: heartbeatMs,
676
- elapsed,
677
- emit: onHeartbeat,
678
- label: "swap"
679
- });
680
- let outcome;
681
- try {
682
- outcome = await waitForChild(child);
683
- } finally {
684
- stopHeartbeat();
685
- }
686
- reader.out.flush();
687
- reader.err.flush();
688
- if (outcome.error) return fail("bundle", `could not run ${command.file} ${command.args[0]}: ${describe(outcome.error)}`);
689
- if (outcome.code !== 0) {
690
- const how = outcome.signal ? `signal ${outcome.signal}` : `exit code ${outcome.code}`;
691
- return fail("bundle", `\`${command.args.slice(0, 2).join(" ")}\` failed (${how})`, tailLines(transcript, LAST_LINES));
692
- }
693
- if (!exists(bundleOutput)) return fail("bundle", `the bundle command exited 0 but wrote no ${ANDROID_BUNDLE_NAME} at ${bundleOutput}`);
694
- let hermes = false;
695
- let note;
696
- if (hermesEnabled ?? readAndroidHermesEnabled(root)) {
697
- const hermesc = androidHermescPath(root);
698
- if (!exists(hermesc)) note = `hermesc not found at ${hermesc}; embedding the plain JS bundle instead of Hermes bytecode`;
699
- else {
700
- const hbc = `${bundleOutput}.hbc`;
701
- try {
702
- e.runFile(hermesc, androidHermescArgs({
703
- bundle: bundleOutput,
704
- out: hbc
705
- }));
706
- renameSync(hbc, bundleOutput);
707
- hermes = true;
708
- } catch (err) {
709
- return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
710
- }
711
- }
712
- }
713
- const refuse = (reason, assetDiff) => {
714
- logWriter?.write?.({
715
- src: "build",
716
- level: "warn",
717
- msg: `APK swap refused: ${reason}`,
718
- event: "apk_swap"
719
- });
720
- removeTemporaryEntry(tmp);
721
- const result = {
722
- assetMismatch: true,
723
- reason,
724
- durationMs: elapsed()
725
- };
726
- if (assetDiff) result.assetDiff = assetDiff;
727
- return result;
728
- };
729
- if (!storedAssets) return refuse("this cache entry predates asset tracking (no assets-manifest.json beside the artifact), so its asset set cannot be proven to match this one");
730
- const fresh = readManifest(assetsDest);
731
- if (!fresh) return fail("assets", `could not hash the assets emitted into ${assetsDest}, so the asset set cannot be verified`);
732
- const diff = compareAssetManifests(fresh, storedAssets);
733
- if (!diff.same) return refuse(assetDiffReason(diff), diff);
734
- const jar = jarPath();
735
- const updatesManifest = join(stage, "assets", UPDATES_MANIFEST_NAME);
736
- try {
737
- e.runFile(jar, [
738
- "--extract",
739
- "--file",
740
- work,
741
- `assets/${UPDATES_MANIFEST_NAME}`
742
- ], { cwd: stage });
743
- if (exists(updatesManifest)) refreshUpdatesManifestFile(updatesManifest, now());
744
- } catch (err) {
745
- return fail("updates", `could not refresh the expo-updates manifest from ${work}: ${describe(err)}`);
746
- }
747
- try {
748
- e.runFile(jar, [
749
- "--update",
750
- "--file",
751
- work,
752
- "--no-compress",
753
- "-C",
754
- stage,
755
- "assets"
756
- ]);
757
- } catch (err) {
758
- return fail("zip", `${jar} --update ${work} failed: ${describe(err)}`);
759
- }
760
- const tools = buildTools ?? findTool(["zipalign"]);
761
- if (!tools) return fail("zipalign", "no zipalign found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
762
- try {
763
- e.runFile(tools.path, zipalignArgs({
764
- buildToolsMajor: tools.major,
765
- input: work,
766
- output: final
767
- }));
768
- } catch (err) {
769
- return fail("zipalign", `zipalign failed on ${work}: ${describe(err)}`);
770
- }
771
- const signer = buildTools ? {
772
- ...buildTools,
773
- path: join(dirname(buildTools.path), androidBuildToolName("apksigner"))
774
- } : findTool(["apksigner"]);
775
- if (!signer) return fail("apksigner", "no apksigner found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
776
- try {
777
- e.runFile(signer.path, apksignerArgs({
778
- keystore,
779
- apkPath: final
780
- }));
781
- } catch (err) {
782
- return fail("apksigner", `apksigner sign failed on ${final} with ${keystore.path}: ${describe(err)}`);
783
- }
784
- logWriter?.write?.({
785
- src: "build",
786
- level: "info",
787
- msg: `APK swap done: ${hermes ? "hermes bytecode" : "plain JS"} into ${final} in ${elapsed()}ms`,
788
- event: "apk_swap"
789
- });
790
- const result = {
791
- ok: true,
792
- apkPath: final,
793
- tmpDir: tmp,
794
- hermes,
795
- durationMs: elapsed()
796
- };
797
- if (note) result.note = note;
798
- return result;
799
- }
800
- function copyApk(e, from, to, platform) {
801
- if (platform === "darwin") try {
802
- e.runFile("cp", [
803
- "-c",
804
- from,
805
- to
806
- ]);
807
- return;
808
- } catch {}
809
- copyFileSync(from, to, constants.COPYFILE_FICLONE);
810
- }
811
- function describe(err) {
812
- const e = err;
813
- const streams = [e?.stdout, e?.stderr].map((s) => s ? String(s).trim() : "").filter(Boolean).join("\n");
814
- const message = e?.message ? String(e.message).trim() : String(err);
815
- return streams ? `${message}: ${streams}` : message;
816
- }
817
- //#endregion
818
- //#region src/commands/android/artifact.ts
819
- const FALLBACK_LINES = 5;
820
- function fail$1(code, message, remedy, extra = {}) {
821
- return {
822
- code,
823
- message,
824
- remedy,
825
- extra
826
- };
827
- }
828
- var ArtifactRefusal = class extends Error {
829
- failure;
830
- constructor(failure) {
831
- super(failure.message ?? failure.code);
832
- this.failure = failure;
833
- }
834
- };
835
- async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, hostedAbi, remote: remoteTarget, buildPlan, cacheProviderConfig, requestedBuildCache, easBuild, androidPackage: initialPackage, record, maxBuilds, progress }, { deviceAbi, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, loadCacheProviderModule, acquireSlot, releaseSlot, planPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
836
- const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
837
- let fallbackMachine = null;
838
- let hereReason = "no remote Mac is paired";
839
- let slotWaitMs;
840
- const buildMachine = record.buildMachine ?? "auto";
841
- const fallBack = (reason, line = reason, info = {}) => {
842
- writer.write(buildPlacementRecord({
843
- platform: PLATFORM,
844
- buildMachine,
845
- candidates: info.candidates,
846
- event: "placement_fallback",
847
- fallback: {
848
- code: info.code ?? "fallback",
849
- reason,
850
- machine: info.machine
851
- }
852
- }));
853
- if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
854
- requireLocalBuild(buildMachine);
855
- record.offloadFallback = reason;
856
- phase("build", `${line} -> building here`);
857
- };
858
- const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
859
- const useBuildCache = cachePolicy.read;
860
- let androidPackage = initialPackage;
861
- let ccacheActivity = CCACHE_NOT_RUN;
862
- let phaseFailure = null;
863
- let buildLock = null;
864
- const releaseHeldLock = () => {
865
- if (!buildLock) return;
866
- const held = buildLock;
867
- buildLock = null;
868
- try {
869
- releaseLock(held);
870
- } catch (err) {
871
- out(phaseLine("build", chalk.dim(`could not release the build lock at ${held.path}: ${err?.message || err}`)));
872
- }
873
- };
874
- let buildSlot = null;
875
- const releaseHeldSlot = () => {
876
- if (!buildSlot) return;
877
- const held = buildSlot;
878
- buildSlot = null;
879
- try {
880
- releaseSlot(held);
881
- } catch (err) {
882
- out(phaseLine("build", chalk.dim(`could not release the build slot: ${err?.message || err}`)));
883
- }
884
- };
885
- const { abi: buildAbi, runOptions: buildRunOptions, remoteRunOptions } = androidBuildOptions({
886
- release,
887
- physical,
888
- device,
889
- variant,
890
- deviceAbi,
891
- compiler: cas?.id,
892
- buildProfile,
893
- targetAbiOnly: buildPlan.targetAbiOnly,
894
- hostedAbi
895
- });
896
- let hash = "";
897
- let providerUpload = null;
898
- let providerName = null;
899
- let providerLoad = null;
900
- const cacheWarn = createWarnOnce((line) => phase("cache", chalk.yellow(line)));
901
- const loadTieredProvider = cachePolicy.remote && cacheProviderConfig ? () => providerLoad ??= loadCacheProviderModule({
902
- projectRoot: root,
903
- config: cacheProviderConfig
904
- }) : null;
905
- let fingerprintSources = [];
906
- let cacheKey = "";
907
- let storeHash = "";
908
- let storeKey = "";
909
- let storeSources = [];
910
- let apkPath = null;
911
- let handoff = null;
912
- let swapFellBack = false;
913
- async function resolveInitialFingerprint() {
914
- if (easBuild?.ok) {
915
- hash = storeHash = easBuild.fingerprint;
916
- cacheKey = storeKey = easBuild.cacheKey;
917
- apkPath = easBuild.path;
918
- record.fingerprint = hash;
919
- record.cacheKey = cacheKey;
920
- record.cacheHit = easBuild.cacheHit;
921
- record.cacheSkipped = !useBuildCache;
922
- providerName = "eas";
923
- stats.setCacheKey(cacheKey);
924
- return true;
925
- }
926
- if (useBuildCache) step("cache-lookup");
927
- const fingerprintTimer = stepTimer(now);
928
- try {
929
- const computed = await fingerprint(root, { platform: PLATFORM });
930
- hash = computed?.hash ?? "";
931
- fingerprintSources = computed?.sources ?? [];
932
- } catch (err) {
933
- const message = String(err?.message || err);
934
- record.cacheSkipped = !useBuildCache;
935
- record.missReason = fingerprintErrorMissReason(message);
936
- phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${message}`, "Fix the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
937
- return false;
938
- }
939
- if (!hash) {
940
- record.cacheSkipped = !useBuildCache;
941
- record.missReason = fingerprintErrorMissReason("no hash");
942
- phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
943
- return false;
944
- }
945
- record.fingerprint = hash;
946
- cacheKey = buildCacheKey(PLATFORM, hash, buildRunOptions);
947
- stats.setCacheKey(cacheKey);
948
- record.cacheKey = cacheKey;
949
- storeHash = hash;
950
- storeKey = cacheKey;
951
- storeSources = fingerprintSources;
952
- const found = await resolveTieredBuild({
953
- local: filesystemBuildCapability({
954
- resolve: resolveCached,
955
- store: storeCached,
956
- sources: fingerprintSources
957
- }),
958
- loadProvider: loadTieredProvider,
959
- target: {
960
- projectRoot: root,
961
- platform: PLATFORM,
962
- key: cacheKey
963
- },
964
- destinationDir: providerDownloadPath(workspaceDir(root)),
965
- ensureDestination: prepareProviderDownloadDir,
966
- skipRead: !useBuildCache,
967
- warn: cacheWarn
968
- });
969
- const cached = found?.tier === "local" ? found.path : null;
970
- record.cacheHit = cached ? "local" : false;
971
- record.cacheSkipped = !useBuildCache;
972
- phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
973
- if (found?.tier === "provider") {
974
- record.cacheHit = "remote";
975
- providerName = found.providerName ?? null;
976
- phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
977
- }
978
- apkPath = found?.path ?? null;
979
- return true;
980
- }
981
- let remote = null;
982
- let abandonedRemote = false;
983
- let uploadPending = null;
984
- async function resolveRemoteArtifact() {
985
- if (buildAbi || cas || buildProfile || !cachePolicy.remote) return;
986
- if (!apkPath) {
987
- const loaded = await loadProvider(root, { isExpo });
988
- if (loaded?.unavailable) phase("cache", chalk.yellow(`provider not usable: ${loaded.unavailable}`));
989
- else if (loaded?.provider) remote = loaded;
990
- if (remote?.name === "eas") {
991
- const auth = easAuth({
992
- projectRoot: root,
993
- owner: loaded?.owner || null
994
- });
995
- const authNote = easAuthNote(auth);
996
- if (authNote) phase("cache", chalk.yellow(authNote));
997
- if (auth?.code === "logged-out") remote = null;
998
- }
999
- }
1000
- if (remote && useBuildCache) {
1001
- const remoteTimer = stepTimer(now);
1002
- const hit = await resolveRemoteBuild({
1003
- logWriter: writer,
1004
- provider: remote.provider,
1005
- platform: PLATFORM,
1006
- projectRoot: root,
1007
- fingerprintHash: hash,
1008
- runOptions: remoteRunOptions
1009
- });
1010
- if (hit?.appPath) {
1011
- let stored = null;
1012
- try {
1013
- stored = storeCached(PLATFORM, cacheKey, hit.appPath, { sources: fingerprintSources });
1014
- } catch (err) {
1015
- phase("cache", chalk.yellow(`remote hit could not be stored locally: ${err?.message || err}`));
1016
- }
1017
- apkPath = stored || hit.appPath;
1018
- record.cacheHit = "remote";
1019
- phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
1020
- } else if (hit?.timedOut) {
1021
- abandonedRemote = true;
1022
- phase("cache", chalk.yellow(`${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`));
1023
- } else if (hit?.failed) {
1024
- const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
1025
- code: "logged-out",
1026
- reason: hit.failed
1027
- }) : null;
1028
- phase("cache", chalk.yellow(authNote || `${remote.name} could not be used: ${hit.failed}; building instead`));
1029
- } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
1030
- }
1031
- }
1032
- let waitedForBuild = null;
1033
- let releasedWait = null;
1034
- async function awaitSharedBuild() {
1035
- const shared = await waitForSharedBuild({
1036
- platform: PLATFORM,
1037
- key: cacheKey,
1038
- fingerprint: hash,
1039
- root,
1040
- logFile: buildLog,
1041
- command: "stim android",
1042
- acquire: acquireLock,
1043
- wait: waitForBuild,
1044
- now,
1045
- phase: (text) => {
1046
- step("wait");
1047
- phase("build", text);
1048
- },
1049
- waitingOn,
1050
- warn: (text) => phase("build", chalk.yellow(text)),
1051
- out
1052
- });
1053
- if (shared.refusal) {
1054
- const { code, message, remedy } = shared.refusal;
1055
- phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
1056
- throw new ArtifactRefusal(phaseFailure);
1057
- }
1058
- buildLock = shared.lock;
1059
- releasedWait = shared.released;
1060
- if (shared.hit) {
1061
- apkPath = shared.hit.path;
1062
- record.cacheHit = "local";
1063
- waitedForBuild = shared.hit.waited;
1064
- }
1065
- return apkPath;
1066
- }
1067
- let swapDir = null;
1068
- const installableCachedApk = async (key, cachedPath) => {
1069
- if (!release) return cachedPath;
1070
- phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
1071
- const swap = await swapApk({
1072
- root,
1073
- isExpo,
1074
- cachedApkPath: cachedPath,
1075
- keystore: resolveKeystore(root, settings),
1076
- logWriter: writer,
1077
- storedAssets: storedAssets(PLATFORM, key)
1078
- });
1079
- if (swap?.ok && swap.apkPath) {
1080
- if (swap.note) phase("swap", chalk.yellow(swap.note));
1081
- swapDir = swap.tmpDir ?? null;
1082
- phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} repacked (store), zipaligned and re-signed (${formatDuration(swap.durationMs)})`);
1083
- return swap.apkPath;
1084
- }
1085
- if (swap?.assetMismatch) phase("swap", chalk.yellow(`${swap.reason} -- building fresh instead` + (swap.assetDiff ? " (an APK cannot be made to carry an asset AAPT did not package)" : "")));
1086
- else phase("swap", chalk.yellow(`failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${variant} APK carries its builder's JS; it is never installed after a failed swap)`));
1087
- for (const line of swap?.lastLines ?? []) phase("", chalk.dim(line));
1088
- swapFellBack = true;
1089
- return null;
1090
- };
1091
- async function prepareCachedArtifact(cachedPath) {
1092
- const prepared = await installableCachedApk(cacheKey, cachedPath);
1093
- if (!prepared) {
1094
- record.cacheHit = false;
1095
- waitedForBuild = null;
1096
- }
1097
- return prepared;
1098
- }
1099
- function reasonForMiss(rekeyedBy) {
1100
- if (swapFellBack) return {
1101
- reason: skippedMissReason("the cached APK could not be reused, so this run built it fresh"),
1102
- diff: null
1103
- };
1104
- if (!useBuildCache) return {
1105
- reason: skippedMissReason(requestedBuildCache ? "cache reuse off in config" : "cache reuse turned off by --no-build-cache"),
1106
- diff: null
1107
- };
1108
- const current = {
1109
- hash: storeHash,
1110
- sources: storeSources
1111
- };
1112
- const explained = explainBuildMiss({
1113
- root,
1114
- platform: PLATFORM,
1115
- current,
1116
- rekeyedBy,
1117
- baselineDeps: { readState }
1118
- });
1119
- return {
1120
- reason: explained.reason,
1121
- diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
1122
- changed: explained.changedNames,
1123
- previousHash: explained.previousHash,
1124
- hash: current.hash
1125
- }) : null
1126
- };
1127
- }
1128
- function explainMiss(rekeyedBy) {
1129
- const explained = reasonForMiss(rekeyedBy);
1130
- record.missReason = explained.reason;
1131
- if (explained.diff) writer.write(explained.diff);
1132
- miss(record.missReason);
1133
- phase("cache", `miss: ${record.missReason.summary}`);
1134
- if (record.missReason.kind === "no-baseline") {
1135
- const line = untrackedMissLine(untracked({ projectRoot: root }));
1136
- if (line) phase("fingerprint", chalk.dim(line));
1137
- }
1138
- }
1139
- async function takeBuildSlot() {
1140
- requireLocalBuild(buildMachine);
1141
- if (!maxBuilds) return true;
1142
- try {
1143
- buildSlot = await acquireSlot({
1144
- automatic: buildMachine === "auto",
1145
- max: maxBuilds,
1146
- root,
1147
- logFile: buildLog,
1148
- out,
1149
- waitingFor: (info) => waitingFor?.(info, "build-slot")
1150
- });
1151
- slotWaitMs = buildSlot.slotWaitMs;
1152
- } catch (err) {
1153
- requireLocalBuild(buildMachine);
1154
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
1155
- if (refusal) {
1156
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1157
- return false;
1158
- }
1159
- phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
1160
- }
1161
- return true;
1162
- }
1163
- /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
1164
- function placeBuild() {
1165
- const { mode, machines, localEnabled } = buildPlacementCandidates(buildMachine);
1166
- if (localEnabled && machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
1167
- const unsupported = physical ? "device builds build here" : remoteTarget ? "remote device builds build here" : release ? `${variant} builds build here` : cas ? "Apple Clang CAS builds build here" : !cachePolicy.write ? "the build cache is off" : null;
1168
- const here = machineCapacity();
1169
- const placement = offloadPlacement({
1170
- mode,
1171
- localEnabled,
1172
- machines: machines.length,
1173
- here,
1174
- unsupported,
1175
- selected: buildMachine
1176
- });
1177
- if (!placement.offload) {
1178
- hereReason = placement.reason;
1179
- writer.write(buildPlacementRecord({
1180
- platform: PLATFORM,
1181
- buildMachine,
1182
- stays: {
1183
- code: placement.code,
1184
- reason: placement.reason
1185
- }
1186
- }));
1187
- if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
1188
- return null;
1189
- }
1190
- return {
1191
- mode,
1192
- here,
1193
- code: placement.code,
1194
- reason: placement.reason,
1195
- machines
1196
- };
1197
- }
1198
- const openOffload = { choice: null };
1199
- /** Asks the paired machines once the post-mutation key is known; null builds here. */
1200
- async function chooseMachine(candidate) {
1201
- let asked = [];
1202
- const choice = await chooseBuildMachine({
1203
- projectRoot: root,
1204
- target: {
1205
- platform: "android",
1206
- local: androidToolchain(),
1207
- requires: androidRequirements(root)
1208
- },
1209
- mode: candidate.mode,
1210
- here: candidate.here,
1211
- note: (line) => phase("build", chalk.dim(`offload: ${line}`)),
1212
- machines: candidate.machines,
1213
- selected: buildMachine,
1214
- onCandidates: (each) => asked = each
1215
- });
1216
- if (typeof choice === "string") {
1217
- const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
1218
- if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
1219
- fallBack(choice, choice, {
1220
- code: "no-remote-mac-took-it",
1221
- candidates: asked
1222
- });
1223
- return null;
1224
- }
1225
- openOffload.choice = choice;
1226
- writer.write(buildPlacementRecord({
1227
- platform: PLATFORM,
1228
- buildMachine,
1229
- candidates: asked,
1230
- chose: {
1231
- machine: choice.machine,
1232
- reason: `${candidate.reason}${placementLoad(choice)}`
1233
- }
1234
- }));
1235
- phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
1236
- return choice;
1237
- }
1238
- /** Builds on the chosen machine and stores the APK under the post-mutation key; false builds here instead unless a machine was named. */
1239
- async function compileElsewhere(choice, candidate) {
1240
- if (!storeKey || !storeHash) {
1241
- const reason = "the build fingerprint or cache key is unavailable";
1242
- if (namedBuildMachine(buildMachine)) fallBack(reason, reason, { machine: choice.machine });
1243
- else writer.write(buildPlacementRecord({
1244
- platform: PLATFORM,
1245
- buildMachine,
1246
- event: "placement_fallback",
1247
- fallback: {
1248
- code: "fallback",
1249
- reason,
1250
- machine: choice.machine
1251
- }
1252
- }));
1253
- return false;
1254
- }
1255
- const stagingDir = join(workspaceDir(root), "offload", PLATFORM);
1256
- const outcome = await offloadBuild({
1257
- choice,
1258
- expectedFingerprint: storeHash,
1259
- request: {
1260
- platform: "android",
1261
- isExpo,
1262
- android: {
1263
- variant,
1264
- abi: buildAbi,
1265
- gradleBuildCache: buildPlan.gradleBuildCache,
1266
- pch: buildPlan.pch,
1267
- compilerCache: buildPlan.compilerCache === "none" ? "none" : "ccache"
1268
- }
1269
- },
1270
- stagingDir,
1271
- onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
1272
- onEnter: (name) => {
1273
- if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") record.builtOn = choice.machine;
1274
- place({
1275
- host: choice.machine,
1276
- phase: name
1277
- });
1278
- step(name === "prebuild" ? name : "compile");
1279
- },
1280
- onRecord: (entry) => writer.write({
1281
- ...entry,
1282
- offloadedTo: choice.machine
1283
- }),
1284
- note: (line) => phase("build", line)
1285
- });
1286
- place(null);
1287
- let stored = null;
1288
- let reason = outcome.ok ? null : outcome.reason;
1289
- if (outcome.ok) {
1290
- const settled = await refingerprintAfterMutation({
1291
- projectRoot: root,
1292
- platform: PLATFORM,
1293
- previousHash: storeHash,
1294
- fingerprint
1295
- });
1296
- if (!settled || settled.moved) reason = "the checkout here changed while it built";
1297
- else try {
1298
- stored = storeCached(PLATFORM, storeKey, outcome.artifactPath, {
1299
- sources: storeSources,
1300
- overwrite: !useBuildCache
1301
- });
1302
- } catch (err) {
1303
- reason = `could not store the APK: ${err?.message || err}`;
1304
- }
1305
- }
1306
- try {
1307
- rmSync(stagingDir, {
1308
- recursive: true,
1309
- force: true
1310
- });
1311
- } catch {}
1312
- step("compile");
1313
- if (!outcome.ok || !stored) {
1314
- const why = reason ?? "the APK was not stored";
1315
- fallbackMachine = choice.machine;
1316
- fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`, {
1317
- code: "offload-failed",
1318
- machine: choice.machine
1319
- });
1320
- writer.write({
1321
- src: "build",
1322
- level: "warn",
1323
- event: "offload_failed",
1324
- msg: reason,
1325
- machine: choice.machine
1326
- });
1327
- return false;
1328
- }
1329
- const { timings } = outcome;
1330
- apkPath = stored;
1331
- handoff = outcome.handoff ?? null;
1332
- record.builtOn = outcome.machine;
1333
- record.offloadedTo = outcome.machine;
1334
- stats.setPlacement({
1335
- decision: "offloaded",
1336
- machine: outcome.machine,
1337
- reason: `${candidate.reason}${placementLoad(choice)}`,
1338
- buildMs: timings.totalMs
1339
- });
1340
- ccacheActivity = outcome.ccache;
1341
- phase("build", `built on ${outcome.machine} in ${formatDuration(timings.totalMs)}: offer ${formatDuration(timings.offerMs)}, sync ${formatDuration(timings.syncMs)}, build ${formatDuration(timings.workerMs)}, fetch ${formatDuration(timings.fetchMs)}`);
1342
- phase("cache", `compilation cache on ${outcome.machine} ${ccacheActivityLine(ccacheActivity)}`);
1343
- writer.write({
1344
- src: "build",
1345
- level: "info",
1346
- event: "offload_done",
1347
- msg: `built on ${outcome.machine}`,
1348
- timings
1349
- });
1350
- return true;
1351
- }
1352
- async function prepareSource() {
1353
- const rekeyedBy = [];
1354
- const prebuildPlan = planPrebuildFor(root, PLATFORM, {
1355
- isExpo,
1356
- fingerprint: hash,
1357
- sources: fingerprintSources
1358
- });
1359
- const prebuildRan = prebuildPlan === "generate" || prebuildPlan === "regenerate";
1360
- if (prebuildPlan === "refuse") {
1361
- const refusal = staleNativeDirRefusal(PLATFORM);
1362
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1363
- throw new ArtifactRefusal(phaseFailure);
1364
- }
1365
- if (prebuildRan) {
1366
- miss(reasonForMiss([]).reason, true);
1367
- step("prebuild");
1368
- recordPrebuild(root, PLATFORM, null);
1369
- const pre = await prebuild(root, PLATFORM, writer, {
1370
- isExpo,
1371
- clean: prebuildPlan === "regenerate"
1372
- });
1373
- if (pre.failed) {
1374
- phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
1375
- lastBuildStatus: true,
1376
- lines: tail(pre.lastLines),
1377
- logPath: displayPath(root, buildLog)
1378
- });
1379
- throw new ArtifactRefusal(phaseFailure);
1380
- }
1381
- phase("prebuild", `${prebuildPlan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
1382
- androidPackage = detectAndroidPackage(root) || androidPackage;
1383
- record.bundleId = androidPackage;
1384
- }
1385
- return {
1386
- prebuildRan,
1387
- rekeyedBy,
1388
- editedConfig: []
1389
- };
1390
- }
1391
- async function revalidateSource(preparation) {
1392
- const { prebuildRan, rekeyedBy } = preparation;
1393
- if (prebuildRan) {
1394
- const after = await refingerprintAfterMutation({
1395
- projectRoot: root,
1396
- platform: PLATFORM,
1397
- previousHash: hash,
1398
- fingerprint
1399
- });
1400
- preparation.editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
1401
- const { editedConfig } = preparation;
1402
- if (after && !editedConfig.length) recordPrebuild(root, PLATFORM, after.hash);
1403
- if (after?.moved && !editedConfig.length) {
1404
- rekeyedBy.push("prebuild");
1405
- storeHash = after.hash;
1406
- storeSources = after.sources;
1407
- storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
1408
- record.fingerprint = storeHash;
1409
- record.cacheKey = storeKey;
1410
- phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
1411
- const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
1412
- if (late) {
1413
- const prepared = await installableCachedApk(storeKey, late);
1414
- if (prepared) {
1415
- apkPath = prepared;
1416
- record.cacheHit = "local";
1417
- lateHit();
1418
- phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1419
- if (releasedWait) {
1420
- waitedForBuild = releasedWait.facts;
1421
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1422
- }
1423
- }
1424
- }
1425
- }
1426
- }
1427
- return apkPath ? { ready: apkPath } : null;
1428
- }
1429
- async function acquireRemoteArtifact(offload, { rekeyedBy, editedConfig }) {
1430
- explainMiss(rekeyedBy);
1431
- step("compile");
1432
- if (editedConfig.length) fallBack("prebuild changed config inputs, so the APK cannot be cached", "offload failed: prebuild changed config inputs, so the APK cannot be cached");
1433
- else {
1434
- const choice = await chooseMachine(offload);
1435
- if (choice) await compileElsewhere(choice, offload);
1436
- }
1437
- return apkPath ? { ready: apkPath } : null;
1438
- }
1439
- async function compileHere(offload, { rekeyedBy }) {
1440
- if (!offload) {
1441
- explainMiss(rekeyedBy);
1442
- step("compile");
1443
- }
1444
- if (offload) stats.setPlacement({
1445
- decision: "fell-back",
1446
- slotWaitMs,
1447
- deviceSlotWaitMs: stats.deviceSlotWaitMs(),
1448
- reason: record.offloadFallback ?? "offload failed",
1449
- ...fallbackMachine ? { machine: fallbackMachine } : {}
1450
- });
1451
- else stats.setPlacement({
1452
- decision: "here",
1453
- reason: hereReason,
1454
- slotWaitMs,
1455
- deviceSlotWaitMs: stats.deviceSlotWaitMs()
1456
- });
1457
- requireLocalBuild(buildMachine);
1458
- record.builtOn = "here";
1459
- phase("build", `compiling ${variant || "debug"} with Gradle`);
1460
- const built = await build({
1461
- root,
1462
- logWriter: writer,
1463
- variant,
1464
- abi: buildAbi
1465
- }, {
1466
- estimateMs: estimates().coldBuildMs,
1467
- ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
1468
- root,
1469
- onNote: out
1470
- }) : null,
1471
- cas,
1472
- buildCache: buildPlan.gradleBuildCache,
1473
- pch: buildPlan.pch,
1474
- compilerCacheDisabled: buildPlan.compilerCache === "none"
1475
- });
1476
- ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
1477
- phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
1478
- if (!built.ok) {
1479
- const diagnostics = built.diagnostics;
1480
- for (const diag of diagnostics) writer.write({
1481
- src: "build",
1482
- level: "error",
1483
- event: "gradle_diagnostic",
1484
- msg: formatDiagnostic(diag)
1485
- });
1486
- phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
1487
- const extracted = diagnostics.map(formatDiagnostic);
1488
- if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
1489
- phaseFailure = fail$1(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
1490
- lastBuildStatus: true,
1491
- diagnostics: extracted,
1492
- buildDiagnostics: diagnostics,
1493
- lines: extracted.length ? [] : tail(built.lastLines),
1494
- logPath: displayPath(root, buildLog)
1495
- });
1496
- throw new ArtifactRefusal(phaseFailure);
1497
- }
1498
- apkPath = built.apkPath;
1499
- stats.setBuildMs(built.durationMs);
1500
- phase("build", `ok (${formatDuration(built.durationMs)})`);
1501
- if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
1502
- return apkPath;
1503
- }
1504
- async function validateCompiled({ prebuildRan, editedConfig }) {
1505
- const beforeBuildHash = storeHash;
1506
- const afterBuild = editedConfig.length ? null : await refingerprintAfterMutation({
1507
- projectRoot: root,
1508
- platform: PLATFORM,
1509
- previousHash: beforeBuildHash,
1510
- fingerprint
1511
- });
1512
- const changedDuringBuild = afterBuild ? inputsChangedDuringBuild({
1513
- platform: PLATFORM,
1514
- lookup: fingerprintSources,
1515
- prebuildRan,
1516
- compiled: storeSources,
1517
- current: afterBuild.sources
1518
- }) : editedConfig;
1519
- if (!afterBuild || changedDuringBuild.length) {
1520
- record.fingerprint = null;
1521
- record.cacheKey = null;
1522
- phase("fingerprint", chalk.yellow(changedDuringBuild.length ? changedDuringBuildLine(changedDuringBuild) : "unavailable after Gradle; the build will be installed but not cached"));
1523
- return "uncacheable";
1524
- }
1525
- if (afterBuild.moved) {
1526
- storeHash = afterBuild.hash;
1527
- storeSources = afterBuild.sources;
1528
- storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
1529
- record.fingerprint = storeHash;
1530
- record.cacheKey = storeKey;
1531
- phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
1532
- }
1533
- return "cacheable";
1534
- }
1535
- async function storeArtifact(artifactPath) {
1536
- if (cachePolicy.write) {
1537
- const assetManifest = release ? captureAssets(root, { variant }) : null;
1538
- try {
1539
- const stored = await storeTieredBuild({
1540
- local: filesystemBuildCapability({
1541
- resolve: resolveCached,
1542
- store: storeCached,
1543
- sources: storeSources,
1544
- assetManifest
1545
- }),
1546
- loadProvider: loadTieredProvider,
1547
- target: {
1548
- projectRoot: root,
1549
- platform: PLATFORM,
1550
- key: storeKey
1551
- },
1552
- sourcePath: artifactPath,
1553
- overwrite: !useBuildCache || swapFellBack,
1554
- warn: cacheWarn
1555
- });
1556
- providerUpload = stored.providerUpload;
1557
- providerName = stored.providerName ?? providerName;
1558
- } catch (err) {
1559
- phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
1560
- }
1561
- }
1562
- if (remote) uploadPending = uploadRemoteBuild({
1563
- logWriter: writer,
1564
- provider: remote.provider,
1565
- platform: PLATFORM,
1566
- projectRoot: root,
1567
- fingerprintHash: storeHash,
1568
- buildPath: artifactPath,
1569
- runOptions: remoteRunOptions
1570
- });
1571
- }
1572
- try {
1573
- apkPath = await runArtifactLifecycle({
1574
- resolve: async () => {
1575
- if (!await resolveInitialFingerprint()) throw new ArtifactRefusal(phaseFailure);
1576
- if (easBuild) return {
1577
- kind: "ready",
1578
- artifact: apkPath
1579
- };
1580
- await resolveRemoteArtifact();
1581
- if (apkPath) return {
1582
- kind: "cached",
1583
- artifact: apkPath
1584
- };
1585
- miss(reasonForMiss([]).reason);
1586
- return { kind: "miss" };
1587
- },
1588
- claim: useBuildCache ? awaitSharedBuild : void 0,
1589
- reuse: async (cached) => {
1590
- apkPath = await prepareCachedArtifact(cached);
1591
- if (apkPath) lateHit();
1592
- else if (swapFellBack) miss(reasonForMiss([]).reason);
1593
- return apkPath;
1594
- },
1595
- build: {
1596
- placement: {
1597
- select: placeBuild,
1598
- acquire: acquireRemoteArtifact
1599
- },
1600
- admit: async () => {
1601
- if (!await takeBuildSlot()) throw new ArtifactRefusal(phaseFailure);
1602
- },
1603
- prepare: prepareSource,
1604
- revalidate: revalidateSource,
1605
- compile: compileHere,
1606
- validate: validateCompiled,
1607
- store: storeArtifact
1608
- },
1609
- release: () => {
1610
- if (openOffload.choice) closeOffload(openOffload.choice);
1611
- releaseHeldLock();
1612
- releaseHeldSlot();
1613
- }
1614
- });
1615
- } catch (error) {
1616
- if (error instanceof OffloadRefusal) {
1617
- const { code, message, remedy } = error;
1618
- return {
1619
- ok: false,
1620
- failure: fail$1(code, message, remedy, { lastBuildStatus: true }),
1621
- ccache: ccacheActivity
1622
- };
1623
- }
1624
- if (error instanceof ArtifactRefusal) return {
1625
- ok: false,
1626
- failure: error.failure,
1627
- ccache: ccacheActivity
1628
- };
1629
- throw error;
1630
- }
1631
- return {
1632
- ok: true,
1633
- artifact: {
1634
- apkPath,
1635
- handoff,
1636
- androidPackage,
1637
- swapDir,
1638
- waitedForBuild,
1639
- ccache: ccacheActivity,
1640
- uploadPending,
1641
- providerUpload,
1642
- providerName,
1643
- remote,
1644
- abandonedRemote
1645
- }
1646
- };
1647
- }
1648
- function tail(lines, n = FALLBACK_LINES) {
1649
- return (Array.isArray(lines) ? lines : []).slice(-n);
1650
- }
1651
- //#endregion
1652
- //#region src/commands/android/collector.ts
1653
- function collectorEntry() {
1654
- return spawnEntry("collector-run");
1655
- }
1656
- function collectorLogFile(root, slot = "default") {
1657
- return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
1658
- }
1659
- const COLLECTOR_EXIT_WAIT_MS = 2e3;
1660
- const COLLECTOR_POLL_MS = 25;
1661
- function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
1662
- const record = (collectors ?? readCollectors(root))?.[deviceSlotKey(platform, slot)];
1663
- const pid = Number(record?.pid);
1664
- if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null;
1665
- const ownership = verify({
1666
- pid,
1667
- platform: deviceSlotKey(platform, slot),
1668
- root,
1669
- isAlive,
1670
- expected: record
1671
- });
1672
- if (ownership.status === "gone") return null;
1673
- if (ownership.status === "unverified") {
1674
- note(chalk.dim(`Previous ${platform} log collector (pid ${pid}): ${ownership.reason}, so it was not signalled -- starting a replacement anyway`));
1675
- return null;
1676
- }
1677
- try {
1678
- kill(pid, "SIGTERM");
1679
- return pid;
1680
- } catch {
1681
- return null;
1682
- }
1683
- }
1684
- 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 }) {
1685
- const previousPid = killPreviousCollector(root, {
1686
- slot,
1687
- kill,
1688
- isAlive: alive,
1689
- verify,
1690
- note: (line) => out(phaseLine("logs", line))
1691
- });
1692
- if (previousPid) {
1693
- const deadline = Date.now() + waitMs;
1694
- while (Date.now() < deadline && alive(previousPid)) await sleep(COLLECTOR_POLL_MS);
1695
- }
1696
- let stdio = "ignore";
1697
- try {
1698
- mkdirSync(workspaceLogsDir$1(root), { recursive: true });
1699
- const fd = openSync(collectorLogFile(root, slot), "a");
1700
- stdio = [
1701
- "ignore",
1702
- fd,
1703
- fd
1704
- ];
1705
- } catch {}
1706
- try {
1707
- const child = spawn(process.execPath, [
1708
- collectorEntry(),
1709
- "--platform",
1710
- PLATFORM,
1711
- "--root",
1712
- root,
1713
- ...slot === "default" ? [] : ["--slot", slot],
1714
- "--serial",
1715
- serial,
1716
- "--package",
1717
- packageName
1718
- ], {
1719
- cwd: androidToolCwd() ?? root,
1720
- detached: true,
1721
- stdio,
1722
- env: process.env
1723
- });
1724
- child?.unref?.();
1725
- return child?.pid ?? null;
1726
- } catch (err) {
1727
- out(phaseLine("logs", chalk.yellow(`could not start the device log collector: ${err?.message || err}`)));
1728
- return null;
1729
- }
1730
- }
1731
- //#endregion
1732
- //#region src/commands/android/launch.ts
1733
- function androidProcessRuntime(prepare) {
1734
- return {
1735
- prepare,
1736
- scheme: () => void 0,
1737
- verificationWaitMs: RELEASE_VERIFY_WAIT_MS,
1738
- launch: async (_prepared, { serial, androidPackage, remoteDevice, launchRelease }) => remoteDevice ? remoteDevice.launch({
1739
- serial,
1740
- packageName: androidPackage,
1741
- metroPort: null
1742
- }) : launchRelease({
1743
- serial,
1744
- packageName: androidPackage
1745
- }),
1746
- verify: async (_prepared, context) => verifyAndroidProcessRun(context)
1747
- };
1748
- }
1749
- function androidMetroRuntime(prepare) {
1750
- return {
1751
- prepare,
1752
- scheme: (root, apkPath, resolve) => resolve(root, apkPath),
1753
- verificationWaitMs: DEBUG_VERIFY_STEP_MS,
1754
- launch: async ({ metroPort }, { serial, androidPackage, scheme, physical, launch }) => launch({
1755
- serial,
1756
- packageName: androidPackage,
1757
- metroPort: metroPort ?? 8081,
1758
- devClientScheme: scheme,
1759
- physical
1760
- }),
1761
- verify: async ({ metroPort }, context) => verifyAndroidMetroRun({
1762
- ...context,
1763
- metroPort
1764
- })
1765
- };
1766
- }
1767
- async function verifyAndroidProcessRun({ root, slot, remoteDevice, verifyReleaseLaunched, serial, androidPackage, variant, logsDir, launchedAt, phase }) {
1768
- const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1769
- root,
1770
- slot,
1771
- platform: "android",
1772
- deviceId: serial,
1773
- appId: androidPackage,
1774
- since: launchedAt
1775
- }, logsDir);
1776
- if (remoteDevice) {
1777
- phase("verify", chalk.yellow("UNVERIFIED: remote adapter launch accepted; process verification is unavailable"));
1778
- return { state: LAUNCH_UNVERIFIED };
1779
- }
1780
- const processCheck = await verifyReleaseLaunched({
1781
- serial,
1782
- packageName: androidPackage
1783
- });
1784
- const crashes = readNativeCrashes();
1785
- if (processCheck?.verified && !crashes.length) {
1786
- phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${variant}: no bundle fetch to observe)`);
1787
- return { state: true };
1788
- }
1789
- if (processCheck?.reason === "probe-failed" && !crashes.length) {
1790
- phase("verify", chalk.yellow("UNVERIFIED: the app process check failed"));
1791
- return { state: LAUNCH_UNVERIFIED };
1792
- }
1793
- for (const line of launchErrorPreview(crashes, root)) phase("launch", chalk.red(line));
1794
- if (!crashes.length) phase("logs", "Process missing; no attributable native crash report captured. Read `stim logs --source device` for available output.");
1795
- 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`));
1796
- phase("", chalk.yellow("A release process exited before readiness. Run `stim logs --errors` for captured crash reports, or `stim logs --source device` for the full device output."));
1797
- return { state: LAUNCH_FATAL };
1798
- }
1799
- async function verifyAndroidMetroRun({ root, slot, remoteDevice, verifyLaunched, serial, androidPackage, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
1800
- const runCommand = nativeRunCommand("android", slot, {
1801
- physical,
1802
- deviceId: serial
1803
- });
1804
- const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1805
- root,
1806
- slot,
1807
- platform: "android",
1808
- deviceId: serial,
1809
- appId: androidPackage,
1810
- since: launchedAt
1811
- }, logsDir);
1812
- const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
1813
- const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1814
- if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
1815
- const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
1816
- const verification = metroCheck ? await verifyLaunched({
1817
- timeoutMs,
1818
- requireBundleResponse: true,
1819
- slot: launchSlotScope(root, slot),
1820
- platformShared: siblings.length > 0,
1821
- onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
1822
- logsDir,
1823
- since: launchedAt,
1824
- metroPort,
1825
- platform: "android",
1826
- mode: isExpo ? MODE_EXPO : MODE_BARE,
1827
- readNativeCrashes,
1828
- processAlive: () => {
1829
- const pid = androidAppProcess(serial, androidPackage);
1830
- return pid === void 0 ? null : pid !== null;
1831
- }
1832
- }) : {
1833
- verified: false,
1834
- skipped: true
1835
- };
1836
- const nativeCrashes = verification.errors?.some((record) => record.event === "native_crash") ? [] : readNativeCrashes();
1837
- if (nativeCrashes.length) verification.fatal = true;
1838
- verification.errors = await errorDiagnostics([...verification.errors ?? [], ...nativeCrashes], {
1839
- root,
1840
- logsDir,
1841
- port: metroPort,
1842
- allowRequest: true
1843
- });
1844
- if (verification.readiness) phase("readiness", appReadinessMessage(verification.readiness, verification.waitedMs ?? 0));
1845
- if (verification?.fatal) {
1846
- const nativeFatal = verification.errors?.some((record) => record.event === "native_crash");
1847
- const deliveryFailed = verification.record?.event === "bundle_response_failed";
1848
- 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";
1849
- phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
1850
- for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
1851
- 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.");
1852
- 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.`));
1853
- else if (verification.processAlive === true && metroPort !== null) {
1854
- const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
1855
- 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.`));
1856
- }
1857
- return { state: LAUNCH_FATAL };
1858
- }
1859
- if (verification?.verified) {
1860
- 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)`);
1861
- const report = launchErrorReport(verification.errors ?? [], root);
1862
- if (report.summary) phase("launch", chalk.dim(report.summary));
1863
- for (const line of report.lines) phase("launch", chalk.yellow(line));
1864
- if (report.lines.length > 0 && verification.processAlive === true && metroPort !== null) {
1865
- const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
1866
- 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.`));
1867
- }
1868
- return {
1869
- state: true,
1870
- 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
1871
- };
1872
- }
1873
- if (verification?.skipped) {
1874
- phase("verify", "skipped (--no-metro-check): the launch is reported as unverified");
1875
- return { state: LAUNCH_UNVERIFIED };
1876
- }
1877
- if (verification?.requested) {
1878
- 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)`);
1879
- phase("", chalk.dim("Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute."));
1880
- return { state: LAUNCH_BUNDLING };
1881
- }
1882
- if (verification?.unattributed) {
1883
- const [headline, ...lines] = unattributedLaunchLines({
1884
- platform: "android",
1885
- metroPort,
1886
- slot,
1887
- siblings
1888
- });
1889
- phase("verify", chalk.yellow(headline));
1890
- for (const line of lines) phase("", chalk.yellow(line));
1891
- return {
1892
- state: LAUNCH_UNVERIFIED,
1893
- unattributed: true
1894
- };
1895
- }
1896
- phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
1897
- for (const line of unverifiedLaunchLines({
1898
- platform: PLATFORM,
1899
- metroPort: metroPort ?? 8081,
1900
- waitedMs: verification?.waitedMs,
1901
- bundleId: androidPackage,
1902
- serial,
1903
- component,
1904
- devClientUrl: scheme ? androidDevClientUrl(scheme, metroPort ?? 8081, physical) : null,
1905
- devClient: Boolean(scheme),
1906
- mode: isExpo ? MODE_EXPO : MODE_BARE
1907
- })) phase("", chalk.yellow(line));
1908
- return { state: LAUNCH_UNVERIFIED };
1909
- }
1910
- const ADOPTED_INSTALL_HEADROOM_BYTES = 536870912;
1911
- const INSUFFICIENT_STORAGE = /INSTALL_FAILED_INSUFFICIENT_STORAGE/;
1912
- async function wipeAdoptedAvd(room, free) {
1913
- 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`));
1914
- const expectedRecord = deviceSlotPlatforms(loadConfig$1()?.projects?.[room.root], room.slot)?.android;
1915
- if (!expectedRecord?.adoptionPending || expectedRecord.avdName !== room.avdName) throw new Error("the emulator assignment changed before the wipe; its data was kept");
1916
- const wiped = room.wipeDevice(room.avdName, {
1917
- wipe: true,
1918
- owner: {
1919
- projectPath: room.root,
1920
- slot: room.slot,
1921
- expectedRecord
1922
- },
1923
- workspace: room.root
1924
- });
1925
- if (wiped.status !== "torn-down") throw new Error(`could not wipe it: ${wiped.reason ?? wiped.status}`);
1926
- const rebooted = await room.rebootDevice();
1927
- if (!rebooted.ok || !rebooted.serial) throw new Error(`it did not boot after its user data was wiped: ${rebooted.reason ?? "no serial"}`);
1928
- if (room.resolveAvdSerial(room.avdName).serial !== rebooted.serial) throw new Error(`${rebooted.serial} is not running ${room.avdName} after the wipe`);
1929
- room.phase("device", `${room.avdName} (${rebooted.serial}) booted with wiped user data`);
1930
- return rebooted.serial;
1931
- }
1932
- async function installOnAdoptedAvd(room, serial, installApk) {
1933
- const neededBytes = 2 * statSync(room.apkPath).size + ADOPTED_INSTALL_HEADROOM_BYTES;
1934
- const roomy = (free) => free === null || free >= neededBytes;
1935
- const outOfSpace = (result) => Boolean(result.failed) && INSUFFICIENT_STORAGE.test(result.reason ?? "");
1936
- let target = serial;
1937
- let wiped = false;
1938
- if (!roomy(room.dataFreeBytes(target))) {
1939
- room.trimCaches(target, neededBytes);
1940
- const free = room.dataFreeBytes(target);
1941
- if (roomy(free)) room.phase("install", `trimmed app caches on ${room.avdName} to make room for the APK`);
1942
- else {
1943
- target = await wipeAdoptedAvd(room, free);
1944
- wiped = true;
1945
- }
1946
- }
1947
- let installed = installApk(target);
1948
- if (wiped || !outOfSpace(installed)) return {
1949
- serial: target,
1950
- installed
1951
- };
1952
- room.trimCaches(target, neededBytes);
1953
- installed = installApk(target);
1954
- if (!outOfSpace(installed)) return {
1955
- serial: target,
1956
- installed
1957
- };
1958
- target = await wipeAdoptedAvd(room, room.dataFreeBytes(target));
1959
- return {
1960
- serial: target,
1961
- installed: installApk(target)
1962
- };
1963
- }
1964
- async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
1965
- if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
1966
- const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
1967
- if (!resolved.serial) throw new Error(`Could not verify a running owned AVD ${device.avdName} before installation.`);
1968
- if (resolved.serial !== serial) {
1969
- phase("device", `${device.avdName} changed serial (${serial} -> ${resolved.serial}); checking readiness before installation`);
1970
- const ready = await waitForDeviceBoot(resolved.serial, 6e4, { commandTimeoutMs: 5e3 });
1971
- if (!ready.ok) throw new Error(`Emulator ${resolved.serial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`);
1972
- 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.`);
1973
- phase("device", `reopen agent-device on ${resolved.serial}`);
1974
- }
1975
- const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
1976
- withConfigLock$1(() => {
1977
- const current = deviceSlotPlatforms(loadConfig$1()?.projects?.[root], slot)?.android;
1978
- if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
1979
- if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
1980
- ...current,
1981
- consolePort
1982
- }, slot);
1983
- });
1984
- return resolved.serial;
1985
- }
1986
- 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, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, recordBuild, now, out, emit, recordRun, reclaimed, devServer, enterPhase, rebootDevice, dataFreeBytes = androidDataFreeBytes, trimCaches = trimAndroidCaches, wipeDevice = teardownOwnedAvd }) {
1987
- let androidPackage = initialPackage;
1988
- let leaseWarned = false;
1989
- const raiseLeaseFor = (boundMs, beforeInstall) => {
1990
- const step = lease?.raise(boundMs);
1991
- if (!step || step.ok) return null;
1992
- if (beforeInstall) {
1993
- const refusal = lostRefusal(step.holder, step.expiresAt, now());
1994
- return fail(refusal.code, refusal.message, refusal.remedy, { lease: refusal.lease });
1995
- }
1996
- if (!leaseWarned) {
1997
- leaseWarned = true;
1998
- phase("lease", chalk.yellow(lostLine(step.holder, step.expiresAt, now())));
1999
- }
2000
- return null;
2001
- };
2002
- enterPhase("device");
2003
- const booted = await bootPromise;
2004
- const runCommand = nativeRunCommand("android", slot, {
2005
- physical,
2006
- deviceId: booted.serial
2007
- });
2008
- if (booted.failed) {
2009
- const diag = diagnoseBootFailure(booted, emuLog, runCommand, physical);
2010
- return fail(diag.code, diag.message, diag.remedy, {
2011
- lines: diag.lines,
2012
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
2013
- });
2014
- }
2015
- let serial;
2016
- try {
2017
- serial = await resolveInstallSerial({
2018
- root,
2019
- slot,
2020
- device,
2021
- physical,
2022
- remoteDevice,
2023
- serial: booted.serial,
2024
- resolveAvdSerial,
2025
- waitForDeviceBoot,
2026
- phase
2027
- });
2028
- } catch (error) {
2029
- 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.`);
2030
- }
2031
- phase("device", physical ? `${device.deviceName || serial} (${serial}) connected, not owned by Stim` : `${device.avdName || serial} (${serial}) ${device.adopted || device.adoptionPending ? "adopted" : "booted"} ${bootDuration()}`);
2032
- const packageFromApk = readApkPackage(apkPath);
2033
- if (packageFromApk && androidPackage && packageFromApk !== androidPackage) phase("install", chalk.dim(`applicationId ${packageFromApk} (from the APK; project files say ${androidPackage})`));
2034
- androidPackage = packageFromApk || androidPackage || detectAndroidPackage(root);
2035
- const adopting = !physical && !remoteDevice && Boolean(device.adoptionPending);
2036
- if (adopting) {
2037
- 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.");
2038
- try {
2039
- await resetAdoptedAvd(device.avdName, serial, androidPackage);
2040
- } catch (error) {
2041
- 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.");
2042
- }
2043
- }
2044
- const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
2045
- if (lostBeforeInstall) return lostBeforeInstall;
2046
- enterPhase("install");
2047
- let installTimer = stepTimer(now);
2048
- const installApk = (target) => {
2049
- installTimer = stepTimer(now);
2050
- return install({
2051
- serial: target,
2052
- apkPath,
2053
- packageName: androidPackage,
2054
- allowUninstall: release || adopting
2055
- });
2056
- };
2057
- let installed;
2058
- if (adopting && device.avdName && apkPath) {
2059
- const room = {
2060
- root,
2061
- slot,
2062
- avdName: device.avdName,
2063
- apkPath,
2064
- phase,
2065
- rebootDevice,
2066
- dataFreeBytes,
2067
- trimCaches,
2068
- wipeDevice,
2069
- resolveAvdSerial
2070
- };
2071
- try {
2072
- ({serial, installed} = await installOnAdoptedAvd(room, serial, installApk));
2073
- } catch (error) {
2074
- 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.`);
2075
- }
2076
- } else installed = installApk(serial);
2077
- if (installed.failed) {
2078
- const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
2079
- reason: installed.reason,
2080
- packageName: androidPackage,
2081
- serial,
2082
- useBuildCache
2083
- });
2084
- return fail(installed.code || "STIM_INSTALL_FAILED", installed.reason, installRemedy, { lastBuildStatus: true });
2085
- }
2086
- if (adopting) try {
2087
- withConfigLock$1(() => {
2088
- const config = loadConfig$1();
2089
- const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
2090
- if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
2091
- delete current.adoptionPending;
2092
- saveConfig(config);
2093
- });
2094
- } catch (error) {
2095
- 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.");
2096
- }
2097
- const installSkipped = Boolean(installed.skipped);
2098
- phase("install", installSkipped ? `unchanged (${serial} already has this build) ${installTimer()}` : `${basename(apkPath)} -> ${serial} ${installTimer()}`);
2099
- if (installed.note) {
2100
- phase("install", chalk.yellow(installed.note));
2101
- writer.write({
2102
- src: "build",
2103
- level: "warn",
2104
- event: "install_uninstalled_first",
2105
- msg: installed.note
2106
- });
2107
- }
2108
- if (swapDir) try {
2109
- rmSync(swapDir, {
2110
- recursive: true,
2111
- force: true
2112
- });
2113
- } catch {}
2114
- if (androidPackage) upsertProject(root, { androidPackage });
2115
- record.bundleId = androidPackage;
2116
- if (!androidPackage) return fail(LAUNCH_FAILED, "Could not determine this app's Android package name, so there is nothing to launch.", "Set `expo.android.package` in app.json / app.config.js, or `namespace` in android/app/build.gradle.", { lastBuildStatus: true });
2117
- if (physical) raiseLeaseFor(0, false);
2118
- const scheme = runtime.scheme(root, apkPath, resolveDevClientScheme);
2119
- enterPhase("launch");
2120
- const launchTimer = stepTimer(now);
2121
- const launchedAt = now();
2122
- writer.write({
2123
- src: "build",
2124
- level: "info",
2125
- marker: true,
2126
- event: "launch_attempt",
2127
- ts: launchedAt,
2128
- platform: "android",
2129
- appId: androidPackage,
2130
- deviceId: serial,
2131
- remote: Boolean(remoteDevice),
2132
- physical,
2133
- msg: `launching ${androidPackage} on ${serial}`
2134
- });
2135
- const launched = await runtime.launch(runtimePreparation, {
2136
- serial,
2137
- androidPackage,
2138
- scheme,
2139
- physical,
2140
- remoteDevice,
2141
- launch,
2142
- launchRelease
2143
- });
2144
- if (launched.failed) {
2145
- printNativeCrashReport({
2146
- root,
2147
- slot,
2148
- platform: "android",
2149
- deviceId: serial,
2150
- appId: androidPackage,
2151
- since: launchedAt
2152
- }, logsDir, (line) => phase("launch", chalk.red(line)), Boolean(remoteDevice));
2153
- return fail(launched.code || "STIM_LAUNCH_FAILED", launched.reason, launchRemedy(remoteDevice, serial, androidPackage), { lastBuildStatus: true });
2154
- }
2155
- writer.write({
2156
- src: "build",
2157
- level: "info",
2158
- event: "app_launched",
2159
- msg: (release ? `launched ${androidPackage} on ${serial} (${variant}, embedded JS bundle, no Metro)` : `launched ${androidPackage} on ${serial} against Metro port ${metroPort}`) + restartedAppNote(launched.restartedPid, "; ")
2160
- });
2161
- phase("launch", `${androidPackage}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
2162
- if (!physical && !remoteDevice) try {
2163
- writeLaunch(root, "android", {
2164
- appId: androidPackage,
2165
- deviceId: serial,
2166
- metroPort,
2167
- release,
2168
- launchedAt: new Date(launchedAt).toISOString()
2169
- });
2170
- } catch (error) {
2171
- out(phaseLine("state", chalk.yellow(`could not record Android launch: ${error?.message || error}`)));
2172
- }
2173
- if (!release) reportMetroRoute({
2174
- launched,
2175
- remote: Boolean(remoteDevice),
2176
- metroPort,
2177
- androidPackage,
2178
- serial,
2179
- phase,
2180
- writer
2181
- });
2182
- if (launched.devClientNote) {
2183
- phase("metro", chalk.yellow(launched.devClientNote));
2184
- writer.write({
2185
- src: "build",
2186
- level: "warn",
2187
- event: "dev_client_link_failed",
2188
- msg: launched.devClientNote
2189
- });
2190
- }
2191
- const uploadWasAbandoned = await finishAndroidUpload(uploadPending, remote, phase);
2192
- const providerOutcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
2193
- if (providerOutcome) phase("cache", providerOutcome.warn ? chalk.yellow(providerOutcome.line) : providerOutcome.line);
2194
- persistLastBuild({
2195
- recordBuild,
2196
- root,
2197
- record,
2198
- startedAt,
2199
- durationMs: now() - started,
2200
- status: "ok",
2201
- out
2202
- });
2203
- const remoteRelease = Boolean(remoteDevice && release);
2204
- if (remoteDevice) {
2205
- killPreviousCollector(root, {
2206
- slot,
2207
- kill,
2208
- isAlive: pidAlive,
2209
- verify: verifyCollector,
2210
- note: (line) => out(phaseLine("logs", line))
2211
- });
2212
- writer.write({
2213
- src: "build",
2214
- level: "info",
2215
- event: "collector_skipped",
2216
- msg: `remote session ${serial}: device logs come from agent-device/EAS, so no adb logcat collector is attached`
2217
- });
2218
- } else {
2219
- if (physical) raiseLeaseFor(0, false);
2220
- const collectorPid = await startCollector({
2221
- root,
2222
- slot,
2223
- serial,
2224
- packageName: androidPackage,
2225
- spawn,
2226
- kill,
2227
- alive: pidAlive,
2228
- verify: verifyCollector,
2229
- out
2230
- });
2231
- phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
2232
- }
2233
- if (physical) raiseLeaseFor(runtime.verificationWaitMs, false);
2234
- const { state: launchState, warning: launchWarning, unattributed } = await runtime.verify(runtimePreparation, {
2235
- root,
2236
- slot,
2237
- remoteDevice: Boolean(remoteDevice),
2238
- verifyReleaseLaunched,
2239
- verifyLaunched,
2240
- serial,
2241
- androidPackage,
2242
- variant,
2243
- metroCheck,
2244
- logsDir,
2245
- launchedAt,
2246
- metroPort,
2247
- isExpo,
2248
- physical,
2249
- device,
2250
- scheme,
2251
- component: launched.component ?? null,
2252
- phase
2253
- });
2254
- 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}.`, {
2255
- lastBuildStatus: true,
2256
- logPath: displayPath(root, logsDir)
2257
- });
2258
- writer.write(launchOutcomeRecord({
2259
- launchState,
2260
- release,
2261
- bundleId: androidPackage,
2262
- configuration: variant,
2263
- metroPort,
2264
- unattributed
2265
- }));
2266
- const leaseFacts = lease?.facts() ?? null;
2267
- releaseLease();
2268
- const facts = reportAndroidResult({
2269
- root,
2270
- slot,
2271
- json,
2272
- useBuildCache,
2273
- variant,
2274
- release,
2275
- metroCheck,
2276
- metroPort,
2277
- logsDir: remoteRelease ? null : logsDir,
2278
- serial,
2279
- apkPath,
2280
- androidPackage,
2281
- installSkipped,
2282
- record,
2283
- waitedForBuild,
2284
- remote,
2285
- providerName,
2286
- launchState,
2287
- launchWarning,
2288
- launched,
2289
- ccache,
2290
- durationMs: now() - started,
2291
- writer,
2292
- emit,
2293
- lease: physical ? leaseFacts : void 0,
2294
- recordRun,
2295
- reclaimed,
2296
- devServer,
2297
- links: workspaceLinks(root, {
2298
- platform: "android",
2299
- slot
2300
- })
2301
- });
2302
- if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
2303
- return {
2304
- ok: true,
2305
- facts
2306
- };
2307
- }
2308
- function localInstallRemedy({ reason, packageName, serial, useBuildCache }) {
2309
- const conflict = installConflictKind(reason);
2310
- if (!conflict) return `Check that ${serial} is still connected (\`adb devices\`) and has room for the APK.`;
2311
- const rerun = useBuildCache ? " Then run this command again; it installs the APK from cache without building it again." : " Then run this command again.";
2312
- 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;
2313
- }
2314
- function launchRemedy(remoteDevice, serial, packageName) {
2315
- return remoteDevice?.failureRemedy() ?? `Check the app installed correctly (\`adb -s ${serial} shell pm list packages ${packageName}\`).`;
2316
- }
2317
- function reportMetroRoute({ launched, remote, metroPort, androidPackage, serial, phase, writer }) {
2318
- if (remote) {
2319
- phase("metro", `public origin ${launched.jsLocation}`);
2320
- return;
2321
- }
2322
- const reversedSummary = (launched.reversed ?? []).join(", ") || `tcp:${metroPort}->tcp:${metroPort}`;
2323
- if (launched.debugHttpHost) {
2324
- phase("metro", `debug_http_host ${launched.debugHttpHost} + adb reverse ${reversedSummary}`);
2325
- return;
2326
- }
2327
- phase("metro", chalk.yellow(`adb reverse ${reversedSummary}; ${launched.debugHttpHostNote || "debug_http_host not written"}`));
2328
- writer.write({
2329
- src: "build",
2330
- level: "warn",
2331
- event: "debug_http_host_failed",
2332
- msg: `debug_http_host was not written for ${androidPackage} on ${serial}: ${launched.debugHttpHostNote || "unknown reason"}`
2333
- });
2334
- }
2335
- function diagnoseBootFailure(booted, logFile, runCommand, physical) {
2336
- if (booted.code) return {
2337
- code: booted.code,
2338
- message: booted.reason ?? "The emulator did not boot.",
2339
- remedy: booted.remedy ?? null,
2340
- lines: [],
2341
- logPath: null
2342
- };
2343
- const diag = noDeviceDiagnostic({
2344
- reason: booted.reason ?? "The emulator did not boot.",
2345
- logFile,
2346
- remedy: booted.remedy ?? `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
2347
- localEmulator: !physical
2348
- });
2349
- return {
2350
- code: NO_DEVICE,
2351
- ...diag
2352
- };
2353
- }
2354
- //#endregion
2355
- //#region src/commands/android/plan.ts
2356
- function slotOwnedAvd(projectPath, slot) {
2357
- const android = deviceSlotPlatforms(getProject(projectPath), slot)?.android;
2358
- if (!android?.owned || !android.avdName || android.setupIncomplete) return null;
2359
- const systemImage = ownedAvdSystemImage(android.avdName);
2360
- return systemImage ? {
2361
- avdName: android.avdName,
2362
- systemImage,
2363
- deviceProfile: ownedAvdDeviceProfile(android.avdName)
2364
- } : null;
2365
- }
2366
- function fail(code, message, remedy, { lines = [] } = {}) {
2367
- return {
2368
- ok: false,
2369
- code,
2370
- message,
2371
- remedy,
2372
- lines
2373
- };
2374
- }
2375
- function androidCompilerCache({ root, optimizations, settingsContext }) {
2376
- const { cas, optimizations: resolved } = resolveAndroidCompilerCache({
2377
- optimizations,
2378
- use: (manifest) => resolveAndroidCas(root, {
2379
- ...process.env,
2380
- STIM_ANDROID_CAS_TOOLCHAIN: manifest
2381
- })
2382
- });
2383
- const fallback = resolved.android.compilerCacheFallback;
2384
- if (!fallback) return {
2385
- cas,
2386
- optimizations: resolved,
2387
- warning: null
2388
- };
2389
- return {
2390
- cas,
2391
- optimizations: resolved,
2392
- warning: `Warning: ${compilerCacheFallbackMessage({
2393
- fallback,
2394
- compilerCache: resolved.android.compilerCache === "none" ? "none" : "ccache",
2395
- file: settingFile(settingsContext, fallback.key)
2396
- })}`
2397
- };
2398
- }
2399
- function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, variant: variantFlag, systemImage: systemImageFlag, deviceProfile: deviceProfileFlag, device: deviceFlag, wait: waitFlag, waitConflict, remote: commandRemoteBackend, buildCache: requestedBuildCache }, { warn, resolveCompilerCache = androidCompilerCache, resolveCacheProvider = resolveCacheProviderConfig, validateAvdConfig = androidAvdConfigSettingError, readFlavors = readProductFlavors, detectExpo = detectIsExpo, listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, parkedLimit = parkedMaxSetting, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd }) {
2400
- const root = settingsContext.projectPath;
2401
- const [shapeError, ...moreShapeErrors] = [parkedLimit("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
2402
- if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
2403
- for (const key of unknownSettingKeys(settings)) warn("setting", `Warning: setting "${key}" is not read by Stim and will be ignored.`);
2404
- let optimizations;
2405
- try {
2406
- optimizations = resolveOptimizations(settings);
2407
- } catch (error) {
2408
- return fail("STIM_BAD_ARG", `Could not configure Android build: ${error.message}`, SETTING_SHAPE_REMEDY);
2409
- }
2410
- const compilerCache = resolveCompilerCache({
2411
- root,
2412
- optimizations,
2413
- settingsContext
2414
- });
2415
- const cas = compilerCache.cas;
2416
- optimizations = compilerCache.optimizations;
2417
- if (compilerCache.warning) warn("cache", compilerCache.warning);
2418
- const buildProfile = optimizationBuildProfile("android", optimizations);
2419
- const cacheProviderConfig = resolveCacheProvider(settingsContext);
2420
- const cacheProviderError = cacheProviderSettingError(settings);
2421
- if (cacheProviderError) warn("cache", `${cacheProviderError} Using the local cache.`);
2422
- const dataPartitionSizeError = androidDataPartitionSizeGbSettingError(settings);
2423
- if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
2424
- const avdConfigError = validateAvdConfig(settings, root);
2425
- if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
2426
- const systemImage = resolveSystemImage(systemImageFlag, settings);
2427
- const deviceProfile = resolveDeviceProfile(deviceProfileFlag, settings);
2428
- const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
2429
- const flavorRefusal = productFlavorRefusal({
2430
- flavors: readFlavors(root),
2431
- variant
2432
- });
2433
- if (flavorRefusal) return fail(flavorRefusal.code, flavorRefusal.reason, flavorRefusal.remedy);
2434
- const release = isReleaseVariant(variant);
2435
- const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
2436
- const isExpo = detectExpo(root);
2437
- const physical = isPhysicalDeviceRequest(deviceFlag);
2438
- if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
2439
- if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
2440
- const noWait = waitFlag === false;
2441
- if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass only one of `--wait <seconds>` and `--no-wait`.");
2442
- const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
2443
- if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a busy lease or device slot at once.");
2444
- const waitSeconds = waitParsed.seconds;
2445
- const remoteTarget = commandRemoteBackend !== null ? parseIosRemote(commandRemoteBackend) : remoteAndroidSetting(settings);
2446
- if (physical && remoteTarget?.kind === "machine") return fail("STIM_BAD_ARG", "--device installs on a phone connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote; unset android.remote for a local phone.");
2447
- const machine = remoteTarget?.kind === "machine" ? remoteTarget.machine : null;
2448
- const remoteBackend = !physical && remoteTarget?.kind === "backend" ? remoteTarget.backend : null;
2449
- if (!machine) {
2450
- const refusal = androidDeviceSelectorRefusal({
2451
- settingsContext,
2452
- slot,
2453
- systemImageFlag,
2454
- deviceProfileFlag,
2455
- systemImage,
2456
- deviceProfile,
2457
- physical,
2458
- remoteBackend
2459
- }, {
2460
- listSystemImages,
2461
- listDeviceProfiles,
2462
- supportsFold,
2463
- ownedAvd
2464
- });
2465
- if (refusal) return refusal;
2466
- }
2467
- const target = physical ? {
2468
- kind: "physical",
2469
- serial: typeof deviceFlag === "string" ? deviceFlag : null,
2470
- lease: {
2471
- waitSeconds,
2472
- noWait
2473
- }
2474
- } : machine ? {
2475
- kind: "hosted",
2476
- machine,
2477
- systemImage,
2478
- deviceProfile
2479
- } : remoteBackend ? {
2480
- kind: "remote",
2481
- backend: remoteBackend,
2482
- systemImage,
2483
- deviceProfile
2484
- } : {
2485
- kind: "emulator",
2486
- systemImage,
2487
- deviceProfile
2488
- };
2489
- return {
2490
- ok: true,
2491
- plan: {
2492
- build: {
2493
- variant,
2494
- release,
2495
- profile: buildProfile,
2496
- cas,
2497
- cache: cachePolicy,
2498
- compilerCache: optimizations.android.compilerCache,
2499
- gradleBuildCache: optimizations.android.gradleBuildCache,
2500
- pch: optimizations.android.pch,
2501
- targetAbiOnly: optimizations.android.targetAbiOnly
2502
- },
2503
- target,
2504
- deviceSlotWaitMs: noWait ? 0 : waitFlag === void 0 && !physical ? DEFAULT_DEVICE_SLOT_WAIT_MS : waitSeconds * 1e3,
2505
- isExpo,
2506
- metroWarmup: optimizations.metroWarmup,
2507
- cacheProviderConfig
2508
- }
2509
- };
2510
- }
2511
- function androidDeviceSelectorRefusal({ settingsContext, slot, systemImageFlag, deviceProfileFlag, systemImage, deviceProfile, physical, remoteBackend }, { listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd } = {}) {
2512
- const root = settingsContext.projectPath;
2513
- const settingsLayersForOrigin = settingsLayers(settingsContext);
2514
- const imageRefusal = systemImageRefusal({
2515
- slot,
2516
- flag: systemImageFlag,
2517
- resolved: systemImage,
2518
- origin: settingOriginScope(settingsLayersForOrigin, "android.systemImage"),
2519
- physical,
2520
- remoteBackend,
2521
- listImages: listSystemImages
2522
- });
2523
- if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
2524
- const profileRefusal = deviceProfileRefusal({
2525
- flag: deviceProfileFlag,
2526
- resolved: deviceProfile,
2527
- origin: settingOriginScope(settingsLayersForOrigin, "android.deviceProfile"),
2528
- physical,
2529
- remoteBackend,
2530
- listProfiles: listDeviceProfiles
2531
- });
2532
- if (profileRefusal) return fail(profileRefusal.code, profileRefusal.message, profileRefusal.remedy);
2533
- const avdFlagRefusal = remoteAvdFlagRefusal({
2534
- systemImageFlag,
2535
- deviceProfileFlag,
2536
- remoteBackend
2537
- });
2538
- if (avdFlagRefusal) return fail(avdFlagRefusal.code, avdFlagRefusal.message, avdFlagRefusal.remedy);
2539
- if (!physical && !remoteBackend) {
2540
- const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
2541
- const existing = ownedAvd(root, slot);
2542
- const profile = deviceProfile ?? existing?.deviceProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
2543
- const image = () => flagImage ?? existing?.systemImage ?? systemImage ?? pickDefaultSystemImage(listSystemImages())?.pkg ?? null;
2544
- const checked = profileNeedsFoldFeature(profile) ? image() : null;
2545
- const foldRefusal = checked !== null && foldableImageRefusal({
2546
- profile,
2547
- image: checked,
2548
- avdName: existing && existing.systemImage === checked && existing.deviceProfile === profile ? existing.avdName : null,
2549
- images: listSystemImages,
2550
- supportsFold
2551
- });
2552
- if (foldRefusal) return fail(foldRefusal.code, foldRefusal.message, foldRefusal.remedy);
2553
- }
2554
- return null;
2555
- }
2556
- //#endregion
2557
- //#region src/commands/android/next-build.ts
2558
- const DEFAULT_PLAN_DEPS = {
2559
- findRoot: findProjectRoot,
2560
- fingerprint: fingerprintProject,
2561
- listSystemImages: listInstalledSystemImages,
2562
- avdSystemImage: ownedAvdSystemImage,
2563
- avdDirectory: ownedAvdDirectory,
2564
- listAvds,
2565
- loadCacheProvider,
2566
- loadProjectProvider,
2567
- checkEasAuth,
2568
- resolveRemote,
2569
- planPrebuild
2570
- };
2571
- function note(line) {
2572
- console.error(line);
2573
- }
2574
- function runOnlyFlag(opts) {
2575
- if (isPhysicalDeviceRequest(opts.device)) return "--device";
2576
- if (opts.remote) return "--remote";
2577
- if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
2578
- if (opts.metroCheck === false) return "--no-metro-check";
2579
- return null;
2580
- }
2581
- function emulatorImage(root, slot, requested, deps) {
2582
- const record = deviceSlotPlatforms(getProject(root), slot)?.android;
2583
- if (record?.avdName && !record.setupIncomplete) {
2584
- if (record.owned && deps.avdDirectory(record.avdName)) return { systemImage: deps.avdSystemImage(record.avdName) };
2585
- if (!record.owned && avdListed(record.avdName, deps.listAvds)) return { systemImage: null };
2586
- }
2587
- let images;
2588
- try {
2589
- images = deps.listSystemImages();
2590
- } catch (error) {
2591
- return { refusal: {
2592
- code: NO_DEVICE,
2593
- message: `Could not read the installed Android system images: ${error?.message || error}`,
2594
- remedy: "Check that ANDROID_HOME points at a readable SDK, then try again."
2595
- } };
2596
- }
2597
- const picked = pickDefaultSystemImage(images, requested ? { systemImage: requested } : {});
2598
- if (picked) return { systemImage: picked.pkg };
2599
- const arch = hostSystemImageArch();
2600
- return { refusal: {
2601
- code: NO_DEVICE,
2602
- message: `No ${arch} Android system image is installed, so the emulator build's ABI and cache key are unknown.`,
2603
- remedy: `Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}", then try again.`
2604
- } };
2605
- }
2606
- function avdListed(avdName, list) {
2607
- try {
2608
- return list({ timeoutMs: 1e4 }).includes(avdName);
2609
- } catch {
2610
- return false;
2611
- }
2612
- }
2613
- async function planAndroid(opts, overrides = {}) {
2614
- const deps = {
2615
- ...DEFAULT_PLAN_DEPS,
2616
- ...overrides
2617
- };
2618
- const json = Boolean(opts.json);
2619
- const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
2620
- const flag = runOnlyFlag(opts);
2621
- if (flag) return refuse(planFlagRefusal(flag));
2622
- const root = deps.findRoot(process.cwd());
2623
- if (!root) return refuse(NO_PROJECT_REFUSAL);
2624
- const problem = projectProblem(root, "android");
2625
- if (problem) return refuse({
2626
- code: "STIM_NO_PROJECT",
2627
- ...problem
2628
- });
2629
- const slot = validateDeviceSlot(opts.slot);
2630
- const settingsContext = {
2631
- projectPath: root,
2632
- gitCommonDir: gitCommonDir(root),
2633
- repoRoot: repoRoot(root)
2634
- };
2635
- const settings = resolveSettings(settingsContext);
2636
- const planned = resolveAndroidRunPlan({
2637
- settings,
2638
- settingsContext,
2639
- slot,
2640
- easProfile: opts.easProfile,
2641
- variant: opts.variant ?? null,
2642
- systemImage: opts.systemImage ?? null,
2643
- device: null,
2644
- wait: void 0,
2645
- waitConflict: false,
2646
- remote: null,
2647
- buildCache: opts.buildCache !== false
2648
- }, {
2649
- warn: (label, message) => note(phaseLine(label, chalk.yellow(message))),
2650
- resolveCompilerCache: ({ optimizations }) => ({
2651
- cas: null,
2652
- optimizations,
2653
- warning: null
2654
- }),
2655
- listSystemImages: deps.listSystemImages
2656
- });
2657
- if (!planned.ok) return refuse(planned, planned.lines);
2658
- const { build, target, isExpo, cacheProviderConfig } = planned.plan;
2659
- const planTarget = {
2660
- platform: PLATFORM,
2661
- slot,
2662
- root,
2663
- projectKey: statsProjectKey$1({
2664
- root,
2665
- commonDir: settingsContext.gitCommonDir,
2666
- repoRoot: settingsContext.repoRoot
2667
- })
2668
- };
2669
- if (opts.easProfile !== void 0) {
2670
- const eas = await planEasDevelopmentBuild({
2671
- root,
2672
- platform: PLATFORM,
2673
- profile: opts.easProfile,
2674
- note,
2675
- isExpo,
2676
- selectors: [opts.variant],
2677
- buildCache: opts.buildCache
2678
- });
2679
- if (!eas.ok) return refuse(eas);
2680
- return printPlan(planPayload(planTarget, {
2681
- fingerprint: eas.fingerprint,
2682
- cacheKey: eas.cacheKey,
2683
- cacheHit: eas.buildId ? "remote" : false,
2684
- provider: "eas",
2685
- cacheSkipped: false,
2686
- prebuild: null,
2687
- refusal: eas.missing
2688
- }), json);
2689
- }
2690
- if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
2691
- if (target.kind === "remote") return refuse({
2692
- code: "STIM_BAD_ARG",
2693
- message: `android.remote is ${target.backend}, whose ABI --plan cannot read without a session.`,
2694
- remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
2695
- });
2696
- if (build.compilerCache === "cas") return refuse({
2697
- code: "STIM_BAD_ARG",
2698
- message: "The experimental Android compiler CAS keys builds by a toolchain Stim sets up, which --plan does not.",
2699
- remedy: "Run `stim android` to build with the CAS, or set optimizations.android.compilerCache to ccache."
2700
- });
2701
- const abiKeyed = build.targetAbiOnly && !build.release;
2702
- const localImage = () => abiKeyed ? emulatorImage(root, slot, target.systemImage, deps) : { systemImage: null };
2703
- let image;
2704
- let hostedAbi = null;
2705
- let placement;
2706
- if (target.kind === "hosted") {
2707
- const hostedPlan = await planHostedDevice({
2708
- root,
2709
- slot,
2710
- platform: "android",
2711
- machine: target.machine,
2712
- selectors: {
2713
- ...target.systemImage ? { systemImage: target.systemImage } : {},
2714
- ...target.deviceProfile ? { deviceProfile: target.deviceProfile } : {}
2715
- },
2716
- sameKey: (choice) => {
2717
- if (build.targetAbiOnly && build.release) return !("architecture" in choice && choice.architecture);
2718
- if (!abiKeyed) return true;
2719
- const local = localImage();
2720
- if ("refusal" in local || !("systemImage" in choice)) return null;
2721
- return androidSystemImageAbi(local.systemImage) === androidSystemImageAbi(choice.systemImage);
2722
- },
2723
- ...remoteEasFallbackSetting(settings) ? { eas: () => checkEasFallback({
2724
- root,
2725
- platform: "android",
2726
- slot,
2727
- release: build.release,
2728
- isExpo,
2729
- tunnelMode: tunnelModeSetting(settings),
2730
- publicUrl: publicUrlSetting(settings),
2731
- localOnlyFlags: typeof opts.systemImage === "string" ? ["--system-image"] : []
2732
- }) } : {}
2733
- });
2734
- if (hostedPlan.kind === "unknown") return refuse({
2735
- code: "STIM_BAD_ARG",
2736
- message: `android.remote is ${target.machine}, and its emulator ABI is unknown here: ${hostedPlan.reason}.`,
2737
- remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
2738
- });
2739
- placement = hostedPlan.placement;
2740
- if (hostedPlan.kind === "hosted" && "architecture" in hostedPlan.choice) hostedAbi = hostedPlan.choice.architecture ?? null;
2741
- image = hostedPlan.kind === "hosted" ? { systemImage: abiKeyed && "systemImage" in hostedPlan.choice ? hostedPlan.choice.systemImage : null } : localImage();
2742
- } else image = localImage();
2743
- if ("refusal" in image) return refuse(image.refusal);
2744
- const { abi, runOptions, remoteRunOptions } = androidBuildOptions({
2745
- release: build.release,
2746
- physical: false,
2747
- device: { systemImage: image.systemImage },
2748
- variant: build.variant,
2749
- deviceAbi: () => null,
2750
- buildProfile: build.profile,
2751
- targetAbiOnly: build.targetAbiOnly,
2752
- hostedAbi
2753
- });
2754
- let fingerprint;
2755
- try {
2756
- fingerprint = await deps.fingerprint(root, { platform: PLATFORM });
2757
- } catch (error) {
2758
- return refuse({
2759
- code: NO_FINGERPRINT,
2760
- message: `@expo/fingerprint could not fingerprint ${root}: ${error?.message || error}`,
2761
- remedy: "Fix the @expo/fingerprint error above, then retry."
2762
- });
2763
- }
2764
- if (!fingerprint?.hash) return refuse({
2765
- code: NO_FINGERPRINT,
2766
- message: `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`,
2767
- remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
2768
- });
2769
- const plan = await planCachedBuild({
2770
- ...planTarget,
2771
- isExpo,
2772
- fingerprint: fingerprint.hash,
2773
- sources: fingerprint.sources ?? [],
2774
- cacheKey: buildCacheKey(PLATFORM, fingerprint.hash, runOptions),
2775
- cachePolicy: build.cache,
2776
- providerConfig: cacheProviderConfig,
2777
- expoRemote: abi || build.profile || !build.cache.remote ? null : { runOptions: remoteRunOptions }
2778
- }, {
2779
- ...deps,
2780
- note
2781
- });
2782
- printPlan(placement ? {
2783
- ...plan,
2784
- placement
2785
- } : plan, json);
2786
- }
2787
- //#endregion
2788
- //#region src/commands/android.ts
2789
- function androidCommand(program) {
2790
- registerAndroid(program);
2791
- }
2792
- function registerAndroid(program) {
2793
- program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--remote-build <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--no-metro-check", "Skip the reserved-port Metro health check and do not start it (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device-profile <id>", "Hardware profile to create this workspace's owned AVD with, as an `avdmanager list device -c` id (e.g. \"pixel_fold\", \"pixel_tablet\"). Overrides the android.deviceProfile setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the available ids.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or a named approved Mac from remote.machines; auto places on an approved Mac when this Mac is full or busy; local runs here, whatever android.remote says", (value) => {
2794
- if (!parseMachine(value)) throw new InvalidArgumentError("expected eas, proxy, auto, local, or a tailnet machine name");
2795
- return value;
2796
- }).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Without --device, wait for an owned-device slot (default 600 seconds; --no-wait refuses immediately).").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Without --device, refuse a full device cap or existing slot queue immediately.").action(async (opts) => {
2797
- if (opts.plan) return planAndroid(opts);
2798
- const root = findProjectRoot(process.cwd());
2799
- if (!root) {
2800
- refuseNoProject({ json: Boolean(opts.json) });
2801
- return;
2802
- }
2803
- if (!(await runAndroidOperation(root, {
2804
- ...opts,
2805
- waitConflict: waitFlagConflict(process.argv)
2806
- })).ok) process.exit(runCancellation() ? 130 : 1);
2807
- });
2808
- }
2809
- function runAndroidOperation(root, opts = {}) {
2810
- return withNativeBuildRun(root, {
2811
- command: "android",
2812
- platform: PLATFORM,
2813
- slot: opts.slot ?? "default"
2814
- }, async (claim) => {
2815
- recordWorkspaceUse(root);
2816
- const progress = startBuildProgress({
2817
- root,
2818
- platform: PLATFORM,
2819
- slot: opts.slot ?? "default",
2820
- claim,
2821
- note: (line) => console.error(chalk.dim(line))
2822
- });
2823
- try {
2824
- return await runAndroid({
2825
- root,
2826
- buildMachine: opts.remoteBuild,
2827
- slot: opts.slot,
2828
- easProfile: opts.easProfile,
2829
- json: Boolean(opts.json),
2830
- metroCheck: opts.metroCheck !== false,
2831
- useBuildCache: opts.buildCache !== false,
2832
- variant: opts.variant ?? null,
2833
- systemImage: opts.systemImage ?? null,
2834
- deviceProfile: opts.deviceProfile ?? null,
2835
- remoteDevice: opts.remote ?? null,
2836
- device: opts.device ?? null,
2837
- wait: opts.wait,
2838
- waitConflict: opts.waitConflict,
2839
- progress
2840
- });
2841
- } finally {
2842
- progress.clear();
2843
- }
2844
- }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) });
2845
- }
2846
- function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, deviceProfile: deviceProfileFlag, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, listDeviceProfiles, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState$1, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, fingerprint = fingerprintProject, untracked = untrackedNativeFiles, resolveCached = resolveBuild, storeCached = storeBuild, storedAssets = storedAssetManifest, captureAssets = captureAssetManifest, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, loadProvider = loadProjectProvider, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, planPrebuildFor = planPrebuild, prebuild = runPrebuild, build = buildAndroid, ccacheFor = resolveCcache, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, swapApk = swapApkBundle, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordBuild, recordStats = recordRunStats, recordCapacityRefusal: recordRefusal, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
2847
- return {
2848
- root,
2849
- json,
2850
- commandRemoteBackend,
2851
- resolveSettingsFor,
2852
- resolveRemoteDeviceContext,
2853
- makeRemoteDeviceDeps,
2854
- resolveEasBin,
2855
- ensureRemoteMetro,
2856
- ensureRemoteOwned,
2857
- detectRemoteProviders,
2858
- metroCheck,
2859
- useBuildCache,
2860
- easProfile,
2861
- resolveEasBuild,
2862
- variantFlag,
2863
- systemImageFlag,
2864
- deviceProfileFlag,
2865
- deviceFlag,
2866
- waitFlag,
2867
- waitConflict,
2868
- acquireLease,
2869
- makeRunLease,
2870
- selectPool,
2871
- onLeaseSignal,
2872
- listDevices,
2873
- deviceModel,
2874
- deviceAbi,
2875
- isEmulatorDevice,
2876
- readApkPackage,
2877
- getLimits,
2878
- checkCapacity,
2879
- acquireSlot,
2880
- releaseSlot,
2881
- ensureDevice,
2882
- listSystemImages,
2883
- listDeviceProfiles,
2884
- ensureDeviceBooted,
2885
- resolveAvdSerial,
2886
- waitForDeviceBoot,
2887
- dataFreeBytes,
2888
- trimCaches,
2889
- wipeDevice,
2890
- resolveMetro,
2891
- prewarmMetro,
2892
- startServer,
2893
- readState,
2894
- pidAlive,
2895
- verifyCollector,
2896
- verifyLaunched,
2897
- ensureStorage,
2898
- fingerprint,
2899
- untracked,
2900
- resolveCached,
2901
- storeCached,
2902
- storedAssets,
2903
- captureAssets,
2904
- acquireLock,
2905
- releaseLock,
2906
- waitForBuild: waitForBuild$1,
2907
- loadProvider,
2908
- easAuth,
2909
- resolveRemoteBuild,
2910
- uploadRemoteBuild,
2911
- resolveCacheProvider,
2912
- loadCacheProviderModule,
2913
- planPrebuildFor,
2914
- prebuild,
2915
- build,
2916
- ccacheFor,
2917
- install,
2918
- launch,
2919
- launchRelease,
2920
- verifyReleaseLaunched,
2921
- swapApk,
2922
- resolveDevClientScheme,
2923
- spawn,
2924
- kill,
2925
- createWriter,
2926
- writeLaunch,
2927
- writeState,
2928
- recordBuild,
2929
- recordStats,
2930
- recordCapacityRefusal: recordRefusal,
2931
- readEstimates,
2932
- now,
2933
- out,
2934
- emit
2935
- };
2936
- }
2937
- function androidSlotOptions(options) {
2938
- const base = resolveRunAndroidOptions(options);
2939
- const slot = validateDeviceSlot(options.slot);
2940
- if (slot === "default") return base;
2941
- return {
2942
- ...base,
2943
- ensureDevice: (args) => base.ensureDevice({
2944
- ...args,
2945
- slot
2946
- }),
2947
- ensureDeviceBooted: (args) => base.ensureDeviceBooted({
2948
- ...args,
2949
- slot
2950
- }),
2951
- checkCapacity: (args) => base.checkCapacity({
2952
- ...args,
2953
- slot
2954
- }),
2955
- selectPool: (args) => base.selectPool({
2956
- ...args,
2957
- slot
2958
- }),
2959
- acquireLease: (args) => base.acquireLease({
2960
- ...args,
2961
- slot
2962
- }),
2963
- makeRunLease: (args) => base.makeRunLease({
2964
- ...args,
2965
- slot
2966
- }),
2967
- writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
2968
- };
2969
- }
2970
- function avdSetupFailure(error, root, logFile) {
2971
- const refusal = (0, ownership_claim_exports.claimFailure)(error, "stim android");
2972
- if (refusal) return refusal;
2973
- if (error instanceof DeviceAdmissionRefusal) return {
2974
- code: error.code,
2975
- message: error.message,
2976
- remedy: error.remedy
2977
- };
2978
- const diag = noDeviceDiagnostic({
2979
- reason: `Could not ensure an owned Android emulator: ${error?.message || error}`,
2980
- logFile,
2981
- localEmulator: !(error instanceof AvdRecoveryError),
2982
- remedy: error instanceof AvdBootError ? error.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
2983
- });
2984
- return {
2985
- code: NO_DEVICE,
2986
- message: diag.message,
2987
- remedy: diag.remedy,
2988
- extra: {
2989
- lines: diag.lines,
2990
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
2991
- }
2992
- };
2993
- }
2994
- async function runAndroid(options = {}) {
2995
- let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, recordCapacityRefusal: recordRefusal = recordCapacityRefusal, readEstimates, now, out, emit } = androidSlotOptions(options);
2996
- const progress = options.progress ?? NO_BUILD_PROGRESS;
2997
- const slot = validateDeviceSlot(options.slot);
2998
- const started = now();
2999
- const startedAt = new Date(started).toISOString();
3000
- const problem = projectProblem(root, "android");
3001
- if (problem) {
3002
- const { message, remedy } = problem;
3003
- out(phaseLine("error", chalk.red(`STIM_NO_PROJECT: ${message}`)));
3004
- out(phaseLine("remedy", remedy));
3005
- if (json) emit(JSON.stringify({
3006
- code: "STIM_NO_PROJECT",
3007
- message,
3008
- remedy
3009
- }));
3010
- return {
3011
- ok: false,
3012
- error: {
3013
- code: "STIM_NO_PROJECT",
3014
- message,
3015
- remedy
3016
- }
3017
- };
3018
- }
3019
- try {
3020
- await ensureStorage(root, { note: out });
3021
- } catch (error) {
3022
- const code = error?.code || "STIM_WORKSPACE_STATE";
3023
- const message = `Could not prepare this workspace's Stim state: ${error?.message || error}`;
3024
- const remedy = "Check that STIM_HOME is writable and has free space. An EPERM on a directory you can write is a sandbox: allow writes to STIM_HOME, or run Stim with the sandbox disabled (`stim guide errors sandbox`).";
3025
- out(phaseLine("error", chalk.red(`${code}: ${message}`)));
3026
- out(phaseLine("remedy", remedy));
3027
- if (json) emit(JSON.stringify({
3028
- code,
3029
- message,
3030
- remedy
3031
- }));
3032
- return {
3033
- ok: false,
3034
- error: {
3035
- code,
3036
- message,
3037
- remedy
3038
- }
3039
- };
3040
- }
3041
- const logsDir = workspaceLogsDir$1(root);
3042
- const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
3043
- const writer = tapBuildLog(createWriter(buildLog, {
3044
- truncate: true,
3045
- fields: {
3046
- platform: PLATFORM,
3047
- slot
3048
- }
3049
- }), progress);
3050
- setRemoteLogSink((entry) => writer.write(entry));
3051
- const record = {
3052
- fingerprint: null,
3053
- cacheKey: null,
3054
- cacheHit: false,
3055
- appPath: null,
3056
- bundleId: null,
3057
- avdName: null,
3058
- deviceName: null
3059
- };
3060
- const phase = (label, text) => out(phaseLine(label, text));
3061
- const stats = createRunRecorder({
3062
- platform: PLATFORM,
3063
- write: recordStats,
3064
- now,
3065
- note: (line) => out(phaseLine("stats", chalk.dim(line))),
3066
- phases: () => progress.durations(),
3067
- deviceSetup: () => progress.deviceSetupKnown()
3068
- });
3069
- const recordRun = stats.record;
3070
- let ccacheActivity = CCACHE_NOT_RUN;
3071
- let reclaimed = [];
3072
- const fail = (code, message, remedy, { lastBuildStatus = false, setup = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
3073
- const cancellation = cancelledFailure(PLATFORM, {
3074
- code,
3075
- message
3076
- });
3077
- if (cancellation) ({code, message, remedy, lines} = cancellation);
3078
- if (lastBuildStatus) persistLastBuild({
3079
- recordBuild,
3080
- root,
3081
- record,
3082
- startedAt,
3083
- durationMs: now() - started,
3084
- status: "failed",
3085
- errorCode: code,
3086
- diagnostics: rawDiagnostics,
3087
- out
3088
- });
3089
- out(phaseLine("error", chalk.red(`${code}: ${message}`)));
3090
- for (const diagnostic of diagnostics) out(phaseLine("error", chalk.red(diagnostic)));
3091
- for (const line of lines) out(phaseLine("", chalk.dim(line)));
3092
- if (remedy) out(phaseLine("remedy", remedy));
3093
- if (logPath) out(phaseLine("log", logPath));
3094
- if (!setup) recordRun({
3095
- failed: true,
3096
- durationMs: now() - started
3097
- });
3098
- if (json) emit(JSON.stringify({
3099
- code,
3100
- message,
3101
- remedy: remedy ?? null,
3102
- ...record.devicePlacement ? { devicePlacement: record.devicePlacement } : {},
3103
- ...ccacheActivity.status === "not-run" ? {} : { ccache: ccacheActivity },
3104
- ...lease === void 0 ? {} : { lease },
3105
- ...reclaimed.length ? { reclaimed } : {}
3106
- }));
3107
- writer.close();
3108
- return {
3109
- ok: false,
3110
- error: {
3111
- code,
3112
- message,
3113
- remedy: remedy ?? null
3114
- }
3115
- };
3116
- };
3117
- const settingsRepoRoot = repoRoot(root);
3118
- const settingsRoot = root;
3119
- const settingsContext = {
3120
- projectPath: root,
3121
- gitCommonDir: gitCommonDir(root),
3122
- repoRoot: settingsRepoRoot
3123
- };
3124
- const projectKey = statsProjectKey$1({
3125
- root,
3126
- commonDir: settingsContext.gitCommonDir,
3127
- repoRoot: settingsRepoRoot
3128
- });
3129
- stats.setProject(projectKey);
3130
- progress.estimate(projectKey);
3131
- let estimatesRead = null;
3132
- const estimates = () => estimatesRead ??= readEstimates({
3133
- projectKey,
3134
- platform: PLATFORM
3135
- });
3136
- const settings = resolveSettingsFor(settingsContext);
3137
- const placement = resolveBuildPlacement(options.buildMachine);
3138
- record.buildMachine = placement.selected;
3139
- if (placement.failure) {
3140
- const { code, message, remedy } = placement.failure;
3141
- return fail(code, message, remedy, { setup: true });
3142
- }
3143
- const planned = resolveAndroidRunPlan({
3144
- settings,
3145
- settingsContext,
3146
- slot,
3147
- easProfile,
3148
- variant: variantFlag,
3149
- systemImage: systemImageFlag,
3150
- deviceProfile: deviceProfileFlag,
3151
- device: deviceFlag,
3152
- wait: waitFlag,
3153
- waitConflict,
3154
- remote: commandRemoteBackend,
3155
- buildCache: requestedBuildCache
3156
- }, {
3157
- resolveCacheProvider,
3158
- listSystemImages,
3159
- listDeviceProfiles,
3160
- warn: (label, message) => out(phaseLine(label, chalk.yellow(message)))
3161
- });
3162
- if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
3163
- const { plan } = planned;
3164
- const deviceSlotWait = {
3165
- automatic: plan.target.kind === "hosted" && plan.target.machine === "auto",
3166
- signal: runCancellationSignal(),
3167
- waitMs: plan.deviceSlotWaitMs,
3168
- displayName: basename(root),
3169
- waitingFor: (info) => progress.waitingFor(info, "device-slot"),
3170
- onWait: (ms) => {
3171
- stats.addDeviceSlotWaitMs(ms);
3172
- if (record.devicePlacement?.decision === "local" && ms > 0) record.devicePlacement.decision = "waited-locally";
3173
- }
3174
- };
3175
- const { build: buildPlan, isExpo, cacheProviderConfig } = plan;
3176
- const { variant, release, cache: cachePolicy } = buildPlan;
3177
- record.configuration = variant ?? "debug";
3178
- const useBuildCache = cachePolicy.read;
3179
- const hosting = await selectAndroidPlacement({
3180
- root,
3181
- slot,
3182
- target: plan.target,
3183
- release,
3184
- metroCheck,
3185
- noWait: plan.deviceSlotWaitMs === 0,
3186
- buildMachine: options.buildMachine,
3187
- read: options.readHostedAndroid,
3188
- resolveSerial: resolveAvdSerial,
3189
- prepare: options.prepareHostedAndroid,
3190
- automatic: options.automaticDevicePlacement,
3191
- ...remoteEasFallbackSetting(settings) ? { eas: () => (options.checkEasFallback ?? checkEasFallback)({
3192
- root,
3193
- platform: PLATFORM,
3194
- slot,
3195
- release,
3196
- isExpo,
3197
- tunnelMode: tunnelModeSetting(settings),
3198
- publicUrl: publicUrlSetting(settings),
3199
- localOnlyFlags: [...typeof systemImageFlag === "string" ? ["--system-image"] : [], ...typeof deviceProfileFlag === "string" ? ["--device-profile"] : []]
3200
- }) } : {},
3201
- localSelectors: (target) => androidDeviceSelectorRefusal({
3202
- settingsContext,
3203
- slot,
3204
- systemImageFlag,
3205
- deviceProfileFlag,
3206
- systemImage: target.systemImage,
3207
- deviceProfile: target.deviceProfile,
3208
- physical: false,
3209
- remoteBackend: null
3210
- }, {
3211
- listSystemImages,
3212
- listDeviceProfiles
3213
- }),
3214
- checkBudget: options.checkBudget,
3215
- note: out,
3216
- phase,
3217
- log: (entry) => writer.write(entry),
3218
- fromFlag: commandRemoteBackend !== null
3219
- });
3220
- if ("failure" in hosting) return fail(hosting.failure.code, hosting.failure.message, hosting.failure.remedy);
3221
- const { target, hostedTarget, budget, selectors } = hosting;
3222
- record.devicePlacement = hosting.devicePlacement;
3223
- writeDevicePlacement(root, slot, PLATFORM, record.devicePlacement);
3224
- const physical = target.kind === "physical";
3225
- const remoteBackend = target.kind === "remote" ? target.backend : null;
3226
- if (hostedTarget) {
3227
- if (settings.androidEmulatorApp !== void 0) phase("device", "androidEmulatorApp is ignored on a hosting Mac; the emulator boots headless.");
3228
- if (publicUrlSetting(settings) || tunnelModeSetting(settings)) phase("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.");
3229
- }
3230
- reclaimed = budget.reclaimed;
3231
- if (budget.refusal) return fail(budget.refusal.code, budget.refusal.message, budget.refusal.remedy);
3232
- const remoteContext = remoteBackend ? await resolveRemoteDeviceContext({
3233
- root,
3234
- platform: PLATFORM,
3235
- backend: remoteBackend,
3236
- easBin: resolveEasBin(root)?.file ?? null
3237
- }) : null;
3238
- if (remoteContext && "failed" in remoteContext) return fail(remoteContext.code ?? "STIM_NO_REMOTE_SESSION", remoteContext.failed, remoteContext.remedy);
3239
- const easBuild = await resolveEasBuild({
3240
- root,
3241
- platform: PLATFORM,
3242
- profile: easProfile,
3243
- note: out,
3244
- isExpo,
3245
- physical,
3246
- selectors: [variantFlag],
3247
- buildCache: requestedBuildCache
3248
- });
3249
- if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
3250
- const appIds = detectAppIds(root);
3251
- let androidPackage = appIds.androidPackage;
3252
- record.bundleId = androidPackage;
3253
- const registerProject = () => upsertProject(root, {
3254
- bundleId: appIds.bundleId ?? void 0,
3255
- androidPackage: androidPackage ?? void 0,
3256
- isExpo
3257
- });
3258
- if (remoteBackend !== "eas") registerProject();
3259
- const project = getProject(root);
3260
- const label = projectShortcut(root, project);
3261
- let remoteDevice = null;
3262
- const reservedPort = project?.metroPort ?? null;
3263
- let metroPort = null;
3264
- let devServer = null;
3265
- async function prepareMetro() {
3266
- if (metroCheck) {
3267
- const gate = await ensureDevServer({
3268
- root,
3269
- port: reservedPort,
3270
- settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
3271
- remote: remoteContext !== null,
3272
- note: out,
3273
- resolve: resolveMetro,
3274
- start: startServer,
3275
- readState
3276
- });
3277
- reclaimed = [...reclaimed, ...gate.reclaimed];
3278
- if (!gate.ok) return {
3279
- ok: false,
3280
- error: {
3281
- code: gate.code,
3282
- message: gate.message,
3283
- remedy: gate.remedy,
3284
- lines: gate.lines
3285
- }
3286
- };
3287
- metroPort = gate.port;
3288
- devServer = gate.devServer;
3289
- phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
3290
- return {
3291
- ok: true,
3292
- prepared: { metroPort }
3293
- };
3294
- } else {
3295
- const pin = metroPortSetting(root);
3296
- if (pin.error) return {
3297
- ok: false,
3298
- error: {
3299
- code: "STIM_BAD_ARG",
3300
- message: pin.error,
3301
- remedy: SETTING_SHAPE_REMEDY
3302
- }
3303
- };
3304
- metroPort = pin.port ?? reservedPort ?? 8081;
3305
- phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
3306
- }
3307
- return {
3308
- ok: true,
3309
- prepared: { metroPort }
3310
- };
3311
- }
3312
- const runtime = options.runtimePlan ?? (release ? androidProcessRuntime(async () => {
3313
- phase("metro", `skipped (${variant}: the JS bundle is embedded, no dev server is used)`);
3314
- return {
3315
- ok: true,
3316
- prepared: { metroPort: null }
3317
- };
3318
- }) : androidMetroRuntime(prepareMetro));
3319
- const preparation = await runtime.prepare();
3320
- if (!preparation.ok) {
3321
- const { code, message, remedy, lines } = preparation.error;
3322
- return fail(code, message, remedy, { lines });
3323
- }
3324
- const runtimePreparation = preparation.prepared;
3325
- metroPort = runtimePreparation.metroPort;
3326
- if (remoteContext) {
3327
- remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
3328
- if (metroPort !== null) {
3329
- const reachable = await ensureRemoteMetro({
3330
- ctx: remoteDevice.ctx,
3331
- metroPort,
3332
- isExpo,
3333
- tunnelMode: tunnelModeSetting(settings) ?? void 0,
3334
- publicUrl: publicUrlSetting(settings),
3335
- available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
3336
- });
3337
- if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
3338
- }
3339
- checkCapacity = remoteDevice.checkCapacity;
3340
- ensureDevice = remoteDevice.ensureDevice;
3341
- ensureDeviceBooted = remoteDevice.ensureDeviceBooted;
3342
- install = remoteDevice.install;
3343
- launch = remoteDevice.launch;
3344
- }
3345
- const emuLog = emulatorLogFile(root);
3346
- const limits = getLimits();
3347
- let device;
3348
- let bootDuration = "";
3349
- let bootPromise;
3350
- let startRemoteBoot = null;
3351
- if (hostedTarget) {
3352
- device = {
3353
- owned: false,
3354
- systemImage: hostedTarget.choice.systemImage,
3355
- deviceProfile: hostedTarget.choice.deviceProfile
3356
- };
3357
- bootPromise = Promise.resolve({ ok: true });
3358
- } else if (target.kind === "physical" && !target.serial) {
3359
- const pooled = await pooledAndroidDevice({
3360
- root,
3361
- selectPool,
3362
- listDevices,
3363
- isEmulatorDevice,
3364
- deviceModel,
3365
- waitSeconds: target.lease.waitSeconds,
3366
- noWait: target.lease.noWait,
3367
- now,
3368
- warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
3369
- });
3370
- if ("code" in pooled) return fail(pooled.code, pooled.message, pooled.remedy, pooled.extra);
3371
- device = pooled.device;
3372
- bootPromise = Promise.resolve({
3373
- ok: true,
3374
- serial: pooled.device.serial
3375
- });
3376
- } else if (target.kind === "physical") {
3377
- const resolved = resolvePhysicalDevice(target.serial, listDevices(), isEmulatorDevice);
3378
- if (!resolved.serial) return fail(NO_DEVICE, resolved.error, resolved.remedy);
3379
- device = {
3380
- serial: resolved.serial,
3381
- deviceName: deviceModel(resolved.serial) ?? resolved.serial,
3382
- owned: false
3383
- };
3384
- bootPromise = Promise.resolve({
3385
- ok: true,
3386
- serial: resolved.serial
3387
- });
3388
- } else {
3389
- if (!remoteDevice) try {
3390
- configuredAndroidEmulatorApp();
3391
- } catch (err) {
3392
- return fail("STIM_BAD_ARG", err.message, "Run `stim settings set androidEmulatorApp emulator` or `stim settings set androidEmulatorApp stim-desktop`.");
3393
- }
3394
- const capacity = plan.deviceSlotWaitMs === 0 ? checkCapacity({
3395
- platform: PLATFORM,
3396
- project,
3397
- max: limits.maxDevices
3398
- }) : null;
3399
- if (capacity) {
3400
- if (capacity.code === "STIM_AT_CAPACITY") recordRefusal({
3401
- platform: PLATFORM,
3402
- max: limits.maxDevices,
3403
- workspace: workspaceId(root)
3404
- }, now());
3405
- return fail(capacity.code, capacity.message, capacity.remedy);
3406
- }
3407
- const prepare = stepClock(now);
3408
- try {
3409
- device = await ensureDevice({
3410
- platform: PLATFORM,
3411
- project,
3412
- projectPath: root,
3413
- settingsRoot,
3414
- settings,
3415
- flags: {
3416
- deviceSlotWait,
3417
- systemImage: target.systemImage,
3418
- systemImageFlag: systemImageFlag?.trim() || null,
3419
- deviceProfile: target.deviceProfile
3420
- },
3421
- note: out,
3422
- out,
3423
- logFile: emuLog
3424
- });
3425
- } catch (err) {
3426
- const failure = avdSetupFailure(err, root, emuLog);
3427
- return fail(failure.code, failure.message, failure.remedy, failure.extra);
3428
- }
3429
- progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
3430
- const prepareMs = prepare();
3431
- if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
3432
- const boot = () => Promise.resolve(ensureDeviceBooted({
3433
- platform: PLATFORM,
3434
- device,
3435
- projectPath: root,
3436
- deviceSlotWait,
3437
- out,
3438
- logFile: emuLog
3439
- })).catch((e) => ({
3440
- failed: true,
3441
- reason: String(e?.message || e),
3442
- serial: void 0
3443
- }));
3444
- const startBoot = () => {
3445
- const bootTimer = stepTimer(now);
3446
- return (remoteDevice?.ctx.backend === "eas" ? ensureRemoteOwned({
3447
- root,
3448
- platform: PLATFORM,
3449
- sessionName: ownedSessionName(remoteDevice.ctx.label),
3450
- startedAt: new Date(now()).toISOString(),
3451
- boot,
3452
- createdSessionId: remoteDevice.createdSessionId,
3453
- abandonCreatedSession: remoteDevice.abandonCreatedSession,
3454
- webPreviewUrl: remoteDevice.webPreviewUrl,
3455
- writeState,
3456
- register: registerProject,
3457
- notice: (line) => out(chalk.dim(phaseLine("lock", line)))
3458
- }) : boot()).then((result) => {
3459
- bootDuration = bootTimer();
3460
- return result;
3461
- });
3462
- };
3463
- if (remoteDevice) startRemoteBoot = startBoot;
3464
- else bootPromise = startBoot();
3465
- }
3466
- record.avdName = device.avdName ?? null;
3467
- record.deviceName = device.deviceName ?? device.avdName ?? null;
3468
- record.systemImage = device.systemImage;
3469
- record.deviceProfile = device.deviceProfile;
3470
- const runFromFingerprint = async () => {
3471
- if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
3472
- port: metroPort,
3473
- platform: "android",
3474
- isExpo,
3475
- appId: androidPackage,
3476
- bundleUrl: metroWarmupUrlSetting(settings, "android")
3477
- });
3478
- const acquiredArtifact = await acquireAndroidArtifact({
3479
- root,
3480
- buildLog,
3481
- writer,
3482
- settings,
3483
- isExpo,
3484
- device,
3485
- physical,
3486
- hostedAbi: hostedTarget?.choice.architecture,
3487
- remote: Boolean(remoteDevice),
3488
- buildPlan,
3489
- cacheProviderConfig,
3490
- requestedBuildCache,
3491
- easBuild,
3492
- androidPackage,
3493
- record,
3494
- maxBuilds: limits.maxBuilds,
3495
- progress: {
3496
- phase,
3497
- out,
3498
- estimates,
3499
- stats,
3500
- step: progress.step,
3501
- miss: progress.miss,
3502
- hit: progress.hit,
3503
- place: progress.place,
3504
- waitingOn: progress.waitingOn,
3505
- waitingFor: progress.waitingFor
3506
- }
3507
- }, {
3508
- deviceAbi,
3509
- fingerprint,
3510
- untracked,
3511
- resolveCached,
3512
- storeCached,
3513
- storedAssets,
3514
- captureAssets,
3515
- acquireLock,
3516
- releaseLock,
3517
- waitForBuild,
3518
- loadProvider,
3519
- easAuth,
3520
- resolveRemoteBuild,
3521
- uploadRemoteBuild,
3522
- loadCacheProviderModule,
3523
- acquireSlot,
3524
- releaseSlot,
3525
- planPrebuildFor,
3526
- prebuild,
3527
- build,
3528
- ccacheFor,
3529
- swapApk,
3530
- readState,
3531
- now
3532
- });
3533
- if (!acquiredArtifact.ok) {
3534
- ccacheActivity = acquiredArtifact.ccache;
3535
- const { failure } = acquiredArtifact;
3536
- return fail(failure.code, failure.message, failure.remedy, failure.extra);
3537
- }
3538
- const { artifact } = acquiredArtifact;
3539
- const { apkPath } = artifact;
3540
- ccacheActivity = artifact.ccache;
3541
- androidPackage = artifact.androidPackage;
3542
- record.appPath = apkPath;
3543
- if (runCancellation()) return fail("STIM_CANCELLED", "before install");
3544
- if (hostedTarget) return finishHostedAndroidRun({
3545
- target: hostedTarget,
3546
- artifact,
3547
- root,
3548
- slot,
3549
- release,
3550
- isExpo,
3551
- metroPort,
3552
- logsDir,
3553
- selectors,
3554
- place: options.placeHostedAndroid ?? placeHostedAndroid,
3555
- writePlacement: options.writeHostedAndroid ?? writeHostedAndroid,
3556
- verifyLaunch: verifyLaunched,
3557
- writeLaunch,
3558
- readApkPackage,
3559
- resolveDevClientScheme,
3560
- now,
3561
- started,
3562
- startedAt,
3563
- record,
3564
- fail,
3565
- phase,
3566
- enterPhase: progress.step,
3567
- out,
3568
- json,
3569
- useBuildCache,
3570
- variant,
3571
- metroCheck,
3572
- writer,
3573
- emit,
3574
- recordRun,
3575
- reclaimed,
3576
- devServer,
3577
- recordBuild
3578
- });
3579
- if (startRemoteBoot) {
3580
- progress.step("device");
3581
- bootPromise = startRemoteBoot();
3582
- const booted = await bootPromise;
3583
- if (booted.failed) {
3584
- if (booted.code) return fail(booted.code, booted.reason ?? "The remote device did not boot.", booted.remedy ?? null);
3585
- const diag = noDeviceDiagnostic({
3586
- reason: booted.reason ?? "The remote device did not boot.",
3587
- logFile: emuLog,
3588
- remedy: "Run `stim status` to inspect the remote device, then retry the command.",
3589
- localEmulator: false
3590
- });
3591
- return fail(NO_DEVICE, diag.message, diag.remedy, {
3592
- lines: diag.lines,
3593
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
3594
- });
3595
- }
3596
- }
3597
- let leaseHandle = null;
3598
- let stopLeaseSignals = null;
3599
- const releaseLease = () => {
3600
- const held = leaseHandle;
3601
- const stopSignals = stopLeaseSignals;
3602
- leaseHandle = null;
3603
- stopLeaseSignals = null;
3604
- stopSignals?.();
3605
- try {
3606
- held?.release();
3607
- } catch (err) {
3608
- out(phaseLine("lease", chalk.dim(`could not release this run's lease: ${err?.message || err}`)));
3609
- }
3610
- };
3611
- if (physical) {
3612
- progress.step("device");
3613
- const acquired = await acquireLease({
3614
- root,
3615
- platform: PLATFORM,
3616
- id: device.serial,
3617
- deviceName: device.deviceName ?? null,
3618
- idLabel: "serial",
3619
- waitSeconds: target.lease.waitSeconds,
3620
- noWait: target.lease.noWait,
3621
- installBoundMs: ADB_INSTALL_TIMEOUT_MS,
3622
- appId: androidPackage,
3623
- holderAppId: (holder) => getProject(holder)?.androidPackage ?? null,
3624
- now,
3625
- warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
3626
- });
3627
- if (acquired.status === "refused") return fail(acquired.refusal.code, acquired.refusal.message, acquired.refusal.remedy, { lease: acquired.refusal.lease });
3628
- leaseHandle = makeRunLease({
3629
- root,
3630
- platform: PLATFORM,
3631
- kind: acquired.status === "leased" ? acquired.kind : null,
3632
- expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
3633
- });
3634
- if (acquired.status === "leased") {
3635
- stopLeaseSignals = onLeaseSignal(releaseLease);
3636
- phase("lease", `${acquired.kind} lease on ${device.serial} until ${leaseExpiryText(acquired.expiresAt, now())}`);
3637
- }
3638
- }
3639
- try {
3640
- return await finishAndroidRun({
3641
- runtime,
3642
- runtimePreparation,
3643
- slot,
3644
- lease: leaseHandle,
3645
- releaseLease,
3646
- root,
3647
- json,
3648
- metroCheck,
3649
- useBuildCache,
3650
- variant,
3651
- release,
3652
- isExpo,
3653
- metroPort,
3654
- logsDir,
3655
- emuLog,
3656
- device,
3657
- physical,
3658
- remoteDevice,
3659
- bootPromise,
3660
- resolveAvdSerial,
3661
- waitForDeviceBoot,
3662
- dataFreeBytes,
3663
- trimCaches,
3664
- wipeDevice,
3665
- bootDuration: () => bootDuration,
3666
- apkPath,
3667
- androidPackage,
3668
- swapDir: artifact.swapDir,
3669
- record,
3670
- waitedForBuild: artifact.waitedForBuild,
3671
- ccache: ccacheActivity,
3672
- uploadPending: artifact.uploadPending,
3673
- providerUpload: artifact.providerUpload,
3674
- providerName: artifact.providerName,
3675
- remote: artifact.remote,
3676
- abandonedRemote: artifact.abandonedRemote,
3677
- started,
3678
- startedAt,
3679
- writer,
3680
- phase,
3681
- fail,
3682
- readApkPackage,
3683
- install,
3684
- launch,
3685
- launchRelease,
3686
- resolveDevClientScheme,
3687
- verifyLaunched,
3688
- verifyReleaseLaunched,
3689
- spawn,
3690
- kill,
3691
- pidAlive,
3692
- verifyCollector,
3693
- writeLaunch,
3694
- recordBuild,
3695
- now,
3696
- out,
3697
- emit,
3698
- recordRun,
3699
- reclaimed,
3700
- devServer,
3701
- enterPhase: progress.step,
3702
- rebootDevice: () => Promise.resolve(ensureDeviceBooted({
3703
- platform: PLATFORM,
3704
- device: {
3705
- ...device,
3706
- serial: void 0
3707
- },
3708
- projectPath: root,
3709
- deviceSlotWait,
3710
- out,
3711
- logFile: emuLog
3712
- })).catch((e) => ({
3713
- failed: true,
3714
- reason: String(e?.message || e)
3715
- }))
3716
- });
3717
- } finally {
3718
- releaseLease();
3719
- }
3720
- };
3721
- try {
3722
- return await runFromFingerprint();
3723
- } catch (error) {
3724
- recordRun({
3725
- failed: true,
3726
- durationMs: now() - started
3727
- });
3728
- throw error;
3729
- }
3730
- }
3731
- //#endregion
3732
- export { collectorLogFile as a, androidFacts as c, runAndroidOperation as i, lastBuildRecord as l, registerAndroid as n, killPreviousCollector as o, runAndroid as r, startCollector as s, androidCommand as t };