stim 1.14.0 → 1.16.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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,38 +1,48 @@
1
- import { A as ownership_claim_exports, B as deviceSlotPlatforms, C as setDevice, E as withConfigLock$1, G as validateDeviceSlot, H as parseDeviceSlotOption, R as deviceSlotFileKey, S as saveConfig, T as upsertProject, _ as loadConfig$1, p as getProject, u as getConcurrencyLimits, z as deviceSlotKey } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
- import { M as workspaceLogsDir, a as pidExists, b as writeWorkspaceState, c as resolveProjectMetro, g as recordWorkspaceUse, h as readWorkspaceState$1, k as workspaceDir, l as signalProcessTree, x as emulatorLogFile } from "./metro-CcEo8Fl8.mjs";
5
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
6
- import { a as findProjectRoot, l as projectShortcut, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-CWlvEaeN.mjs";
7
- import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DHqvsbqC.mjs";
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";
8
4
  import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
9
- 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-DVcpw6eE.mjs";
10
- 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-5c3WrVRm.mjs";
11
- import { $ as waitForBoot, A as listInstalledSystemImages, C as extractEmulatorFailure, D as listAdbDevices, E as hostSystemImageArch, F as ownedAvdDirectory, G as profileNeedsFoldFeature, H as physicalDeviceModel, J as resolvePhysicalDevice, K as resetAdoptedAvd, M as memoizeEmulatorProbe, O as listAvdDeviceProfiles, P as ownedAvdDeviceProfile, S as emulatorFailureRemedy, U as pickDefaultSystemImage, W as probeEmulatorSerial, Z as systemImageSupportsFold, a as androidHome, c as androidSystemImageAbi, ct as parkedMaxSetting, ft as configuredAndroidEmulatorApp, i as androidDeviceAbi, k as listAvds, l as androidToolCwd, o as androidPoolCandidates, q as resolveOwnedAvdSerial, r as androidBuildToolName, s as androidPoolNoCandidatesRefusal, t as DEFAULT_AVD_DEVICE_PROFILE, w as findBuildTool, x as emulatorDiskSpaceRemedy, z as ownedAvdSystemImage } from "./android-d6rGq1pA.mjs";
12
- import { A as resolveCacheProviderConfig, B as detectProviders, E as publicUrlSetting, G as resolveOptimizations, H as artifactCachePolicy, I as tunnelModeSetting, L as unknownSettingKeys, M as settingFile, O as remoteAndroidSetting, P as settingShapeErrors, S as metroWarmupUrlSetting, U as compilerCacheFallbackMessage, W as optimizationBuildProfile, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as resolveSettings, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError } from "./settings-D61iNOsB.mjs";
13
- import { c as recordFinishedBuild, l as startBuildProgress, n as NO_BUILD_PROGRESS } from "./build-progress-DxKpwnaS.mjs";
14
- import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-DlqBDKqY.mjs";
15
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-DyYDF5ww.mjs";
16
- import { t as cleanLine } from "./server-expo-BVuwQ65v.mjs";
17
- 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-CcR6o6lI.mjs";
18
- import { F as ownedSessionName, S as withNativeBuildRun, a as ensureMetroReachable, c as remoteAndroidDeps, m as cancelledFailure, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, x as runCancellation } from "./device-remote-D87zEzaq.mjs";
19
- import { _ as launchAndroidReleaseApp, f as installAndroidApp, g as launchAndroidApp, o as androidAppProcess, p as installConflictKind, r as DEFAULT_METRO_PORT, s as androidDevClientUrl, t as ADB_INSTALL_TIMEOUT_MS, y as restartedAppNote } from "./app-install-BpgSgkJj.mjs";
20
- 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-Deg4tB3Z.mjs";
21
- import { c as unknownAndroidSystemImageRefusal, h as resolveEasDevelopmentBuild, m as planEasDevelopmentBuild, p as isEasBuildFailure, r as checkDeviceCapacity, s as unknownAndroidDeviceProfileRefusal } from "./simslim-DrDN4WuV.mjs";
22
- import { f as spawnEntry, m as detectAppIds, p as detectAndroidPackage, s as startDevServer } from "./start-s4RXp1wy.mjs";
23
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-BiyDyPP-.mjs";
24
- import { _ as staleNativeDirRefusal, a as refusePlan, b as AvdBootError, c as skippedMissReason, f as detectEntryFile, g as runPrebuild, h as recordPrebuild, i as printPlan, l as devClientScheme, m as planPrebuild, n as planFlagRefusal, o as explainBuildMiss, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as pickDevClientScheme, v as ensureBooted, w as warmMetro, x as AvdRecoveryError, y as ensureOwnedDevice } from "./build-plan-Dcr-3ie0.mjs";
25
- import { t as budgetGate } from "./budget-CGDkhvv_.mjs";
26
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-DjBHRg5m.mjs";
27
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-CX1Z8Tca.mjs";
28
- import { c as readProductFlavors, d as ccacheActivityLine, l as CCACHE_NOT_RUN, m as formatDiagnostic, n as resolveAndroidCas, o as buildAndroid, p as resolveCcache, r as resolveAndroidCompilerCache, s as productFlavorRefusal, u as CCACHE_UNAVAILABLE } from "./android-cas-iZK3DOCr.mjs";
29
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-Cp5KvK9x.mjs";
30
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-D5t8EiMf.mjs";
31
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-C5HzcwCW.mjs";
32
- import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
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-DnJMdEY-.mjs";
6
+ import { B as settingsLayers, F as settingFile, G as detectProviders, H as unknownSettingKeys, J as artifactCachePolicy, L as settingOriginScope, N as resolveCacheProviderConfig, P as resolveSettings, R as settingShapeErrors, S as metroPortSetting, Tt as removeTemporaryEntry, V as tunnelModeSetting, X as optimizationBuildProfile, Y as compilerCacheFallbackMessage, Z as resolveOptimizations, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, gt as repoRoot, it as gitCommonDir, j as remoteAndroidSetting, k as publicUrlSetting, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, w as metroWarmupUrlSetting, wt as makeTemporaryDirectory } from "./settings-oTSkkNrM.mjs";
7
+ import { n as appProjectProblem, o as findProjectRoot$1, r as detectIsExpo, t as NO_PROJECT_REFUSAL, u as projectShortcut } from "./project-Du91Ilvc.mjs";
8
+ import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-BeK1ZPz1.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-B8-aJ1_5.mjs";
10
+ import { t as budgetGate } from "./budget-DjrURfQn.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-D-hLqx1b.mjs";
12
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-rUk77xqh.mjs";
13
+ import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DcW6vOd_.mjs";
14
+ import { S as statsProjectKey$1, _ as tapBuildLog, g as startBuildProgress, h as recordFinishedBuild, l as NO_BUILD_PROGRESS, v as createRunRecorder, x as recordRunStats, y as readRunEstimates } from "./idle-DYVymG92.mjs";
15
+ 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_NumVyQ.mjs";
16
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
17
+ import { i as teardownOwnedAvd } from "./teardown-CL6vDs2Y.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-DkUflduf.mjs";
20
+ import { t as cleanLine } from "./server-expo-MC0rHlOC.mjs";
21
+ import { a as offloadBuild, c as placementLoad, d as OffloadRefusal, f as namedBuildMachine, i as closeOffload, l as remotePhaseText, m as resolveBuildPlacement, n as buildPlacementCandidates, p as parseBuildMachineOption, r as chooseBuildMachine, s as offloadPlacement } from "./client-BTY686R6.mjs";
22
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-DiA16AsN.mjs";
23
+ 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-CKWdbXIX.mjs";
24
+ import { t as checkDeviceCapacity } from "./device-capacity-BHwxNBlu.mjs";
25
+ 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-CAP8qDUF.mjs";
26
+ import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
27
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-D2fsca_Z.mjs";
28
+ import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-BsO1MdsH.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-2DBRizjo.mjs";
30
+ 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-CqjShqWW.mjs";
31
+ 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-C8-V13XN.mjs";
32
+ import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-D5WVWqOq.mjs";
33
+ import { d as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-NaBrRFtQ.mjs";
34
+ import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-B8-7AoTI.mjs";
35
+ import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-ClDfwXa4.mjs";
36
+ import { _ as AvdRecoveryError, a as refusePlan, 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 } from "./build-plan-DjeSb2Xl.mjs";
37
+ import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-DBzRnEKS.mjs";
38
+ import { t as selectFromPool } from "./device-pool-CUaEWMMm.mjs";
39
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-B8VrHxn1.mjs";
40
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-B9L4Ae0x.mjs";
41
+ import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-8U4DPqeR.mjs";
42
+ import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
43
+ import { basename, dirname, isAbsolute, join } from "node:path";
33
44
  import { InvalidArgumentError } from "commander";
34
- import { buildDiagnostics } from "@stim-cli/core/state";
35
- import { basename, dirname, isAbsolute, join, relative } from "node:path";
45
+ import { buildDiagnostics, machineCapacity, parseMachine } from "@stim-cli/core/state";
36
46
  import chalk from "chalk";
37
47
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
38
48
  //#region src/engine/apk-swap.ts
@@ -316,6 +326,18 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
316
326
  const diff = compareAssetManifests(fresh, storedAssets);
317
327
  if (!diff.same) return refuse(assetDiffReason(diff), diff);
318
328
  const jar = jarPath();
329
+ const updatesManifest = join(stage, "assets", UPDATES_MANIFEST_NAME);
330
+ try {
331
+ e.runFile(jar, [
332
+ "--extract",
333
+ "--file",
334
+ work,
335
+ `assets/${UPDATES_MANIFEST_NAME}`
336
+ ], { cwd: stage });
337
+ if (exists(updatesManifest)) refreshUpdatesManifestFile(updatesManifest, now());
338
+ } catch (err) {
339
+ return fail("updates", `could not refresh the expo-updates manifest from ${work}: ${describe(err)}`);
340
+ }
319
341
  try {
320
342
  e.runFile(jar, [
321
343
  "--update",
@@ -387,299 +409,6 @@ function describe(err) {
387
409
  return streams ? `${message}: ${streams}` : message;
388
410
  }
389
411
  //#endregion
390
- //#region src/commands/android/support.ts
391
- const PLATFORM = "android";
392
- function androidVariantSetting(settings) {
393
- const android = settings?.["android"];
394
- if (!android || typeof android !== "object" || Array.isArray(android)) return null;
395
- const raw = android["variant"];
396
- return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
397
- }
398
- function resolveVariant(flag, settings) {
399
- return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || androidVariantSetting(settings);
400
- }
401
- function androidSystemImageSetting(settings) {
402
- const android = settings?.["android"];
403
- if (!android || typeof android !== "object" || Array.isArray(android)) return null;
404
- const raw = android["systemImage"];
405
- return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
406
- }
407
- function resolveSystemImage(flag, settings) {
408
- return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || androidSystemImageSetting(settings);
409
- }
410
- function systemImageRefusal({ slot, flag, resolved, physical, remoteBackend, listImages }) {
411
- if (slot && slot !== "default" && remoteBackend) return {
412
- code: "STIM_BAD_ARG",
413
- message: "Named slots currently support local simulators and physical devices.",
414
- remedy: "Use the default slot for a remote session."
415
- };
416
- if (typeof flag === "string" && flag.trim() === "") return {
417
- code: "STIM_BAD_ARG",
418
- message: "--system-image was given an empty id.",
419
- remedy: "Pass `--system-image <id>` with an sdkmanager package id, e.g. \"system-images;android-36;google_apis;arm64-v8a\"."
420
- };
421
- if (physical || remoteBackend) return null;
422
- if (!resolved) return null;
423
- let images;
424
- try {
425
- images = listImages();
426
- } catch (error) {
427
- return {
428
- code: NO_DEVICE,
429
- message: `Could not read the installed Android system images: ${error?.message || error}`,
430
- remedy: "Check that ANDROID_HOME points at a readable SDK, then try again."
431
- };
432
- }
433
- const refusal = unknownAndroidSystemImageRefusal(resolved, images);
434
- return refusal ? {
435
- code: "STIM_BAD_ARG",
436
- message: refusal.message,
437
- remedy: refusal.remedy
438
- } : null;
439
- }
440
- function resolveDeviceProfile(flag, settings) {
441
- const fromFlag = typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null;
442
- const android = settings?.["android"];
443
- const raw = android && typeof android === "object" && !Array.isArray(android) ? android["deviceProfile"] : void 0;
444
- return fromFlag || (typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null);
445
- }
446
- function deviceProfileRefusal({ flag, resolved, physical, remoteBackend, listProfiles }) {
447
- if (typeof flag === "string" && flag.trim() === "") return {
448
- code: "STIM_BAD_ARG",
449
- message: "--device-profile was given an empty id.",
450
- remedy: "Pass `--device-profile <id>` with an id `avdmanager list device -c` prints, e.g. \"pixel_fold\"."
451
- };
452
- if (physical || remoteBackend || !resolved) return null;
453
- let profiles;
454
- try {
455
- profiles = listProfiles();
456
- } catch (error) {
457
- return {
458
- code: NO_DEVICE,
459
- message: `Could not list the Android hardware profiles with avdmanager: ${error?.message || error}`,
460
- remedy: "Check that JAVA_HOME and ANDROID_HOME are set and the SDK command-line tools are installed, then try again."
461
- };
462
- }
463
- const refusal = unknownAndroidDeviceProfileRefusal(resolved, profiles);
464
- return refusal ? {
465
- code: "STIM_BAD_ARG",
466
- message: refusal.message,
467
- remedy: refusal.remedy
468
- } : null;
469
- }
470
- function foldableImageRefusal({ profile, image, avdName, images, supportsFold }) {
471
- if (!profileNeedsFoldFeature(profile) || !image || supportsFold(image)) return null;
472
- const compatible = pickDefaultSystemImage(images().filter((candidate) => supportsFold(candidate.pkg)))?.pkg;
473
- const message = `${avdName ? `This workspace's emulator ${avdName} uses device profile ${profile} on ${image}` : `Device profile ${profile} with system image ${image}`}, which the emulator refuses to boot: ${profile} needs a system image with foldable support (SupportPixelFold in its advancedFeatures.ini).`;
474
- if (!compatible) return {
475
- code: "STIM_BAD_ARG",
476
- message,
477
- remedy: `Install an image with foldable support, e.g. \`sdkmanager "system-images;android-36;google_apis;${hostSystemImageArch()}"\`, then pass it with \`--system-image\`, or pick another \`--device-profile\`.`
478
- };
479
- return {
480
- code: "STIM_BAD_ARG",
481
- message,
482
- remedy: avdName ? `Pass \`--system-image "${compatible}"\`: it replaces ${avdName} if that emulator never finished a boot. Otherwise add \`--slot <name>\` to create the foldable beside it.` : `Pass \`--system-image "${compatible}"\` (or set android.systemImage to it), or pick another \`--device-profile\`.`
483
- };
484
- }
485
- function isReleaseVariant(variant) {
486
- return typeof variant === "string" && /release$/i.test(variant.trim());
487
- }
488
- const NO_FINGERPRINT = "STIM_NO_FINGERPRINT";
489
- const NO_DEVICE = "STIM_NO_DEVICE";
490
- const LAUNCH_FAILED = "STIM_LAUNCH_FAILED";
491
- function findAapt(home = androidHome(), { readDir = readdirSync, exists = existsSync } = {}) {
492
- const found = findBuildTool(["aapt", "aapt2"], {
493
- home,
494
- readDir,
495
- exists
496
- });
497
- return found ? {
498
- path: found.path,
499
- tool: found.tool,
500
- version: found.version
501
- } : null;
502
- }
503
- function dumpApkManifest(apkPath, { exec = null, aapt = null } = {}) {
504
- if (typeof apkPath !== "string" || apkPath.trim() === "") return null;
505
- const tool = aapt || findAapt();
506
- if (!tool) return null;
507
- const e = exec || getExecutor();
508
- const args = tool.tool === "aapt2" ? [
509
- "dump",
510
- "xmltree",
511
- "--file",
512
- "AndroidManifest.xml",
513
- apkPath
514
- ] : [
515
- "dump",
516
- "xmltree",
517
- apkPath,
518
- "AndroidManifest.xml"
519
- ];
520
- try {
521
- const out = e.runFile(tool.path, args);
522
- return typeof out === "string" && out.includes("E: manifest") ? out : null;
523
- } catch {
524
- return null;
525
- }
526
- }
527
- function parseXmltree(text) {
528
- const root = {
529
- tag: "#root",
530
- attrs: {},
531
- children: [],
532
- indent: -1
533
- };
534
- const stack = [root];
535
- for (const raw of String(text ?? "").split("\n")) {
536
- const indent = raw.search(/\S/);
537
- if (indent < 0) continue;
538
- const line = raw.trim();
539
- const element = /^E: ([\w.:-]+)/.exec(line);
540
- if (element) {
541
- while (stack.length > 1 && stack[stack.length - 1].indent >= indent) stack.pop();
542
- const node = {
543
- tag: element[1],
544
- attrs: {},
545
- children: [],
546
- indent
547
- };
548
- stack[stack.length - 1].children.push(node);
549
- stack.push(node);
550
- continue;
551
- }
552
- const attr = /^A: ([^(=]+?)(?:\(0x[0-9a-f]+\))?=(.*)$/.exec(line);
553
- if (attr && stack.length > 1) {
554
- const name = attr[1].replace(/^http:\/\/schemas\.android\.com\/apk\/res\/android:/, "android:");
555
- const value = /^"((?:[^"\\]|\\.)*)"/.exec(attr[2]);
556
- stack[stack.length - 1].attrs[name] = value ? value[1] : null;
557
- }
558
- }
559
- return root;
560
- }
561
- function eachNode(node, fn) {
562
- fn(node);
563
- for (const child of node.children) eachNode(child, fn);
564
- }
565
- function apkPackage(text) {
566
- const pkg = parseXmltree(text).children.find((c) => c.tag === "manifest")?.attrs["package"];
567
- return typeof pkg === "string" && pkg.trim() ? pkg.trim() : null;
568
- }
569
- function apkDevClientFacts(text) {
570
- const root = parseXmltree(text);
571
- const facts = {
572
- devClient: false,
573
- schemes: []
574
- };
575
- let launchable = null;
576
- eachNode(root, (node) => {
577
- const name = node.attrs["android:name"];
578
- if (typeof name === "string" && name.startsWith("expo.modules.devlauncher")) facts.devClient = true;
579
- if (node.tag !== "activity" && node.tag !== "activity-alias") return;
580
- const filters = node.children.filter((c) => c.tag === "intent-filter");
581
- if (!filters.some((f) => f.children.some((c) => c.tag === "action" && c.attrs["android:name"] === "android.intent.action.MAIN")) || launchable) return;
582
- launchable = node;
583
- for (const filter of filters) for (const data of filter.children) {
584
- if (data.tag !== "data") continue;
585
- const scheme = data.attrs["android:scheme"];
586
- if (typeof scheme === "string" && scheme.trim()) facts.schemes.push(scheme.trim());
587
- }
588
- });
589
- return facts;
590
- }
591
- function androidDevClientScheme(root, apkPath, { exec = null, dump = dumpApkManifest, aapt = null } = {}) {
592
- const text = dump(apkPath, {
593
- exec,
594
- aapt
595
- });
596
- if (text) {
597
- const facts = apkDevClientFacts(text);
598
- if (!facts.devClient) return void 0;
599
- const scheme = pickDevClientScheme(facts.schemes);
600
- if (scheme) return scheme;
601
- }
602
- return devClientScheme(root, null);
603
- }
604
- function noDeviceDiagnostic({ reason, logFile, remedy, localEmulator = true, readLog = readEmulatorLogTail }) {
605
- const text = localEmulator ? readLog(logFile) : "";
606
- const logPath = text.trim() ? logFile : null;
607
- const found = extractEmulatorFailure(text);
608
- const diskRemedy = localEmulator ? emulatorDiskSpaceRemedy([reason, ...found]) : null;
609
- if (found.length === 0) return {
610
- message: reason,
611
- remedy: diskRemedy ?? remedy,
612
- lines: [],
613
- logPath
614
- };
615
- return {
616
- message: `${reason} The emulator reported: ${found[0]}`,
617
- remedy: diskRemedy ?? emulatorFailureRemedy(found),
618
- lines: found.slice(1),
619
- logPath
620
- };
621
- }
622
- function readEmulatorLogTail(file) {
623
- try {
624
- return readFileSync(file, "utf-8").split("\n").slice(-400).join("\n");
625
- } catch {
626
- return "";
627
- }
628
- }
629
- function displayPath(root, path) {
630
- const rel = relative(root, path);
631
- return rel && !rel.startsWith("..") ? rel : path;
632
- }
633
- async function pooledAndroidDevice({ root, selectPool, listDevices, isEmulatorDevice, deviceModel, waitSeconds, noWait, now, warn }) {
634
- const isEmulator = memoizeEmulatorProbe(isEmulatorDevice);
635
- const pooled = await selectPool({
636
- root,
637
- platform: PLATFORM,
638
- idLabel: "serial",
639
- list: () => androidPoolCandidates(listDevices(), isEmulator).map((entry) => ({ id: entry.serial })),
640
- noCandidates: () => {
641
- const resolved = androidPoolNoCandidatesRefusal(listDevices(), isEmulator);
642
- return {
643
- message: resolved.error,
644
- remedy: resolved.remedy
645
- };
646
- },
647
- waitSeconds,
648
- noWait,
649
- now,
650
- warn
651
- });
652
- if (pooled.status === "refused") {
653
- const { code, message, remedy, lease } = pooled.refusal;
654
- return {
655
- code,
656
- message,
657
- remedy,
658
- extra: lease === null ? {} : { lease }
659
- };
660
- }
661
- return { device: {
662
- serial: pooled.candidate.id,
663
- deviceName: deviceModel(pooled.candidate.id) ?? pooled.candidate.id,
664
- owned: false
665
- } };
666
- }
667
- function androidBuildOptions({ release, physical, device, variant, deviceAbi, compiler, buildProfile, targetAbiOnly = true }) {
668
- let abi = null;
669
- if (targetAbiOnly && !release && physical && device.serial) abi = deviceAbi(device.serial);
670
- if (targetAbiOnly && !release && !physical) abi = androidSystemImageAbi(device.systemImage);
671
- const runOptions = {};
672
- if (variant) runOptions.variant = variant;
673
- if (abi) runOptions.abi = abi;
674
- if (compiler) runOptions.compiler = compiler;
675
- if (buildProfile) runOptions.buildProfile = buildProfile;
676
- return {
677
- abi,
678
- runOptions,
679
- remoteRunOptions: Object.keys(runOptions).length === 0 ? null : runOptions
680
- };
681
- }
682
- //#endregion
683
412
  //#region src/commands/android/artifact.ts
684
413
  const FALLBACK_LINES = 5;
685
414
  function fail$1(code, message, remedy, extra = {}) {
@@ -690,8 +419,17 @@ function fail$1(code, message, remedy, extra = {}) {
690
419
  extra
691
420
  };
692
421
  }
693
- async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, 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 }) {
694
- const { phase, out, estimates, stats, step } = progress;
422
+ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, 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 }) {
423
+ const { phase, out, estimates, stats, step, miss, hit: lateHit, place, waitingOn } = progress;
424
+ let fallbackMachine = null;
425
+ let hereReason = "no build machine is paired";
426
+ let slotWaitMs;
427
+ const buildMachine = record.buildMachine ?? "auto";
428
+ const fallBack = (reason, line = reason) => {
429
+ if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
430
+ record.offloadFallback = reason;
431
+ phase("build", `${line} -> building here`);
432
+ };
695
433
  const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
696
434
  const useBuildCache = cachePolicy.read;
697
435
  let androidPackage = initialPackage;
@@ -749,6 +487,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
749
487
  let storeKey = "";
750
488
  let storeSources = [];
751
489
  let apkPath = null;
490
+ let swapFellBack = false;
752
491
  async function resolveInitialFingerprint() {
753
492
  if (easBuild?.ok) {
754
493
  hash = storeHash = easBuild.fingerprint;
@@ -869,7 +608,10 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
869
608
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
870
609
  }
871
610
  }
872
- if (!easBuild) await resolveRemoteArtifact();
611
+ if (!easBuild) {
612
+ await resolveRemoteArtifact();
613
+ if (!apkPath) miss(reasonForMiss([]).reason);
614
+ }
873
615
  let waitedForBuild = null;
874
616
  let releasedWait = null;
875
617
  if (!easBuild && useBuildCache && !apkPath) {
@@ -887,6 +629,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
887
629
  step("wait");
888
630
  phase("build", text);
889
631
  },
632
+ waitingOn,
890
633
  warn: (text) => phase("build", chalk.yellow(text)),
891
634
  out
892
635
  });
@@ -904,7 +647,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
904
647
  }
905
648
  }
906
649
  let swapDir = null;
907
- let swapFellBack = false;
908
650
  const installableCachedApk = async (key, cachedPath) => {
909
651
  if (!release) return cachedPath;
910
652
  phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
@@ -938,51 +680,220 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
938
680
  }
939
681
  }
940
682
  }
941
- function explainMiss(rekeyedBy) {
942
- if (swapFellBack) record.missReason = skippedMissReason("the cached APK could not be reused, so this run built it fresh");
943
- else if (!useBuildCache) record.missReason = skippedMissReason(requestedBuildCache ? "cache reuse off in config" : "cache reuse turned off by --no-build-cache");
944
- else {
945
- const current = {
946
- hash: storeHash,
947
- sources: storeSources
948
- };
949
- const explained = explainBuildMiss({
950
- root,
951
- platform: PLATFORM,
952
- current,
953
- rekeyedBy,
954
- baselineDeps: { readState }
955
- });
956
- record.missReason = explained.reason;
957
- if (explained.previousHash && explained.changedNames.length) writer.write(fingerprintDiffRecord({
683
+ function reasonForMiss(rekeyedBy) {
684
+ if (swapFellBack) return {
685
+ reason: skippedMissReason("the cached APK could not be reused, so this run built it fresh"),
686
+ diff: null
687
+ };
688
+ if (!useBuildCache) return {
689
+ reason: skippedMissReason(requestedBuildCache ? "cache reuse off in config" : "cache reuse turned off by --no-build-cache"),
690
+ diff: null
691
+ };
692
+ const current = {
693
+ hash: storeHash,
694
+ sources: storeSources
695
+ };
696
+ const explained = explainBuildMiss({
697
+ root,
698
+ platform: PLATFORM,
699
+ current,
700
+ rekeyedBy,
701
+ baselineDeps: { readState }
702
+ });
703
+ return {
704
+ reason: explained.reason,
705
+ diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
958
706
  changed: explained.changedNames,
959
707
  previousHash: explained.previousHash,
960
708
  hash: current.hash
961
- }));
962
- }
709
+ }) : null
710
+ };
711
+ }
712
+ function explainMiss(rekeyedBy) {
713
+ const explained = reasonForMiss(rekeyedBy);
714
+ record.missReason = explained.reason;
715
+ if (explained.diff) writer.write(explained.diff);
716
+ miss(record.missReason);
963
717
  phase("cache", `miss: ${record.missReason.summary}`);
964
718
  if (record.missReason.kind === "no-baseline") {
965
719
  const line = untrackedMissLine(untracked({ projectRoot: root }));
966
720
  if (line) phase("fingerprint", chalk.dim(line));
967
721
  }
968
722
  }
969
- async function buildArtifact() {
970
- if (!apkPath) try {
971
- if (maxBuilds) try {
972
- buildSlot = await acquireSlot({
973
- max: maxBuilds,
974
- root,
975
- logFile: buildLog,
976
- out
723
+ async function takeBuildSlot() {
724
+ if (!maxBuilds) return true;
725
+ try {
726
+ buildSlot = await acquireSlot({
727
+ max: maxBuilds,
728
+ root,
729
+ logFile: buildLog,
730
+ out
731
+ });
732
+ slotWaitMs = buildSlot.slotWaitMs;
733
+ } catch (err) {
734
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
735
+ if (refusal) {
736
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
737
+ return false;
738
+ }
739
+ phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
740
+ }
741
+ return true;
742
+ }
743
+ /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
744
+ function placeBuild() {
745
+ const { mode, machines } = buildPlacementCandidates(buildMachine);
746
+ if (machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
747
+ 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;
748
+ const here = machineCapacity();
749
+ const placement = offloadPlacement({
750
+ mode,
751
+ machines: machines.length,
752
+ here,
753
+ unsupported,
754
+ selected: buildMachine
755
+ });
756
+ if (!placement.offload) {
757
+ hereReason = placement.reason;
758
+ if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
759
+ return null;
760
+ }
761
+ return {
762
+ mode,
763
+ here,
764
+ reason: placement.reason,
765
+ machines
766
+ };
767
+ }
768
+ const openOffload = { choice: null };
769
+ /** Asks the paired machines once the post-mutation key is known; null builds here. */
770
+ async function chooseMachine(candidate) {
771
+ const choice = await chooseBuildMachine({
772
+ projectRoot: root,
773
+ target: {
774
+ platform: "android",
775
+ local: androidToolchain(),
776
+ requires: androidRequirements(root)
777
+ },
778
+ mode: candidate.mode,
779
+ here: candidate.here,
780
+ note: (line) => phase("build", chalk.dim(`offload: ${line}`)),
781
+ machines: candidate.machines,
782
+ selected: buildMachine
783
+ });
784
+ if (typeof choice === "string") {
785
+ const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
786
+ if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
787
+ fallBack(choice);
788
+ return null;
789
+ }
790
+ openOffload.choice = choice;
791
+ phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
792
+ return choice;
793
+ }
794
+ /** Builds on the chosen machine and stores the APK under the post-mutation key; false builds here instead unless a machine was named. */
795
+ async function compileElsewhere(choice, candidate) {
796
+ if (!storeKey || !storeHash) {
797
+ if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
798
+ return false;
799
+ }
800
+ const stagingDir = join(workspaceDir(root), "offload", PLATFORM);
801
+ const outcome = await offloadBuild({
802
+ choice,
803
+ expectedFingerprint: storeHash,
804
+ request: {
805
+ platform: "android",
806
+ isExpo,
807
+ android: {
808
+ variant,
809
+ abi: buildAbi,
810
+ gradleBuildCache: buildPlan.gradleBuildCache,
811
+ pch: buildPlan.pch,
812
+ compilerCache: buildPlan.compilerCache === "none" ? "none" : "ccache"
813
+ }
814
+ },
815
+ stagingDir,
816
+ onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
817
+ onEnter: (name) => {
818
+ if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") record.builtOn = choice.machine;
819
+ place({
820
+ host: choice.machine,
821
+ phase: name
822
+ });
823
+ step(name === "prebuild" ? name : "compile");
824
+ },
825
+ onRecord: (entry) => writer.write({
826
+ ...entry,
827
+ offloadedTo: choice.machine
828
+ }),
829
+ note: (line) => phase("build", line)
830
+ });
831
+ place(null);
832
+ let stored = null;
833
+ let reason = outcome.ok ? null : outcome.reason;
834
+ if (outcome.ok) {
835
+ const settled = await refingerprintAfterMutation({
836
+ projectRoot: root,
837
+ platform: PLATFORM,
838
+ previousHash: storeHash,
839
+ fingerprint
840
+ });
841
+ if (!settled || settled.moved) reason = "the checkout here changed while it built";
842
+ else try {
843
+ stored = storeCached(PLATFORM, storeKey, outcome.artifactPath, {
844
+ sources: storeSources,
845
+ overwrite: !useBuildCache
977
846
  });
978
847
  } catch (err) {
979
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
980
- if (refusal) {
981
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
982
- return false;
983
- }
984
- phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
848
+ reason = `could not store the APK: ${err?.message || err}`;
985
849
  }
850
+ }
851
+ try {
852
+ rmSync(stagingDir, {
853
+ recursive: true,
854
+ force: true
855
+ });
856
+ } catch {}
857
+ step("compile");
858
+ if (!outcome.ok || !stored) {
859
+ const why = reason ?? "the APK was not stored";
860
+ fallbackMachine = choice.machine;
861
+ fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`);
862
+ writer.write({
863
+ src: "build",
864
+ level: "warn",
865
+ event: "offload_failed",
866
+ msg: reason,
867
+ machine: choice.machine
868
+ });
869
+ return false;
870
+ }
871
+ const { timings } = outcome;
872
+ apkPath = stored;
873
+ record.builtOn = outcome.machine;
874
+ record.offloadedTo = outcome.machine;
875
+ stats.setPlacement({
876
+ decision: "offloaded",
877
+ machine: outcome.machine,
878
+ reason: `${candidate.reason}${placementLoad(choice)}`,
879
+ buildMs: timings.totalMs
880
+ });
881
+ ccacheActivity = outcome.ccache;
882
+ phase("build", `built on ${outcome.machine} in ${formatDuration(timings.totalMs)}: offer ${formatDuration(timings.offerMs)}, sync ${formatDuration(timings.syncMs)}, build ${formatDuration(timings.workerMs)}, fetch ${formatDuration(timings.fetchMs)}`);
883
+ phase("cache", `compilation cache on ${outcome.machine} ${ccacheActivityLine(ccacheActivity)}`);
884
+ writer.write({
885
+ src: "build",
886
+ level: "info",
887
+ event: "offload_done",
888
+ msg: `built on ${outcome.machine}`,
889
+ timings
890
+ });
891
+ return true;
892
+ }
893
+ async function buildArtifact() {
894
+ if (!apkPath) try {
895
+ const offload = placeBuild();
896
+ if (!offload && !await takeBuildSlot()) return false;
986
897
  const rekeyedBy = [];
987
898
  let editedConfig = [];
988
899
  const prebuildPlan = planPrebuildFor(root, PLATFORM, {
@@ -997,6 +908,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
997
908
  return false;
998
909
  }
999
910
  if (prebuildRan) {
911
+ miss(reasonForMiss([]).reason, true);
1000
912
  step("prebuild");
1001
913
  recordPrebuild(root, PLATFORM, null);
1002
914
  const pre = await prebuild(root, PLATFORM, writer, {
@@ -1036,6 +948,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1036
948
  if (prepared) {
1037
949
  apkPath = prepared;
1038
950
  record.cacheHit = "local";
951
+ lateHit();
1039
952
  phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1040
953
  if (releasedWait) {
1041
954
  waitedForBuild = releasedWait.facts;
@@ -1045,9 +958,34 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1045
958
  }
1046
959
  }
1047
960
  }
1048
- if (!apkPath) {
961
+ if (offload && !apkPath) {
1049
962
  explainMiss(rekeyedBy);
1050
963
  step("compile");
964
+ let built = false;
965
+ 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");
966
+ else {
967
+ const choice = await chooseMachine(offload);
968
+ if (choice) built = await compileElsewhere(choice, offload);
969
+ }
970
+ if (!built && !await takeBuildSlot()) return false;
971
+ }
972
+ if (!apkPath) {
973
+ if (!offload) {
974
+ explainMiss(rekeyedBy);
975
+ step("compile");
976
+ }
977
+ if (offload) stats.setPlacement({
978
+ decision: "fell-back",
979
+ slotWaitMs,
980
+ reason: record.offloadFallback ?? "offload failed",
981
+ ...fallbackMachine ? { machine: fallbackMachine } : {}
982
+ });
983
+ else stats.setPlacement({
984
+ decision: "here",
985
+ reason: hereReason,
986
+ slotWaitMs
987
+ });
988
+ record.builtOn = "here";
1051
989
  phase("build", `compiling ${variant || "debug"} with Gradle`);
1052
990
  const built = await build({
1053
991
  root,
@@ -1155,7 +1093,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1155
1093
  });
1156
1094
  }
1157
1095
  }
1096
+ } catch (error) {
1097
+ if (!(error instanceof OffloadRefusal)) throw error;
1098
+ const refusal = error;
1099
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1100
+ return false;
1158
1101
  } finally {
1102
+ if (openOffload.choice) closeOffload(openOffload.choice);
1159
1103
  releaseHeldLock();
1160
1104
  releaseHeldSlot();
1161
1105
  }
@@ -1163,6 +1107,8 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1163
1107
  }
1164
1108
  if (!easBuild) {
1165
1109
  await prepareCachedArtifact();
1110
+ if (apkPath) lateHit();
1111
+ else if (swapFellBack) miss(reasonForMiss([]).reason);
1166
1112
  if (!await buildArtifact()) return refused();
1167
1113
  }
1168
1114
  return {
@@ -1186,7 +1132,7 @@ function tail(lines, n = FALLBACK_LINES) {
1186
1132
  }
1187
1133
  //#endregion
1188
1134
  //#region src/commands/android/result.ts
1189
- function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null }) {
1135
+ function androidFacts({ root, slot, serial, avdName = null, deviceName = null, systemImage = null, deviceProfile = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
1190
1136
  return {
1191
1137
  platform: PLATFORM,
1192
1138
  ...slot && slot !== "default" ? { slot } : {},
@@ -1201,6 +1147,10 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1201
1147
  metroPort: metroPort ?? null,
1202
1148
  cacheHit: cacheLevel(cacheHit),
1203
1149
  cacheSkipped: Boolean(cacheSkipped),
1150
+ buildMachine,
1151
+ ...builtOn ? { builtOn } : {},
1152
+ ...offloadedTo ? { offloadedTo } : {},
1153
+ ...offloadFallback ? { offloadFallback } : {},
1204
1154
  waitedForBuild: waitedForBuild ? {
1205
1155
  pid: waitedForBuild.pid ?? null,
1206
1156
  ms: waitedForBuild.ms ?? 0
@@ -1214,12 +1164,13 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1214
1164
  debugHttpHostNote: debugHttpHostNote ?? null,
1215
1165
  devClientUrl: devClientUrl ?? null,
1216
1166
  logs: logs ?? null,
1167
+ ...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
1217
1168
  durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
1218
1169
  ...lease === void 0 ? {} : { lease },
1219
1170
  ...devServer ? { devServer } : {}
1220
1171
  };
1221
1172
  }
1222
- function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null }) {
1173
+ function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
1223
1174
  const record = {
1224
1175
  platform: PLATFORM,
1225
1176
  avdName: avdName ?? null,
@@ -1236,6 +1187,10 @@ function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false
1236
1187
  configuration: configuration ?? null
1237
1188
  };
1238
1189
  if (errorCode) record.errorCode = errorCode;
1190
+ record.buildMachine = buildMachine;
1191
+ if (builtOn) record.builtOn = builtOn;
1192
+ if (offloadedTo) record.offloadedTo = offloadedTo;
1193
+ if (offloadFallback) record.offloadFallback = offloadFallback;
1239
1194
  if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
1240
1195
  const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
1241
1196
  if (recorded.length) record.diagnostics = recorded;
@@ -1258,14 +1213,17 @@ async function finishAndroidUpload(uploadPending, remote, phase) {
1258
1213
  }
1259
1214
  return false;
1260
1215
  }
1261
- function reportAndroidResult({ slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null }) {
1216
+ function reportAndroidResult({ root, slot, json, useBuildCache, variant, release, metroCheck, metroPort, logsDir, serial, apkPath, androidPackage, installSkipped, record, waitedForBuild, remote, providerName, launchState, launchWarning, launched, ccache, durationMs, writer, emit, lease, recordRun, reclaimed = [], devServer = null, links }) {
1217
+ const offloadedTo = record.offloadedTo ?? null;
1262
1218
  recordRun({
1263
1219
  failed: false,
1264
1220
  cacheHit: cacheLevel(record.cacheHit),
1265
1221
  waited: waitedForBuild,
1266
- durationMs
1222
+ durationMs,
1223
+ offloadedTo
1267
1224
  });
1268
1225
  const facts = androidFacts({
1226
+ root,
1269
1227
  slot,
1270
1228
  serial,
1271
1229
  avdName: record.avdName,
@@ -1290,18 +1248,23 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1290
1248
  logs: logsDir,
1291
1249
  durationMs,
1292
1250
  lease,
1293
- devServer
1251
+ devServer,
1252
+ buildMachine: record.buildMachine,
1253
+ builtOn: record.builtOn ?? void 0,
1254
+ offloadedTo,
1255
+ offloadFallback: record.offloadFallback ?? null
1294
1256
  });
1295
1257
  writer.close();
1296
- if (json) emit(JSON.stringify(reclaimed.length ? {
1258
+ if (json) emit(JSON.stringify({
1297
1259
  ...facts,
1298
- reclaimed
1299
- } : facts));
1260
+ ...links ? { links } : {},
1261
+ ...reclaimed.length ? { reclaimed } : {}
1262
+ }));
1300
1263
  else {
1301
- const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName)})`;
1264
+ const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo)})`;
1302
1265
  const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
1303
1266
  const deviceName = record.avdName || record.deviceName || serial;
1304
- const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName) : "bypassed; built";
1267
+ const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
1305
1268
  const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
1306
1269
  emit([
1307
1270
  outcome,
@@ -1310,15 +1273,17 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1310
1273
  phaseLine("metro", metroResult),
1311
1274
  phaseLine("cache", cacheResult),
1312
1275
  ...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
1313
- phaseLine("logs", logsDir || "unavailable (remote device)")
1276
+ phaseLine("logs", logsDir || "unavailable (remote device)"),
1277
+ phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)
1314
1278
  ].join("\n"));
1279
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
1315
1280
  }
1316
1281
  return facts;
1317
1282
  }
1318
- function cacheOutcome(cacheHit, providerName = null) {
1283
+ function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
1319
1284
  if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
1320
1285
  if (cacheHit === "local") return "cache hit";
1321
- return "built";
1286
+ return offloadedTo ? `built on ${offloadedTo}` : "built";
1322
1287
  }
1323
1288
  function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
1324
1289
  const lastBuild = lastBuildRecord({
@@ -1342,7 +1307,7 @@ function collectorEntry() {
1342
1307
  return spawnEntry("collector-run");
1343
1308
  }
1344
1309
  function collectorLogFile(root, slot = "default") {
1345
- return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
1310
+ return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
1346
1311
  }
1347
1312
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
1348
1313
  const COLLECTOR_POLL_MS = 25;
@@ -1383,7 +1348,7 @@ async function startCollector({ root, slot = "default", serial, packageName, spa
1383
1348
  }
1384
1349
  let stdio = "ignore";
1385
1350
  try {
1386
- mkdirSync(workspaceLogsDir(root), { recursive: true });
1351
+ mkdirSync(workspaceLogsDir$1(root), { recursive: true });
1387
1352
  const fd = openSync(collectorLogFile(root, slot), "a");
1388
1353
  stdio = [
1389
1354
  "ignore",
@@ -1455,9 +1420,9 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1455
1420
  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."));
1456
1421
  return { state: LAUNCH_FATAL };
1457
1422
  }
1458
- const newEmulator = Boolean(device.created && device.owned && !remoteDevice);
1459
- const timeoutMs = newEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1460
- if (metroCheck && newEmulator) phase("verify", "waiting up to 60s for bundle load (new emulator)");
1423
+ const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
1424
+ const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1425
+ if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
1461
1426
  const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
1462
1427
  const verification = metroCheck ? await verifyLaunched({
1463
1428
  timeoutMs,
@@ -1553,6 +1518,60 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1553
1518
  })) phase("", chalk.yellow(line));
1554
1519
  return { state: LAUNCH_UNVERIFIED };
1555
1520
  }
1521
+ const ADOPTED_INSTALL_HEADROOM_BYTES = 536870912;
1522
+ const INSUFFICIENT_STORAGE = /INSTALL_FAILED_INSUFFICIENT_STORAGE/;
1523
+ async function wipeAdoptedAvd(room, free) {
1524
+ room.phase("device", chalk.yellow(`${room.avdName} has ${free === null ? "too little space" : `${formatBytes(free)} free`} on /data after removing other apps and trimming caches; wiping its user data`));
1525
+ const expectedRecord = deviceSlotPlatforms(loadConfig$1()?.projects?.[room.root], room.slot)?.android;
1526
+ if (!expectedRecord?.adoptionPending || expectedRecord.avdName !== room.avdName) throw new Error("the emulator assignment changed before the wipe; its data was kept");
1527
+ const wiped = room.wipeDevice(room.avdName, {
1528
+ wipe: true,
1529
+ owner: {
1530
+ projectPath: room.root,
1531
+ slot: room.slot,
1532
+ expectedRecord
1533
+ },
1534
+ workspace: room.root
1535
+ });
1536
+ if (wiped.status !== "torn-down") throw new Error(`could not wipe it: ${wiped.reason ?? wiped.status}`);
1537
+ const rebooted = await room.rebootDevice();
1538
+ if (!rebooted.ok || !rebooted.serial) throw new Error(`it did not boot after its user data was wiped: ${rebooted.reason ?? "no serial"}`);
1539
+ if (room.resolveAvdSerial(room.avdName).serial !== rebooted.serial) throw new Error(`${rebooted.serial} is not running ${room.avdName} after the wipe`);
1540
+ room.phase("device", `${room.avdName} (${rebooted.serial}) booted with wiped user data`);
1541
+ return rebooted.serial;
1542
+ }
1543
+ async function installOnAdoptedAvd(room, serial, installApk) {
1544
+ const neededBytes = 2 * statSync(room.apkPath).size + ADOPTED_INSTALL_HEADROOM_BYTES;
1545
+ const roomy = (free) => free === null || free >= neededBytes;
1546
+ const outOfSpace = (result) => Boolean(result.failed) && INSUFFICIENT_STORAGE.test(result.reason ?? "");
1547
+ let target = serial;
1548
+ let wiped = false;
1549
+ if (!roomy(room.dataFreeBytes(target))) {
1550
+ room.trimCaches(target, neededBytes);
1551
+ const free = room.dataFreeBytes(target);
1552
+ if (roomy(free)) room.phase("install", `trimmed app caches on ${room.avdName} to make room for the APK`);
1553
+ else {
1554
+ target = await wipeAdoptedAvd(room, free);
1555
+ wiped = true;
1556
+ }
1557
+ }
1558
+ let installed = installApk(target);
1559
+ if (wiped || !outOfSpace(installed)) return {
1560
+ serial: target,
1561
+ installed
1562
+ };
1563
+ room.trimCaches(target, neededBytes);
1564
+ installed = installApk(target);
1565
+ if (!outOfSpace(installed)) return {
1566
+ serial: target,
1567
+ installed
1568
+ };
1569
+ target = await wipeAdoptedAvd(room, room.dataFreeBytes(target));
1570
+ return {
1571
+ serial: target,
1572
+ installed: installApk(target)
1573
+ };
1574
+ }
1556
1575
  async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
1557
1576
  if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
1558
1577
  const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
@@ -1575,7 +1594,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1575
1594
  });
1576
1595
  return resolved.serial;
1577
1596
  }
1578
- 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 }) {
1597
+ 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 }) {
1579
1598
  let androidPackage = initialPackage;
1580
1599
  let leaseWarned = false;
1581
1600
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1591,7 +1610,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1591
1610
  }
1592
1611
  return null;
1593
1612
  };
1594
- enterPhase("install");
1613
+ enterPhase("device");
1595
1614
  const booted = await bootPromise;
1596
1615
  const runCommand = nativeRunCommand("android", slot, {
1597
1616
  physical,
@@ -1635,13 +1654,37 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1635
1654
  }
1636
1655
  const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
1637
1656
  if (lostBeforeInstall) return lostBeforeInstall;
1638
- const installTimer = stepTimer(now);
1639
- const installed = install({
1640
- serial,
1641
- apkPath,
1642
- packageName: androidPackage,
1643
- allowUninstall: release || adopting
1644
- });
1657
+ enterPhase("install");
1658
+ let installTimer = stepTimer(now);
1659
+ const installApk = (target) => {
1660
+ installTimer = stepTimer(now);
1661
+ return install({
1662
+ serial: target,
1663
+ apkPath,
1664
+ packageName: androidPackage,
1665
+ allowUninstall: release || adopting
1666
+ });
1667
+ };
1668
+ let installed;
1669
+ if (adopting && device.avdName && apkPath) {
1670
+ const room = {
1671
+ root,
1672
+ slot,
1673
+ avdName: device.avdName,
1674
+ apkPath,
1675
+ phase,
1676
+ rebootDevice,
1677
+ dataFreeBytes,
1678
+ trimCaches,
1679
+ wipeDevice,
1680
+ resolveAvdSerial
1681
+ };
1682
+ try {
1683
+ ({serial, installed} = await installOnAdoptedAvd(room, serial, installApk));
1684
+ } catch (error) {
1685
+ return fail(INSTALL_FAILED, `Could not make room on adopted emulator ${device.avdName}: ${String(error?.message || error)}`, `Run \`${runCommand}\` again; adoption remains pending, so it retries the cleanup.`);
1686
+ }
1687
+ } else installed = installApk(serial);
1645
1688
  if (installed.failed) {
1646
1689
  const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
1647
1690
  reason: installed.reason,
@@ -1841,6 +1884,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1841
1884
  const leaseFacts = lease?.facts() ?? null;
1842
1885
  releaseLease();
1843
1886
  const facts = reportAndroidResult({
1887
+ root,
1844
1888
  slot,
1845
1889
  json,
1846
1890
  useBuildCache,
@@ -1867,7 +1911,11 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1867
1911
  lease: physical ? leaseFacts : void 0,
1868
1912
  recordRun,
1869
1913
  reclaimed,
1870
- devServer
1914
+ devServer,
1915
+ links: workspaceLinks(root, {
1916
+ platform: "android",
1917
+ slot
1918
+ })
1871
1919
  });
1872
1920
  if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
1873
1921
  return {
@@ -2004,10 +2052,12 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2004
2052
  if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once.");
2005
2053
  const waitSeconds = waitParsed.seconds;
2006
2054
  const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
2055
+ const settingsLayersForOrigin = settingsLayers(settingsContext);
2007
2056
  const imageRefusal = systemImageRefusal({
2008
2057
  slot,
2009
2058
  flag: systemImageFlag,
2010
2059
  resolved: systemImage,
2060
+ origin: settingOriginScope(settingsLayersForOrigin, "android.systemImage"),
2011
2061
  physical,
2012
2062
  remoteBackend,
2013
2063
  listImages: listSystemImages
@@ -2016,11 +2066,18 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2016
2066
  const profileRefusal = deviceProfileRefusal({
2017
2067
  flag: deviceProfileFlag,
2018
2068
  resolved: deviceProfile,
2069
+ origin: settingOriginScope(settingsLayersForOrigin, "android.deviceProfile"),
2019
2070
  physical,
2020
2071
  remoteBackend,
2021
2072
  listProfiles: listDeviceProfiles
2022
2073
  });
2023
2074
  if (profileRefusal) return fail(profileRefusal.code, profileRefusal.message, profileRefusal.remedy);
2075
+ const avdFlagRefusal = remoteAvdFlagRefusal({
2076
+ systemImageFlag,
2077
+ deviceProfileFlag,
2078
+ remoteBackend
2079
+ });
2080
+ if (avdFlagRefusal) return fail(avdFlagRefusal.code, avdFlagRefusal.message, avdFlagRefusal.remedy);
2024
2081
  if (!physical && !remoteBackend) {
2025
2082
  const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
2026
2083
  const existing = ownedAvd(root, slot);
@@ -2077,7 +2134,7 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2077
2134
  //#endregion
2078
2135
  //#region src/commands/android/next-build.ts
2079
2136
  const DEFAULT_PLAN_DEPS = {
2080
- findRoot: findProjectRoot,
2137
+ findRoot: findProjectRoot$1,
2081
2138
  fingerprint: fingerprintProject,
2082
2139
  listSystemImages: listInstalledSystemImages,
2083
2140
  avdSystemImage: ownedAvdSystemImage,
@@ -2180,7 +2237,7 @@ async function planAndroid(opts, overrides = {}) {
2180
2237
  platform: PLATFORM,
2181
2238
  slot,
2182
2239
  root,
2183
- projectKey: statsProjectKey({
2240
+ projectKey: statsProjectKey$1({
2184
2241
  root,
2185
2242
  commonDir: settingsContext.gitCommonDir,
2186
2243
  repoRoot: settingsContext.repoRoot
@@ -2267,12 +2324,13 @@ function androidCommand(program) {
2267
2324
  registerAndroid(program);
2268
2325
  }
2269
2326
  function registerAndroid(program) {
2270
- 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("--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 <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
2327
+ 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("--build-machine <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 <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
2271
2328
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2272
- throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2329
+ if (!parseMachine(value)) throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2330
+ throw new InvalidArgumentError("STIM_BAD_ARG: Android on a paired Mac is not available yet. Use eas or proxy, or run Android locally.");
2273
2331
  }).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). Only with --device.").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. Only with --device.").action(async (opts) => {
2274
2332
  if (opts.plan) return planAndroid(opts);
2275
- const root = findProjectRoot(process.cwd());
2333
+ const root = findProjectRoot$1(process.cwd());
2276
2334
  if (!root) {
2277
2335
  refuseNoProject({ json: Boolean(opts.json) });
2278
2336
  return;
@@ -2293,6 +2351,7 @@ function registerAndroid(program) {
2293
2351
  try {
2294
2352
  return await runAndroid({
2295
2353
  root,
2354
+ buildMachine: opts.buildMachine,
2296
2355
  slot: opts.slot,
2297
2356
  easProfile: opts.easProfile,
2298
2357
  json: Boolean(opts.json),
@@ -2313,7 +2372,7 @@ function registerAndroid(program) {
2313
2372
  }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
2314
2373
  });
2315
2374
  }
2316
- 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, 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, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
2375
+ 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, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
2317
2376
  return {
2318
2377
  root,
2319
2378
  json,
@@ -2354,6 +2413,9 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2354
2413
  ensureDeviceBooted,
2355
2414
  resolveAvdSerial,
2356
2415
  waitForDeviceBoot,
2416
+ dataFreeBytes,
2417
+ trimCaches,
2418
+ wipeDevice,
2357
2419
  resolveMetro,
2358
2420
  prewarmMetro,
2359
2421
  startServer,
@@ -2453,7 +2515,7 @@ function avdSetupFailure(error, root, logFile) {
2453
2515
  };
2454
2516
  }
2455
2517
  async function runAndroid(options = {}) {
2456
- let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2518
+ let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, planPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2457
2519
  const progress = options.progress ?? NO_BUILD_PROGRESS;
2458
2520
  const slot = validateDeviceSlot(options.slot);
2459
2521
  const started = now();
@@ -2499,12 +2561,15 @@ async function runAndroid(options = {}) {
2499
2561
  }
2500
2562
  };
2501
2563
  }
2502
- const logsDir = workspaceLogsDir(root);
2564
+ const logsDir = workspaceLogsDir$1(root);
2503
2565
  const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
2504
- const writer = createWriter(buildLog, {
2566
+ const writer = tapBuildLog(createWriter(buildLog, {
2505
2567
  truncate: true,
2506
- fields: { slot }
2507
- });
2568
+ fields: {
2569
+ platform: PLATFORM,
2570
+ slot
2571
+ }
2572
+ }), progress);
2508
2573
  const record = {
2509
2574
  fingerprint: null,
2510
2575
  cacheKey: null,
@@ -2520,12 +2585,13 @@ async function runAndroid(options = {}) {
2520
2585
  write: recordStats,
2521
2586
  now,
2522
2587
  note: (line) => out(phaseLine("stats", chalk.dim(line))),
2523
- phases: () => progress.durations()
2588
+ phases: () => progress.durations(),
2589
+ deviceSetup: () => progress.deviceSetupKnown()
2524
2590
  });
2525
2591
  const recordRun = stats.record;
2526
2592
  let ccacheActivity = CCACHE_NOT_RUN;
2527
2593
  let reclaimed = [];
2528
- const fail = (code, message, remedy, { lastBuildStatus = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
2594
+ const fail = (code, message, remedy, { lastBuildStatus = false, setup = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
2529
2595
  const cancellation = cancelledFailure(PLATFORM, {
2530
2596
  code,
2531
2597
  message
@@ -2547,7 +2613,7 @@ async function runAndroid(options = {}) {
2547
2613
  for (const line of lines) out(phaseLine("", chalk.dim(line)));
2548
2614
  if (remedy) out(phaseLine("remedy", remedy));
2549
2615
  if (logPath) out(phaseLine("log", logPath));
2550
- recordRun({
2616
+ if (!setup) recordRun({
2551
2617
  failed: true,
2552
2618
  durationMs: now() - started
2553
2619
  });
@@ -2576,18 +2642,25 @@ async function runAndroid(options = {}) {
2576
2642
  gitCommonDir: gitCommonDir(root),
2577
2643
  repoRoot: settingsRepoRoot
2578
2644
  };
2579
- const projectKey = statsProjectKey({
2645
+ const projectKey = statsProjectKey$1({
2580
2646
  root,
2581
2647
  commonDir: settingsContext.gitCommonDir,
2582
2648
  repoRoot: settingsRepoRoot
2583
2649
  });
2584
2650
  stats.setProject(projectKey);
2651
+ progress.estimate(projectKey);
2585
2652
  let estimatesRead = null;
2586
2653
  const estimates = () => estimatesRead ??= readEstimates({
2587
2654
  projectKey,
2588
2655
  platform: PLATFORM
2589
2656
  });
2590
2657
  const settings = resolveSettingsFor(settingsContext);
2658
+ const placement = resolveBuildPlacement(options.buildMachine);
2659
+ record.buildMachine = placement.selected;
2660
+ if (placement.failure) {
2661
+ const { code, message, remedy } = placement.failure;
2662
+ return fail(code, message, remedy, { setup: true });
2663
+ }
2591
2664
  const planned = resolveAndroidRunPlan({
2592
2665
  settings,
2593
2666
  settingsContext,
@@ -2677,8 +2750,16 @@ async function runAndroid(options = {}) {
2677
2750
  devServer = gate.devServer;
2678
2751
  phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
2679
2752
  return true;
2680
- } else phase("metro", reservedPort ? `port ${reservedPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
2681
- metroPort = release ? null : reservedPort ?? 8081;
2753
+ } else {
2754
+ const pin = metroPortSetting(root);
2755
+ if (pin.error) {
2756
+ phaseFailure = fail("STIM_BAD_ARG", pin.error, SETTING_SHAPE_REMEDY);
2757
+ return false;
2758
+ }
2759
+ metroPort = pin.port ?? reservedPort ?? 8081;
2760
+ phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
2761
+ }
2762
+ if (release) metroPort = null;
2682
2763
  return true;
2683
2764
  }
2684
2765
  if (!await resolveMetroPort()) return phaseFailure;
@@ -2691,7 +2772,7 @@ async function runAndroid(options = {}) {
2691
2772
  isExpo,
2692
2773
  tunnelMode: tunnelModeSetting(settings) ?? void 0,
2693
2774
  publicUrl: publicUrlSetting(settings),
2694
- available: detectRemoteProviders(binOnPath)
2775
+ available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
2695
2776
  });
2696
2777
  if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
2697
2778
  }
@@ -2770,6 +2851,7 @@ async function runAndroid(options = {}) {
2770
2851
  const failure = avdSetupFailure(err, root, emuLog);
2771
2852
  return fail(failure.code, failure.message, failure.remedy, failure.extra);
2772
2853
  }
2854
+ progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
2773
2855
  const prepareMs = prepare();
2774
2856
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
2775
2857
  const boot = () => Promise.resolve(ensureDeviceBooted({
@@ -2825,6 +2907,7 @@ async function runAndroid(options = {}) {
2825
2907
  isExpo,
2826
2908
  device,
2827
2909
  physical,
2910
+ remote: Boolean(remoteDevice),
2828
2911
  buildPlan,
2829
2912
  cacheProviderConfig,
2830
2913
  requestedBuildCache,
@@ -2837,7 +2920,11 @@ async function runAndroid(options = {}) {
2837
2920
  out,
2838
2921
  estimates,
2839
2922
  stats,
2840
- step: progress.step
2923
+ step: progress.step,
2924
+ miss: progress.miss,
2925
+ hit: progress.hit,
2926
+ place: progress.place,
2927
+ waitingOn: progress.waitingOn
2841
2928
  }
2842
2929
  }, {
2843
2930
  deviceAbi,
@@ -2877,7 +2964,7 @@ async function runAndroid(options = {}) {
2877
2964
  record.appPath = apkPath;
2878
2965
  if (runCancellation()) return fail("STIM_CANCELLED", "before install");
2879
2966
  if (startRemoteBoot) {
2880
- progress.step("install");
2967
+ progress.step("device");
2881
2968
  bootPromise = startRemoteBoot();
2882
2969
  const booted = await bootPromise;
2883
2970
  if (booted.failed) {
@@ -2909,6 +2996,7 @@ async function runAndroid(options = {}) {
2909
2996
  }
2910
2997
  };
2911
2998
  if (physical) {
2999
+ progress.step("device");
2912
3000
  const acquired = await acquireLease({
2913
3001
  root,
2914
3002
  platform: PLATFORM,
@@ -2956,6 +3044,9 @@ async function runAndroid(options = {}) {
2956
3044
  bootPromise,
2957
3045
  resolveAvdSerial,
2958
3046
  waitForDeviceBoot,
3047
+ dataFreeBytes,
3048
+ trimCaches,
3049
+ wipeDevice,
2959
3050
  bootDuration: () => bootDuration,
2960
3051
  apkPath,
2961
3052
  androidPackage,
@@ -2992,7 +3083,20 @@ async function runAndroid(options = {}) {
2992
3083
  recordRun,
2993
3084
  reclaimed,
2994
3085
  devServer,
2995
- enterPhase: progress.step
3086
+ enterPhase: progress.step,
3087
+ rebootDevice: () => Promise.resolve(ensureDeviceBooted({
3088
+ platform: PLATFORM,
3089
+ device: {
3090
+ ...device,
3091
+ serial: void 0
3092
+ },
3093
+ projectPath: root,
3094
+ out,
3095
+ logFile: emuLog
3096
+ })).catch((e) => ({
3097
+ failed: true,
3098
+ reason: String(e?.message || e)
3099
+ }))
2996
3100
  });
2997
3101
  } finally {
2998
3102
  releaseLease();