stim 1.13.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 (133) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
  5. package/dist/{android-qx_31wJo.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-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
  9. package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
  10. package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
  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-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
  22. package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-2Wo81np9.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-Cf7vl-so.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-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
  39. package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
  41. package/dist/{guide-status-BaMYu55M.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-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
  47. package/dist/{ios-device-BW-t_Lkv.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-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
  50. package/dist/{logs-DcSRbC2t.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-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-C5UutqpJ.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-DGeclhvO.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-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
  77. package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/slot-launch-BY0BrOLh.mjs +65 -0
  84. package/dist/{spawn-claims-PT8aQpCa.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-BZhItkiL.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-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
  95. package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -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-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +44 -12
  105. package/dist/supervisor-run.mjs +276 -33
  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-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
  113. package/dist/web-run.mjs +326 -13
  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-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +55 -10
  121. package/shim/bundle-response.d.cts +1 -0
  122. package/shim/expo-metro-config.cjs +10 -2
  123. package/dist/build-progress-l7d-X-ZP.mjs +0 -247
  124. package/dist/in-use-wYEaKQM9.mjs +0 -249
  125. package/dist/ownership-PUP4Dwai.mjs +0 -1138
  126. package/dist/server-expo-BeYwP_Xy.mjs +0 -2
  127. package/dist/slot-launch-BIWEW-wI.mjs +0 -19
  128. package/dist/start-Rw2GD3nV.mjs +0 -2
  129. package/dist/stats-CaSwmsSZ.mjs +0 -62
  130. package/dist/stats-D8rUTPHd.mjs +0 -368
  131. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  132. package/dist/worktree-3cBa-bQt.mjs +0 -2
  133. 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-0jCcMM0E.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-BPeY1YMh.mjs";
5
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-DeBQSVtR.mjs";
6
- import { a as findProjectRoot, l as projectShortcut, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-DGeclhvO.mjs";
7
- import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DwAU2_-D.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-BIL7rlX7.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-CL9dZf73.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-qx_31wJo.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-xB0Frkg6.mjs";
13
- import { c as recordFinishedBuild, l as startBuildProgress, n as NO_BUILD_PROGRESS } from "./build-progress-l7d-X-ZP.mjs";
14
- import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-wYEaKQM9.mjs";
15
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-Bz2kETAS.mjs";
16
- import { t as cleanLine } from "./server-expo-DUCg52-K.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--aehr4Pr.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-DT9ngZiD.mjs";
19
- import { _ as launchAndroidReleaseApp, d as deviceShellArg, 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 } from "./app-install-CDFY2nUL.mjs";
20
- import { a as VERIFY_TIMEOUT_MS, d as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unverifiedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as verifyAndroidReleaseLaunch } from "./launch-verify-CuJaBpEa.mjs";
21
- import { c as unknownAndroidSystemImageRefusal, h as resolveEasDevelopmentBuild, m as planEasDevelopmentBuild, p as isEasBuildFailure, r as checkDeviceCapacity, s as unknownAndroidDeviceProfileRefusal } from "./simslim-Bbg9UWrt.mjs";
22
- import { f as spawnEntry, m as detectAppIds, p as detectAndroidPackage, s as startDevServer } from "./start-BZhItkiL.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-MuoiyRcu.mjs";
24
- import { C as warmMetro, _ 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, x as AvdRecoveryError, y as ensureOwnedDevice } from "./build-plan-BTa93o_r.mjs";
25
- import { t as budgetGate } from "./budget-E5o91NR1.mjs";
26
- import { i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-CiiFHt0e.mjs";
27
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-D8rUTPHd.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-B9Kv9aIy.mjs";
29
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-LM6A8cNZ.mjs";
30
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-BIWEW-wI.mjs";
31
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-BoiOhck8.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,13 +1413,15 @@ 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)`);
1419
+ const siblings = metroCheck ? siblingPlatformSlots(root, "android", slot) : [];
1461
1420
  const verification = metroCheck ? await verifyLaunched({
1462
1421
  timeoutMs,
1463
1422
  requireBundleResponse: true,
1464
1423
  slot: launchSlotScope(root, slot),
1424
+ platformShared: siblings.length > 0,
1465
1425
  onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
1466
1426
  logsDir,
1467
1427
  since: launchedAt,
@@ -1493,7 +1453,7 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1493
1453
  phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
1494
1454
  for (const line of launchErrorPreview(verification.errors ?? [], root)) phase("", chalk.red(line));
1495
1455
  if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) phase("logs", "No attributable native crash report captured yet. Run `stim logs --errors` again for delayed reports, or `stim logs --source device` for available output.");
1496
- if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then restart the app: \`adb -s ${deviceShellArg(serial)} shell am force-stop ${deviceShellArg(androidPackage)}\`, then run \`${runCommand}\` again. A crashed Android process can remain alive behind the system crash dialog; Metro reload cannot recover it.`));
1456
+ if (nativeFatal || verification.processAlive === false) phase("remedy", chalk.yellow(`Fix the crash, then run \`${runCommand}\` again; it force-stops a crashed process that remains alive behind the system crash dialog. Metro reload cannot recover it.`));
1497
1457
  else if (verification.processAlive === true && metroPort !== null) {
1498
1458
  const reloadRemedy = physical ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload android`.";
1499
1459
  phase("remedy", chalk.yellow(`The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`));
@@ -1523,6 +1483,20 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1523
1483
  phase("", chalk.dim("Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute."));
1524
1484
  return { state: LAUNCH_BUNDLING };
1525
1485
  }
1486
+ if (verification?.unattributed) {
1487
+ const [headline, ...lines] = unattributedLaunchLines({
1488
+ platform: "android",
1489
+ metroPort,
1490
+ slot,
1491
+ siblings
1492
+ });
1493
+ phase("verify", chalk.yellow(headline));
1494
+ for (const line of lines) phase("", chalk.yellow(line));
1495
+ return {
1496
+ state: LAUNCH_UNVERIFIED,
1497
+ unattributed: true
1498
+ };
1499
+ }
1526
1500
  phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
1527
1501
  for (const line of unverifiedLaunchLines({
1528
1502
  platform: PLATFORM,
@@ -1537,6 +1511,60 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
1537
1511
  })) phase("", chalk.yellow(line));
1538
1512
  return { state: LAUNCH_UNVERIFIED };
1539
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
+ }
1540
1568
  async function resolveInstallSerial({ root, slot, device, physical, remoteDevice, serial, resolveAvdSerial, waitForDeviceBoot, phase }) {
1541
1569
  if (physical || remoteDevice || !device.owned || !device.avdName) return serial;
1542
1570
  const resolved = resolveAvdSerial(device.avdName, { timeoutMs: 5e3 });
@@ -1559,7 +1587,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1559
1587
  });
1560
1588
  return resolved.serial;
1561
1589
  }
1562
- 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 }) {
1563
1591
  let androidPackage = initialPackage;
1564
1592
  let leaseWarned = false;
1565
1593
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1575,7 +1603,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1575
1603
  }
1576
1604
  return null;
1577
1605
  };
1578
- enterPhase("install");
1606
+ enterPhase("device");
1579
1607
  const booted = await bootPromise;
1580
1608
  const runCommand = nativeRunCommand("android", slot, {
1581
1609
  physical,
@@ -1619,13 +1647,37 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1619
1647
  }
1620
1648
  const lostBeforeInstall = physical ? raiseLeaseFor(ADB_INSTALL_TIMEOUT_MS, true) : null;
1621
1649
  if (lostBeforeInstall) return lostBeforeInstall;
1622
- const installTimer = stepTimer(now);
1623
- const installed = install({
1624
- serial,
1625
- apkPath,
1626
- packageName: androidPackage,
1627
- allowUninstall: release || adopting
1628
- });
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);
1629
1681
  if (installed.failed) {
1630
1682
  const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
1631
1683
  reason: installed.reason,
@@ -1713,9 +1765,9 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1713
1765
  src: "build",
1714
1766
  level: "info",
1715
1767
  event: "app_launched",
1716
- msg: release ? `launched ${androidPackage} on ${serial} (${variant}, embedded JS bundle, no Metro)` : `launched ${androidPackage} on ${serial} against Metro port ${metroPort}`
1768
+ msg: (release ? `launched ${androidPackage} on ${serial} (${variant}, embedded JS bundle, no Metro)` : `launched ${androidPackage} on ${serial} against Metro port ${metroPort}`) + restartedAppNote(launched.restartedPid, "; ")
1717
1769
  });
1718
- phase("launch", `${androidPackage} ${launchTimer()}`);
1770
+ phase("launch", `${androidPackage}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
1719
1771
  if (!physical && !remoteDevice) try {
1720
1772
  writeLaunch(root, "android", {
1721
1773
  appId: androidPackage,
@@ -1788,7 +1840,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1788
1840
  phase("logs", `${displayPath(root, logsDir)}${collectorPid ? ` (collector pid ${collectorPid})` : ""}`);
1789
1841
  }
1790
1842
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
1791
- const { state: launchState, warning: launchWarning } = await verifyAndroidRun({
1843
+ const { state: launchState, warning: launchWarning, unattributed } = await verifyAndroidRun({
1792
1844
  root,
1793
1845
  slot,
1794
1846
  release,
@@ -1819,7 +1871,8 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1819
1871
  release,
1820
1872
  bundleId: androidPackage,
1821
1873
  configuration: variant,
1822
- metroPort
1874
+ metroPort,
1875
+ unattributed
1823
1876
  }));
1824
1877
  const leaseFacts = lease?.facts() ?? null;
1825
1878
  releaseLease();
@@ -1850,7 +1903,11 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1850
1903
  lease: physical ? leaseFacts : void 0,
1851
1904
  recordRun,
1852
1905
  reclaimed,
1853
- devServer
1906
+ devServer,
1907
+ links: workspaceLinks(root, {
1908
+ platform: "android",
1909
+ slot
1910
+ })
1854
1911
  });
1855
1912
  if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
1856
1913
  return {
@@ -1987,10 +2044,12 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1987
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.");
1988
2045
  const waitSeconds = waitParsed.seconds;
1989
2046
  const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
2047
+ const settingsLayersForOrigin = settingsLayers(settingsContext);
1990
2048
  const imageRefusal = systemImageRefusal({
1991
2049
  slot,
1992
2050
  flag: systemImageFlag,
1993
2051
  resolved: systemImage,
2052
+ origin: settingOriginScope(settingsLayersForOrigin, "android.systemImage"),
1994
2053
  physical,
1995
2054
  remoteBackend,
1996
2055
  listImages: listSystemImages
@@ -1999,11 +2058,18 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1999
2058
  const profileRefusal = deviceProfileRefusal({
2000
2059
  flag: deviceProfileFlag,
2001
2060
  resolved: deviceProfile,
2061
+ origin: settingOriginScope(settingsLayersForOrigin, "android.deviceProfile"),
2002
2062
  physical,
2003
2063
  remoteBackend,
2004
2064
  listProfiles: listDeviceProfiles
2005
2065
  });
2006
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);
2007
2073
  if (!physical && !remoteBackend) {
2008
2074
  const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
2009
2075
  const existing = ownedAvd(root, slot);
@@ -2060,7 +2126,7 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
2060
2126
  //#endregion
2061
2127
  //#region src/commands/android/next-build.ts
2062
2128
  const DEFAULT_PLAN_DEPS = {
2063
- findRoot: findProjectRoot,
2129
+ findRoot: findProjectRoot$1,
2064
2130
  fingerprint: fingerprintProject,
2065
2131
  listSystemImages: listInstalledSystemImages,
2066
2132
  avdSystemImage: ownedAvdSystemImage,
@@ -2163,7 +2229,7 @@ async function planAndroid(opts, overrides = {}) {
2163
2229
  platform: PLATFORM,
2164
2230
  slot,
2165
2231
  root,
2166
- projectKey: statsProjectKey({
2232
+ projectKey: statsProjectKey$1({
2167
2233
  root,
2168
2234
  commonDir: settingsContext.gitCommonDir,
2169
2235
  repoRoot: settingsContext.repoRoot
@@ -2250,12 +2316,12 @@ function androidCommand(program) {
2250
2316
  registerAndroid(program);
2251
2317
  }
2252
2318
  function registerAndroid(program) {
2253
- 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) => {
2254
2320
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2255
2321
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2256
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) => {
2257
2323
  if (opts.plan) return planAndroid(opts);
2258
- const root = findProjectRoot(process.cwd());
2324
+ const root = findProjectRoot$1(process.cwd());
2259
2325
  if (!root) {
2260
2326
  refuseNoProject({ json: Boolean(opts.json) });
2261
2327
  return;
@@ -2276,6 +2342,7 @@ function registerAndroid(program) {
2276
2342
  try {
2277
2343
  return await runAndroid({
2278
2344
  root,
2345
+ buildMachine: opts.buildMachine,
2279
2346
  slot: opts.slot,
2280
2347
  easProfile: opts.easProfile,
2281
2348
  json: Boolean(opts.json),
@@ -2296,7 +2363,7 @@ function registerAndroid(program) {
2296
2363
  }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
2297
2364
  });
2298
2365
  }
2299
- 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) } = {}) {
2300
2367
  return {
2301
2368
  root,
2302
2369
  json,
@@ -2337,6 +2404,9 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2337
2404
  ensureDeviceBooted,
2338
2405
  resolveAvdSerial,
2339
2406
  waitForDeviceBoot,
2407
+ dataFreeBytes,
2408
+ trimCaches,
2409
+ wipeDevice,
2340
2410
  resolveMetro,
2341
2411
  prewarmMetro,
2342
2412
  startServer,
@@ -2436,7 +2506,7 @@ function avdSetupFailure(error, root, logFile) {
2436
2506
  };
2437
2507
  }
2438
2508
  async function runAndroid(options = {}) {
2439
- 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);
2440
2510
  const progress = options.progress ?? NO_BUILD_PROGRESS;
2441
2511
  const slot = validateDeviceSlot(options.slot);
2442
2512
  const started = now();
@@ -2484,10 +2554,13 @@ async function runAndroid(options = {}) {
2484
2554
  }
2485
2555
  const logsDir = workspaceLogsDir(root);
2486
2556
  const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
2487
- const writer = createWriter(buildLog, {
2557
+ const writer = tapBuildLog(createWriter(buildLog, {
2488
2558
  truncate: true,
2489
- fields: { slot }
2490
- });
2559
+ fields: {
2560
+ platform: PLATFORM,
2561
+ slot
2562
+ }
2563
+ }), progress);
2491
2564
  const record = {
2492
2565
  fingerprint: null,
2493
2566
  cacheKey: null,
@@ -2503,12 +2576,13 @@ async function runAndroid(options = {}) {
2503
2576
  write: recordStats,
2504
2577
  now,
2505
2578
  note: (line) => out(phaseLine("stats", chalk.dim(line))),
2506
- phases: () => progress.durations()
2579
+ phases: () => progress.durations(),
2580
+ deviceSetup: () => progress.deviceSetupKnown()
2507
2581
  });
2508
2582
  const recordRun = stats.record;
2509
2583
  let ccacheActivity = CCACHE_NOT_RUN;
2510
2584
  let reclaimed = [];
2511
- 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 } = {}) => {
2512
2586
  const cancellation = cancelledFailure(PLATFORM, {
2513
2587
  code,
2514
2588
  message
@@ -2530,7 +2604,7 @@ async function runAndroid(options = {}) {
2530
2604
  for (const line of lines) out(phaseLine("", chalk.dim(line)));
2531
2605
  if (remedy) out(phaseLine("remedy", remedy));
2532
2606
  if (logPath) out(phaseLine("log", logPath));
2533
- recordRun({
2607
+ if (!setup) recordRun({
2534
2608
  failed: true,
2535
2609
  durationMs: now() - started
2536
2610
  });
@@ -2559,18 +2633,25 @@ async function runAndroid(options = {}) {
2559
2633
  gitCommonDir: gitCommonDir(root),
2560
2634
  repoRoot: settingsRepoRoot
2561
2635
  };
2562
- const projectKey = statsProjectKey({
2636
+ const projectKey = statsProjectKey$1({
2563
2637
  root,
2564
2638
  commonDir: settingsContext.gitCommonDir,
2565
2639
  repoRoot: settingsRepoRoot
2566
2640
  });
2567
2641
  stats.setProject(projectKey);
2642
+ progress.estimate(projectKey);
2568
2643
  let estimatesRead = null;
2569
2644
  const estimates = () => estimatesRead ??= readEstimates({
2570
2645
  projectKey,
2571
2646
  platform: PLATFORM
2572
2647
  });
2573
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
+ }
2574
2655
  const planned = resolveAndroidRunPlan({
2575
2656
  settings,
2576
2657
  settingsContext,
@@ -2660,8 +2741,16 @@ async function runAndroid(options = {}) {
2660
2741
  devServer = gate.devServer;
2661
2742
  phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
2662
2743
  return true;
2663
- } else phase("metro", reservedPort ? `port ${reservedPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
2664
- 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;
2665
2754
  return true;
2666
2755
  }
2667
2756
  if (!await resolveMetroPort()) return phaseFailure;
@@ -2674,7 +2763,7 @@ async function runAndroid(options = {}) {
2674
2763
  isExpo,
2675
2764
  tunnelMode: tunnelModeSetting(settings) ?? void 0,
2676
2765
  publicUrl: publicUrlSetting(settings),
2677
- available: detectRemoteProviders(binOnPath)
2766
+ available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
2678
2767
  });
2679
2768
  if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
2680
2769
  }
@@ -2753,6 +2842,7 @@ async function runAndroid(options = {}) {
2753
2842
  const failure = avdSetupFailure(err, root, emuLog);
2754
2843
  return fail(failure.code, failure.message, failure.remedy, failure.extra);
2755
2844
  }
2845
+ progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
2756
2846
  const prepareMs = prepare();
2757
2847
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
2758
2848
  const boot = () => Promise.resolve(ensureDeviceBooted({
@@ -2808,6 +2898,7 @@ async function runAndroid(options = {}) {
2808
2898
  isExpo,
2809
2899
  device,
2810
2900
  physical,
2901
+ remote: Boolean(remoteDevice),
2811
2902
  buildPlan,
2812
2903
  cacheProviderConfig,
2813
2904
  requestedBuildCache,
@@ -2820,7 +2911,11 @@ async function runAndroid(options = {}) {
2820
2911
  out,
2821
2912
  estimates,
2822
2913
  stats,
2823
- step: progress.step
2914
+ step: progress.step,
2915
+ miss: progress.miss,
2916
+ hit: progress.hit,
2917
+ place: progress.place,
2918
+ waitingOn: progress.waitingOn
2824
2919
  }
2825
2920
  }, {
2826
2921
  deviceAbi,
@@ -2860,7 +2955,7 @@ async function runAndroid(options = {}) {
2860
2955
  record.appPath = apkPath;
2861
2956
  if (runCancellation()) return fail("STIM_CANCELLED", "before install");
2862
2957
  if (startRemoteBoot) {
2863
- progress.step("install");
2958
+ progress.step("device");
2864
2959
  bootPromise = startRemoteBoot();
2865
2960
  const booted = await bootPromise;
2866
2961
  if (booted.failed) {
@@ -2892,6 +2987,7 @@ async function runAndroid(options = {}) {
2892
2987
  }
2893
2988
  };
2894
2989
  if (physical) {
2990
+ progress.step("device");
2895
2991
  const acquired = await acquireLease({
2896
2992
  root,
2897
2993
  platform: PLATFORM,
@@ -2939,6 +3035,9 @@ async function runAndroid(options = {}) {
2939
3035
  bootPromise,
2940
3036
  resolveAvdSerial,
2941
3037
  waitForDeviceBoot,
3038
+ dataFreeBytes,
3039
+ trimCaches,
3040
+ wipeDevice,
2942
3041
  bootDuration: () => bootDuration,
2943
3042
  apkPath,
2944
3043
  androidPackage,
@@ -2975,7 +3074,20 @@ async function runAndroid(options = {}) {
2975
3074
  recordRun,
2976
3075
  reclaimed,
2977
3076
  devServer,
2978
- 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
+ }))
2979
3091
  });
2980
3092
  } finally {
2981
3093
  releaseLease();