stim 1.17.2 → 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 (162) hide show
  1. package/README.md +50 -1
  2. package/dist/{activity-IRvJY_ru.mjs → activity-BSw0x84R.mjs} +4 -4
  3. package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +8 -8
  4. package/dist/{android-B9yaNKwa.mjs → android-BXc6BmEi.mjs} +658 -404
  5. package/dist/{android-B_YNZy61.mjs → android-BdQC6zs1.mjs} +9 -9
  6. package/dist/android-BvV1gGdN.mjs +4 -0
  7. package/dist/{android-DE4v59Mb.mjs → android-DE5g_5gv.mjs} +2 -2
  8. package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-C87geAg-.mjs} +5 -5
  9. package/dist/android-cas-compiler.mjs +2 -2
  10. package/dist/api-DIf_s86s.mjs +150 -0
  11. package/dist/api-run.d.mts +1 -0
  12. package/dist/api-run.mjs +145 -0
  13. package/dist/api.d.mts +218 -0
  14. package/dist/api.mjs +2 -0
  15. package/dist/{app-install-VPkI5WEg.mjs → app-install-CFI4ptUL.mjs} +33 -13
  16. package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
  17. package/dist/{web-Dl0KSRHf.mjs → browser-web-CDhqQxhv.mjs} +138 -176
  18. package/dist/{budget-DfcgFdQB.mjs → budget-Dz-kyXLv.mjs} +1391 -58
  19. package/dist/build-facts-B87vo-Im.d.mts +142 -0
  20. package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-DiwKSyv9.mjs} +3 -3
  21. package/dist/cache-manifest.mjs +1 -1
  22. package/dist/{chrome-BrcVWQyD.mjs → chrome-BTUOo8p2.mjs} +1 -1
  23. package/dist/cli-C9Zvgbdc.mjs +68 -0
  24. package/dist/cli.mjs +1 -1
  25. package/dist/{client-SvBdgObD.mjs → client-CuUo8Nvg.mjs} +381 -49
  26. package/dist/collector-run.d.mts +6 -0
  27. package/dist/collector-run.mjs +9 -9
  28. package/dist/{command-output-CDAIFD-8.mjs → command-output-Bt8ygHE7.mjs} +2 -2
  29. package/dist/{config-D0DbpZtM.mjs → config-DC5jEtd1.mjs} +9 -5
  30. package/dist/{created-devices-CpvzBL25.mjs → created-devices-D0s26Hx5.mjs} +2 -2
  31. package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
  32. package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
  33. package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CDY_zAmD.mjs} +2 -2
  34. package/dist/{device-C8IOoJPv.mjs → device-BHw0WlHl.mjs} +8 -8
  35. package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-BoN1jQkO.mjs} +25 -14
  36. package/dist/device-host-worker.mjs +19 -19
  37. package/dist/{gradle-dJpre3Yn.mjs → device-ios-B2ruohE-.mjs} +661 -107
  38. package/dist/{device-lease-DpoS36_u.mjs → device-lease-Cu7PtQpZ.mjs} +9 -9
  39. package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
  40. package/dist/{device-pool-3o7s5lo4.mjs → device-pool-CP4rAsHx.mjs} +3 -3
  41. package/dist/{device-remote-hFSkpfQu.mjs → device-remote-B5GiUifk.mjs} +15 -16
  42. package/dist/{doctor-CFv1fCrF.mjs → doctor-PRJH94v6.mjs} +61 -34
  43. package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
  44. package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics--v5HGOU0.mjs} +11 -11
  45. package/dist/{exec-CaErVv7t.mjs → exec-i8GDPwq9.mjs} +93 -6
  46. package/dist/{gc-DEmkg3Xh.mjs → gc-CyHMcKcn.mjs} +25 -471
  47. package/dist/{guide-CSmhMTf8.mjs → guide-dRaQJJk9.mjs} +681 -507
  48. package/dist/{hosted-android-E1Heoqnv.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
  49. package/dist/{hosted-client-Bp4knSwu.mjs → hosted-client-C5G4h_OJ.mjs} +2 -2
  50. package/dist/{hosted-ios-BnbeYOUD.mjs → hosted-ios-Bik035eL.mjs} +1 -1
  51. package/dist/{hosted-logs-OcgIBvCT.mjs → hosted-logs-CI9Az9a4.mjs} +9 -9
  52. package/dist/{hosted-macos-DcxKjtfH.mjs → hosted-macos-DABm3ScO.mjs} +5 -5
  53. package/dist/{hosted-native-1Ebbig4w.mjs → hosted-native-DbV2aUVw.mjs} +13 -9
  54. package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-DYiMBn34.mjs} +7 -8
  55. package/dist/{ios-DSfnPfse.mjs → ios-BWbHFZVz.mjs} +19 -7
  56. package/dist/{ios-Cr1h5gnO.mjs → ios-DCrkgXEM.mjs} +1 -1
  57. package/dist/ios-DYUr9LGc.mjs +8 -0
  58. package/dist/ios-ITYno9rO.mjs +2241 -0
  59. package/dist/{ios-device-pAN4t54-.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
  60. package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-CrW8aMb5.mjs} +1 -1
  61. package/dist/{ios-state-8L3lgum6.mjs → ios-state-By0XuLTQ.mjs} +1 -1
  62. package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-DCveSE6d.mjs} +5 -5
  63. package/dist/{logs-BAaS7gS2.mjs → logs-Bao1HOMy.mjs} +13 -13
  64. package/dist/{macos-BIRQS90f.mjs → macos-ciyBdTY5.mjs} +194 -132
  65. package/dist/macos-nIOATJmR.mjs +2 -0
  66. package/dist/macos-run.mjs +1 -1
  67. package/dist/maintenance-run.mjs +349 -47
  68. package/dist/manifest-CX0d1IkZ.mjs +148 -0
  69. package/dist/{metro-g8NqiJp-.mjs → metro-DI2AjPF_.mjs} +196 -43
  70. package/dist/{metro-gateway-C-ijGI2F.mjs → metro-gateway-BxpJLIwh.mjs} +4 -4
  71. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
  72. package/dist/{build-plan-BDsSi09V.mjs → miss-reason-CjQCOKqq.mjs} +207 -249
  73. package/dist/{named-ports-C8m8EQs2.mjs → named-ports-B4iWH4E-.mjs} +70 -8
  74. package/dist/{native-runtime-BEK171xQ.mjs → native-runtime-BGxsbfny.mjs} +6 -6
  75. package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
  76. package/dist/offload-worker.d.mts +3 -45
  77. package/dist/offload-worker.mjs +11 -11
  78. package/dist/{ownership-DyRQru0q.mjs → ownership-EB0ySdGK.mjs} +3 -3
  79. package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
  80. package/dist/placement-log-Dxe34rJm.mjs +88 -0
  81. package/dist/plan-placement-BVY59qa8.mjs +290 -0
  82. package/dist/{ports-4nYqxPWC.mjs → ports-ByPc0_97.mjs} +3 -3
  83. package/dist/{prebuild-D7RUMkwN.mjs → prebuild-BKJ3wJI1.mjs} +5 -5
  84. package/dist/preview-BbSottmO.mjs +986 -0
  85. package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
  86. package/dist/project-B5eQ1USs.mjs +612 -0
  87. package/dist/{pull-request-pxnDCQzn.mjs → pull-request-IHNdMnG8.mjs} +1 -1
  88. package/dist/pull-requests.mjs +1 -1
  89. package/dist/react-native-ios-BMh2i25I.mjs +2 -0
  90. package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
  91. package/dist/{recordings-BNgsD1Tr.mjs → recordings-Bv8McZN4.mjs} +1 -1
  92. package/dist/{reload-DcRxj3qG.mjs → reload-rAPdqeRR.mjs} +14 -15
  93. package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-N3hAEaAL.mjs} +5 -5
  94. package/dist/{run-CkE9y0nc.mjs → run-lAkle1-c.mjs} +5 -5
  95. package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-CkIjnegq.mjs} +5 -6
  96. package/dist/server-expo-DGsdUGX2.mjs +2 -0
  97. package/dist/{server-expo-CHTyXD0G.mjs → server-expo-n3tUi3Em.mjs} +5 -6
  98. package/dist/{settings-SR9OmW9h.mjs → settings-2am7TSxp.mjs} +5 -5
  99. package/dist/{settings-Cfd-0r2d.mjs → settings-CxgngUZk.mjs} +116 -103
  100. package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
  101. package/dist/settings.schema.json +198 -9
  102. package/dist/{simslim-BWNrc3M3.mjs → simslim-D9LkJxIO.mjs} +7 -7
  103. package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
  104. package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
  105. package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
  106. package/dist/{stage-CzaiwFsI.mjs → stage-BgaZJVZY.mjs} +14 -3
  107. package/dist/{start-AlfCPRVU.mjs → start-D8NbxNBb.mjs} +1 -1
  108. package/dist/{start-DUz3mYdb.mjs → start-DtxthMfI.mjs} +34 -27
  109. package/dist/{state-PeQiJuIu.mjs → state-D2KtnALG.mjs} +4 -4
  110. package/dist/{state-DyaNTgfR.mjs → state-Tp4uImDS.mjs} +5 -3
  111. package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
  112. package/dist/{stats-Bq4fJOdB.mjs → stats-8EJ5ivFT.mjs} +6 -6
  113. package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
  114. package/dist/{status-BA2pehwf.mjs → status-BOyp0CUl.mjs} +31 -34
  115. package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
  116. package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
  117. package/dist/stop-D0UIpZuh.mjs +72 -0
  118. package/dist/{stop-zF1y04f2.mjs → stop-DRipAg-e.mjs} +2 -2
  119. package/dist/{stop-DQIasgKp.mjs → stop-zDlknFoz.mjs} +79 -33
  120. package/dist/supervisor-run.d.mts +24 -22
  121. package/dist/supervisor-run.mjs +21 -22
  122. package/dist/{support-CsJNVfOc.mjs → support-CNqYkV0y.mjs} +7 -7
  123. package/dist/{support-WvD__tSV.mjs → support-DsNaFg6D.mjs} +5 -5
  124. package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
  125. package/dist/{toolchain-DNyR3NPq.mjs → toolchain-C3xSoyUl.mjs} +475 -269
  126. package/dist/{trigger-Bemu8ytw.mjs → trigger-CAhmG-4s.mjs} +6 -6
  127. package/dist/trigger-CoalsMTV.mjs +2 -0
  128. package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-pFLB2e6P.mjs} +135 -38
  129. package/dist/{status-CzS4bPO9.mjs → watchman-BL1IdOtq.mjs} +75 -7
  130. package/dist/web-DFpFeZu5.mjs +2 -0
  131. package/dist/web-DbbrPUn0.mjs +127 -0
  132. package/dist/web-run.mjs +10 -10
  133. package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-CR9X86xJ.mjs} +3 -3
  134. package/dist/{ownership-BMLqyBgJ.mjs → workspaces-Djb2gfi9.mjs} +661 -23
  135. package/dist/{worktree-Be_7NLKy.mjs → worktree-BL4M1rhB.mjs} +252 -50
  136. package/dist/{worktree-j3IcFAI5.mjs → worktree-DV-A0LNL.mjs} +1 -1
  137. package/package.json +10 -5
  138. package/shim/bundle-response.cjs +2 -1
  139. package/shim/bundle-response.d.cts +1 -1
  140. package/shim/expo-metro-config.cjs +1 -1
  141. package/dist/build-progress-D0WkjCPd.mjs +0 -1036
  142. package/dist/build-slots-41v_d2x9.mjs +0 -142
  143. package/dist/cli-CYXb29oE.mjs +0 -51
  144. package/dist/deps-iKw0eD4P.mjs +0 -438
  145. package/dist/device-ios-C2Um-XUR.mjs +0 -412
  146. package/dist/errors-B6W7FkxQ.mjs +0 -15
  147. package/dist/guide-status-Du-ZQ3FD.mjs +0 -125
  148. package/dist/idle-BXG__1BA.mjs +0 -176
  149. package/dist/in-use-CvGOUn-x.mjs +0 -130
  150. package/dist/ios-BJ4XUSe1.mjs +0 -3867
  151. package/dist/native-run-D1Q56rAD.mjs +0 -158
  152. package/dist/preview-B-g9d7kV.mjs +0 -318
  153. package/dist/process-identity-BmRm1qV0.mjs +0 -12
  154. package/dist/project-D47Rh0ly.mjs +0 -276
  155. package/dist/server-expo-CeakhI8P.mjs +0 -2
  156. package/dist/state-BKa8wNN5.mjs +0 -148
  157. package/dist/stop-BIiM49EY.mjs +0 -48
  158. package/dist/stop-cause-rhmGfwvt.mjs +0 -97
  159. package/dist/trigger-CXts6qt1.mjs +0 -2
  160. package/dist/watchman-APYjXSQa.mjs +0 -48
  161. package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
  162. package/dist/workspaces-Bcib97-6.mjs +0 -306
@@ -1,53 +1,52 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- 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-D0DbpZtM.mjs";
3
- import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
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
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-CDAIFD-8.mjs";
6
- import { B as settingsLayers, C as metroWarmupUrlSetting, F as settingFile, G as detectProviders, H as unknownSettingKeys, J as artifactCachePolicy, L as settingOriginScope, N as resolveCacheProviderConfig, P as resolveSettings, R as settingShapeErrors, T as parseIosRemote, Tt as removeTemporaryEntry, V as tunnelModeSetting, X as optimizationBuildProfile, Y as compilerCacheFallbackMessage, Z as resolveOptimizations, gt as repoRoot, i as SETTING_SHAPE_REMEDY, it as gitCommonDir, j as remoteAndroidSetting, k as publicUrlSetting, l as androidDataPartitionSizeGbSettingError, o as androidAvdConfigSettingError, u as cacheProviderSettingError, wt as makeTemporaryDirectory, x as metroPortSetting } from "./settings-Cfd-0r2d.mjs";
7
- import { d as projectShortcut, n as appProjectProblem, r as detectIsExpo, s as findProjectRoot$1, t as NO_PROJECT_REFUSAL } from "./project-D47Rh0ly.mjs";
8
- import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-g8NqiJp-.mjs";
9
- 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-DqcCbpE7.mjs";
10
- import { t as budgetGate } from "./budget-DfcgFdQB.mjs";
11
- 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-DpoS36_u.mjs";
12
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-41v_d2x9.mjs";
13
- import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-BKa8wNN5.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, q as resetAdoptedAvd, r as androidBuildToolName, rt as configuredAndroidEmulatorApp, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-B_YNZy61.mjs";
15
- import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
16
- import { m as teardownOwnedAvd } from "./ownership-BMLqyBgJ.mjs";
17
- import { _ as statsProjectKey$1, c as recordFinishedBuild, d as createRunRecorder, f as readRunEstimates, g as recordRunStats, l as startBuildProgress, m as recordCapacityRefusal, n as NO_BUILD_PROGRESS, u as tapBuildLog } from "./build-progress-D0WkjCPd.mjs";
18
- import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
19
- import { E as ccacheActivityLine, T as CCACHE_UNAVAILABLE, b as tailLines, k as resolveCcache, n as androidToolchain, t as androidRequirements, u as HEARTBEAT_INTERVAL_MS, w as CCACHE_NOT_RUN, y as startBuildHeartbeat } from "./toolchain-DNyR3NPq.mjs";
20
- import { t as cleanLine } from "./server-expo-CHTyXD0G.mjs";
21
- import { a as offloadBuild, c as placementLoad, d as OffloadRefusal, f as namedBuildMachine, h as resolveBuildPlacement, i as closeOffload, l as remotePhaseText, n as buildPlacementCandidates, p as parseBuildMachineOption, r as chooseBuildMachine, s as offloadPlacement } from "./client-SvBdgObD.mjs";
22
- import { r as hostedMetroSettings } from "./metro-gateway-C-ijGI2F.mjs";
23
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-DyRQru0q.mjs";
24
- import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity, t as DEFAULT_DEVICE_SLOT_WAIT_MS } from "./device-capacity-ZC4XSZ4R.mjs";
25
- 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-VPkI5WEg.mjs";
26
- import { C as resolveSystemImage, S as resolveDeviceProfile, T as systemImageRefusal, _ as isReleaseVariant, a as PLATFORM, b as pooledAndroidDevice, c as androidSystemImageSetting, d as apkPackage, f as deviceProfileRefusal, g as foldableImageRefusal, h as findAapt, i as NO_FINGERPRINT, l as androidVariantSetting, m as dumpApkManifest, n as LAUNCH_FAILED, o as androidBuildOptions, p as displayPath, r as NO_DEVICE, s as androidDevClientScheme, t as INSTALL_FAILED, u as apkDevClientFacts, v as noDeviceDiagnostic, w as resolveVariant, x as remoteAvdFlagRefusal, y as parseXmltree } from "./support-WvD__tSV.mjs";
27
- import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
28
- import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-D7RUMkwN.mjs";
29
- import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-dJpre3Yn.mjs";
30
- 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-lTnWZikR.mjs";
31
- 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-B8YltIH_.mjs";
32
- import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-8L3lgum6.mjs";
33
- import { _ as AvdRecoveryError, a as refusePlan, b as devicePlacementLine, c as skippedMissReason, d as refreshUpdatesManifestFile, g as AvdBootError, h as ensureOwnedDevice, i as printPlan, l as UPDATES_MANIFEST_NAME, m as ensureBooted, n as planFlagRefusal, o as explainBuildMiss, p as didSetUpDevice, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as detectEntryFile, v as warmMetro, y as automaticDevicePlacement } from "./build-plan-BDsSi09V.mjs";
34
- import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-E1Heoqnv.mjs";
35
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-DmjBmpQV.mjs";
36
- 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-Ci4DQYYj.mjs";
37
- import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-hFSkpfQu.mjs";
38
- import { d as runCancellationSignal, f as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-D1Q56rAD.mjs";
39
- import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-BWNrc3M3.mjs";
40
- import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-DUz3mYdb.mjs";
41
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-BEK171xQ.mjs";
42
- import { t as selectFromPool } from "./device-pool-3o7s5lo4.mjs";
43
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-C-NMnhrk.mjs";
44
- import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-ZFpjJBL_.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";
45
44
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
46
45
  import { basename, dirname, isAbsolute, join } from "node:path";
47
- import { workspaceId } from "@stim-cli/core";
48
- import { InvalidArgumentError } from "commander";
49
46
  import { buildDiagnostics, hostedAndroidStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
50
47
  import chalk from "chalk";
48
+ import { InvalidArgumentError } from "commander";
49
+ import { workspaceId } from "@stim-cli/core";
51
50
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
52
51
  //#region src/commands/android/remote.ts
53
52
  async function connectAndroidHosting({ root, slot, machine, release, metroCheck, selectors, read = readHostedAndroid, resolveSerial = resolveOwnedAvdSerial, prepare = prepareHostedAndroid }) {
@@ -93,7 +92,7 @@ async function connectAndroidHosting({ root, slot, machine, release, metroCheck,
93
92
  } };
94
93
  }
95
94
  }
96
- async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, noWait, buildMachine, localSelectors, ...args }) {
95
+ async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, eas, ...args }) {
97
96
  const selectors = selected.kind === "hosted" ? {
98
97
  ...selected.systemImage ? { systemImage: selected.systemImage } : {},
99
98
  ...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
@@ -127,9 +126,31 @@ async function selectAndroidPlacement({ target: selected, automatic = automaticD
127
126
  platform: "android",
128
127
  selectors,
129
128
  buildMachine,
130
- noWait
129
+ noWait,
130
+ ...eas ? { eas } : {}
131
131
  });
132
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
+ };
133
154
  if (placed.target && !args.release && !args.metroCheck) return { failure: {
134
155
  code: "STIM_BAD_ARG",
135
156
  message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
@@ -340,7 +361,7 @@ function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, sta
340
361
  }
341
362
  //#endregion
342
363
  //#region src/commands/android/hosted.ts
343
- 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 }) {
344
365
  const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
345
366
  if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
346
367
  const launchedAt = now();
@@ -356,7 +377,8 @@ async function finishHostedAndroidRun({ target, artifact, root, slot = "default"
356
377
  selectors,
357
378
  ...!release && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
358
379
  reserved: (placement) => writePlacement(root, slot, placement),
359
- note: out
380
+ note: out,
381
+ enterPhase
360
382
  });
361
383
  writePlacement(root, slot, run.placement);
362
384
  writeLaunch(root, "android", {
@@ -803,14 +825,33 @@ function fail$1(code, message, remedy, extra = {}) {
803
825
  extra
804
826
  };
805
827
  }
828
+ var ArtifactRefusal = class extends Error {
829
+ failure;
830
+ constructor(failure) {
831
+ super(failure.message ?? failure.code);
832
+ this.failure = failure;
833
+ }
834
+ };
806
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 }) {
807
836
  const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
808
837
  let fallbackMachine = null;
809
838
  let hereReason = "no remote Mac is paired";
810
839
  let slotWaitMs;
811
840
  const buildMachine = record.buildMachine ?? "auto";
812
- const fallBack = (reason, line = reason) => {
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
+ }));
813
853
  if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
854
+ requireLocalBuild(buildMachine);
814
855
  record.offloadFallback = reason;
815
856
  phase("build", `${line} -> building here`);
816
857
  };
@@ -819,11 +860,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
819
860
  let androidPackage = initialPackage;
820
861
  let ccacheActivity = CCACHE_NOT_RUN;
821
862
  let phaseFailure = null;
822
- const refused = () => ({
823
- ok: false,
824
- failure: phaseFailure,
825
- ccache: ccacheActivity
826
- });
827
863
  let buildLock = null;
828
864
  const releaseHeldLock = () => {
829
865
  if (!buildLock) return;
@@ -942,7 +978,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
942
978
  apkPath = found?.path ?? null;
943
979
  return true;
944
980
  }
945
- if (!await resolveInitialFingerprint()) return refused();
946
981
  let remote = null;
947
982
  let abandonedRemote = false;
948
983
  let uploadPending = null;
@@ -994,13 +1029,9 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
994
1029
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
995
1030
  }
996
1031
  }
997
- if (!easBuild) {
998
- await resolveRemoteArtifact();
999
- if (!apkPath) miss(reasonForMiss([]).reason);
1000
- }
1001
1032
  let waitedForBuild = null;
1002
1033
  let releasedWait = null;
1003
- if (!easBuild && useBuildCache && !apkPath) {
1034
+ async function awaitSharedBuild() {
1004
1035
  const shared = await waitForSharedBuild({
1005
1036
  platform: PLATFORM,
1006
1037
  key: cacheKey,
@@ -1022,7 +1053,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1022
1053
  if (shared.refusal) {
1023
1054
  const { code, message, remedy } = shared.refusal;
1024
1055
  phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
1025
- return refused();
1056
+ throw new ArtifactRefusal(phaseFailure);
1026
1057
  }
1027
1058
  buildLock = shared.lock;
1028
1059
  releasedWait = shared.released;
@@ -1031,6 +1062,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1031
1062
  record.cacheHit = "local";
1032
1063
  waitedForBuild = shared.hit.waited;
1033
1064
  }
1065
+ return apkPath;
1034
1066
  }
1035
1067
  let swapDir = null;
1036
1068
  const installableCachedApk = async (key, cachedPath) => {
@@ -1056,15 +1088,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1056
1088
  swapFellBack = true;
1057
1089
  return null;
1058
1090
  };
1059
- async function prepareCachedArtifact() {
1060
- if (apkPath && record.cacheHit) {
1061
- const prepared = await installableCachedApk(cacheKey, apkPath);
1062
- apkPath = prepared;
1063
- if (!prepared) {
1064
- record.cacheHit = false;
1065
- waitedForBuild = null;
1066
- }
1091
+ async function prepareCachedArtifact(cachedPath) {
1092
+ const prepared = await installableCachedApk(cacheKey, cachedPath);
1093
+ if (!prepared) {
1094
+ record.cacheHit = false;
1095
+ waitedForBuild = null;
1067
1096
  }
1097
+ return prepared;
1068
1098
  }
1069
1099
  function reasonForMiss(rekeyedBy) {
1070
1100
  if (swapFellBack) return {
@@ -1107,9 +1137,11 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1107
1137
  }
1108
1138
  }
1109
1139
  async function takeBuildSlot() {
1140
+ requireLocalBuild(buildMachine);
1110
1141
  if (!maxBuilds) return true;
1111
1142
  try {
1112
1143
  buildSlot = await acquireSlot({
1144
+ automatic: buildMachine === "auto",
1113
1145
  max: maxBuilds,
1114
1146
  root,
1115
1147
  logFile: buildLog,
@@ -1118,6 +1150,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1118
1150
  });
1119
1151
  slotWaitMs = buildSlot.slotWaitMs;
1120
1152
  } catch (err) {
1153
+ requireLocalBuild(buildMachine);
1121
1154
  const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
1122
1155
  if (refusal) {
1123
1156
  phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
@@ -1129,12 +1162,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1129
1162
  }
1130
1163
  /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
1131
1164
  function placeBuild() {
1132
- const { mode, machines } = buildPlacementCandidates(buildMachine);
1133
- 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;
1134
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;
1135
1168
  const here = machineCapacity();
1136
1169
  const placement = offloadPlacement({
1137
1170
  mode,
1171
+ localEnabled,
1138
1172
  machines: machines.length,
1139
1173
  here,
1140
1174
  unsupported,
@@ -1142,12 +1176,21 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1142
1176
  });
1143
1177
  if (!placement.offload) {
1144
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
+ }));
1145
1187
  if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
1146
1188
  return null;
1147
1189
  }
1148
1190
  return {
1149
1191
  mode,
1150
1192
  here,
1193
+ code: placement.code,
1151
1194
  reason: placement.reason,
1152
1195
  machines
1153
1196
  };
@@ -1155,6 +1198,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1155
1198
  const openOffload = { choice: null };
1156
1199
  /** Asks the paired machines once the post-mutation key is known; null builds here. */
1157
1200
  async function chooseMachine(candidate) {
1201
+ let asked = [];
1158
1202
  const choice = await chooseBuildMachine({
1159
1203
  projectRoot: root,
1160
1204
  target: {
@@ -1166,22 +1210,46 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1166
1210
  here: candidate.here,
1167
1211
  note: (line) => phase("build", chalk.dim(`offload: ${line}`)),
1168
1212
  machines: candidate.machines,
1169
- selected: buildMachine
1213
+ selected: buildMachine,
1214
+ onCandidates: (each) => asked = each
1170
1215
  });
1171
1216
  if (typeof choice === "string") {
1172
1217
  const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
1173
1218
  if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
1174
- fallBack(choice);
1219
+ fallBack(choice, choice, {
1220
+ code: "no-remote-mac-took-it",
1221
+ candidates: asked
1222
+ });
1175
1223
  return null;
1176
1224
  }
1177
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
+ }));
1178
1235
  phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
1179
1236
  return choice;
1180
1237
  }
1181
1238
  /** Builds on the chosen machine and stores the APK under the post-mutation key; false builds here instead unless a machine was named. */
1182
1239
  async function compileElsewhere(choice, candidate) {
1183
1240
  if (!storeKey || !storeHash) {
1184
- if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
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
+ }));
1185
1253
  return false;
1186
1254
  }
1187
1255
  const stagingDir = join(workspaceDir(root), "offload", PLATFORM);
@@ -1245,7 +1313,10 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1245
1313
  if (!outcome.ok || !stored) {
1246
1314
  const why = reason ?? "the APK was not stored";
1247
1315
  fallbackMachine = choice.machine;
1248
- fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`);
1316
+ fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`, {
1317
+ code: "offload-failed",
1318
+ machine: choice.machine
1319
+ });
1249
1320
  writer.write({
1250
1321
  src: "build",
1251
1322
  level: "warn",
@@ -1278,228 +1349,284 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1278
1349
  });
1279
1350
  return true;
1280
1351
  }
1281
- async function buildArtifact() {
1282
- if (!apkPath) try {
1283
- const offload = placeBuild();
1284
- if (!offload && !await takeBuildSlot()) return false;
1285
- const rekeyedBy = [];
1286
- let editedConfig = [];
1287
- 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, {
1288
1370
  isExpo,
1289
- fingerprint: hash,
1290
- sources: fingerprintSources
1371
+ clean: prebuildPlan === "regenerate"
1291
1372
  });
1292
- const prebuildRan = prebuildPlan === "generate" || prebuildPlan === "regenerate";
1293
- if (prebuildPlan === "refuse") {
1294
- const refusal = staleNativeDirRefusal(PLATFORM);
1295
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1296
- return false;
1297
- }
1298
- if (prebuildRan) {
1299
- miss(reasonForMiss([]).reason, true);
1300
- step("prebuild");
1301
- recordPrebuild(root, PLATFORM, null);
1302
- const pre = await prebuild(root, PLATFORM, writer, {
1303
- isExpo,
1304
- clean: prebuildPlan === "regenerate"
1305
- });
1306
- if (pre.failed) {
1307
- phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
1308
- lastBuildStatus: true,
1309
- lines: tail(pre.lastLines),
1310
- logPath: displayPath(root, buildLog)
1311
- });
1312
- return false;
1313
- }
1314
- phase("prebuild", `${prebuildPlan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
1315
- androidPackage = detectAndroidPackage(root) || androidPackage;
1316
- record.bundleId = androidPackage;
1317
- const after = await refingerprintAfterMutation({
1318
- projectRoot: root,
1319
- platform: PLATFORM,
1320
- previousHash: hash,
1321
- 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)
1322
1378
  });
1323
- editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
1324
- if (after && !editedConfig.length) recordPrebuild(root, PLATFORM, after.hash);
1325
- if (after?.moved && !editedConfig.length) {
1326
- rekeyedBy.push("prebuild");
1327
- storeHash = after.hash;
1328
- storeSources = after.sources;
1329
- storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
1330
- record.fingerprint = storeHash;
1331
- record.cacheKey = storeKey;
1332
- phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
1333
- const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
1334
- if (late) {
1335
- const prepared = await installableCachedApk(storeKey, late);
1336
- if (prepared) {
1337
- apkPath = prepared;
1338
- record.cacheHit = "local";
1339
- lateHit();
1340
- phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1341
- if (releasedWait) {
1342
- waitedForBuild = releasedWait.facts;
1343
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1344
- }
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`);
1345
1422
  }
1346
1423
  }
1347
1424
  }
1348
1425
  }
1349
- if (offload && !apkPath) {
1350
- explainMiss(rekeyedBy);
1351
- step("compile");
1352
- let built = false;
1353
- 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");
1354
- else {
1355
- const choice = await chooseMachine(offload);
1356
- if (choice) built = await compileElsewhere(choice, offload);
1357
- }
1358
- if (!built && !await takeBuildSlot()) return false;
1359
- }
1360
- if (!apkPath) {
1361
- if (!offload) {
1362
- explainMiss(rekeyedBy);
1363
- step("compile");
1364
- }
1365
- if (offload) stats.setPlacement({
1366
- decision: "fell-back",
1367
- slotWaitMs,
1368
- deviceSlotWaitMs: stats.deviceSlotWaitMs(),
1369
- reason: record.offloadFallback ?? "offload failed",
1370
- ...fallbackMachine ? { machine: fallbackMachine } : {}
1371
- });
1372
- else stats.setPlacement({
1373
- decision: "here",
1374
- reason: hereReason,
1375
- slotWaitMs,
1376
- deviceSlotWaitMs: stats.deviceSlotWaitMs()
1377
- });
1378
- record.builtOn = "here";
1379
- phase("build", `compiling ${variant || "debug"} with Gradle`);
1380
- const built = await build({
1381
- root,
1382
- logWriter: writer,
1383
- variant,
1384
- abi: buildAbi
1385
- }, {
1386
- estimateMs: estimates().coldBuildMs,
1387
- ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
1388
- root,
1389
- onNote: out
1390
- }) : null,
1391
- cas,
1392
- buildCache: buildPlan.gradleBuildCache,
1393
- pch: buildPlan.pch,
1394
- compilerCacheDisabled: buildPlan.compilerCache === "none"
1395
- });
1396
- ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
1397
- phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
1398
- if (!built.ok) {
1399
- const diagnostics = built.diagnostics;
1400
- for (const diag of diagnostics) writer.write({
1401
- src: "build",
1402
- level: "error",
1403
- event: "gradle_diagnostic",
1404
- msg: formatDiagnostic(diag)
1405
- });
1406
- phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
1407
- const extracted = diagnostics.map(formatDiagnostic);
1408
- if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
1409
- phaseFailure = fail$1(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
1410
- lastBuildStatus: true,
1411
- diagnostics: extracted,
1412
- buildDiagnostics: diagnostics,
1413
- lines: extracted.length ? [] : tail(built.lastLines),
1414
- logPath: displayPath(root, buildLog)
1415
- });
1416
- return false;
1417
- }
1418
- apkPath = built.apkPath;
1419
- stats.setBuildMs(built.durationMs);
1420
- phase("build", `ok (${formatDuration(built.durationMs)})`);
1421
- if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
1422
- const beforeBuildHash = storeHash;
1423
- const afterBuild = editedConfig.length ? null : await refingerprintAfterMutation({
1424
- projectRoot: root,
1425
- platform: PLATFORM,
1426
- previousHash: beforeBuildHash,
1427
- fingerprint
1428
- });
1429
- const changedDuringBuild = afterBuild ? inputsChangedDuringBuild({
1430
- platform: PLATFORM,
1431
- lookup: fingerprintSources,
1432
- prebuildRan,
1433
- compiled: storeSources,
1434
- current: afterBuild.sources
1435
- }) : editedConfig;
1436
- if (!afterBuild || changedDuringBuild.length) {
1437
- record.fingerprint = null;
1438
- record.cacheKey = null;
1439
- phase("fingerprint", chalk.yellow(changedDuringBuild.length ? changedDuringBuildLine(changedDuringBuild) : "unavailable after Gradle; the build will be installed but not cached"));
1440
- } else {
1441
- if (afterBuild.moved) {
1442
- storeHash = afterBuild.hash;
1443
- storeSources = afterBuild.sources;
1444
- storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
1445
- record.fingerprint = storeHash;
1446
- record.cacheKey = storeKey;
1447
- phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
1448
- }
1449
- if (cachePolicy.write) {
1450
- const assetManifest = release ? captureAssets(root, { variant }) : null;
1451
- try {
1452
- const stored = await storeTieredBuild({
1453
- local: filesystemBuildCapability({
1454
- resolve: resolveCached,
1455
- store: storeCached,
1456
- sources: storeSources,
1457
- assetManifest
1458
- }),
1459
- loadProvider: loadTieredProvider,
1460
- target: {
1461
- projectRoot: root,
1462
- platform: PLATFORM,
1463
- key: storeKey
1464
- },
1465
- sourcePath: apkPath,
1466
- overwrite: !useBuildCache || swapFellBack,
1467
- warn: cacheWarn
1468
- });
1469
- providerUpload = stored.providerUpload;
1470
- providerName = stored.providerName ?? providerName;
1471
- } catch (err) {
1472
- phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
1473
- }
1474
- }
1475
- if (remote) uploadPending = uploadRemoteBuild({
1476
- logWriter: writer,
1477
- provider: remote.provider,
1478
- 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: {
1479
1548
  projectRoot: root,
1480
- fingerprintHash: storeHash,
1481
- buildPath: apkPath,
1482
- runOptions: remoteRunOptions
1483
- });
1484
- }
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}`));
1485
1560
  }
1486
- } catch (error) {
1487
- if (!(error instanceof OffloadRefusal)) throw error;
1488
- const refusal = error;
1489
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1490
- return false;
1491
- } finally {
1492
- if (openOffload.choice) closeOffload(openOffload.choice);
1493
- releaseHeldLock();
1494
- releaseHeldSlot();
1495
1561
  }
1496
- 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
+ });
1497
1571
  }
1498
- if (!easBuild) {
1499
- await prepareCachedArtifact();
1500
- if (apkPath) lateHit();
1501
- else if (swapFellBack) miss(reasonForMiss([]).reason);
1502
- 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;
1503
1630
  }
1504
1631
  return {
1505
1632
  ok: true,
@@ -1603,11 +1730,41 @@ async function startCollector({ root, slot = "default", serial, packageName, spa
1603
1730
  }
1604
1731
  //#endregion
1605
1732
  //#region src/commands/android/launch.ts
1606
- async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevice, verifyReleaseLaunched, verifyLaunched, serial, androidPackage, variant, metroCheck, logsDir, launchedAt, metroPort, isExpo, physical, device, scheme, component = null, phase }) {
1607
- const runCommand = nativeRunCommand("android", slot, {
1608
- physical,
1609
- deviceId: serial
1610
- });
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 }) {
1611
1768
  const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1612
1769
  root,
1613
1770
  slot,
@@ -1616,30 +1773,42 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1616
1773
  appId: androidPackage,
1617
1774
  since: launchedAt
1618
1775
  }, logsDir);
1619
- if (remoteRelease) {
1776
+ if (remoteDevice) {
1620
1777
  phase("verify", chalk.yellow("UNVERIFIED: remote adapter launch accepted; process verification is unavailable"));
1621
1778
  return { state: LAUNCH_UNVERIFIED };
1622
1779
  }
1623
- if (release) {
1624
- const processCheck = await verifyReleaseLaunched({
1625
- serial,
1626
- packageName: androidPackage
1627
- });
1628
- const crashes = readNativeCrashes();
1629
- if (processCheck?.verified && !crashes.length) {
1630
- phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${variant}: no bundle fetch to observe)`);
1631
- return { state: true };
1632
- }
1633
- if (processCheck?.reason === "probe-failed" && !crashes.length) {
1634
- phase("verify", chalk.yellow("UNVERIFIED: the app process check failed"));
1635
- return { state: LAUNCH_UNVERIFIED };
1636
- }
1637
- for (const line of launchErrorPreview(crashes, root)) phase("launch", chalk.red(line));
1638
- if (!crashes.length) phase("logs", "Process missing; no attributable native crash report captured. Read `stim logs --source device` for available output.");
1639
- 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`));
1640
- 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."));
1641
- 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 };
1642
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);
1643
1812
  const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
1644
1813
  const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1645
1814
  if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
@@ -1814,7 +1983,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1814
1983
  });
1815
1984
  return resolved.serial;
1816
1985
  }
1817
- 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 }) {
1818
1987
  let androidPackage = initialPackage;
1819
1988
  let leaseWarned = false;
1820
1989
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1946,7 +2115,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1946
2115
  record.bundleId = androidPackage;
1947
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 });
1948
2117
  if (physical) raiseLeaseFor(0, false);
1949
- const scheme = release ? void 0 : resolveDevClientScheme(root, apkPath);
2118
+ const scheme = runtime.scheme(root, apkPath, resolveDevClientScheme);
1950
2119
  enterPhase("launch");
1951
2120
  const launchTimer = stepTimer(now);
1952
2121
  const launchedAt = now();
@@ -1963,19 +2132,14 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1963
2132
  physical,
1964
2133
  msg: `launching ${androidPackage} on ${serial}`
1965
2134
  });
1966
- const launched = release ? remoteDevice ? remoteDevice.launch({
1967
- serial,
1968
- packageName: androidPackage,
1969
- metroPort: null
1970
- }) : launchRelease({
2135
+ const launched = await runtime.launch(runtimePreparation, {
1971
2136
  serial,
1972
- packageName: androidPackage
1973
- }) : launch({
1974
- serial,
1975
- packageName: androidPackage,
1976
- metroPort: metroPort ?? 8081,
1977
- devClientScheme: scheme,
1978
- physical
2137
+ androidPackage,
2138
+ scheme,
2139
+ physical,
2140
+ remoteDevice,
2141
+ launch,
2142
+ launchRelease
1979
2143
  });
1980
2144
  if (launched.failed) {
1981
2145
  printNativeCrashReport({
@@ -2066,12 +2230,10 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
2066
2230
  });
2067
2231
  phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
2068
2232
  }
2069
- if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
2070
- 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, {
2071
2235
  root,
2072
2236
  slot,
2073
- release,
2074
- remoteRelease,
2075
2237
  remoteDevice: Boolean(remoteDevice),
2076
2238
  verifyReleaseLaunched,
2077
2239
  verifyLaunched,
@@ -2394,7 +2556,7 @@ function androidDeviceSelectorRefusal({ settingsContext, slot, systemImageFlag,
2394
2556
  //#endregion
2395
2557
  //#region src/commands/android/next-build.ts
2396
2558
  const DEFAULT_PLAN_DEPS = {
2397
- findRoot: findProjectRoot$1,
2559
+ findRoot: findProjectRoot,
2398
2560
  fingerprint: fingerprintProject,
2399
2561
  listSystemImages: listInstalledSystemImages,
2400
2562
  avdSystemImage: ownedAvdSystemImage,
@@ -2459,10 +2621,10 @@ async function planAndroid(opts, overrides = {}) {
2459
2621
  if (flag) return refuse(planFlagRefusal(flag));
2460
2622
  const root = deps.findRoot(process.cwd());
2461
2623
  if (!root) return refuse(NO_PROJECT_REFUSAL);
2462
- const projectProblem = appProjectProblem(root);
2463
- if (projectProblem) return refuse({
2624
+ const problem = projectProblem(root, "android");
2625
+ if (problem) return refuse({
2464
2626
  code: "STIM_NO_PROJECT",
2465
- ...projectProblem
2627
+ ...problem
2466
2628
  });
2467
2629
  const slot = validateDeviceSlot(opts.slot);
2468
2630
  const settingsContext = {
@@ -2470,8 +2632,9 @@ async function planAndroid(opts, overrides = {}) {
2470
2632
  gitCommonDir: gitCommonDir(root),
2471
2633
  repoRoot: repoRoot(root)
2472
2634
  };
2635
+ const settings = resolveSettings(settingsContext);
2473
2636
  const planned = resolveAndroidRunPlan({
2474
- settings: resolveSettings(settingsContext),
2637
+ settings,
2475
2638
  settingsContext,
2476
2639
  slot,
2477
2640
  easProfile: opts.easProfile,
@@ -2524,20 +2687,59 @@ async function planAndroid(opts, overrides = {}) {
2524
2687
  refusal: eas.missing
2525
2688
  }), json);
2526
2689
  }
2527
- if (target.kind !== "emulator") {
2528
- if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
2529
- return refuse({
2530
- code: "STIM_BAD_ARG",
2531
- message: `android.remote routes this workspace's runs to a ${target.kind === "hosted" ? target.machine : target.backend} device, whose ABI --plan cannot read without a session.`,
2532
- remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
2533
- });
2534
- }
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
+ });
2535
2696
  if (build.compilerCache === "cas") return refuse({
2536
2697
  code: "STIM_BAD_ARG",
2537
2698
  message: "The experimental Android compiler CAS keys builds by a toolchain Stim sets up, which --plan does not.",
2538
2699
  remedy: "Run `stim android` to build with the CAS, or set optimizations.android.compilerCache to ccache."
2539
2700
  });
2540
- const image = build.targetAbiOnly && !build.release ? emulatorImage(root, slot, target.systemImage, deps) : { systemImage: null };
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();
2541
2743
  if ("refusal" in image) return refuse(image.refusal);
2542
2744
  const { abi, runOptions, remoteRunOptions } = androidBuildOptions({
2543
2745
  release: build.release,
@@ -2546,7 +2748,8 @@ async function planAndroid(opts, overrides = {}) {
2546
2748
  variant: build.variant,
2547
2749
  deviceAbi: () => null,
2548
2750
  buildProfile: build.profile,
2549
- targetAbiOnly: build.targetAbiOnly
2751
+ targetAbiOnly: build.targetAbiOnly,
2752
+ hostedAbi
2550
2753
  });
2551
2754
  let fingerprint;
2552
2755
  try {
@@ -2576,7 +2779,10 @@ async function planAndroid(opts, overrides = {}) {
2576
2779
  ...deps,
2577
2780
  note
2578
2781
  });
2579
- printPlan(plan, json);
2782
+ printPlan(placement ? {
2783
+ ...plan,
2784
+ placement
2785
+ } : plan, json);
2580
2786
  }
2581
2787
  //#endregion
2582
2788
  //#region src/commands/android.ts
@@ -2584,52 +2790,58 @@ function androidCommand(program) {
2584
2790
  registerAndroid(program);
2585
2791
  }
2586
2792
  function registerAndroid(program) {
2587
- 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) => {
2588
- 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");
2589
2795
  return value;
2590
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) => {
2591
2797
  if (opts.plan) return planAndroid(opts);
2592
- const root = findProjectRoot$1(process.cwd());
2798
+ const root = findProjectRoot(process.cwd());
2593
2799
  if (!root) {
2594
2800
  refuseNoProject({ json: Boolean(opts.json) });
2595
2801
  return;
2596
2802
  }
2597
- if (!(await withNativeBuildRun(root, {
2598
- command: "android",
2599
- platform: "android",
2600
- slot: opts.slot ?? "default"
2601
- }, async (claim) => {
2602
- recordWorkspaceUse(root);
2603
- const progress = startBuildProgress({
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({
2604
2825
  root,
2605
- platform: "android",
2606
- slot: opts.slot ?? "default",
2607
- claim,
2608
- note: (line) => console.error(chalk.dim(line))
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
2609
2840
  });
2610
- try {
2611
- return await runAndroid({
2612
- root,
2613
- buildMachine: opts.remoteBuild,
2614
- slot: opts.slot,
2615
- easProfile: opts.easProfile,
2616
- json: Boolean(opts.json),
2617
- metroCheck: opts.metroCheck !== false,
2618
- useBuildCache: opts.buildCache !== false,
2619
- variant: opts.variant ?? null,
2620
- systemImage: opts.systemImage ?? null,
2621
- deviceProfile: opts.deviceProfile ?? null,
2622
- remoteDevice: opts.remote ?? null,
2623
- device: opts.device ?? null,
2624
- wait: opts.wait,
2625
- waitConflict: waitFlagConflict(process.argv),
2626
- progress
2627
- });
2628
- } finally {
2629
- progress.clear();
2630
- }
2631
- }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
2632
- });
2841
+ } finally {
2842
+ progress.clear();
2843
+ }
2844
+ }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) });
2633
2845
  }
2634
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) } = {}) {
2635
2847
  return {
@@ -2785,9 +2997,9 @@ async function runAndroid(options = {}) {
2785
2997
  const slot = validateDeviceSlot(options.slot);
2786
2998
  const started = now();
2787
2999
  const startedAt = new Date(started).toISOString();
2788
- const projectProblem = appProjectProblem(root);
2789
- if (projectProblem) {
2790
- const { message, remedy } = projectProblem;
3000
+ const problem = projectProblem(root, "android");
3001
+ if (problem) {
3002
+ const { message, remedy } = problem;
2791
3003
  out(phaseLine("error", chalk.red(`STIM_NO_PROJECT: ${message}`)));
2792
3004
  out(phaseLine("remedy", remedy));
2793
3005
  if (json) emit(JSON.stringify({
@@ -2835,6 +3047,7 @@ async function runAndroid(options = {}) {
2835
3047
  slot
2836
3048
  }
2837
3049
  }), progress);
3050
+ setRemoteLogSink((entry) => writer.write(entry));
2838
3051
  const record = {
2839
3052
  fingerprint: null,
2840
3053
  cacheKey: null,
@@ -2949,6 +3162,7 @@ async function runAndroid(options = {}) {
2949
3162
  if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
2950
3163
  const { plan } = planned;
2951
3164
  const deviceSlotWait = {
3165
+ automatic: plan.target.kind === "hosted" && plan.target.machine === "auto",
2952
3166
  signal: runCancellationSignal(),
2953
3167
  waitMs: plan.deviceSlotWaitMs,
2954
3168
  displayName: basename(root),
@@ -2974,6 +3188,16 @@ async function runAndroid(options = {}) {
2974
3188
  resolveSerial: resolveAvdSerial,
2975
3189
  prepare: options.prepareHostedAndroid,
2976
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
+ }) } : {},
2977
3201
  localSelectors: (target) => androidDeviceSelectorRefusal({
2978
3202
  settingsContext,
2979
3203
  slot,
@@ -2989,7 +3213,9 @@ async function runAndroid(options = {}) {
2989
3213
  }),
2990
3214
  checkBudget: options.checkBudget,
2991
3215
  note: out,
2992
- phase
3216
+ phase,
3217
+ log: (entry) => writer.write(entry),
3218
+ fromFlag: commandRemoteBackend !== null
2993
3219
  });
2994
3220
  if ("failure" in hosting) return fail(hosting.failure.code, hosting.failure.message, hosting.failure.remedy);
2995
3221
  const { target, hostedTarget, budget, selectors } = hosting;
@@ -3036,10 +3262,8 @@ async function runAndroid(options = {}) {
3036
3262
  const reservedPort = project?.metroPort ?? null;
3037
3263
  let metroPort = null;
3038
3264
  let devServer = null;
3039
- let phaseFailure = null;
3040
- async function resolveMetroPort() {
3041
- if (release) phase("metro", `skipped (${variant}: the JS bundle is embedded, no dev server is used)`);
3042
- else if (metroCheck) {
3265
+ async function prepareMetro() {
3266
+ if (metroCheck) {
3043
3267
  const gate = await ensureDevServer({
3044
3268
  root,
3045
3269
  port: reservedPort,
@@ -3051,27 +3275,54 @@ async function runAndroid(options = {}) {
3051
3275
  readState
3052
3276
  });
3053
3277
  reclaimed = [...reclaimed, ...gate.reclaimed];
3054
- if (!gate.ok) {
3055
- phaseFailure = fail(gate.code, gate.message, gate.remedy, { lines: gate.lines });
3056
- return false;
3057
- }
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
+ };
3058
3287
  metroPort = gate.port;
3059
3288
  devServer = gate.devServer;
3060
3289
  phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
3061
- return true;
3290
+ return {
3291
+ ok: true,
3292
+ prepared: { metroPort }
3293
+ };
3062
3294
  } else {
3063
3295
  const pin = metroPortSetting(root);
3064
- if (pin.error) {
3065
- phaseFailure = fail("STIM_BAD_ARG", pin.error, SETTING_SHAPE_REMEDY);
3066
- return false;
3067
- }
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
+ };
3068
3304
  metroPort = pin.port ?? reservedPort ?? 8081;
3069
3305
  phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
3070
3306
  }
3071
- if (release) metroPort = null;
3072
- return true;
3307
+ return {
3308
+ ok: true,
3309
+ prepared: { metroPort }
3310
+ };
3073
3311
  }
3074
- 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;
3075
3326
  if (remoteContext) {
3076
3327
  remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
3077
3328
  if (metroPort !== null) {
@@ -3312,6 +3563,7 @@ async function runAndroid(options = {}) {
3312
3563
  record,
3313
3564
  fail,
3314
3565
  phase,
3566
+ enterPhase: progress.step,
3315
3567
  out,
3316
3568
  json,
3317
3569
  useBuildCache,
@@ -3386,6 +3638,8 @@ async function runAndroid(options = {}) {
3386
3638
  }
3387
3639
  try {
3388
3640
  return await finishAndroidRun({
3641
+ runtime,
3642
+ runtimePreparation,
3389
3643
  slot,
3390
3644
  lease: leaseHandle,
3391
3645
  releaseLease,
@@ -3475,4 +3729,4 @@ async function runAndroid(options = {}) {
3475
3729
  }
3476
3730
  }
3477
3731
  //#endregion
3478
- export { NO_DEVICE, NO_FINGERPRINT, androidDevClientScheme, androidFacts, androidSystemImageSetting, androidVariantSetting, apkDevClientFacts, apkPackage, collectorLogFile, androidCommand as default, displayPath, dumpApkManifest, findAapt, formatDuration, isReleaseVariant, killPreviousCollector, lastBuildRecord, noDeviceDiagnostic, parseXmltree, phaseLine, registerAndroid, resolveSystemImage, resolveVariant, runAndroid, shortHash, startCollector };
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 };