stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -1,37 +1,39 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { B as validateDeviceSlot, C as withConfigLock, E as ownership_claim_exports, F as deviceSlotKey, I as deviceSlotPlatforms, P as deviceSlotFileKey, S as upsertProject, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-B7iyKWkL.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { _ as workspaceLogsDir, h as workspaceDir, o as writeWorkspaceState, r as readWorkspaceState, s as emulatorLogFile } from "./workspace-state-BT5E3SA2.mjs";
5
- import { T as removeTemporaryEntry, g as repoRoot, s as gitCommonDir, w as makeTemporaryDirectory } from "./worktree-FG_ZKKdb.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-DDZWf_16.mjs";
7
- import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-DZobzxLW.mjs";
8
- import { r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
9
- import { a as resolveProjectMetro, i as pidExists, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
10
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-Cp52QAK1.mjs";
11
- import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, E as remoteAndroidSetting, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as settingFile, k as resolveCacheProviderConfig, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, x as metroWarmupUrlSetting } from "./settings-BXXorVM2.mjs";
12
- import { B as waitForBoot, E as memoizeEmulatorProbe, F as resetAdoptedAvd, I as resolveOwnedAvdSerial, L as resolvePhysicalDevice, M as physicalDeviceModel, P as probeEmulatorSerial, S as listAdbDevices, _ as emulatorFailureRemedy, a as androidPoolNoCandidatesRefusal, g as emulatorDiskSpaceRemedy, i as androidPoolCandidates, n as androidDeviceAbi, o as androidSystemImageAbi, r as androidHome, s as androidToolCwd, t as androidBuildToolName, v as extractEmulatorFailure, w as listInstalledSystemImages, y as findBuildTool } from "./android-DWssqHQ8.mjs";
13
- import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, n as appReadinessMessage, p as shortHash } from "./command-output-B3ycpsYv.mjs";
14
- import { C as untrackedNativeFiles, D as readAssetManifest, E as compareAssetManifests, S as untrackedMissLine, T as captureAssetManifest, _ as providerUploadOutcome, b as storeBuild, c as buildCacheKey, d as filesystemBuildCapability, f as fingerprintDiffRecord, g as providerDownloadPath, h as prepareProviderDownloadDir, i as releaseBuildLock, l as describeFingerprintMiss, m as fingerprintProject, n as acquireBuildLock, o as takeoverLine, p as fingerprintDiffSuffix, s as waitForBuild, t as WAIT_CEILING_MS, v as refingerprintAfterMutation, w as assetDiffReason, x as storedAssetManifest, y as resolveBuild } from "./build-lock-CgtsB6TV.mjs";
15
- import { t as cleanLine } from "./server-expo-BVAA1OqJ.mjs";
16
- import { C as resolveRemote, S as resolveEasCliBin, _ as exitAfterFlush, f as RESOLVE_TIMEOUT_MS, g as easAuthNote, h as checkEasAuth, l as parkedMaxSetting, m as cacheLevel, p as UPLOAD_TIMEOUT_MS, v as isEasAuthFailureText, w as uploadRemote, y as loadProjectProvider } from "./teardown-BJqEzPo_.mjs";
17
- import { c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, i as detectEntryFile, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-BmT-ioFI.mjs";
18
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
19
- import { D as verifyAndroidReleaseLaunch, E as unverifiedLaunchLines, O as verifyLaunch, S as launchAndroidReleaseApp, _ as installConflictKind, c as RELEASE_VERIFY_WAIT_MS, d as androidDevClientUrl, g as installAndroidApp, h as deviceShellArg, i as LAUNCH_BUNDLING, l as VERIFY_TIMEOUT_MS, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, t as ADB_INSTALL_TIMEOUT_MS, u as androidAppProcess, x as launchAndroidApp } from "./app-install-BDbsSgCl.mjs";
20
- 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-BxRwdufO.mjs";
21
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C-oAHRT1.mjs";
22
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BYcZ6n1h.mjs";
23
- import { i as ensureOwnedDevice, l as AvdBootError, o as unknownAndroidSystemImageRefusal, r as ensureBooted, t as checkDeviceCapacity, u as AvdRecoveryError } from "./device-BUoZyMoR.mjs";
24
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-ByN8P6CB.mjs";
25
- import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-SWIjzeuL.mjs";
26
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B6YaWeg0.mjs";
27
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-ZUUe0TQP.mjs";
28
- import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
29
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
1
+ import { L as deviceSlotFileKey, R as deviceSlotKey, S as setDevice, T as withConfigLock, V as parseDeviceSlotOption, W as validateDeviceSlot, f as getProject, g as loadConfig, k as ownership_claim_exports, l as getConcurrencyLimits, w as upsertProject, x as saveConfig, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
+ import { O as workspaceDir, b as emulatorLogFile, c as signalProcessTree, h as recordWorkspaceUse, i as pidExists, j as workspaceLogsDir, m as readWorkspaceState, s as resolveProjectMetro, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
5
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-BlizKDpZ.mjs";
6
+ import { a as detectIsExpo, i as detectBundleId, l as projectShortcut, n as appProjectProblem, o as findProjectRoot, r as detectAndroidPackage } from "./project-BXZzOABt.mjs";
7
+ import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-WGdIbi_d.mjs";
8
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
9
+ import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
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-D5z2_bcF.mjs";
11
+ import { A as resolveBuild, C as fingerprintDiffRecord, D as providerDownloadPath, E as prepareProviderDownloadDir, F as assetDiffReason, I as captureAssetManifest, L as compareAssetManifests, M as storedAssetManifest, N as untrackedMissLine, O as providerUploadOutcome, P as untrackedNativeFiles, R as readAssetManifest, S as filesystemBuildCapability, T as fingerprintProject, _ as waitForBuild, b as describeFingerprintMiss, g as releaseBuildLock, j as storeBuild, k as refingerprintAfterMutation, m as acquireBuildLock, v as waitForSharedBuild, w as fingerprintDiffSuffix, y as buildCacheKey } from "./device-lease-CbEaIk7b.mjs";
12
+ import { n as NO_BUILD_PROGRESS, s as startBuildProgress } from "./build-progress-BXx-ZBe4.mjs";
13
+ import { B as resolvePhysicalDevice, E as listInstalledSystemImages, F as physicalDeviceModel, L as probeEmulatorSerial, O as memoizeEmulatorProbe, R as resetAdoptedAvd, W as waitForBoot, _ as emulatorDiskSpaceRemedy, a as androidPoolNoCandidatesRefusal, b as findBuildTool, i as androidPoolCandidates, n as androidDeviceAbi, o as androidSystemImageAbi, r as androidHome, s as androidToolCwd, t as androidBuildToolName, v as emulatorFailureRemedy, w as listAdbDevices, y as extractEmulatorFailure, z as resolveOwnedAvdSerial } from "./android-787HhP5w.mjs";
14
+ import { A as resolveCacheProviderConfig, E as publicUrlSetting, H as compilerCacheFallbackMessage, I as tunnelModeSetting, L as unknownSettingKeys, M as settingFile, O as remoteAndroidSetting, P as settingShapeErrors, S as metroWarmupUrlSetting, U as optimizationBuildProfile, V as artifactCachePolicy, W as resolveOptimizations, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as resolveSettings, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, z as detectProviders } from "./settings-DOugPlPz.mjs";
15
+ import { y as parkedMaxSetting } from "./ownership-DZjdx8Gq.mjs";
16
+ import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-CjUluqIX.mjs";
17
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-D2xbLVoz.mjs";
18
+ import { t as cleanLine } from "./server-expo-BvOCeakp.mjs";
19
+ import { A as UPLOAD_TIMEOUT_MS, B as resolveRemote, F as isEasAuthFailureText, I as loadProjectProvider, M as checkEasAuth, N as easAuthNote, P as exitAfterFlush, V as uploadRemote, a as ensureMetroReachable, c as remoteAndroidDeps, j as cacheLevel, k as RESOLVE_TIMEOUT_MS, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext, w as ownedSessionName, z as resolveEasCliBin } from "./device-remote-Dy1jGj3U.mjs";
20
+ 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-CrJ6t_bv.mjs";
21
+ 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-CQS_Kg-C.mjs";
22
+ import { d as isEasBuildFailure, f as resolveEasDevelopmentBuild, o as unknownAndroidSystemImageRefusal, r as checkDeviceCapacity } from "./simslim-Ca3487H6.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-Di8Q-9Ro.mjs";
24
+ import { _ as isPhysicalDeviceRequest, b as warmMetro, c as runPrebuild, d as ensureOwnedDevice, f as AvdBootError, g as ensureWorkspaceStorageSafely, h as ensureDevServer, i as detectEntryFile, l as staleNativeDirRefusal, n as pickDevClientScheme, o as planPrebuild, p as AvdRecoveryError, s as recordPrebuild, t as devClientScheme, u as ensureBooted, v as launchOutcomeRecord } from "./dev-client-92U593KT.mjs";
25
+ import { t as budgetGate } from "./budget-BP4jZZL4.mjs";
26
+ import { f as spawnEntry, s as startDevServer } from "./start-F9gv5dcC.mjs";
27
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BAgJaLA4.mjs";
28
+ import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-CITmIeWY.mjs";
29
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B2tBkf0m.mjs";
30
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-C5cg0fmk.mjs";
31
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-xxWKNO3-.mjs";
30
32
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
31
33
  import { InvalidArgumentError } from "commander";
32
34
  import { basename, dirname, isAbsolute, join, relative } from "node:path";
33
- import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
34
35
  import chalk from "chalk";
36
+ import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
35
37
  //#region src/engine/apk-swap.ts
36
38
  const ANDROID_BUNDLE_NAME = "index.android.bundle";
37
39
  const LAST_LINES = 5;
@@ -437,7 +439,6 @@ function systemImageRefusal({ slot, flag, resolved, physical, remoteBackend, lis
437
439
  function isReleaseVariant(variant) {
438
440
  return typeof variant === "string" && /release$/i.test(variant.trim());
439
441
  }
440
- const NO_METRO = "STIM_NO_METRO";
441
442
  const NO_FINGERPRINT = "STIM_NO_FINGERPRINT";
442
443
  const NO_DEVICE = "STIM_NO_DEVICE";
443
444
  const LAUNCH_FAILED = "STIM_LAUNCH_FAILED";
@@ -643,8 +644,8 @@ function fail$1(code, message, remedy, extra = {}) {
643
644
  extra
644
645
  };
645
646
  }
646
- 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, needsPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
647
- const { phase, out, estimates, stats } = progress;
647
+ 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 }) {
648
+ const { phase, out, estimates, stats, step } = progress;
648
649
  const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
649
650
  const useBuildCache = cachePolicy.read;
650
651
  let androidPackage = initialPackage;
@@ -715,6 +716,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
715
716
  stats.setCacheKey(cacheKey);
716
717
  return true;
717
718
  }
719
+ step("cache-lookup");
718
720
  const fingerprintTimer = stepTimer(now);
719
721
  try {
720
722
  const computed = await fingerprint(root, { platform: PLATFORM });
@@ -841,88 +843,37 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
841
843
  if (!easBuild) await resolveRemoteArtifact();
842
844
  let waitedForBuild = null;
843
845
  let releasedWait = null;
844
- async function waitForSharedBuild() {
845
- if (!useBuildCache) return true;
846
- const waitStarted = now();
847
- let failedHolder;
848
- while (!apkPath) {
849
- let attempt = null;
850
- try {
851
- attempt = acquireLock({
852
- platform: PLATFORM,
853
- key: cacheKey,
854
- root,
855
- logFile: buildLog
856
- });
857
- } catch (err) {
858
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
859
- if (refusal) {
860
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
861
- return false;
862
- }
863
- phase("build", chalk.yellow(`could not take the build lock: ${err?.message || err}; building anyway`));
864
- }
865
- if (attempt?.acquired) {
866
- buildLock = attempt;
867
- const previous = attempt.tookOver ?? failedHolder;
868
- if (previous) phase("build", chalk.yellow(takeoverLine(previous)));
869
- break;
870
- } else if (attempt?.held) {
871
- releasedWait = null;
872
- const holder = attempt.held;
873
- const who = holder.projectRoot || "another workspace";
874
- phase("build", `${who} is already building ${shortHash(hash)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""} -- stim guide lifecycle concurrency`);
875
- let waited = null;
876
- try {
877
- const ceilingMs = WAIT_CEILING_MS - (now() - waitStarted);
878
- if (ceilingMs <= 0) throw Object.assign(/* @__PURE__ */ new Error(`Waited ${formatDuration(now() - waitStarted)} for shared builds without an artifact; ${who} (pid ${holder.pid}) holds ${attempt.path}.`), {
879
- code: "STIM_BUILD_WAIT_TIMEOUT",
880
- lockPath: attempt.path
881
- });
882
- waited = await waitForBuild({
883
- platform: PLATFORM,
884
- key: cacheKey,
885
- out,
886
- ceilingMs
887
- });
888
- } catch (err) {
889
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
890
- if (refusal) {
891
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
892
- return false;
893
- }
894
- const wtErr = err;
895
- if (wtErr?.code !== "STIM_BUILD_WAIT_TIMEOUT") throw err;
896
- phaseFailure = fail$1("STIM_BUILD_WAIT_TIMEOUT", wtErr.message, `Check pid ${holder.pid}; if it is not really building, remove ${wtErr.lockPath} and run \`stim android\` again.`, { lastBuildStatus: true });
897
- return false;
898
- }
899
- if (waited?.hit) {
900
- apkPath = waited.hit ?? null;
901
- record.cacheHit = "local";
902
- waitedForBuild = {
903
- pid: holder.pid,
904
- ms: waited.waitedMs
905
- };
906
- phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
907
- } else if (waited?.lockReleased) {
908
- failedHolder = void 0;
909
- releasedWait = {
910
- facts: {
911
- pid: holder.pid,
912
- ms: waited.waitedMs
913
- },
914
- who
915
- };
916
- phase("build", `${who}'s build lock was released; rechecking this workspace before building`);
917
- } else {
918
- phase("build", chalk.yellow(`${who}'s build ended without an artifact (${waited?.builderFailed}); retrying the build lock`));
919
- failedHolder = holder;
920
- }
921
- } else break;
846
+ if (!easBuild && useBuildCache && !apkPath) {
847
+ const shared = await waitForSharedBuild({
848
+ platform: PLATFORM,
849
+ key: cacheKey,
850
+ fingerprint: hash,
851
+ root,
852
+ logFile: buildLog,
853
+ command: "stim android",
854
+ acquire: acquireLock,
855
+ wait: waitForBuild,
856
+ now,
857
+ phase: (text) => {
858
+ step("wait");
859
+ phase("build", text);
860
+ },
861
+ warn: (text) => phase("build", chalk.yellow(text)),
862
+ out
863
+ });
864
+ if (shared.refusal) {
865
+ const { code, message, remedy } = shared.refusal;
866
+ phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
867
+ return refused();
868
+ }
869
+ buildLock = shared.lock;
870
+ releasedWait = shared.released;
871
+ if (shared.hit) {
872
+ apkPath = shared.hit.path;
873
+ record.cacheHit = "local";
874
+ waitedForBuild = shared.hit.waited;
922
875
  }
923
- return true;
924
876
  }
925
- if (!easBuild && !await waitForSharedBuild()) return refused();
926
877
  let swapDir = null;
927
878
  let swapFellBack = false;
928
879
  const installableCachedApk = async (key, cachedPath) => {
@@ -975,8 +926,23 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
975
926
  }
976
927
  phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
977
928
  }
978
- if (needsPrebuildFor(root, "android", isExpo)) {
979
- const pre = await prebuild(root, PLATFORM, writer, { isExpo });
929
+ const prebuildPlan = planPrebuildFor(root, PLATFORM, {
930
+ isExpo,
931
+ fingerprint: hash,
932
+ sources: fingerprintSources
933
+ });
934
+ if (prebuildPlan === "refuse") {
935
+ const refusal = staleNativeDirRefusal(PLATFORM);
936
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
937
+ return false;
938
+ }
939
+ if (prebuildPlan === "generate" || prebuildPlan === "regenerate") {
940
+ step("prebuild");
941
+ recordPrebuild(root, PLATFORM, null);
942
+ const pre = await prebuild(root, PLATFORM, writer, {
943
+ isExpo,
944
+ clean: prebuildPlan === "regenerate"
945
+ });
980
946
  if (pre.failed) {
981
947
  phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
982
948
  lastBuildStatus: true,
@@ -985,8 +951,8 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
985
951
  });
986
952
  return false;
987
953
  }
988
- phase("prebuild", `android/ generated (${formatDuration(pre.durationMs)})`);
989
- androidPackage = androidPackage || detectAndroidPackage(root);
954
+ phase("prebuild", `${prebuildPlan === "generate" ? "android/ generated" : "android/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(pre.durationMs)})`);
955
+ androidPackage = detectAndroidPackage(root) || androidPackage;
990
956
  record.bundleId = androidPackage;
991
957
  const after = await refingerprintAfterMutation({
992
958
  projectRoot: root,
@@ -994,6 +960,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
994
960
  previousHash: hash,
995
961
  fingerprint
996
962
  });
963
+ if (after) recordPrebuild(root, PLATFORM, after.hash);
997
964
  if (after?.moved) {
998
965
  storeHash = after.hash;
999
966
  storeSources = after.sources;
@@ -1017,6 +984,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1017
984
  }
1018
985
  }
1019
986
  if (!apkPath) {
987
+ step("compile");
1020
988
  phase("build", `compiling ${variant || "debug"} with Gradle`);
1021
989
  const built = await build({
1022
990
  root,
@@ -1147,7 +1115,7 @@ function tail(lines, n = FALLBACK_LINES) {
1147
1115
  }
1148
1116
  //#endregion
1149
1117
  //#region src/commands/android/result.ts
1150
- function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = 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 }) {
1118
+ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = 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 }) {
1151
1119
  return {
1152
1120
  platform: PLATFORM,
1153
1121
  ...slot && slot !== "default" ? { slot } : {},
@@ -1175,7 +1143,8 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1175
1143
  devClientUrl: devClientUrl ?? null,
1176
1144
  logs: logs ?? null,
1177
1145
  durationMs: typeof durationMs === "number" && Number.isFinite(durationMs) ? durationMs : null,
1178
- ...lease === void 0 ? {} : { lease }
1146
+ ...lease === void 0 ? {} : { lease },
1147
+ ...devServer ? { devServer } : {}
1179
1148
  };
1180
1149
  }
1181
1150
  function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null }) {
@@ -1213,7 +1182,7 @@ async function finishAndroidUpload(uploadPending, remote, phase) {
1213
1182
  }
1214
1183
  return false;
1215
1184
  }
1216
- 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 }) {
1185
+ 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 }) {
1217
1186
  recordRun({
1218
1187
  failed: false,
1219
1188
  cacheHit: cacheLevel(record.cacheHit),
@@ -1243,16 +1212,20 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1243
1212
  ccache,
1244
1213
  logs: logsDir,
1245
1214
  durationMs,
1246
- lease
1215
+ lease,
1216
+ devServer
1247
1217
  });
1248
1218
  writer.close();
1249
- if (json) emit(JSON.stringify(facts));
1219
+ if (json) emit(JSON.stringify(reclaimed.length ? {
1220
+ ...facts,
1221
+ reclaimed
1222
+ } : facts));
1250
1223
  else {
1251
1224
  const summary = `${launchWarning ? "WARNING" : "OK"}: ${androidPackage} launched on ${serial}, ${release ? `${variant} (embedded JS, no Metro)` : `Metro port ${metroPort}`} (${cacheOutcome(record.cacheHit, remote?.name ?? providerName)})`;
1252
1225
  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);
1253
1226
  const deviceName = record.avdName || record.deviceName || serial;
1254
1227
  const cacheResult = useBuildCache ? cacheOutcome(record.cacheHit, remote?.name ?? providerName) : "bypassed; built";
1255
- const metroResult = release ? `embedded (${variant})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}`;
1228
+ 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})` : ""}`;
1256
1229
  emit([
1257
1230
  outcome,
1258
1231
  phaseLine("device", `${deviceName} (${serial})`),
@@ -1508,7 +1481,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1508
1481
  });
1509
1482
  return resolved.serial;
1510
1483
  }
1511
- 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, writeState, now, out, emit, recordRun }) {
1484
+ 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, writeState, now, out, emit, recordRun, reclaimed, devServer, enterPhase }) {
1512
1485
  let androidPackage = initialPackage;
1513
1486
  let leaseWarned = false;
1514
1487
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1524,6 +1497,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1524
1497
  }
1525
1498
  return null;
1526
1499
  };
1500
+ enterPhase("install");
1527
1501
  const booted = await bootPromise;
1528
1502
  const runCommand = nativeRunCommand("android", slot, {
1529
1503
  physical,
@@ -1575,9 +1549,12 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1575
1549
  allowUninstall: release || adopting
1576
1550
  });
1577
1551
  if (installed.failed) {
1578
- const conflict = installConflictKind(installed.reason);
1579
- const rerun = useBuildCache ? " Then run this command again; it installs the APK from cache without building it again." : " Then run this command again.";
1580
- const installRemedy = conflict ? `${androidPackage} is already installed on ${serial} ` + (conflict === "signature" ? "with a different signer" : "at a higher versionCode") + `. Uninstall it first (\`adb -s ${serial} uninstall ${androidPackage}\`); its data goes with it.` + rerun : `Check that ${serial} is still connected (\`adb devices\`) and has room for the APK.`;
1552
+ const installRemedy = remoteDevice?.failureRemedy() ?? localInstallRemedy({
1553
+ reason: installed.reason,
1554
+ packageName: androidPackage,
1555
+ serial,
1556
+ useBuildCache
1557
+ });
1581
1558
  return fail(installed.code || "STIM_INSTALL_FAILED", installed.reason, installRemedy, { lastBuildStatus: true });
1582
1559
  }
1583
1560
  if (adopting) try {
@@ -1613,6 +1590,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1613
1590
  if (!androidPackage) return fail(LAUNCH_FAILED, "Could not determine this app's Android package name, so there is nothing to launch.", "Set `expo.android.package` in app.json / app.config.js, or `namespace` in android/app/build.gradle.", { lastBuildStatus: true });
1614
1591
  if (physical) raiseLeaseFor(0, false);
1615
1592
  const scheme = release ? void 0 : resolveDevClientScheme(root, apkPath);
1593
+ enterPhase("launch");
1616
1594
  const launchTimer = stepTimer(now);
1617
1595
  const launchedAt = now();
1618
1596
  writer.write({
@@ -1651,7 +1629,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1651
1629
  appId: androidPackage,
1652
1630
  since: launchedAt
1653
1631
  }, logsDir, (line) => phase("launch", chalk.red(line)), Boolean(remoteDevice));
1654
- return fail(launched.code || "STIM_LAUNCH_FAILED", launched.reason, `Check the app installed correctly (\`adb -s ${serial} shell pm list packages ${androidPackage}\`).`, { lastBuildStatus: true });
1632
+ return fail(launched.code || "STIM_LAUNCH_FAILED", launched.reason, launchRemedy(remoteDevice, serial, androidPackage), { lastBuildStatus: true });
1655
1633
  }
1656
1634
  writer.write({
1657
1635
  src: "build",
@@ -1671,17 +1649,15 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1671
1649
  } catch (error) {
1672
1650
  out(phaseLine("state", chalk.yellow(`could not record Android launch: ${error?.message || error}`)));
1673
1651
  }
1674
- const reversedSummary = (launched.reversed ?? []).join(", ") || `tcp:${metroPort}->tcp:${metroPort}`;
1675
- if (!release && launched.debugHttpHost) phase("metro", `debug_http_host ${launched.debugHttpHost} + adb reverse ${reversedSummary}`);
1676
- else if (!release) {
1677
- phase("metro", chalk.yellow(`adb reverse ${reversedSummary}; ${launched.debugHttpHostNote || "debug_http_host not written"}`));
1678
- writer.write({
1679
- src: "build",
1680
- level: "warn",
1681
- event: "debug_http_host_failed",
1682
- msg: `debug_http_host was not written for ${androidPackage} on ${serial}: ${launched.debugHttpHostNote || "unknown reason"}`
1683
- });
1684
- }
1652
+ if (!release) reportMetroRoute({
1653
+ launched,
1654
+ remote: Boolean(remoteDevice),
1655
+ metroPort,
1656
+ androidPackage,
1657
+ serial,
1658
+ phase,
1659
+ writer
1660
+ });
1685
1661
  if (launched.devClientNote) {
1686
1662
  phase("metro", chalk.yellow(launched.devClientNote));
1687
1663
  writer.write({
@@ -1704,7 +1680,21 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1704
1680
  out
1705
1681
  });
1706
1682
  const remoteRelease = Boolean(remoteDevice && release);
1707
- if (!remoteRelease) {
1683
+ if (remoteDevice) {
1684
+ killPreviousCollector(root, {
1685
+ slot,
1686
+ kill,
1687
+ isAlive: pidAlive,
1688
+ verify: verifyCollector,
1689
+ note: (line) => out(phaseLine("logs", line))
1690
+ });
1691
+ writer.write({
1692
+ src: "build",
1693
+ level: "info",
1694
+ event: "collector_skipped",
1695
+ msg: `remote session ${serial}: device logs come from agent-device/EAS, so no adb logcat collector is attached`
1696
+ });
1697
+ } else {
1708
1698
  if (physical) raiseLeaseFor(0, false);
1709
1699
  const collectorPid = await startCollector({
1710
1700
  root,
@@ -1780,7 +1770,9 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1780
1770
  writer,
1781
1771
  emit,
1782
1772
  lease: physical ? leaseFacts : void 0,
1783
- recordRun
1773
+ recordRun,
1774
+ reclaimed,
1775
+ devServer
1784
1776
  });
1785
1777
  if (remoteWasAbandoned || uploadWasAbandoned) exitAfterFlush(0);
1786
1778
  return {
@@ -1788,6 +1780,33 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1788
1780
  facts
1789
1781
  };
1790
1782
  }
1783
+ function localInstallRemedy({ reason, packageName, serial, useBuildCache }) {
1784
+ const conflict = installConflictKind(reason);
1785
+ if (!conflict) return `Check that ${serial} is still connected (\`adb devices\`) and has room for the APK.`;
1786
+ const rerun = useBuildCache ? " Then run this command again; it installs the APK from cache without building it again." : " Then run this command again.";
1787
+ return `${packageName} is already installed on ${serial} ` + (conflict === "signature" ? "with a different signer" : "at a higher versionCode") + `. Uninstall it first (\`adb -s ${serial} uninstall ${packageName}\`); its data goes with it.` + rerun;
1788
+ }
1789
+ function launchRemedy(remoteDevice, serial, packageName) {
1790
+ return remoteDevice?.failureRemedy() ?? `Check the app installed correctly (\`adb -s ${serial} shell pm list packages ${packageName}\`).`;
1791
+ }
1792
+ function reportMetroRoute({ launched, remote, metroPort, androidPackage, serial, phase, writer }) {
1793
+ if (remote) {
1794
+ phase("metro", `public origin ${launched.jsLocation}`);
1795
+ return;
1796
+ }
1797
+ const reversedSummary = (launched.reversed ?? []).join(", ") || `tcp:${metroPort}->tcp:${metroPort}`;
1798
+ if (launched.debugHttpHost) {
1799
+ phase("metro", `debug_http_host ${launched.debugHttpHost} + adb reverse ${reversedSummary}`);
1800
+ return;
1801
+ }
1802
+ phase("metro", chalk.yellow(`adb reverse ${reversedSummary}; ${launched.debugHttpHostNote || "debug_http_host not written"}`));
1803
+ writer.write({
1804
+ src: "build",
1805
+ level: "warn",
1806
+ event: "debug_http_host_failed",
1807
+ msg: `debug_http_host was not written for ${androidPackage} on ${serial}: ${launched.debugHttpHostNote || "unknown reason"}`
1808
+ });
1809
+ }
1791
1810
  function diagnoseBootFailure(booted, logFile, runCommand, physical) {
1792
1811
  return noDeviceDiagnostic({
1793
1812
  reason: booted.reason ?? "The emulator did not boot.",
@@ -1858,8 +1877,6 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1858
1877
  if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
1859
1878
  const avdConfigError = validateAvdConfig(settings, root);
1860
1879
  if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1861
- const remoteSettingError = remoteDeviceSettingError(settings);
1862
- if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1863
1880
  const systemImage = resolveSystemImage(systemImageFlag, settings);
1864
1881
  const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1865
1882
  const flavorRefusal = productFlavorRefusal({
@@ -1932,36 +1949,51 @@ function androidCommand(program) {
1932
1949
  registerAndroid(program);
1933
1950
  }
1934
1951
  function registerAndroid(program) {
1935
- 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. Never starts the bundler -- run `stim start` first.").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)", validateDeviceSlot).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--no-metro-check", "Skip the reserved-port Metro health check (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 [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) => {
1952
+ 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("--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 [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) => {
1936
1953
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1937
1954
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1938
1955
  }).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) => {
1939
1956
  const root = findProjectRoot(process.cwd());
1940
1957
  if (!root) {
1941
- console.error(chalk.red("Not in a React Native project (no package.json found)."));
1942
- process.exit(1);
1958
+ refuseNoProject({ json: Boolean(opts.json) });
1943
1959
  return;
1944
1960
  }
1945
- if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", () => runAndroid({
1946
- root,
1947
- slot: opts.slot,
1948
- easProfile: opts.easProfile,
1949
- json: Boolean(opts.json),
1950
- metroCheck: opts.metroCheck !== false,
1951
- useBuildCache: opts.buildCache !== false,
1952
- variant: opts.variant ?? null,
1953
- systemImage: opts.systemImage ?? null,
1954
- remoteDevice: opts.remote ?? null,
1955
- device: opts.device ?? null,
1956
- wait: opts.wait,
1957
- waitConflict: waitFlagConflict(process.argv)
1958
- }), {
1961
+ if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", async (claim) => {
1962
+ recordWorkspaceUse(root);
1963
+ const progress = startBuildProgress({
1964
+ root,
1965
+ platform: "android",
1966
+ slot: opts.slot ?? "default",
1967
+ claim,
1968
+ note: (line) => console.error(chalk.dim(line))
1969
+ });
1970
+ try {
1971
+ return await runAndroid({
1972
+ root,
1973
+ slot: opts.slot,
1974
+ easProfile: opts.easProfile,
1975
+ json: Boolean(opts.json),
1976
+ metroCheck: opts.metroCheck !== false,
1977
+ useBuildCache: opts.buildCache !== false,
1978
+ variant: opts.variant ?? null,
1979
+ systemImage: opts.systemImage ?? null,
1980
+ remoteDevice: opts.remote ?? null,
1981
+ device: opts.device ?? null,
1982
+ wait: opts.wait,
1983
+ waitConflict: waitFlagConflict(process.argv),
1984
+ progress
1985
+ });
1986
+ } finally {
1987
+ progress.clear();
1988
+ }
1989
+ }, {
1959
1990
  external: true,
1960
- waitMs: 18e5
1991
+ waitMs: 18e5,
1992
+ declareSpawns: true
1961
1993
  })).ok) process.exit(1);
1962
1994
  });
1963
1995
  }
1964
- 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, 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, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, resolveMetroRetrying = resolveMetroWithRetry, readState = readWorkspaceState, 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, needsPrebuildFor = needsPrebuild, 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, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
1996
+ 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, 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, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState, 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, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
1965
1997
  return {
1966
1998
  root,
1967
1999
  json,
@@ -2002,7 +2034,7 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2002
2034
  waitForDeviceBoot,
2003
2035
  resolveMetro,
2004
2036
  prewarmMetro,
2005
- resolveMetroRetrying,
2037
+ startServer,
2006
2038
  readState,
2007
2039
  pidAlive,
2008
2040
  verifyCollector,
@@ -2023,7 +2055,7 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2023
2055
  uploadRemoteBuild,
2024
2056
  resolveCacheProvider,
2025
2057
  loadCacheProviderModule,
2026
- needsPrebuildFor,
2058
+ planPrebuildFor,
2027
2059
  prebuild,
2028
2060
  build,
2029
2061
  ccacheFor,
@@ -2055,6 +2087,10 @@ function androidSlotOptions(options) {
2055
2087
  ...args,
2056
2088
  slot
2057
2089
  }),
2090
+ ensureDeviceBooted: (args) => base.ensureDeviceBooted({
2091
+ ...args,
2092
+ slot
2093
+ }),
2058
2094
  checkCapacity: (args) => base.checkCapacity({
2059
2095
  ...args,
2060
2096
  slot
@@ -2094,7 +2130,8 @@ function avdSetupFailure(error, root, logFile) {
2094
2130
  };
2095
2131
  }
2096
2132
  async function runAndroid(options = {}) {
2097
- let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, resolveMetro, prewarmMetro, resolveMetroRetrying, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, needsPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2133
+ let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, 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, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2134
+ const progress = options.progress ?? NO_BUILD_PROGRESS;
2098
2135
  const slot = validateDeviceSlot(options.slot);
2099
2136
  const started = now();
2100
2137
  const startedAt = new Date(started).toISOString();
@@ -2159,10 +2196,12 @@ async function runAndroid(options = {}) {
2159
2196
  platform: PLATFORM,
2160
2197
  write: recordStats,
2161
2198
  now,
2162
- note: (line) => out(phaseLine("stats", chalk.dim(line)))
2199
+ note: (line) => out(phaseLine("stats", chalk.dim(line))),
2200
+ phases: () => progress.durations()
2163
2201
  });
2164
2202
  const recordRun = stats.record;
2165
2203
  let ccacheActivity = CCACHE_NOT_RUN;
2204
+ let reclaimed = [];
2166
2205
  const fail = (code, message, remedy, { lastBuildStatus = false, diagnostics = [], lines = [], logPath = null, lease } = {}) => {
2167
2206
  if (lastBuildStatus) persistLastBuild({
2168
2207
  writeState,
@@ -2188,7 +2227,8 @@ async function runAndroid(options = {}) {
2188
2227
  message,
2189
2228
  remedy: remedy ?? null,
2190
2229
  ...ccacheActivity.status === "not-run" ? {} : { ccache: ccacheActivity },
2191
- ...lease === void 0 ? {} : { lease }
2230
+ ...lease === void 0 ? {} : { lease },
2231
+ ...reclaimed.length ? { reclaimed } : {}
2192
2232
  }));
2193
2233
  writer.close();
2194
2234
  return {
@@ -2243,6 +2283,19 @@ async function runAndroid(options = {}) {
2243
2283
  const useBuildCache = cachePolicy.read;
2244
2284
  const physical = target.kind === "physical";
2245
2285
  const remoteBackend = target.kind === "remote" ? target.backend : null;
2286
+ const budget = await (options.checkBudget ?? budgetGate)({
2287
+ root,
2288
+ note: out
2289
+ });
2290
+ reclaimed = budget.reclaimed;
2291
+ if (budget.refusal) return fail(budget.refusal.code, budget.refusal.message, budget.refusal.remedy);
2292
+ const remoteContext = remoteBackend ? await resolveRemoteDeviceContext({
2293
+ root,
2294
+ platform: PLATFORM,
2295
+ backend: remoteBackend,
2296
+ easBin: resolveEasBin(root)?.file ?? null
2297
+ }) : null;
2298
+ if (remoteContext && "failed" in remoteContext) return fail(remoteContext.code ?? "STIM_NO_REMOTE_SESSION", remoteContext.failed, remoteContext.remedy);
2246
2299
  const easBuild = await resolveEasBuild({
2247
2300
  root,
2248
2301
  platform: PLATFORM,
@@ -2267,46 +2320,37 @@ async function runAndroid(options = {}) {
2267
2320
  let remoteDevice = null;
2268
2321
  const reservedPort = project?.metroPort ?? null;
2269
2322
  let metroPort = null;
2323
+ let devServer = null;
2270
2324
  let phaseFailure = null;
2271
2325
  async function resolveMetroPort() {
2272
2326
  if (release) phase("metro", `skipped (${variant}: the JS bundle is embedded, no dev server is used)`);
2273
2327
  else if (metroCheck) {
2274
- if (!reservedPort) {
2275
- phaseFailure = fail(NO_METRO, "No Metro port is reserved for this workspace.", "Run `stim start` first, or pass --no-metro-check.");
2276
- return false;
2277
- }
2278
- const held = await resolveMetroRetrying(resolveMetro, reservedPort, root, { onRetry: ({ delayMs }) => phase("metro", `port ${reservedPort} did not verify yet; retrying in ${Math.round(delayMs / 1e3)}s (Metro may still be indexing)`) });
2279
- if (!held.metro) {
2280
- const supervisor = readState(root)?.supervisor ?? null;
2281
- const supervisorAlive = Boolean(supervisor?.pid && pidAlive(supervisor.pid));
2282
- phaseFailure = fail(NO_METRO, noMetroMessage({
2283
- port: reservedPort,
2284
- resolution: held,
2285
- supervisor,
2286
- supervisorAlive
2287
- }), noMetroRemedy({
2288
- port: reservedPort,
2289
- supervisor,
2290
- supervisorAlive
2291
- }));
2328
+ const gate = await ensureDevServer({
2329
+ root,
2330
+ port: reservedPort,
2331
+ settings,
2332
+ remote: remoteContext !== null,
2333
+ note: out,
2334
+ resolve: resolveMetro,
2335
+ start: startServer,
2336
+ readState
2337
+ });
2338
+ reclaimed = [...reclaimed, ...gate.reclaimed];
2339
+ if (!gate.ok) {
2340
+ phaseFailure = fail(gate.code, gate.message, gate.remedy, { lines: gate.lines });
2292
2341
  return false;
2293
2342
  }
2294
- phase("metro", `port ${reservedPort} (pid ${held.metro?.pid})`);
2343
+ metroPort = gate.port;
2344
+ devServer = gate.devServer;
2345
+ phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
2346
+ return true;
2295
2347
  } else phase("metro", reservedPort ? `port ${reservedPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
2296
2348
  metroPort = release ? null : reservedPort ?? 8081;
2297
2349
  return true;
2298
2350
  }
2299
2351
  if (!await resolveMetroPort()) return phaseFailure;
2300
- if (remoteBackend) {
2301
- const resolved = await resolveRemoteDeviceContext({
2302
- root,
2303
- label,
2304
- platform: PLATFORM,
2305
- backend: remoteBackend,
2306
- easBin: resolveEasBin(root)?.file ?? null
2307
- });
2308
- if ("failed" in resolved) return fail(resolved.code ?? "STIM_NO_REMOTE_SESSION", resolved.failed, resolved.remedy);
2309
- remoteDevice = makeRemoteDeviceDeps(resolved.ctx);
2352
+ if (remoteContext) {
2353
+ remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
2310
2354
  if (metroPort !== null) {
2311
2355
  const reachable = await ensureRemoteMetro({
2312
2356
  ctx: remoteDevice.ctx,
@@ -2389,6 +2433,7 @@ async function runAndroid(options = {}) {
2389
2433
  const boot = () => Promise.resolve(ensureDeviceBooted({
2390
2434
  platform: PLATFORM,
2391
2435
  device,
2436
+ projectPath: root,
2392
2437
  out,
2393
2438
  logFile: emuLog
2394
2439
  })).catch((e) => ({
@@ -2404,6 +2449,7 @@ async function runAndroid(options = {}) {
2404
2449
  boot,
2405
2450
  createdSessionId: remoteDevice.createdSessionId,
2406
2451
  abandonCreatedSession: remoteDevice.abandonCreatedSession,
2452
+ webPreviewUrl: remoteDevice.webPreviewUrl,
2407
2453
  writeState,
2408
2454
  register: registerProject
2409
2455
  }) : boot()).then((result) => {
@@ -2457,7 +2503,8 @@ async function runAndroid(options = {}) {
2457
2503
  phase,
2458
2504
  out,
2459
2505
  estimates,
2460
- stats
2506
+ stats,
2507
+ step: progress.step
2461
2508
  }
2462
2509
  }, {
2463
2510
  deviceAbi,
@@ -2477,7 +2524,7 @@ async function runAndroid(options = {}) {
2477
2524
  loadCacheProviderModule,
2478
2525
  acquireSlot,
2479
2526
  releaseSlot,
2480
- needsPrebuildFor,
2527
+ planPrebuildFor,
2481
2528
  prebuild,
2482
2529
  build,
2483
2530
  ccacheFor,
@@ -2590,7 +2637,10 @@ async function runAndroid(options = {}) {
2590
2637
  now,
2591
2638
  out,
2592
2639
  emit,
2593
- recordRun
2640
+ recordRun,
2641
+ reclaimed,
2642
+ devServer,
2643
+ enterPhase: progress.step
2594
2644
  });
2595
2645
  } finally {
2596
2646
  releaseLease();
@@ -2607,4 +2657,4 @@ async function runAndroid(options = {}) {
2607
2657
  }
2608
2658
  }
2609
2659
  //#endregion
2610
- export { NO_DEVICE, NO_FINGERPRINT, NO_METRO, androidDevClientScheme, androidFacts, androidSystemImageSetting, androidVariantSetting, apkDevClientFacts, apkPackage, collectorLogFile, androidCommand as default, displayPath, dumpApkManifest, findAapt, formatDuration, isReleaseVariant, killPreviousCollector, lastBuildRecord, noDeviceDiagnostic, parseXmltree, phaseLine, registerAndroid, resolveSystemImage, resolveVariant, runAndroid, shortHash, startCollector };
2660
+ export { NO_DEVICE, NO_FINGERPRINT, androidDevClientScheme, androidFacts, androidSystemImageSetting, androidVariantSetting, apkDevClientFacts, apkPackage, collectorLogFile, androidCommand as default, displayPath, dumpApkManifest, findAapt, formatDuration, isReleaseVariant, killPreviousCollector, lastBuildRecord, noDeviceDiagnostic, parseXmltree, phaseLine, registerAndroid, resolveSystemImage, resolveVariant, runAndroid, shortHash, startCollector };