stim 1.14.0 → 1.15.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 (130) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-CYVDcrt3.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-XpvCrV7q.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-LbPFRWVL.mjs → android-Cu_L4RpL.mjs} +502 -407
  5. package/dist/{android-d6rGq1pA.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-BpgSgkJj.mjs → app-install-CAAplXAt.mjs} +95 -161
  9. package/dist/{budget-CGDkhvv_.mjs → budget-CMiqghig.mjs} +1038 -73
  10. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-aeGMFX7e.mjs} +193 -653
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-DVcpw6eE.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  22. package/dist/{created-devices-CllH_8eb.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-D9IcKqXS.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-CjuuAJjQ.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-BiyDyPP-.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-D87zEzaq.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-x9pnU9fq.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-ko6mUAYD.mjs → gc-BuL1Genr.mjs} +429 -101
  39. package/dist/{android-cas-iZK3DOCr.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-6R0VNFqI.mjs → guide-cCSGRFd3.mjs} +1959 -222
  41. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-BwDPrnYQ.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-P1f5nm2n.mjs → ios-Chob8ny7.mjs} +512 -263
  47. package/dist/{ios-device-DGglU3E_.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-CLlofxRt.mjs} +4 -4
  50. package/dist/{logs-Civ4gbBH.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-CcEo8Fl8.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Dj_XBNaL.mjs} +6 -59
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-i6OkVg-w.mjs} +9 -5
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-DyYDF5ww.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-BguS83ex.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-CWlvEaeN.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-BiniwsSZ.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-X7PUgizL.mjs} +33 -15
  77. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-D61iNOsB.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-B-fsC-Om.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-DrDN4WuV.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-BY0BrOLh.mjs} +6 -6
  84. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-s4RXp1wy.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DHqvsbqC.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-Dk9RVXI7.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-BsPAnfOI.mjs → status-DYXxNOT0.mjs} +423 -268
  95. package/dist/{status-BgAuxESg.mjs → status-l84Gbvp2.mjs} +116 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BzreOWJp.mjs → stop-Bad61z_2.mjs} +38 -12
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-BWIF_6BK.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +43 -11
  105. package/dist/supervisor-run.mjs +227 -19
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DXtx3RbX.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-BsQs1WlO.mjs → web-k6w8-HnE.mjs} +37 -20
  113. package/dist/web-run.mjs +309 -12
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-BVrHsYZG.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-UrQWXSeb.mjs → worktree-qXKzADSC.mjs} +154 -252
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +28 -12
  121. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  122. package/dist/in-use-DlqBDKqY.mjs +0 -249
  123. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  124. package/dist/start-Cetfvy8l.mjs +0 -2
  125. package/dist/stats-CFpFGlJV.mjs +0 -62
  126. package/dist/stats-CX1Z8Tca.mjs +0 -368
  127. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  128. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  129. package/dist/worktree-CHWWvm5v.mjs +0 -2
  130. 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";
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";
3
4
  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";
5
+ import { C as workspaceLogsDir, b as workspaceDir, c as writeWorkspaceState, f as emulatorLogFile, i as recordWorkspaceUse, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
6
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
7
+ import { a as findProjectRoot$1, l as projectShortcut, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-CV3tDFHl.mjs";
8
+ import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-CaeaHbDA.mjs";
8
9
  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";
10
+ 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-CliHHn7a.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-W5Pi1q38.mjs";
12
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
13
+ 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, dt as parkedMaxSetting, ht as configuredAndroidEmulatorApp, i as androidDataFreeBytes, j as listInstalledSystemImages, k as listAvdDeviceProfiles, q as resetAdoptedAvd, r as androidBuildToolName, t as DEFAULT_AVD_DEVICE_PROFILE, tt as waitForBoot, u as androidToolCwd } from "./android-Db2eEMaf.mjs";
14
+ import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
15
+ import { A as remoteAndroidSetting, B as unknownSettingKeys, I as settingOriginScope, J as optimizationBuildProfile, K as artifactCachePolicy, L as settingShapeErrors, M as resolveCacheProviderConfig, N as resolveSettings, O as publicUrlSetting, P as settingFile, R as settingsLayers, S as metroPortSetting, U as detectProviders, Y as resolveOptimizations, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, q as compilerCacheFallbackMessage, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, w as metroWarmupUrlSetting, z as tunnelModeSetting } from "./settings-CduVcoxj.mjs";
16
+ import { i as teardownOwnedAvd } from "./teardown-BYuQafiA.mjs";
17
+ import { c as recordFinishedBuild, d as createRunRecorder, f as readRunEstimates, h as statsProjectKey$1, l as startBuildProgress, m as recordRunStats, n as NO_BUILD_PROGRESS, u as tapBuildLog } from "./build-progress-C2Jv1AtA.mjs";
18
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-Df1ApSuH.mjs";
19
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-BkYxUtuj.mjs";
20
+ 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-CAAplXAt.mjs";
21
+ import { t as checkDeviceCapacity } from "./device-capacity-DMuKC7nx.mjs";
22
+ 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-CDQbDkL5.mjs";
23
+ 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-DmeYjXD9.mjs";
24
+ import { t as cleanLine } from "./server-expo-DhtX6bom.mjs";
25
+ 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-C2IS36Qf.mjs";
26
+ import { t as spawnEntry } from "./spawn-entry-DncpJvWL.mjs";
27
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-BvxDlD6C.mjs";
28
+ import { c as formatDiagnostic, i as buildAndroid, o as productFlavorRefusal, s as readProductFlavors } from "./gradle-CmdOBq1d.mjs";
29
+ 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-CO2elm_b.mjs";
30
+ import { a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-9ulPsdhY.mjs";
31
+ 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-CLlofxRt.mjs";
32
+ import { d as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-DFDoIRbY.mjs";
33
+ import { a as isEasBuildFailure, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-Ciluxlq4.mjs";
34
+ import { c as startDevServer, m as detectAppIds, p as detectAndroidPackage } from "./start-CUD9nasA.mjs";
35
+ 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-B2r38YjS.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-aeGMFX7e.mjs";
37
+ import { t as budgetGate } from "./budget-CMiqghig.mjs";
38
+ import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-i6OkVg-w.mjs";
39
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-BY0BrOLh.mjs";
40
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Cb0P1WLS.mjs";
41
+ import { n as resolveAndroidCas, r as resolveAndroidCompilerCache } from "./android-cas-PnKfAdB2.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 } 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,16 @@ 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 = null;
426
+ const buildMachine = record.buildMachine ?? "auto";
427
+ const fallBack = (reason, line = reason) => {
428
+ if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
429
+ record.offloadFallback = reason;
430
+ phase("build", `${line} -> building here`);
431
+ };
695
432
  const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
696
433
  const useBuildCache = cachePolicy.read;
697
434
  let androidPackage = initialPackage;
@@ -749,6 +486,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
749
486
  let storeKey = "";
750
487
  let storeSources = [];
751
488
  let apkPath = null;
489
+ let swapFellBack = false;
752
490
  async function resolveInitialFingerprint() {
753
491
  if (easBuild?.ok) {
754
492
  hash = storeHash = easBuild.fingerprint;
@@ -869,7 +607,10 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
869
607
  } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
870
608
  }
871
609
  }
872
- if (!easBuild) await resolveRemoteArtifact();
610
+ if (!easBuild) {
611
+ await resolveRemoteArtifact();
612
+ if (!apkPath) miss(reasonForMiss([]).reason);
613
+ }
873
614
  let waitedForBuild = null;
874
615
  let releasedWait = null;
875
616
  if (!easBuild && useBuildCache && !apkPath) {
@@ -887,6 +628,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
887
628
  step("wait");
888
629
  phase("build", text);
889
630
  },
631
+ waitingOn,
890
632
  warn: (text) => phase("build", chalk.yellow(text)),
891
633
  out
892
634
  });
@@ -904,7 +646,6 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
904
646
  }
905
647
  }
906
648
  let swapDir = null;
907
- let swapFellBack = false;
908
649
  const installableCachedApk = async (key, cachedPath) => {
909
650
  if (!release) return cachedPath;
910
651
  phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
@@ -938,51 +679,219 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
938
679
  }
939
680
  }
940
681
  }
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({
682
+ function reasonForMiss(rekeyedBy) {
683
+ if (swapFellBack) return {
684
+ reason: skippedMissReason("the cached APK could not be reused, so this run built it fresh"),
685
+ diff: null
686
+ };
687
+ if (!useBuildCache) return {
688
+ reason: skippedMissReason(requestedBuildCache ? "cache reuse off in config" : "cache reuse turned off by --no-build-cache"),
689
+ diff: null
690
+ };
691
+ const current = {
692
+ hash: storeHash,
693
+ sources: storeSources
694
+ };
695
+ const explained = explainBuildMiss({
696
+ root,
697
+ platform: PLATFORM,
698
+ current,
699
+ rekeyedBy,
700
+ baselineDeps: { readState }
701
+ });
702
+ return {
703
+ reason: explained.reason,
704
+ diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
958
705
  changed: explained.changedNames,
959
706
  previousHash: explained.previousHash,
960
707
  hash: current.hash
961
- }));
962
- }
708
+ }) : null
709
+ };
710
+ }
711
+ function explainMiss(rekeyedBy) {
712
+ const explained = reasonForMiss(rekeyedBy);
713
+ record.missReason = explained.reason;
714
+ if (explained.diff) writer.write(explained.diff);
715
+ miss(record.missReason);
963
716
  phase("cache", `miss: ${record.missReason.summary}`);
964
717
  if (record.missReason.kind === "no-baseline") {
965
718
  const line = untrackedMissLine(untracked({ projectRoot: root }));
966
719
  if (line) phase("fingerprint", chalk.dim(line));
967
720
  }
968
721
  }
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
722
+ async function takeBuildSlot() {
723
+ if (!maxBuilds) return true;
724
+ try {
725
+ buildSlot = await acquireSlot({
726
+ max: maxBuilds,
727
+ root,
728
+ logFile: buildLog,
729
+ out
730
+ });
731
+ } catch (err) {
732
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
733
+ if (refusal) {
734
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
735
+ return false;
736
+ }
737
+ phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
738
+ }
739
+ return true;
740
+ }
741
+ /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
742
+ function placeBuild() {
743
+ const { mode, machines } = buildPlacementCandidates(buildMachine);
744
+ if (machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
745
+ 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;
746
+ const here = machineCapacity();
747
+ const placement = offloadPlacement({
748
+ mode,
749
+ machines: machines.length,
750
+ here,
751
+ unsupported,
752
+ selected: buildMachine
753
+ });
754
+ if (!placement.offload) {
755
+ hereReason = placement.reason;
756
+ if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
757
+ return null;
758
+ }
759
+ return {
760
+ mode,
761
+ here,
762
+ reason: placement.reason,
763
+ machines
764
+ };
765
+ }
766
+ const openOffload = { choice: null };
767
+ /** Asks the paired machines once the post-mutation key is known; null builds here. */
768
+ async function chooseMachine(candidate) {
769
+ const choice = await chooseBuildMachine({
770
+ projectRoot: root,
771
+ target: {
772
+ platform: "android",
773
+ local: androidToolchain(),
774
+ requires: androidRequirements(root)
775
+ },
776
+ mode: candidate.mode,
777
+ here: candidate.here,
778
+ note: (line) => phase("build", chalk.dim(`offload: ${line}`)),
779
+ machines: candidate.machines,
780
+ selected: buildMachine
781
+ });
782
+ if (typeof choice === "string") {
783
+ const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
784
+ if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
785
+ fallBack(choice);
786
+ return null;
787
+ }
788
+ openOffload.choice = choice;
789
+ phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
790
+ return choice;
791
+ }
792
+ /** Builds on the chosen machine and stores the APK under the post-mutation key; false builds here instead unless a machine was named. */
793
+ async function compileElsewhere(choice, candidate) {
794
+ if (!storeKey || !storeHash) {
795
+ if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
796
+ return false;
797
+ }
798
+ const stagingDir = join(workspaceDir(root), "offload", PLATFORM);
799
+ const outcome = await offloadBuild({
800
+ choice,
801
+ expectedFingerprint: storeHash,
802
+ request: {
803
+ platform: "android",
804
+ isExpo,
805
+ android: {
806
+ variant,
807
+ abi: buildAbi,
808
+ gradleBuildCache: buildPlan.gradleBuildCache,
809
+ pch: buildPlan.pch,
810
+ compilerCache: buildPlan.compilerCache === "none" ? "none" : "ccache"
811
+ }
812
+ },
813
+ stagingDir,
814
+ onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
815
+ onEnter: (name) => {
816
+ if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") record.builtOn = choice.machine;
817
+ place({
818
+ host: choice.machine,
819
+ phase: name
820
+ });
821
+ step(name === "prebuild" ? name : "compile");
822
+ },
823
+ onRecord: (entry) => writer.write({
824
+ ...entry,
825
+ offloadedTo: choice.machine
826
+ }),
827
+ note: (line) => phase("build", line)
828
+ });
829
+ place(null);
830
+ let stored = null;
831
+ let reason = outcome.ok ? null : outcome.reason;
832
+ if (outcome.ok) {
833
+ const settled = await refingerprintAfterMutation({
834
+ projectRoot: root,
835
+ platform: PLATFORM,
836
+ previousHash: storeHash,
837
+ fingerprint
838
+ });
839
+ if (!settled || settled.moved) reason = "the checkout here changed while it built";
840
+ else try {
841
+ stored = storeCached(PLATFORM, storeKey, outcome.artifactPath, {
842
+ sources: storeSources,
843
+ overwrite: !useBuildCache
977
844
  });
978
845
  } 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`));
846
+ reason = `could not store the APK: ${err?.message || err}`;
985
847
  }
848
+ }
849
+ try {
850
+ rmSync(stagingDir, {
851
+ recursive: true,
852
+ force: true
853
+ });
854
+ } catch {}
855
+ step("compile");
856
+ if (!outcome.ok || !stored) {
857
+ const why = reason ?? "the APK was not stored";
858
+ fallbackMachine = choice.machine;
859
+ fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`);
860
+ writer.write({
861
+ src: "build",
862
+ level: "warn",
863
+ event: "offload_failed",
864
+ msg: reason,
865
+ machine: choice.machine
866
+ });
867
+ return false;
868
+ }
869
+ const { timings } = outcome;
870
+ apkPath = stored;
871
+ record.builtOn = outcome.machine;
872
+ record.offloadedTo = outcome.machine;
873
+ stats.setPlacement({
874
+ decision: "offloaded",
875
+ machine: outcome.machine,
876
+ reason: `${candidate.reason}${placementLoad(choice)}`,
877
+ buildMs: timings.totalMs
878
+ });
879
+ ccacheActivity = outcome.ccache;
880
+ 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)}`);
881
+ phase("cache", `compilation cache on ${outcome.machine} ${ccacheActivityLine(ccacheActivity)}`);
882
+ writer.write({
883
+ src: "build",
884
+ level: "info",
885
+ event: "offload_done",
886
+ msg: `built on ${outcome.machine}`,
887
+ timings
888
+ });
889
+ return true;
890
+ }
891
+ async function buildArtifact() {
892
+ if (!apkPath) try {
893
+ const offload = placeBuild();
894
+ if (!offload && !await takeBuildSlot()) return false;
986
895
  const rekeyedBy = [];
987
896
  let editedConfig = [];
988
897
  const prebuildPlan = planPrebuildFor(root, PLATFORM, {
@@ -997,6 +906,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
997
906
  return false;
998
907
  }
999
908
  if (prebuildRan) {
909
+ miss(reasonForMiss([]).reason, true);
1000
910
  step("prebuild");
1001
911
  recordPrebuild(root, PLATFORM, null);
1002
912
  const pre = await prebuild(root, PLATFORM, writer, {
@@ -1036,6 +946,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1036
946
  if (prepared) {
1037
947
  apkPath = prepared;
1038
948
  record.cacheHit = "local";
949
+ lateHit();
1039
950
  phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1040
951
  if (releasedWait) {
1041
952
  waitedForBuild = releasedWait.facts;
@@ -1045,9 +956,32 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1045
956
  }
1046
957
  }
1047
958
  }
1048
- if (!apkPath) {
959
+ if (offload && !apkPath) {
1049
960
  explainMiss(rekeyedBy);
1050
961
  step("compile");
962
+ let built = false;
963
+ 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");
964
+ else {
965
+ const choice = await chooseMachine(offload);
966
+ if (choice) built = await compileElsewhere(choice, offload);
967
+ }
968
+ if (!built && !await takeBuildSlot()) return false;
969
+ }
970
+ if (!apkPath) {
971
+ if (!offload) {
972
+ explainMiss(rekeyedBy);
973
+ step("compile");
974
+ }
975
+ if (offload) stats.setPlacement({
976
+ decision: "fell-back",
977
+ reason: record.offloadFallback ?? "offload failed",
978
+ ...fallbackMachine ? { machine: fallbackMachine } : {}
979
+ });
980
+ else if (hereReason) stats.setPlacement({
981
+ decision: "here",
982
+ reason: hereReason
983
+ });
984
+ record.builtOn = "here";
1051
985
  phase("build", `compiling ${variant || "debug"} with Gradle`);
1052
986
  const built = await build({
1053
987
  root,
@@ -1155,7 +1089,13 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1155
1089
  });
1156
1090
  }
1157
1091
  }
1092
+ } catch (error) {
1093
+ if (!(error instanceof OffloadRefusal)) throw error;
1094
+ const refusal = error;
1095
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
1096
+ return false;
1158
1097
  } finally {
1098
+ if (openOffload.choice) closeOffload(openOffload.choice);
1159
1099
  releaseHeldLock();
1160
1100
  releaseHeldSlot();
1161
1101
  }
@@ -1163,6 +1103,8 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1163
1103
  }
1164
1104
  if (!easBuild) {
1165
1105
  await prepareCachedArtifact();
1106
+ if (apkPath) lateHit();
1107
+ else if (swapFellBack) miss(reasonForMiss([]).reason);
1166
1108
  if (!await buildArtifact()) return refused();
1167
1109
  }
1168
1110
  return {
@@ -1186,7 +1128,7 @@ function tail(lines, n = FALLBACK_LINES) {
1186
1128
  }
1187
1129
  //#endregion
1188
1130
  //#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 }) {
1131
+ 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, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
1190
1132
  return {
1191
1133
  platform: PLATFORM,
1192
1134
  ...slot && slot !== "default" ? { slot } : {},
@@ -1201,6 +1143,10 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1201
1143
  metroPort: metroPort ?? null,
1202
1144
  cacheHit: cacheLevel(cacheHit),
1203
1145
  cacheSkipped: Boolean(cacheSkipped),
1146
+ buildMachine,
1147
+ ...builtOn ? { builtOn } : {},
1148
+ ...offloadedTo ? { offloadedTo } : {},
1149
+ ...offloadFallback ? { offloadFallback } : {},
1204
1150
  waitedForBuild: waitedForBuild ? {
1205
1151
  pid: waitedForBuild.pid ?? null,
1206
1152
  ms: waitedForBuild.ms ?? 0
@@ -1219,7 +1165,7 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1219
1165
  ...devServer ? { devServer } : {}
1220
1166
  };
1221
1167
  }
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 }) {
1168
+ 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
1169
  const record = {
1224
1170
  platform: PLATFORM,
1225
1171
  avdName: avdName ?? null,
@@ -1236,6 +1182,10 @@ function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false
1236
1182
  configuration: configuration ?? null
1237
1183
  };
1238
1184
  if (errorCode) record.errorCode = errorCode;
1185
+ record.buildMachine = buildMachine;
1186
+ if (builtOn) record.builtOn = builtOn;
1187
+ if (offloadedTo) record.offloadedTo = offloadedTo;
1188
+ if (offloadFallback) record.offloadFallback = offloadFallback;
1239
1189
  if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
1240
1190
  const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
1241
1191
  if (recorded.length) record.diagnostics = recorded;
@@ -1258,12 +1208,14 @@ async function finishAndroidUpload(uploadPending, remote, phase) {
1258
1208
  }
1259
1209
  return false;
1260
1210
  }
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 }) {
1211
+ 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, links }) {
1212
+ const offloadedTo = record.offloadedTo ?? null;
1262
1213
  recordRun({
1263
1214
  failed: false,
1264
1215
  cacheHit: cacheLevel(record.cacheHit),
1265
1216
  waited: waitedForBuild,
1266
- durationMs
1217
+ durationMs,
1218
+ offloadedTo
1267
1219
  });
1268
1220
  const facts = androidFacts({
1269
1221
  slot,
@@ -1290,18 +1242,23 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1290
1242
  logs: logsDir,
1291
1243
  durationMs,
1292
1244
  lease,
1293
- devServer
1245
+ devServer,
1246
+ buildMachine: record.buildMachine,
1247
+ builtOn: record.builtOn ?? void 0,
1248
+ offloadedTo,
1249
+ offloadFallback: record.offloadFallback ?? null
1294
1250
  });
1295
1251
  writer.close();
1296
- if (json) emit(JSON.stringify(reclaimed.length ? {
1252
+ if (json) emit(JSON.stringify({
1297
1253
  ...facts,
1298
- reclaimed
1299
- } : facts));
1254
+ ...links ? { links } : {},
1255
+ ...reclaimed.length ? { reclaimed } : {}
1256
+ }));
1300
1257
  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)})`;
1258
+ 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
1259
  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
1260
  const deviceName = record.avdName || record.deviceName || serial;
1304
- const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName) : "bypassed; built";
1261
+ const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName, offloadedTo) : `bypassed; ${cacheOutcome(false, null, offloadedTo)}`;
1305
1262
  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
1263
  emit([
1307
1264
  outcome,
@@ -1312,13 +1269,14 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1312
1269
  ...ccache.status === "not-run" ? [phaseLine("compilation cache", ccacheActivityLine(ccache))] : [],
1313
1270
  phaseLine("logs", logsDir || "unavailable (remote device)")
1314
1271
  ].join("\n"));
1272
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
1315
1273
  }
1316
1274
  return facts;
1317
1275
  }
1318
- function cacheOutcome(cacheHit, providerName = null) {
1276
+ function cacheOutcome(cacheHit, providerName = null, offloadedTo = null) {
1319
1277
  if (cacheHit === "remote") return `cache hit from ${providerName || "the remote cache"}`;
1320
1278
  if (cacheHit === "local") return "cache hit";
1321
- return "built";
1279
+ return offloadedTo ? `built on ${offloadedTo}` : "built";
1322
1280
  }
1323
1281
  function persistLastBuild({ recordBuild = recordFinishedBuild, root, record, startedAt, durationMs, status, errorCode = null, diagnostics = null, out }) {
1324
1282
  const lastBuild = lastBuildRecord({
@@ -1455,9 +1413,9 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1455
1413
  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
1414
  return { state: LAUNCH_FATAL };
1457
1415
  }
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)");
1416
+ const freshEmulator = Boolean((device.created || device.adoptionPending) && device.owned && !remoteDevice);
1417
+ const timeoutMs = freshEmulator ? 6e4 : VERIFY_TIMEOUT_MS;
1418
+ if (metroCheck && freshEmulator) phase("verify", `waiting up to 60s for bundle load (${device.created ? "new" : "adopted"} emulator)`);
1461
1419
  const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
1462
1420
  const verification = metroCheck ? await verifyLaunched({
1463
1421
  timeoutMs,
@@ -1553,6 +1511,60 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1553
1511
  })) phase("", chalk.yellow(line));
1554
1512
  return { state: LAUNCH_UNVERIFIED };
1555
1513
  }
1514
+ const ADOPTED_INSTALL_HEADROOM_BYTES = 536870912;
1515
+ const INSUFFICIENT_STORAGE = /INSTALL_FAILED_INSUFFICIENT_STORAGE/;
1516
+ async function wipeAdoptedAvd(room, free) {
1517
+ 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`));
1518
+ const expectedRecord = deviceSlotPlatforms(loadConfig$1()?.projects?.[room.root], room.slot)?.android;
1519
+ if (!expectedRecord?.adoptionPending || expectedRecord.avdName !== room.avdName) throw new Error("the emulator assignment changed before the wipe; its data was kept");
1520
+ const wiped = room.wipeDevice(room.avdName, {
1521
+ wipe: true,
1522
+ owner: {
1523
+ projectPath: room.root,
1524
+ slot: room.slot,
1525
+ expectedRecord
1526
+ },
1527
+ workspace: room.root
1528
+ });
1529
+ if (wiped.status !== "torn-down") throw new Error(`could not wipe it: ${wiped.reason ?? wiped.status}`);
1530
+ const rebooted = await room.rebootDevice();
1531
+ if (!rebooted.ok || !rebooted.serial) throw new Error(`it did not boot after its user data was wiped: ${rebooted.reason ?? "no serial"}`);
1532
+ if (room.resolveAvdSerial(room.avdName).serial !== rebooted.serial) throw new Error(`${rebooted.serial} is not running ${room.avdName} after the wipe`);
1533
+ room.phase("device", `${room.avdName} (${rebooted.serial}) booted with wiped user data`);
1534
+ return rebooted.serial;
1535
+ }
1536
+ async function installOnAdoptedAvd(room, serial, installApk) {
1537
+ const neededBytes = 2 * statSync(room.apkPath).size + ADOPTED_INSTALL_HEADROOM_BYTES;
1538
+ const roomy = (free) => free === null || free >= neededBytes;
1539
+ const outOfSpace = (result) => Boolean(result.failed) && INSUFFICIENT_STORAGE.test(result.reason ?? "");
1540
+ let target = serial;
1541
+ let wiped = false;
1542
+ if (!roomy(room.dataFreeBytes(target))) {
1543
+ room.trimCaches(target, neededBytes);
1544
+ const free = room.dataFreeBytes(target);
1545
+ if (roomy(free)) room.phase("install", `trimmed app caches on ${room.avdName} to make room for the APK`);
1546
+ else {
1547
+ target = await wipeAdoptedAvd(room, free);
1548
+ wiped = true;
1549
+ }
1550
+ }
1551
+ let installed = installApk(target);
1552
+ if (wiped || !outOfSpace(installed)) return {
1553
+ serial: target,
1554
+ installed
1555
+ };
1556
+ room.trimCaches(target, neededBytes);
1557
+ installed = installApk(target);
1558
+ if (!outOfSpace(installed)) return {
1559
+ serial: target,
1560
+ installed
1561
+ };
1562
+ target = await wipeAdoptedAvd(room, room.dataFreeBytes(target));
1563
+ return {
1564
+ serial: target,
1565
+ installed: installApk(target)
1566
+ };
1567
+ }
1556
1568
  async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
1557
1569
  if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
1558
1570
  const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
@@ -1575,7 +1587,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1575
1587
  });
1576
1588
  return resolved.serial;
1577
1589
  }
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 }) {
1590
+ 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
1591
  let androidPackage = initialPackage;
1580
1592
  let leaseWarned = false;
1581
1593
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1591,7 +1603,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1591
1603
  }
1592
1604
  return null;
1593
1605
  };
1594
- enterPhase("install");
1606
+ enterPhase("device");
1595
1607
  const booted = await bootPromise;
1596
1608
  const runCommand = nativeRunCommand("android", slot, {
1597
1609
  physical,
@@ -1635,13 +1647,37 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1635
1647
  }
1636
1648
  const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
1637
1649
  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
- });
1650
+ enterPhase("install");
1651
+ let installTimer = stepTimer(now);
1652
+ const installApk = (target) => {
1653
+ installTimer = stepTimer(now);
1654
+ return install({
1655
+ serial: target,
1656
+ apkPath,
1657
+ packageName: androidPackage,
1658
+ allowUninstall: release || adopting
1659
+ });
1660
+ };
1661
+ let installed;
1662
+ if (adopting && device.avdName && apkPath) {
1663
+ const room = {
1664
+ root,
1665
+ slot,
1666
+ avdName: device.avdName,
1667
+ apkPath,
1668
+ phase,
1669
+ rebootDevice,
1670
+ dataFreeBytes,
1671
+ trimCaches,
1672
+ wipeDevice,
1673
+ resolveAvdSerial
1674
+ };
1675
+ try {
1676
+ ({serial, installed} = await installOnAdoptedAvd(room, serial, installApk));
1677
+ } catch (error) {
1678
+ 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.`);
1679
+ }
1680
+ } else installed = installApk(serial);
1645
1681
  if (installed.failed) {
1646
1682
  const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
1647
1683
  reason: installed.reason,
@@ -1867,7 +1903,11 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1867
1903
  lease: physical ? leaseFacts : void 0,
1868
1904
  recordRun,
1869
1905
  reclaimed,
1870
- devServer
1906
+ devServer,
1907
+ links: workspaceLinks(root, {
1908
+ platform: "android",
1909
+ slot
1910
+ })
1871
1911
  });
1872
1912
  if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
1873
1913
  return {
@@ -2004,10 +2044,12 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2004
2044
  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
2045
  const waitSeconds = waitParsed.seconds;
2006
2046
  const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
2047
+ const settingsLayersForOrigin = settingsLayers(settingsContext);
2007
2048
  const imageRefusal = systemImageRefusal({
2008
2049
  slot,
2009
2050
  flag: systemImageFlag,
2010
2051
  resolved: systemImage,
2052
+ origin: settingOriginScope(settingsLayersForOrigin, "android.systemImage"),
2011
2053
  physical,
2012
2054
  remoteBackend,
2013
2055
  listImages: listSystemImages
@@ -2016,11 +2058,18 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2016
2058
  const profileRefusal = deviceProfileRefusal({
2017
2059
  flag: deviceProfileFlag,
2018
2060
  resolved: deviceProfile,
2061
+ origin: settingOriginScope(settingsLayersForOrigin, "android.deviceProfile"),
2019
2062
  physical,
2020
2063
  remoteBackend,
2021
2064
  listProfiles: listDeviceProfiles
2022
2065
  });
2023
2066
  if (profileRefusal) return fail(profileRefusal.code, profileRefusal.message, profileRefusal.remedy);
2067
+ const avdFlagRefusal = remoteAvdFlagRefusal({
2068
+ systemImageFlag,
2069
+ deviceProfileFlag,
2070
+ remoteBackend
2071
+ });
2072
+ if (avdFlagRefusal) return fail(avdFlagRefusal.code, avdFlagRefusal.message, avdFlagRefusal.remedy);
2024
2073
  if (!physical && !remoteBackend) {
2025
2074
  const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
2026
2075
  const existing = ownedAvd(root, slot);
@@ -2077,7 +2126,7 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2077
2126
  //#endregion
2078
2127
  //#region src/commands/android/next-build.ts
2079
2128
  const DEFAULT_PLAN_DEPS = {
2080
- findRoot: findProjectRoot,
2129
+ findRoot: findProjectRoot$1,
2081
2130
  fingerprint: fingerprintProject,
2082
2131
  listSystemImages: listInstalledSystemImages,
2083
2132
  avdSystemImage: ownedAvdSystemImage,
@@ -2180,7 +2229,7 @@ async function planAndroid(opts, overrides = {}) {
2180
2229
  platform: PLATFORM,
2181
2230
  slot,
2182
2231
  root,
2183
- projectKey: statsProjectKey({
2232
+ projectKey: statsProjectKey$1({
2184
2233
  root,
2185
2234
  commonDir: settingsContext.gitCommonDir,
2186
2235
  repoRoot: settingsContext.repoRoot
@@ -2267,12 +2316,12 @@ function androidCommand(program) {
2267
2316
  registerAndroid(program);
2268
2317
  }
2269
2318
  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) => {
2319
+ 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
2320
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2272
2321
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2273
2322
  }).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
2323
  if (opts.plan) return planAndroid(opts);
2275
- const root = findProjectRoot(process.cwd());
2324
+ const root = findProjectRoot$1(process.cwd());
2276
2325
  if (!root) {
2277
2326
  refuseNoProject({ json: Boolean(opts.json) });
2278
2327
  return;
@@ -2293,6 +2342,7 @@ function registerAndroid(program) {
2293
2342
  try {
2294
2343
  return await runAndroid({
2295
2344
  root,
2345
+ buildMachine: opts.buildMachine,
2296
2346
  slot: opts.slot,
2297
2347
  easProfile: opts.easProfile,
2298
2348
  json: Boolean(opts.json),
@@ -2313,7 +2363,7 @@ function registerAndroid(program) {
2313
2363
  }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
2314
2364
  });
2315
2365
  }
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) } = {}) {
2366
+ 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
2367
  return {
2318
2368
  root,
2319
2369
  json,
@@ -2354,6 +2404,9 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2354
2404
  ensureDeviceBooted,
2355
2405
  resolveAvdSerial,
2356
2406
  waitForDeviceBoot,
2407
+ dataFreeBytes,
2408
+ trimCaches,
2409
+ wipeDevice,
2357
2410
  resolveMetro,
2358
2411
  prewarmMetro,
2359
2412
  startServer,
@@ -2453,7 +2506,7 @@ function avdSetupFailure(error, root, logFile) {
2453
2506
  };
2454
2507
  }
2455
2508
  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);
2509
+ 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
2510
  const progress = options.progress ?? NO_BUILD_PROGRESS;
2458
2511
  const slot = validateDeviceSlot(options.slot);
2459
2512
  const started = now();
@@ -2501,10 +2554,13 @@ async function runAndroid(options = {}) {
2501
2554
  }
2502
2555
  const logsDir = workspaceLogsDir(root);
2503
2556
  const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
2504
- const writer = createWriter(buildLog, {
2557
+ const writer = tapBuildLog(createWriter(buildLog, {
2505
2558
  truncate: true,
2506
- fields: { slot }
2507
- });
2559
+ fields: {
2560
+ platform: PLATFORM,
2561
+ slot
2562
+ }
2563
+ }), progress);
2508
2564
  const record = {
2509
2565
  fingerprint: null,
2510
2566
  cacheKey: null,
@@ -2520,12 +2576,13 @@ async function runAndroid(options = {}) {
2520
2576
  write: recordStats,
2521
2577
  now,
2522
2578
  note: (line) => out(phaseLine("stats", chalk.dim(line))),
2523
- phases: () => progress.durations()
2579
+ phases: () => progress.durations(),
2580
+ deviceSetup: () => progress.deviceSetupKnown()
2524
2581
  });
2525
2582
  const recordRun = stats.record;
2526
2583
  let ccacheActivity = CCACHE_NOT_RUN;
2527
2584
  let reclaimed = [];
2528
- const fail = (code, message, remedy, { lastBuildStatus = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
2585
+ const fail = (code, message, remedy, { lastBuildStatus = false, setup = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
2529
2586
  const cancellation = cancelledFailure(PLATFORM, {
2530
2587
  code,
2531
2588
  message
@@ -2547,7 +2604,7 @@ async function runAndroid(options = {}) {
2547
2604
  for (const line of lines) out(phaseLine("", chalk.dim(line)));
2548
2605
  if (remedy) out(phaseLine("remedy", remedy));
2549
2606
  if (logPath) out(phaseLine("log", logPath));
2550
- recordRun({
2607
+ if (!setup) recordRun({
2551
2608
  failed: true,
2552
2609
  durationMs: now() - started
2553
2610
  });
@@ -2576,18 +2633,25 @@ async function runAndroid(options = {}) {
2576
2633
  gitCommonDir: gitCommonDir(root),
2577
2634
  repoRoot: settingsRepoRoot
2578
2635
  };
2579
- const projectKey = statsProjectKey({
2636
+ const projectKey = statsProjectKey$1({
2580
2637
  root,
2581
2638
  commonDir: settingsContext.gitCommonDir,
2582
2639
  repoRoot: settingsRepoRoot
2583
2640
  });
2584
2641
  stats.setProject(projectKey);
2642
+ progress.estimate(projectKey);
2585
2643
  let estimatesRead = null;
2586
2644
  const estimates = () => estimatesRead ??= readEstimates({
2587
2645
  projectKey,
2588
2646
  platform: PLATFORM
2589
2647
  });
2590
2648
  const settings = resolveSettingsFor(settingsContext);
2649
+ const placement = resolveBuildPlacement(options.buildMachine);
2650
+ record.buildMachine = placement.selected;
2651
+ if (placement.failure) {
2652
+ const { code, message, remedy } = placement.failure;
2653
+ return fail(code, message, remedy, { setup: true });
2654
+ }
2591
2655
  const planned = resolveAndroidRunPlan({
2592
2656
  settings,
2593
2657
  settingsContext,
@@ -2677,8 +2741,16 @@ async function runAndroid(options = {}) {
2677
2741
  devServer = gate.devServer;
2678
2742
  phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
2679
2743
  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;
2744
+ } else {
2745
+ const pin = metroPortSetting(root);
2746
+ if (pin.error) {
2747
+ phaseFailure = fail("STIM_BAD_ARG", pin.error, SETTING_SHAPE_REMEDY);
2748
+ return false;
2749
+ }
2750
+ metroPort = pin.port ?? reservedPort ?? 8081;
2751
+ phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
2752
+ }
2753
+ if (release) metroPort = null;
2682
2754
  return true;
2683
2755
  }
2684
2756
  if (!await resolveMetroPort()) return phaseFailure;
@@ -2691,7 +2763,7 @@ async function runAndroid(options = {}) {
2691
2763
  isExpo,
2692
2764
  tunnelMode: tunnelModeSetting(settings) ?? void 0,
2693
2765
  publicUrl: publicUrlSetting(settings),
2694
- available: detectRemoteProviders(binOnPath)
2766
+ available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
2695
2767
  });
2696
2768
  if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
2697
2769
  }
@@ -2770,6 +2842,7 @@ async function runAndroid(options = {}) {
2770
2842
  const failure = avdSetupFailure(err, root, emuLog);
2771
2843
  return fail(failure.code, failure.message, failure.remedy, failure.extra);
2772
2844
  }
2845
+ progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
2773
2846
  const prepareMs = prepare();
2774
2847
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
2775
2848
  const boot = () => Promise.resolve(ensureDeviceBooted({
@@ -2825,6 +2898,7 @@ async function runAndroid(options = {}) {
2825
2898
  isExpo,
2826
2899
  device,
2827
2900
  physical,
2901
+ remote: Boolean(remoteDevice),
2828
2902
  buildPlan,
2829
2903
  cacheProviderConfig,
2830
2904
  requestedBuildCache,
@@ -2837,7 +2911,11 @@ async function runAndroid(options = {}) {
2837
2911
  out,
2838
2912
  estimates,
2839
2913
  stats,
2840
- step: progress.step
2914
+ step: progress.step,
2915
+ miss: progress.miss,
2916
+ hit: progress.hit,
2917
+ place: progress.place,
2918
+ waitingOn: progress.waitingOn
2841
2919
  }
2842
2920
  }, {
2843
2921
  deviceAbi,
@@ -2877,7 +2955,7 @@ async function runAndroid(options = {}) {
2877
2955
  record.appPath = apkPath;
2878
2956
  if (runCancellation()) return fail("STIM_CANCELLED", "before install");
2879
2957
  if (startRemoteBoot) {
2880
- progress.step("install");
2958
+ progress.step("device");
2881
2959
  bootPromise = startRemoteBoot();
2882
2960
  const booted = await bootPromise;
2883
2961
  if (booted.failed) {
@@ -2909,6 +2987,7 @@ async function runAndroid(options = {}) {
2909
2987
  }
2910
2988
  };
2911
2989
  if (physical) {
2990
+ progress.step("device");
2912
2991
  const acquired = await acquireLease({
2913
2992
  root,
2914
2993
  platform: PLATFORM,
@@ -2956,6 +3035,9 @@ async function runAndroid(options = {}) {
2956
3035
  bootPromise,
2957
3036
  resolveAvdSerial,
2958
3037
  waitForDeviceBoot,
3038
+ dataFreeBytes,
3039
+ trimCaches,
3040
+ wipeDevice,
2959
3041
  bootDuration: () => bootDuration,
2960
3042
  apkPath,
2961
3043
  androidPackage,
@@ -2992,7 +3074,20 @@ async function runAndroid(options = {}) {
2992
3074
  recordRun,
2993
3075
  reclaimed,
2994
3076
  devServer,
2995
- enterPhase: progress.step
3077
+ enterPhase: progress.step,
3078
+ rebootDevice: () => Promise.resolve(ensureDeviceBooted({
3079
+ platform: PLATFORM,
3080
+ device: {
3081
+ ...device,
3082
+ serial: void 0
3083
+ },
3084
+ projectPath: root,
3085
+ out,
3086
+ logFile: emuLog
3087
+ })).catch((e) => ({
3088
+ failed: true,
3089
+ reason: String(e?.message || e)
3090
+ }))
2996
3091
  });
2997
3092
  } finally {
2998
3093
  releaseLease();