stim 1.18.0 → 1.19.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 (140) hide show
  1. package/README.md +1 -1
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BSw0x84R.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +5 -5
  4. package/dist/{android-BvwuMUJ9.mjs → android-BXc6BmEi.mjs} +507 -347
  5. package/dist/{android-DC3ZLuw0.mjs → android-BdQC6zs1.mjs} +6 -6
  6. package/dist/{android-BKA16DTf.mjs → android-BvV1gGdN.mjs} +3 -3
  7. package/dist/{android-CBwCNicO.mjs → android-DE5g_5gv.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-C87geAg-.mjs} +3 -3
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/{api-D0VtFhqk.mjs → api-DIf_s86s.mjs} +5 -7
  11. package/dist/api-run.mjs +8 -8
  12. package/dist/api.d.mts +8 -2
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-CFI4ptUL.mjs} +33 -13
  15. package/dist/{web-DTU0uiWj.mjs → browser-web-CDhqQxhv.mjs} +134 -172
  16. package/dist/{budget-DIhL2BKx.mjs → budget-Dz-kyXLv.mjs} +15 -202
  17. package/dist/{build-facts-DcLv2HSd.d.mts → build-facts-B87vo-Im.d.mts} +32 -1
  18. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-DiwKSyv9.mjs} +1 -1
  19. package/dist/cache-manifest.mjs +1 -1
  20. package/dist/{chrome-DJnmnS5q.mjs → chrome-BTUOo8p2.mjs} +1 -1
  21. package/dist/{cli-DNJMwADx.mjs → cli-C9Zvgbdc.mjs} +20 -20
  22. package/dist/cli.mjs +1 -1
  23. package/dist/{client-DzhapoMB.mjs → client-CuUo8Nvg.mjs} +248 -23
  24. package/dist/collector-run.d.mts +6 -0
  25. package/dist/collector-run.mjs +6 -6
  26. package/dist/{command-output-CBtzyIgF.mjs → command-output-Bt8ygHE7.mjs} +1 -1
  27. package/dist/{config-CgYMahz7.mjs → config-DC5jEtd1.mjs} +6 -2
  28. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-D0s26Hx5.mjs} +2 -2
  29. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
  30. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
  31. package/dist/{dev-client-CLnIH02x.mjs → dev-client-CDY_zAmD.mjs} +2 -2
  32. package/dist/{device-D7oCHGWi.mjs → device-BHw0WlHl.mjs} +8 -8
  33. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-BoN1jQkO.mjs} +23 -12
  34. package/dist/device-host-worker.mjs +14 -14
  35. package/dist/{gradle-C6GKnunQ.mjs → device-ios-B2ruohE-.mjs} +659 -105
  36. package/dist/{device-lease-Co-EwifR.mjs → device-lease-Cu7PtQpZ.mjs} +5 -5
  37. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
  38. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-CP4rAsHx.mjs} +3 -3
  39. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-B5GiUifk.mjs} +15 -16
  40. package/dist/{doctor-B9ckYUoU.mjs → doctor-PRJH94v6.mjs} +53 -29
  41. package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
  42. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics--v5HGOU0.mjs} +10 -10
  43. package/dist/{exec-DWhCZDGm.mjs → exec-i8GDPwq9.mjs} +32 -8
  44. package/dist/{gc-Dh6mXOKC.mjs → gc-CyHMcKcn.mjs} +19 -19
  45. package/dist/{guide-tm6igAaa.mjs → guide-dRaQJJk9.mjs} +409 -472
  46. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
  47. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-C5G4h_OJ.mjs} +1 -1
  48. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-Bik035eL.mjs} +1 -1
  49. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-CI9Az9a4.mjs} +7 -7
  50. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-DABm3ScO.mjs} +4 -4
  51. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-DbV2aUVw.mjs} +12 -8
  52. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DYiMBn34.mjs} +7 -7
  53. package/dist/{ios-B3SbOWdw.mjs → ios-BWbHFZVz.mjs} +5 -5
  54. package/dist/{ios-Bvbf0SGY.mjs → ios-DCrkgXEM.mjs} +1 -1
  55. package/dist/{ios-DNpdygiv.mjs → ios-DYUr9LGc.mjs} +7 -6
  56. package/dist/ios-ITYno9rO.mjs +2241 -0
  57. package/dist/{ios-device-vOaByMhw.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
  58. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-CrW8aMb5.mjs} +1 -1
  59. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-By0XuLTQ.mjs} +1 -1
  60. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-DCveSE6d.mjs} +4 -4
  61. package/dist/{logs-1Ylc1Ri6.mjs → logs-Bao1HOMy.mjs} +10 -10
  62. package/dist/{macos-DF6C7wUU.mjs → macos-ciyBdTY5.mjs} +108 -130
  63. package/dist/macos-nIOATJmR.mjs +2 -0
  64. package/dist/macos-run.mjs +1 -1
  65. package/dist/maintenance-run.mjs +7 -7
  66. package/dist/manifest-CX0d1IkZ.mjs +148 -0
  67. package/dist/{metro-Dh8T4Ytd.mjs → metro-DI2AjPF_.mjs} +194 -41
  68. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BxpJLIwh.mjs} +2 -2
  69. package/dist/{plan-placement-CtxkCO54.mjs → miss-reason-CjQCOKqq.mjs} +180 -296
  70. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-B4iWH4E-.mjs} +12 -5
  71. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BGxsbfny.mjs} +6 -6
  72. package/dist/offload-worker.d.mts +1 -29
  73. package/dist/offload-worker.mjs +9 -8
  74. package/dist/{ownership-D6Se7gg_.mjs → ownership-EB0ySdGK.mjs} +2 -2
  75. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-Dxe34rJm.mjs} +7 -3
  76. package/dist/plan-placement-BVY59qa8.mjs +290 -0
  77. package/dist/{ports-BLTtfBq-.mjs → ports-ByPc0_97.mjs} +3 -3
  78. package/dist/{prebuild-juCyreaR.mjs → prebuild-BKJ3wJI1.mjs} +4 -4
  79. package/dist/{preview-D5V07Nrl.mjs → preview-BbSottmO.mjs} +16 -17
  80. package/dist/project-B5eQ1USs.mjs +612 -0
  81. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-IHNdMnG8.mjs} +1 -1
  82. package/dist/pull-requests.mjs +1 -1
  83. package/dist/react-native-ios-BMh2i25I.mjs +2 -0
  84. package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
  85. package/dist/{recordings-BAWltsVt.mjs → recordings-Bv8McZN4.mjs} +1 -1
  86. package/dist/{reload-DvyWzVjJ.mjs → reload-rAPdqeRR.mjs} +13 -14
  87. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-N3hAEaAL.mjs} +4 -4
  88. package/dist/{run-vhtDgpHO.mjs → run-lAkle1-c.mjs} +2 -2
  89. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-CkIjnegq.mjs} +3 -3
  90. package/dist/server-expo-DGsdUGX2.mjs +2 -0
  91. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-n3tUi3Em.mjs} +3 -3
  92. package/dist/{settings-Byp2AQ_E.mjs → settings-2am7TSxp.mjs} +5 -6
  93. package/dist/{settings-CQyMElEP.mjs → settings-CxgngUZk.mjs} +767 -15
  94. package/dist/settings.schema.json +72 -6
  95. package/dist/{simslim-EHBQ2R4G.mjs → simslim-D9LkJxIO.mjs} +5 -5
  96. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
  97. package/dist/{stage-g_WqWeaE.mjs → stage-BgaZJVZY.mjs} +1 -1
  98. package/dist/{start-NTc1Ifqs.mjs → start-D8NbxNBb.mjs} +1 -1
  99. package/dist/{start-D-IeOyoK.mjs → start-DtxthMfI.mjs} +29 -22
  100. package/dist/{state-BG-aAk4k.mjs → state-D2KtnALG.mjs} +2 -2
  101. package/dist/{state-xnqpx3AD.mjs → state-Tp4uImDS.mjs} +4 -2
  102. package/dist/{stats-DY6ekz8F.mjs → stats-8EJ5ivFT.mjs} +6 -6
  103. package/dist/{status-DCjZeE-K.mjs → status-BOyp0CUl.mjs} +25 -27
  104. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
  105. package/dist/{stim-installations-aR_WsNOA.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
  106. package/dist/stop-D0UIpZuh.mjs +72 -0
  107. package/dist/{stop-DIJtD2GL.mjs → stop-DRipAg-e.mjs} +2 -2
  108. package/dist/{stop-BTjs3xbc.mjs → stop-zDlknFoz.mjs} +78 -31
  109. package/dist/supervisor-run.mjs +14 -14
  110. package/dist/{support-BaayeYCf.mjs → support-CNqYkV0y.mjs} +7 -7
  111. package/dist/{support-Bwvbrq5-.mjs → support-DsNaFg6D.mjs} +5 -5
  112. package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
  113. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-C3xSoyUl.mjs} +27 -270
  114. package/dist/{trigger-CJMhwXe8.mjs → trigger-CAhmG-4s.mjs} +3 -3
  115. package/dist/trigger-CoalsMTV.mjs +2 -0
  116. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-pFLB2e6P.mjs} +133 -36
  117. package/dist/{status-Dcr0Qexd.mjs → watchman-BL1IdOtq.mjs} +75 -7
  118. package/dist/web-DFpFeZu5.mjs +2 -0
  119. package/dist/web-DbbrPUn0.mjs +127 -0
  120. package/dist/web-run.mjs +7 -7
  121. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CR9X86xJ.mjs} +1 -1
  122. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-Djb2gfi9.mjs} +102 -18
  123. package/dist/{worktree-BSYlofkq.mjs → worktree-BL4M1rhB.mjs} +45 -42
  124. package/dist/{worktree-BJfnU6r5.mjs → worktree-DV-A0LNL.mjs} +1 -1
  125. package/package.json +5 -5
  126. package/shim/bundle-response.cjs +2 -1
  127. package/shim/bundle-response.d.cts +1 -1
  128. package/shim/expo-metro-config.cjs +1 -1
  129. package/dist/device-ios-6MewKKnc.mjs +0 -412
  130. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  131. package/dist/ios-CPdkoKaa.mjs +0 -3953
  132. package/dist/macos-Ck7OBMNY.mjs +0 -2
  133. package/dist/native-run-B06FkmZx.mjs +0 -158
  134. package/dist/project-C3ChRLZy.mjs +0 -212
  135. package/dist/projects-DD7dBTCj.mjs +0 -92
  136. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  137. package/dist/stop-BGhKMOuH.mjs +0 -48
  138. package/dist/trigger-DWAq9hkw.mjs +0 -2
  139. package/dist/web-B6_bFDn7.mjs +0 -2
  140. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,46 +1,46 @@
1
1
  import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
2
+ import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
3
3
  import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
4
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-CBtzyIgF.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-CgYMahz7.mjs";
7
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-CsKYexJM.mjs";
8
- import { d as projectShortcut, i as detectIsExpo, n as appProjectProblem, s as findProjectRoot$1, t as NO_PROJECT_REFUSAL } from "./project-C3ChRLZy.mjs";
9
- import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-Dh8T4Ytd.mjs";
10
- 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-C59dqG-Z.mjs";
11
- import { F as recordFinishedBuild, Ft as MODE_BARE, I as startBuildProgress, It as MODE_EXPO, Jt as writeWorkspaceLaunch, 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-DIhL2BKx.mjs";
12
- import { E as teardownOwnedAvd, N as releaseBuildSlot, j as acquireBuildSlot } from "./workspaces-xXItD8Dj.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-Co-EwifR.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-DC3ZLuw0.mjs";
15
- import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Behws_fi.mjs";
16
- import { A as publicUrlSetting, C as metroWarmupUrlSetting, F as resolveSettings, H as tunnelModeSetting, I as settingFile, K as detectProviders, M as remoteAndroidSetting, P as resolveCacheProviderConfig, Q as resolveOptimizations, R as settingOriginScope, T as parseIosRemote, U as unknownSettingKeys, V as settingsLayers, X as compilerCacheFallbackMessage, Y as artifactCachePolicy, Z as optimizationBuildProfile, i as SETTING_SHAPE_REMEDY, l as androidDataPartitionSizeGbSettingError, o as androidAvdConfigSettingError, u as cacheProviderSettingError, x as metroPortSetting, z as settingShapeErrors } from "./settings-CQyMElEP.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";
17
16
  import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
18
- import { _ as resolveBuildPlacement, a as offloadBuild, c as placementLoad, d as setRemoteLogSink, f as OffloadRefusal, h as parseBuildMachineOption, i as closeOffload, l as remotePhaseText, m as namedBuildMachine, n as buildPlacementCandidates, r as chooseBuildMachine, s as offloadPlacement } from "./client-DzhapoMB.mjs";
19
- import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity, t as DEFAULT_DEVICE_SLOT_WAIT_MS } from "./device-capacity-EUOOYr8T.mjs";
20
- import { A as CCACHE_NOT_RUN, E as tailLines, F as resolveCcache, M as ccacheActivityLine, T as startBuildHeartbeat, g as HEARTBEAT_INTERVAL_MS, j as CCACHE_UNAVAILABLE, n as androidToolchain, t as androidRequirements } from "./toolchain-Bnyd_K3A.mjs";
21
- import { t as cleanLine } from "./server-expo-DoBHhuzK.mjs";
22
- import { r as hostedMetroSettings } from "./metro-gateway-CEUw9RMH.mjs";
23
- import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-B2pES6Vs.mjs";
24
- import { _ as AvdBootError, a as printPlan, b as automaticDevicePlacement, c as fingerprintErrorMissReason, d as detectEntryFile, f as refreshUpdatesManifestFile, g as ensureOwnedDevice, h as ensureBooted, i as planPayload, l as skippedMissReason, m as didSetUpDevice, n as planCachedBuild, o as refusePlan, r as planFlagRefusal, s as explainBuildMiss, t as planHostedDevice, u as UPDATES_MANIFEST_NAME, v as AvdRecoveryError, x as devicePlacementLine, y as warmMetro } from "./plan-placement-CtxkCO54.mjs";
25
- import { n as devicePlacementRecord, t as buildPlacementRecord } from "./placement-log-BcPhRwR4.mjs";
26
- import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-Bv285t_t.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-D-PUENug.mjs";
28
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-C1nqh4mu.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-hskNcWkZ.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-BGg7aGIZ.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-Bwvbrq5-.mjs";
32
- import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-tHbd1fKy.mjs";
33
- import { d as runCancellationSignal, f as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-B06FkmZx.mjs";
34
- import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-EHBQ2R4G.mjs";
35
- import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-D-IeOyoK.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-w1xhCqIW.mjs";
37
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-D6Se7gg_.mjs";
38
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-D_603JiC.mjs";
39
- import { t as selectFromPool } from "./device-pool-F5WvRPtD.mjs";
40
- import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-juCyreaR.mjs";
41
- import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-C6GKnunQ.mjs";
42
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-BRXOqDzG.mjs";
43
- import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-BBFyr9CA.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
44
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
45
45
  import { basename, dirname, isAbsolute, join } from "node:path";
46
46
  import { buildDiagnostics, hostedAndroidStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
@@ -92,7 +92,7 @@ async function connectAndroidHosting({ root, slot, machine, release, metroCheck,
92
92
  } };
93
93
  }
94
94
  }
95
- async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, ...args }) {
95
+ async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, eas, ...args }) {
96
96
  const selectors = selected.kind === "hosted" ? {
97
97
  ...selected.systemImage ? { systemImage: selected.systemImage } : {},
98
98
  ...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
@@ -126,14 +126,31 @@ async function selectAndroidPlacement({ target: selected, automatic = automaticD
126
126
  platform: "android",
127
127
  selectors,
128
128
  buildMachine,
129
- noWait
129
+ noWait,
130
+ ...eas ? { eas } : {}
130
131
  });
131
132
  phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
132
133
  log?.(devicePlacementRecord({
133
134
  platform: "android",
134
135
  fromFlag,
136
+ easFallback: Boolean(eas),
135
137
  placed
136
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
+ };
137
154
  if (placed.target && !args.release && !args.metroCheck) return { failure: {
138
155
  code: "STIM_BAD_ARG",
139
156
  message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
@@ -344,7 +361,7 @@ function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, sta
344
361
  }
345
362
  //#endregion
346
363
  //#region src/commands/android/hosted.ts
347
- 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, out, recordBuild, ...report }) {
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 }) {
348
365
  const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
349
366
  if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
350
367
  const launchedAt = now();
@@ -360,7 +377,8 @@ async function finishHostedAndroidRun({ target, artifact, root, slot = "default"
360
377
  selectors,
361
378
  ...!release && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
362
379
  reserved: (placement) => writePlacement(root, slot, placement),
363
- note: out
380
+ note: out,
381
+ enterPhase
364
382
  });
365
383
  writePlacement(root, slot, run.placement);
366
384
  writeLaunch(root, "android", {
@@ -807,6 +825,13 @@ function fail$1(code, message, remedy, extra = {}) {
807
825
  extra
808
826
  };
809
827
  }
828
+ var ArtifactRefusal = class extends Error {
829
+ failure;
830
+ constructor(failure) {
831
+ super(failure.message ?? failure.code);
832
+ this.failure = failure;
833
+ }
834
+ };
810
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 }) {
811
836
  const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
812
837
  let fallbackMachine = null;
@@ -826,6 +851,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
826
851
  }
827
852
  }));
828
853
  if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
854
+ requireLocalBuild(buildMachine);
829
855
  record.offloadFallback = reason;
830
856
  phase("build", `${line} -> building here`);
831
857
  };
@@ -834,11 +860,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
834
860
  let androidPackage = initialPackage;
835
861
  let ccacheActivity = CCACHE_NOT_RUN;
836
862
  let phaseFailure = null;
837
- const refused = () => ({
838
- ok: false,
839
- failure: phaseFailure,
840
- ccache: ccacheActivity
841
- });
842
863
  let buildLock = null;
843
864
  const releaseHeldLock = () => {
844
865
  if (!buildLock) return;
@@ -957,7 +978,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
957
978
  apkPath = found?.path ?? null;
958
979
  return true;
959
980
  }
960
- if (!await resolveInitialFingerprint()) return refused();
961
981
  let remote = null;
962
982
  let abandonedRemote = false;
963
983
  let uploadPending = null;
@@ -1009,13 +1029,9 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1009
1029
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
1010
1030
  }
1011
1031
  }
1012
- if (!easBuild) {
1013
- await resolveRemoteArtifact();
1014
- if (!apkPath) miss(reasonForMiss([]).reason);
1015
- }
1016
1032
  let waitedForBuild = null;
1017
1033
  let releasedWait = null;
1018
- if (!easBuild && useBuildCache && !apkPath) {
1034
+ async function awaitSharedBuild() {
1019
1035
  const shared = await waitForSharedBuild({
1020
1036
  platform: PLATFORM,
1021
1037
  key: cacheKey,
@@ -1037,7 +1053,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1037
1053
  if (shared.refusal) {
1038
1054
  const { code, message, remedy } = shared.refusal;
1039
1055
  phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
1040
- return refused();
1056
+ throw new ArtifactRefusal(phaseFailure);
1041
1057
  }
1042
1058
  buildLock = shared.lock;
1043
1059
  releasedWait = shared.released;
@@ -1046,6 +1062,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1046
1062
  record.cacheHit = "local";
1047
1063
  waitedForBuild = shared.hit.waited;
1048
1064
  }
1065
+ return apkPath;
1049
1066
  }
1050
1067
  let swapDir = null;
1051
1068
  const installableCachedApk = async (key, cachedPath) => {
@@ -1071,15 +1088,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1071
1088
  swapFellBack = true;
1072
1089
  return null;
1073
1090
  };
1074
- async function prepareCachedArtifact() {
1075
- if (apkPath && record.cacheHit) {
1076
- const prepared = await installableCachedApk(cacheKey, apkPath);
1077
- apkPath = prepared;
1078
- if (!prepared) {
1079
- record.cacheHit = false;
1080
- waitedForBuild = null;
1081
- }
1091
+ async function prepareCachedArtifact(cachedPath) {
1092
+ const prepared = await installableCachedApk(cacheKey, cachedPath);
1093
+ if (!prepared) {
1094
+ record.cacheHit = false;
1095
+ waitedForBuild = null;
1082
1096
  }
1097
+ return prepared;
1083
1098
  }
1084
1099
  function reasonForMiss(rekeyedBy) {
1085
1100
  if (swapFellBack) return {
@@ -1122,9 +1137,11 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1122
1137
  }
1123
1138
  }
1124
1139
  async function takeBuildSlot() {
1140
+ requireLocalBuild(buildMachine);
1125
1141
  if (!maxBuilds) return true;
1126
1142
  try {
1127
1143
  buildSlot = await acquireSlot({
1144
+ automatic: buildMachine === "auto",
1128
1145
  max: maxBuilds,
1129
1146
  root,
1130
1147
  logFile: buildLog,
@@ -1133,6 +1150,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1133
1150
  });
1134
1151
  slotWaitMs = buildSlot.slotWaitMs;
1135
1152
  } catch (err) {
1153
+ requireLocalBuild(buildMachine);
1136
1154
  const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
1137
1155
  if (refusal) {
1138
1156
  phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
@@ -1144,12 +1162,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1144
1162
  }
1145
1163
  /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
1146
1164
  function placeBuild() {
1147
- const { mode, machines } = buildPlacementCandidates(buildMachine);
1148
- if (machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
1165
+ const { mode, machines, localEnabled } = buildPlacementCandidates(buildMachine);
1166
+ if (localEnabled && machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
1149
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;
1150
1168
  const here = machineCapacity();
1151
1169
  const placement = offloadPlacement({
1152
1170
  mode,
1171
+ localEnabled,
1153
1172
  machines: machines.length,
1154
1173
  here,
1155
1174
  unsupported,
@@ -1330,228 +1349,284 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1330
1349
  });
1331
1350
  return true;
1332
1351
  }
1333
- async function buildArtifact() {
1334
- if (!apkPath) try {
1335
- const offload = placeBuild();
1336
- if (!offload && !await takeBuildSlot()) return false;
1337
- const rekeyedBy = [];
1338
- let editedConfig = [];
1339
- const prebuildPlan = planPrebuildFor(root, PLATFORM, {
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, {
1340
1370
  isExpo,
1341
- fingerprint: hash,
1342
- sources: fingerprintSources
1371
+ clean: prebuildPlan === "regenerate"
1343
1372
  });
1344
- const prebuildRan = prebuildPlan === "generate" || prebuildPlan === "regenerate";
1345
- if (prebuildPlan === "refuse") {
1346
- const refusal = staleNativeDirRefusal(PLATFORM);
1347
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1348
- return false;
1349
- }
1350
- if (prebuildRan) {
1351
- miss(reasonForMiss([]).reason, true);
1352
- step("prebuild");
1353
- recordPrebuild(root, PLATFORM, null);
1354
- const pre = await prebuild(root, PLATFORM, writer, {
1355
- isExpo,
1356
- clean: prebuildPlan === "regenerate"
1357
- });
1358
- if (pre.failed) {
1359
- phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
1360
- lastBuildStatus: true,
1361
- lines: tail(pre.lastLines),
1362
- logPath: displayPath(root, buildLog)
1363
- });
1364
- return false;
1365
- }
1366
- phase("prebuild", `${prebuildPlan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
1367
- androidPackage = detectAndroidPackage(root) || androidPackage;
1368
- record.bundleId = androidPackage;
1369
- const after = await refingerprintAfterMutation({
1370
- projectRoot: root,
1371
- platform: PLATFORM,
1372
- previousHash: hash,
1373
- fingerprint
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)
1374
1378
  });
1375
- editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
1376
- if (after && !editedConfig.length) recordPrebuild(root, PLATFORM, after.hash);
1377
- if (after?.moved && !editedConfig.length) {
1378
- rekeyedBy.push("prebuild");
1379
- storeHash = after.hash;
1380
- storeSources = after.sources;
1381
- storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
1382
- record.fingerprint = storeHash;
1383
- record.cacheKey = storeKey;
1384
- phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
1385
- const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
1386
- if (late) {
1387
- const prepared = await installableCachedApk(storeKey, late);
1388
- if (prepared) {
1389
- apkPath = prepared;
1390
- record.cacheHit = "local";
1391
- lateHit();
1392
- phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1393
- if (releasedWait) {
1394
- waitedForBuild = releasedWait.facts;
1395
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1396
- }
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`);
1397
1422
  }
1398
1423
  }
1399
1424
  }
1400
1425
  }
1401
- if (offload && !apkPath) {
1402
- explainMiss(rekeyedBy);
1403
- step("compile");
1404
- let built = false;
1405
- 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");
1406
- else {
1407
- const choice = await chooseMachine(offload);
1408
- if (choice) built = await compileElsewhere(choice, offload);
1409
- }
1410
- if (!built && !await takeBuildSlot()) return false;
1411
- }
1412
- if (!apkPath) {
1413
- if (!offload) {
1414
- explainMiss(rekeyedBy);
1415
- step("compile");
1416
- }
1417
- if (offload) stats.setPlacement({
1418
- decision: "fell-back",
1419
- slotWaitMs,
1420
- deviceSlotWaitMs: stats.deviceSlotWaitMs(),
1421
- reason: record.offloadFallback ?? "offload failed",
1422
- ...fallbackMachine ? { machine: fallbackMachine } : {}
1423
- });
1424
- else stats.setPlacement({
1425
- decision: "here",
1426
- reason: hereReason,
1427
- slotWaitMs,
1428
- deviceSlotWaitMs: stats.deviceSlotWaitMs()
1429
- });
1430
- record.builtOn = "here";
1431
- phase("build", `compiling ${variant || "debug"} with Gradle`);
1432
- const built = await build({
1433
- root,
1434
- logWriter: writer,
1435
- variant,
1436
- abi: buildAbi
1437
- }, {
1438
- estimateMs: estimates().coldBuildMs,
1439
- ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
1440
- root,
1441
- onNote: out
1442
- }) : null,
1443
- cas,
1444
- buildCache: buildPlan.gradleBuildCache,
1445
- pch: buildPlan.pch,
1446
- compilerCacheDisabled: buildPlan.compilerCache === "none"
1447
- });
1448
- ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
1449
- phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
1450
- if (!built.ok) {
1451
- const diagnostics = built.diagnostics;
1452
- for (const diag of diagnostics) writer.write({
1453
- src: "build",
1454
- level: "error",
1455
- event: "gradle_diagnostic",
1456
- msg: formatDiagnostic(diag)
1457
- });
1458
- phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
1459
- const extracted = diagnostics.map(formatDiagnostic);
1460
- if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
1461
- phaseFailure = fail$1(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
1462
- lastBuildStatus: true,
1463
- diagnostics: extracted,
1464
- buildDiagnostics: diagnostics,
1465
- lines: extracted.length ? [] : tail(built.lastLines),
1466
- logPath: displayPath(root, buildLog)
1467
- });
1468
- return false;
1469
- }
1470
- apkPath = built.apkPath;
1471
- stats.setBuildMs(built.durationMs);
1472
- phase("build", `ok (${formatDuration(built.durationMs)})`);
1473
- if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
1474
- const beforeBuildHash = storeHash;
1475
- const afterBuild = editedConfig.length ? null : await refingerprintAfterMutation({
1476
- projectRoot: root,
1477
- platform: PLATFORM,
1478
- previousHash: beforeBuildHash,
1479
- fingerprint
1480
- });
1481
- const changedDuringBuild = afterBuild ? inputsChangedDuringBuild({
1482
- platform: PLATFORM,
1483
- lookup: fingerprintSources,
1484
- prebuildRan,
1485
- compiled: storeSources,
1486
- current: afterBuild.sources
1487
- }) : editedConfig;
1488
- if (!afterBuild || changedDuringBuild.length) {
1489
- record.fingerprint = null;
1490
- record.cacheKey = null;
1491
- phase("fingerprint", chalk.yellow(changedDuringBuild.length ? changedDuringBuildLine(changedDuringBuild) : "unavailable after Gradle; the build will be installed but not cached"));
1492
- } else {
1493
- if (afterBuild.moved) {
1494
- storeHash = afterBuild.hash;
1495
- storeSources = afterBuild.sources;
1496
- storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
1497
- record.fingerprint = storeHash;
1498
- record.cacheKey = storeKey;
1499
- phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
1500
- }
1501
- if (cachePolicy.write) {
1502
- const assetManifest = release ? captureAssets(root, { variant }) : null;
1503
- try {
1504
- const stored = await storeTieredBuild({
1505
- local: filesystemBuildCapability({
1506
- resolve: resolveCached,
1507
- store: storeCached,
1508
- sources: storeSources,
1509
- assetManifest
1510
- }),
1511
- loadProvider: loadTieredProvider,
1512
- target: {
1513
- projectRoot: root,
1514
- platform: PLATFORM,
1515
- key: storeKey
1516
- },
1517
- sourcePath: apkPath,
1518
- overwrite: !useBuildCache || swapFellBack,
1519
- warn: cacheWarn
1520
- });
1521
- providerUpload = stored.providerUpload;
1522
- providerName = stored.providerName ?? providerName;
1523
- } catch (err) {
1524
- phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
1525
- }
1526
- }
1527
- if (remote) uploadPending = uploadRemoteBuild({
1528
- logWriter: writer,
1529
- provider: remote.provider,
1530
- platform: PLATFORM,
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: {
1531
1548
  projectRoot: root,
1532
- fingerprintHash: storeHash,
1533
- buildPath: apkPath,
1534
- runOptions: remoteRunOptions
1535
- });
1536
- }
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}`));
1537
1560
  }
1538
- } catch (error) {
1539
- if (!(error instanceof OffloadRefusal)) throw error;
1540
- const refusal = error;
1541
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1542
- return false;
1543
- } finally {
1544
- if (openOffload.choice) closeOffload(openOffload.choice);
1545
- releaseHeldLock();
1546
- releaseHeldSlot();
1547
1561
  }
1548
- return true;
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
+ });
1549
1571
  }
1550
- if (!easBuild) {
1551
- await prepareCachedArtifact();
1552
- if (apkPath) lateHit();
1553
- else if (swapFellBack) miss(reasonForMiss([]).reason);
1554
- if (!await buildArtifact()) return refused();
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;
1555
1630
  }
1556
1631
  return {
1557
1632
  ok: true,
@@ -1655,11 +1730,41 @@ async function startCollector({ root, slot = "default", serial, packageName, spa
1655
1730
  }
1656
1731
  //#endregion
1657
1732
  //#region src/commands/android/launch.ts
1658
- async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevice, verifyReleaseLaunched, verifyLaunched, serial, androidPackage, variant, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
1659
- const runCommand = nativeRunCommand("android", slot, {
1660
- physical,
1661
- deviceId: serial
1662
- });
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 }) {
1663
1768
  const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1664
1769
  root,
1665
1770
  slot,
@@ -1668,30 +1773,42 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1668
1773
  appId: androidPackage,
1669
1774
  since: launchedAt
1670
1775
  }, logsDir);
1671
- if (remoteRelease) {
1776
+ if (remoteDevice) {
1672
1777
  phase("verify", chalk.yellow("UNVERIFIED: remote adapter launch accepted; process verification is unavailable"));
1673
1778
  return { state: LAUNCH_UNVERIFIED };
1674
1779
  }
1675
- if (release) {
1676
- const processCheck = await verifyReleaseLaunched({
1677
- serial,
1678
- packageName: androidPackage
1679
- });
1680
- const crashes = readNativeCrashes();
1681
- if (processCheck?.verified && !crashes.length) {
1682
- phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${variant}: no bundle fetch to observe)`);
1683
- return { state: true };
1684
- }
1685
- if (processCheck?.reason === "probe-failed" && !crashes.length) {
1686
- phase("verify", chalk.yellow("UNVERIFIED: the app process check failed"));
1687
- return { state: LAUNCH_UNVERIFIED };
1688
- }
1689
- for (const line of launchErrorPreview(crashes, root)) phase("launch", chalk.red(line));
1690
- if (!crashes.length) phase("logs", "Process missing; no attributable native crash report captured. Read `stim logs --source device` for available output.");
1691
- 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`));
1692
- 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."));
1693
- return { state: LAUNCH_FATAL };
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 };
1694
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);
1695
1812
  const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
1696
1813
  const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1697
1814
  if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
@@ -1866,7 +1983,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1866
1983
  });
1867
1984
  return resolved.serial;
1868
1985
  }
1869
- async function finishAndroidRun({ 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 }) {
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 }) {
1870
1987
  let androidPackage = initialPackage;
1871
1988
  let leaseWarned = false;
1872
1989
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1998,7 +2115,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1998
2115
  record.bundleId = androidPackage;
1999
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 });
2000
2117
  if (physical) raiseLeaseFor(0, false);
2001
- const scheme = release ? void 0 : resolveDevClientScheme(root, apkPath);
2118
+ const scheme = runtime.scheme(root, apkPath, resolveDevClientScheme);
2002
2119
  enterPhase("launch");
2003
2120
  const launchTimer = stepTimer(now);
2004
2121
  const launchedAt = now();
@@ -2015,19 +2132,14 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
2015
2132
  physical,
2016
2133
  msg: `launching ${androidPackage} on ${serial}`
2017
2134
  });
2018
- const launched = release ? remoteDevice ? remoteDevice.launch({
2019
- serial,
2020
- packageName: androidPackage,
2021
- metroPort: null
2022
- }) : launchRelease({
2135
+ const launched = await runtime.launch(runtimePreparation, {
2023
2136
  serial,
2024
- packageName: androidPackage
2025
- }) : launch({
2026
- serial,
2027
- packageName: androidPackage,
2028
- metroPort: metroPort ?? 8081,
2029
- devClientScheme: scheme,
2030
- physical
2137
+ androidPackage,
2138
+ scheme,
2139
+ physical,
2140
+ remoteDevice,
2141
+ launch,
2142
+ launchRelease
2031
2143
  });
2032
2144
  if (launched.failed) {
2033
2145
  printNativeCrashReport({
@@ -2118,12 +2230,10 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
2118
2230
  });
2119
2231
  phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
2120
2232
  }
2121
- if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
2122
- const { state: launchState, warning: launchWarning, unattributed } = await verifyAndroidRun({
2233
+ if (physical) raiseLeaseFor(runtime.verificationWaitMs, false);
2234
+ const { state: launchState, warning: launchWarning, unattributed } = await runtime.verify(runtimePreparation, {
2123
2235
  root,
2124
2236
  slot,
2125
- release,
2126
- remoteRelease,
2127
2237
  remoteDevice: Boolean(remoteDevice),
2128
2238
  verifyReleaseLaunched,
2129
2239
  verifyLaunched,
@@ -2446,7 +2556,7 @@ function androidDeviceSelectorRefusal({ settingsContext, slot, systemImageFlag,
2446
2556
  //#endregion
2447
2557
  //#region src/commands/android/next-build.ts
2448
2558
  const DEFAULT_PLAN_DEPS = {
2449
- findRoot: findProjectRoot$1,
2559
+ findRoot: findProjectRoot,
2450
2560
  fingerprint: fingerprintProject,
2451
2561
  listSystemImages: listInstalledSystemImages,
2452
2562
  avdSystemImage: ownedAvdSystemImage,
@@ -2511,10 +2621,10 @@ async function planAndroid(opts, overrides = {}) {
2511
2621
  if (flag) return refuse(planFlagRefusal(flag));
2512
2622
  const root = deps.findRoot(process.cwd());
2513
2623
  if (!root) return refuse(NO_PROJECT_REFUSAL);
2514
- const projectProblem = appProjectProblem(root);
2515
- if (projectProblem) return refuse({
2624
+ const problem = projectProblem(root, "android");
2625
+ if (problem) return refuse({
2516
2626
  code: "STIM_NO_PROJECT",
2517
- ...projectProblem
2627
+ ...problem
2518
2628
  });
2519
2629
  const slot = validateDeviceSlot(opts.slot);
2520
2630
  const settingsContext = {
@@ -2522,8 +2632,9 @@ async function planAndroid(opts, overrides = {}) {
2522
2632
  gitCommonDir: gitCommonDir(root),
2523
2633
  repoRoot: repoRoot(root)
2524
2634
  };
2635
+ const settings = resolveSettings(settingsContext);
2525
2636
  const planned = resolveAndroidRunPlan({
2526
- settings: resolveSettings(settingsContext),
2637
+ settings,
2527
2638
  settingsContext,
2528
2639
  slot,
2529
2640
  easProfile: opts.easProfile,
@@ -2608,7 +2719,17 @@ async function planAndroid(opts, overrides = {}) {
2608
2719
  const local = localImage();
2609
2720
  if ("refusal" in local || !("systemImage" in choice)) return null;
2610
2721
  return androidSystemImageAbi(local.systemImage) === androidSystemImageAbi(choice.systemImage);
2611
- }
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
+ }) } : {}
2612
2733
  });
2613
2734
  if (hostedPlan.kind === "unknown") return refuse({
2614
2735
  code: "STIM_BAD_ARG",
@@ -2669,12 +2790,12 @@ function androidCommand(program) {
2669
2790
  registerAndroid(program);
2670
2791
  }
2671
2792
  function registerAndroid(program) {
2672
- 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", (value) => {
2673
- if (!parseMachine(value)) throw new InvalidArgumentError("expected eas, proxy, auto, or a tailnet machine name");
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");
2674
2795
  return value;
2675
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) => {
2676
2797
  if (opts.plan) return planAndroid(opts);
2677
- const root = findProjectRoot$1(process.cwd());
2798
+ const root = findProjectRoot(process.cwd());
2678
2799
  if (!root) {
2679
2800
  refuseNoProject({ json: Boolean(opts.json) });
2680
2801
  return;
@@ -2876,9 +2997,9 @@ async function runAndroid(options = {}) {
2876
2997
  const slot = validateDeviceSlot(options.slot);
2877
2998
  const started = now();
2878
2999
  const startedAt = new Date(started).toISOString();
2879
- const projectProblem = appProjectProblem(root);
2880
- if (projectProblem) {
2881
- const { message, remedy } = projectProblem;
3000
+ const problem = projectProblem(root, "android");
3001
+ if (problem) {
3002
+ const { message, remedy } = problem;
2882
3003
  out(phaseLine("error", chalk.red(`STIM_NO_PROJECT: ${message}`)));
2883
3004
  out(phaseLine("remedy", remedy));
2884
3005
  if (json) emit(JSON.stringify({
@@ -3041,6 +3162,7 @@ async function runAndroid(options = {}) {
3041
3162
  if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
3042
3163
  const { plan } = planned;
3043
3164
  const deviceSlotWait = {
3165
+ automatic: plan.target.kind === "hosted" && plan.target.machine === "auto",
3044
3166
  signal: runCancellationSignal(),
3045
3167
  waitMs: plan.deviceSlotWaitMs,
3046
3168
  displayName: basename(root),
@@ -3066,6 +3188,16 @@ async function runAndroid(options = {}) {
3066
3188
  resolveSerial: resolveAvdSerial,
3067
3189
  prepare: options.prepareHostedAndroid,
3068
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
+ }) } : {},
3069
3201
  localSelectors: (target) => androidDeviceSelectorRefusal({
3070
3202
  settingsContext,
3071
3203
  slot,
@@ -3130,10 +3262,8 @@ async function runAndroid(options = {}) {
3130
3262
  const reservedPort = project?.metroPort ?? null;
3131
3263
  let metroPort = null;
3132
3264
  let devServer = null;
3133
- let phaseFailure = null;
3134
- async function resolveMetroPort() {
3135
- if (release) phase("metro", `skipped (${variant}: the JS bundle is embedded, no dev server is used)`);
3136
- else if (metroCheck) {
3265
+ async function prepareMetro() {
3266
+ if (metroCheck) {
3137
3267
  const gate = await ensureDevServer({
3138
3268
  root,
3139
3269
  port: reservedPort,
@@ -3145,27 +3275,54 @@ async function runAndroid(options = {}) {
3145
3275
  readState
3146
3276
  });
3147
3277
  reclaimed = [...reclaimed, ...gate.reclaimed];
3148
- if (!gate.ok) {
3149
- phaseFailure = fail(gate.code, gate.message, gate.remedy, { lines: gate.lines });
3150
- return false;
3151
- }
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
+ };
3152
3287
  metroPort = gate.port;
3153
3288
  devServer = gate.devServer;
3154
3289
  phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
3155
- return true;
3290
+ return {
3291
+ ok: true,
3292
+ prepared: { metroPort }
3293
+ };
3156
3294
  } else {
3157
3295
  const pin = metroPortSetting(root);
3158
- if (pin.error) {
3159
- phaseFailure = fail("STIM_BAD_ARG", pin.error, SETTING_SHAPE_REMEDY);
3160
- return false;
3161
- }
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
+ };
3162
3304
  metroPort = pin.port ?? reservedPort ?? 8081;
3163
3305
  phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
3164
3306
  }
3165
- if (release) metroPort = null;
3166
- return true;
3307
+ return {
3308
+ ok: true,
3309
+ prepared: { metroPort }
3310
+ };
3167
3311
  }
3168
- if (!await resolveMetroPort()) return phaseFailure;
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;
3169
3326
  if (remoteContext) {
3170
3327
  remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
3171
3328
  if (metroPort !== null) {
@@ -3406,6 +3563,7 @@ async function runAndroid(options = {}) {
3406
3563
  record,
3407
3564
  fail,
3408
3565
  phase,
3566
+ enterPhase: progress.step,
3409
3567
  out,
3410
3568
  json,
3411
3569
  useBuildCache,
@@ -3480,6 +3638,8 @@ async function runAndroid(options = {}) {
3480
3638
  }
3481
3639
  try {
3482
3640
  return await finishAndroidRun({
3641
+ runtime,
3642
+ runtimePreparation,
3483
3643
  slot,
3484
3644
  lease: leaseHandle,
3485
3645
  releaseLease,