stim 1.10.1 → 1.12.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 (72) hide show
  1. package/dist/{build-progress-BXx-ZBe4.mjs → activity-CCZJs__P.mjs} +9 -182
  2. package/dist/{android-787HhP5w.mjs → android-75Sk2rIR.mjs} +81 -19
  3. package/dist/{android-Bu4mbDzc.mjs → android-B7X-8H7h.mjs} +426 -106
  4. package/dist/{android-DPov-fv2.mjs → android-BQiKJYK0.mjs} +1 -1
  5. package/dist/{android-cas-CITmIeWY.mjs → android-cas-DzLPXiEn.mjs} +7 -7
  6. package/dist/{app-install-Cfmqr3TQ.mjs → app-install-CsZi0SAT.mjs} +1 -1
  7. package/dist/{workspaces-wrH6dXfu.mjs → budget-DbzW5VTD.mjs} +651 -32
  8. package/dist/{dev-client-DExzPiOa.mjs → build-plan-CqntbR7b.mjs} +514 -38
  9. package/dist/build-progress-h60GPV27.mjs +179 -0
  10. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +16 -16
  13. package/dist/collector-run.mjs +4 -4
  14. package/dist/{command-output-D5z2_bcF.mjs → command-output-BWCj5Vh-.mjs} +1 -1
  15. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  16. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  17. package/dist/{deps-DP4eWqea.mjs → deps-CO-_7UOm.mjs} +2 -2
  18. package/dist/{device-Czh5kO8A.mjs → device-9r9xRYDa.mjs} +49 -11
  19. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-B9LAXIUH.mjs} +55 -55
  20. package/dist/{device-pool-BBWLAr55.mjs → device-pool-BoezJd-c.mjs} +4 -4
  21. package/dist/{device-remote-powUHdbK.mjs → device-remote-C0_o-tJH.mjs} +254 -579
  22. package/dist/{doctor-CmqK1LcQ.mjs → doctor-LfDlbR-N.mjs} +20 -19
  23. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-CCxjn31V.mjs} +7 -7
  24. package/dist/{gc-zCDpmdHU.mjs → gc-C4nQFcTX.mjs} +220 -282
  25. package/dist/{guide-DwmaD_Wa.mjs → guide-BPBlh9ML.mjs} +512 -73
  26. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-i9ogHt4v.mjs} +2 -2
  27. package/dist/{idle--jn7pBNe.mjs → idle-S3tFZ53_.mjs} +7 -6
  28. package/dist/{in-use-CNIWU-r-.mjs → in-use-D9XYEgFD.mjs} +7 -7
  29. package/dist/{ios-CbdqTqOc.mjs → ios-CDzWL1mj.mjs} +302 -88
  30. package/dist/{ios-COGcGEcN.mjs → ios-CZWOHTTc.mjs} +24 -19
  31. package/dist/{ios-device-DNjPXBnb.mjs → ios-device-D9cLxVr5.mjs} +1 -1
  32. package/dist/{launch-verify-BhgkYAws.mjs → launch-verify-B2oR_kFN.mjs} +2 -2
  33. package/dist/logs-SzuYudOj.mjs +485 -0
  34. package/dist/merge-state-ytFQCeh4.mjs +262 -0
  35. package/dist/{metro-BWLOJ_Wr.mjs → metro-Dzo30hvJ.mjs} +1 -1
  36. package/dist/{named-ports-DiTfSz6p.mjs → named-ports-Dn1o61D9.mjs} +3 -3
  37. package/dist/{ownership-BfPNRf3-.mjs → ownership-B8d4kXBx.mjs} +49 -16
  38. package/dist/{ownership-D2xbLVoz.mjs → ownership-Dqnrjrio.mjs} +2 -2
  39. package/dist/{ports-CLSSFOuz.mjs → ports-Dp_QTwTC.mjs} +3 -3
  40. package/dist/{project-BXZzOABt.mjs → project-B8rbutVC.mjs} +4 -72
  41. package/dist/{reload-CymyF6Kk.mjs → reload-nocmx8KB.mjs} +9 -9
  42. package/dist/remote-cache-BRsRZru5.mjs +526 -0
  43. package/dist/{server-bare-DVbb7kXH.mjs → server-bare-Gn3ssSdK.mjs} +1 -1
  44. package/dist/{server-expo-BvOCeakp.mjs → server-expo-CeT1nIIk.mjs} +2 -2
  45. package/dist/server-expo-eWi9Coof.mjs +2 -0
  46. package/dist/{settings-DOugPlPz.mjs → settings-BlyDn2Oz.mjs} +14 -5
  47. package/dist/{settings-EWYNwe72.mjs → settings-bE-Gu3Ti.mjs} +23 -11
  48. package/dist/settings.schema.json +73 -1
  49. package/dist/{simslim-DZUxzsjq.mjs → simslim-COtEEP7x.mjs} +152 -96
  50. package/dist/{slot-launch-Dk4oTREM.mjs → slot-launch-DIACGEfH.mjs} +4 -4
  51. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-PT8aQpCa.mjs} +25 -2
  52. package/dist/{start-qWapeB4W.mjs → start-BFstVBlx.mjs} +1 -1
  53. package/dist/{start-RbmFPeZ7.mjs → start-BboE1HIS.mjs} +108 -17
  54. package/dist/{state-WGdIbi_d.mjs → state-DcDgzfu3.mjs} +15 -3
  55. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  56. package/dist/{stats-DBYJ5bxL.mjs → stats-DiCtmluY.mjs} +4 -4
  57. package/dist/{status-Dr4fRkym.mjs → status-BUASYyy_.mjs} +92 -33
  58. package/dist/{status-Zp8kvexe.mjs → status-DI9Tjo7T.mjs} +255 -38
  59. package/dist/stim-desktop-D0xhpZt8.mjs +29 -0
  60. package/dist/stop-BI7QY76M.mjs +3 -0
  61. package/dist/{stop-DcEv5sk-.mjs → stop-Bn6v-GvS.mjs} +205 -37
  62. package/dist/supervisor-run.mjs +11 -11
  63. package/dist/{workspace-process-lock-BXA96myD.mjs → workspace-process-lock-BvvJTfo_.mjs} +10 -5
  64. package/dist/{worktree-DEG2o9F8.mjs → worktree-B5sTRskL.mjs} +1 -1
  65. package/dist/{worktree-Bd4dRvx7.mjs → worktree-ChDL5fO1.mjs} +17 -16
  66. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  67. package/dist/{xcode-B2tBkf0m.mjs → xcode-BzplpQzc.mjs} +7 -7
  68. package/package.json +5 -5
  69. package/dist/budget-nDCEwtJv.mjs +0 -620
  70. package/dist/logs-MARIy6m9.mjs +0 -209
  71. package/dist/server-expo-DBVTdult.mjs +0 -2
  72. package/dist/stop-DI4BG4Os.mjs +0 -3
@@ -1,36 +1,37 @@
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";
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
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
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";
4
+ import { O as workspaceDir, b as emulatorLogFile, c as signalProcessTree, h as recordWorkspaceUse, i as pidExists, j as workspaceLogsDir, m as readWorkspaceState$1, s as resolveProjectMetro, y as writeWorkspaceState } from "./metro-Dzo30hvJ.mjs";
5
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-DeBQSVtR.mjs";
6
+ import { i as findProjectRoot, n as appProjectProblem, r as detectIsExpo, s as projectShortcut, t as NO_PROJECT_REFUSAL } from "./project-B8rbutVC.mjs";
7
+ import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DcDgzfu3.mjs";
9
8
  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-BfPNRf3-.mjs";
16
- import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-CNIWU-r-.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-powUHdbK.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-Cfmqr3TQ.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-BhgkYAws.mjs";
22
- import { d as isEasBuildFailure, f as resolveEasDevelopmentBuild, o as unknownAndroidSystemImageRefusal, r as checkDeviceCapacity } from "./simslim-DZUxzsjq.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-BBWLAr55.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-DExzPiOa.mjs";
25
- import { t as budgetGate } from "./budget-nDCEwtJv.mjs";
26
- import { f as spawnEntry, s as startDevServer } from "./start-RbmFPeZ7.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-Dk4oTREM.mjs";
31
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-xxWKNO3-.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-BWCj5Vh-.mjs";
10
+ import { A as resolveBuild, C as filesystemBuildCapability, D as providerDownloadPath, E as prepareProviderDownloadDir, F as untrackedNativeFiles, I as assetDiffReason, L as captureAssetManifest, M as storedAssetManifest, O as providerUploadOutcome, P as untrackedMissLine, R as compareAssetManifests, T as fingerprintProject, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as storeBuild, k as refingerprintAfterMutation, m as acquireBuildLock, v as waitForSharedBuild, w as fingerprintDiffRecord, z as readAssetManifest } from "./device-lease-B9LAXIUH.mjs";
11
+ import { B as pickDefaultSystemImage, D as listAvdDeviceProfiles, E as listAdbDevices, G as resolvePhysicalDevice, H as profileNeedsFoldFeature, J as systemImageSupportsFold, N as ownedAvdDeviceProfile, O as listAvds, P as ownedAvdDirectory, R as ownedAvdSystemImage, S as findBuildTool, U as resetAdoptedAvd, V as probeEmulatorSerial, W as resolveOwnedAvdSerial, X as waitForBoot, a as androidHome, b as emulatorFailureRemedy, c as androidSystemImageAbi, i as androidDeviceAbi, j as memoizeEmulatorProbe, k as listInstalledSystemImages, l as androidToolCwd, o as androidPoolCandidates, r as androidBuildToolName, s as androidPoolNoCandidatesRefusal, t as DEFAULT_AVD_DEVICE_PROFILE, tt as configuredAndroidEmulatorApp, w as hostSystemImageArch, x as extractEmulatorFailure, y as emulatorDiskSpaceRemedy, z as physicalDeviceModel } from "./android-75Sk2rIR.mjs";
12
+ 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-BlyDn2Oz.mjs";
13
+ import { y as parkedMaxSetting } from "./ownership-B8d4kXBx.mjs";
14
+ import { c as startBuildProgress, n as NO_BUILD_PROGRESS } from "./build-progress-h60GPV27.mjs";
15
+ import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-D9XYEgFD.mjs";
16
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-Dqnrjrio.mjs";
17
+ import { t as cleanLine } from "./server-expo-CeT1nIIk.mjs";
18
+ 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-BRsRZru5.mjs";
19
+ 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-C0_o-tJH.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-CsZi0SAT.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-B2oR_kFN.mjs";
22
+ import { f as isEasBuildFailure, m as resolveEasDevelopmentBuild, o as unknownAndroidDeviceProfileRefusal, p as planEasDevelopmentBuild, r as checkDeviceCapacity, s as unknownAndroidSystemImageRefusal } from "./simslim-COtEEP7x.mjs";
23
+ import { f as spawnEntry, m as detectAppIds, p as detectAndroidPackage, s as startDevServer } from "./start-BboE1HIS.mjs";
24
+ 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-BoezJd-c.mjs";
25
+ import { C as ensureDevServer, E as launchOutcomeRecord, O as warmMetro, T as isPhysicalDeviceRequest, _ as staleNativeDirRefusal, a as refusePlan, b as AvdBootError, c as skippedMissReason, f as detectEntryFile, g as runPrebuild, h as recordPrebuild, i as printPlan, l as devClientScheme, m as planPrebuild, n as planFlagRefusal, o as explainBuildMiss, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as pickDevClientScheme, v as ensureBooted, w as ensureWorkspaceStorageSafely, x as AvdRecoveryError, y as ensureOwnedDevice } from "./build-plan-CqntbR7b.mjs";
26
+ import { t as budgetGate } from "./budget-DbzW5VTD.mjs";
27
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-D8rUTPHd.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-DzLPXiEn.mjs";
29
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-BzplpQzc.mjs";
30
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-DIACGEfH.mjs";
31
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-CCxjn31V.mjs";
32
32
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
33
33
  import { InvalidArgumentError } from "commander";
34
+ import { LAST_BUILD_KEYS } from "@stim-cli/core/state";
34
35
  import { basename, dirname, isAbsolute, join, relative } from "node:path";
35
36
  import chalk from "chalk";
36
37
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
@@ -436,6 +437,51 @@ function systemImageRefusal({ slot, flag, resolved, physical, remoteBackend, lis
436
437
  remedy: refusal.remedy
437
438
  } : null;
438
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
+ }
439
485
  function isReleaseVariant(variant) {
440
486
  return typeof variant === "string" && /release$/i.test(variant.trim());
441
487
  }
@@ -723,11 +769,16 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
723
769
  hash = computed?.hash ?? "";
724
770
  fingerprintSources = computed?.sources ?? [];
725
771
  } catch (err) {
726
- phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${err?.message || err}`, "Fix the @expo/fingerprint error above, then retry.");
772
+ const message = String(err?.message || err);
773
+ record.cacheSkipped = !useBuildCache;
774
+ record.missReason = fingerprintErrorMissReason(message);
775
+ phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${message}`, "Fix the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
727
776
  return false;
728
777
  }
729
778
  if (!hash) {
730
- phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.");
779
+ record.cacheSkipped = !useBuildCache;
780
+ record.missReason = fingerprintErrorMissReason("no hash");
781
+ phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.", { lastBuildStatus: true });
731
782
  return false;
732
783
  }
733
784
  record.fingerprint = hash;
@@ -757,29 +808,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
757
808
  const cached = found?.tier === "local" ? found.path : null;
758
809
  record.cacheHit = cached ? "local" : false;
759
810
  record.cacheSkipped = !useBuildCache;
760
- let missDiff = "";
761
- let missUntracked = null;
762
- if (!cached) {
763
- const lastBuild = readState(root)?.lastBuild ?? null;
764
- const miss = describeFingerprintMiss({
765
- platform: PLATFORM,
766
- current: {
767
- hash,
768
- sources: fingerprintSources
769
- },
770
- lastBuild
771
- });
772
- if (miss) {
773
- missDiff = fingerprintDiffSuffix(miss.changed);
774
- writer.write(fingerprintDiffRecord({
775
- changed: miss.changed,
776
- previousHash: miss.previousHash,
777
- hash
778
- }));
779
- } else if (useBuildCache) missUntracked = untrackedMissLine(untracked({ projectRoot: root }));
780
- }
781
- phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}${missDiff}`);
782
- if (missUntracked) phase("fingerprint", chalk.dim(missUntracked));
811
+ phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
783
812
  if (found?.tier === "provider") {
784
813
  record.cacheHit = "remote";
785
814
  providerName = found.providerName ?? null;
@@ -909,6 +938,34 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
909
938
  }
910
939
  }
911
940
  }
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({
958
+ changed: explained.changedNames,
959
+ previousHash: explained.previousHash,
960
+ hash: current.hash
961
+ }));
962
+ }
963
+ phase("cache", `miss: ${record.missReason.summary}`);
964
+ if (record.missReason.kind === "no-baseline") {
965
+ const line = untrackedMissLine(untracked({ projectRoot: root }));
966
+ if (line) phase("fingerprint", chalk.dim(line));
967
+ }
968
+ }
912
969
  async function buildArtifact() {
913
970
  if (!apkPath) try {
914
971
  if (maxBuilds) try {
@@ -926,6 +983,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
926
983
  }
927
984
  phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
928
985
  }
986
+ const rekeyedBy = [];
929
987
  const prebuildPlan = planPrebuildFor(root, PLATFORM, {
930
988
  isExpo,
931
989
  fingerprint: hash,
@@ -962,6 +1020,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
962
1020
  });
963
1021
  if (after) recordPrebuild(root, PLATFORM, after.hash);
964
1022
  if (after?.moved) {
1023
+ rekeyedBy.push("prebuild");
965
1024
  storeHash = after.hash;
966
1025
  storeSources = after.sources;
967
1026
  storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
@@ -984,6 +1043,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
984
1043
  }
985
1044
  }
986
1045
  if (!apkPath) {
1046
+ explainMiss(rekeyedBy);
987
1047
  step("compile");
988
1048
  phase("build", `compiling ${variant || "debug"} with Gradle`);
989
1049
  const built = await build({
@@ -1115,7 +1175,7 @@ function tail(lines, n = FALLBACK_LINES) {
1115
1175
  }
1116
1176
  //#endregion
1117
1177
  //#region src/commands/android/result.ts
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 }) {
1178
+ 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 }) {
1119
1179
  return {
1120
1180
  platform: PLATFORM,
1121
1181
  ...slot && slot !== "default" ? { slot } : {},
@@ -1123,6 +1183,7 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1123
1183
  avdName: avdName ?? null,
1124
1184
  deviceName: deviceName ?? avdName ?? null,
1125
1185
  systemImage: systemImage ?? null,
1186
+ deviceProfile: deviceProfile ?? null,
1126
1187
  fingerprint: fingerprint ?? null,
1127
1188
  cacheKey: cacheKey ?? null,
1128
1189
  variant: variant ?? null,
@@ -1147,7 +1208,7 @@ function androidFacts({ slot, serial, avdName = null, deviceName = null, systemI
1147
1208
  ...devServer ? { devServer } : {}
1148
1209
  };
1149
1210
  }
1150
- function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null }) {
1211
+ function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false, durationMs, appPath, bundleId, startedAt, status, errorCode = null, avdName = null, deviceName = null, missReason = null }) {
1151
1212
  const record = {
1152
1213
  platform: PLATFORM,
1153
1214
  avdName: avdName ?? null,
@@ -1163,6 +1224,7 @@ function lastBuildRecord({ fingerprint, cacheKey, cacheHit, cacheSkipped = false
1163
1224
  status
1164
1225
  };
1165
1226
  if (errorCode) record.errorCode = errorCode;
1227
+ if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
1166
1228
  return record;
1167
1229
  }
1168
1230
  async function finishAndroidUpload(uploadPending, remote, phase) {
@@ -1195,6 +1257,7 @@ function reportAndroidResult({ slot, json, useBuildCache, variant, release, metr
1195
1257
  avdName: record.avdName,
1196
1258
  deviceName: record.deviceName,
1197
1259
  systemImage: record.systemImage,
1260
+ deviceProfile: record.deviceProfile,
1198
1261
  debugHttpHost: launched.debugHttpHost ?? null,
1199
1262
  debugHttpHostNote: launched.debugHttpHostNote ?? null,
1200
1263
  devClientUrl: launched.devClientUrl ?? null,
@@ -1252,7 +1315,10 @@ function persistLastBuild({ writeState, root, record, startedAt, durationMs, sta
1252
1315
  errorCode
1253
1316
  });
1254
1317
  try {
1255
- writeState(root, { lastBuild });
1318
+ writeState(root, {
1319
+ lastBuild,
1320
+ [LAST_BUILD_KEYS[PLATFORM]]: lastBuild
1321
+ });
1256
1322
  } catch (err) {
1257
1323
  out(phaseLine("state", chalk.yellow(`could not record lastBuild: ${err?.message || err}`)));
1258
1324
  }
@@ -1471,8 +1537,8 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1471
1537
  phase("device", `reopen agent-device on ${resolved.serial}`);
1472
1538
  }
1473
1539
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
1474
- withConfigLock(() => {
1475
- const current = deviceSlotPlatforms(loadConfig()?.projects?.[root], slot)?.android;
1540
+ withConfigLock$1(() => {
1541
+ const current = deviceSlotPlatforms(loadConfig$1()?.projects?.[root], slot)?.android;
1476
1542
  if (!current?.owned || current.avdName !== device.avdName) throw new Error("The owned emulator assignment changed before installation.");
1477
1543
  if (current.consolePort !== consolePort) setDevice(root, PLATFORM, {
1478
1544
  ...current,
@@ -1558,8 +1624,8 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1558
1624
  return fail(installed.code || "STIM_INSTALL_FAILED", installed.reason, installRemedy, { lastBuildStatus: true });
1559
1625
  }
1560
1626
  if (adopting) try {
1561
- withConfigLock(() => {
1562
- const config = loadConfig();
1627
+ withConfigLock$1(() => {
1628
+ const config = loadConfig$1();
1563
1629
  const current = deviceSlotPlatforms(config?.projects?.[root], slot)?.android;
1564
1630
  if (!config || !current || current.avdName !== device.avdName) throw new Error("The adopted emulator assignment changed during installation.");
1565
1631
  delete current.adoptionPending;
@@ -1817,6 +1883,16 @@ function diagnoseBootFailure(booted, logFile, runCommand, physical) {
1817
1883
  }
1818
1884
  //#endregion
1819
1885
  //#region src/commands/android/plan.ts
1886
+ function slotOwnedAvd(projectPath, slot) {
1887
+ const android = deviceSlotPlatforms(getProject(projectPath), slot)?.android;
1888
+ if (!android?.owned || !android.avdName || android.setupIncomplete) return null;
1889
+ const systemImage = ownedAvdSystemImage(android.avdName);
1890
+ return systemImage ? {
1891
+ avdName: android.avdName,
1892
+ systemImage,
1893
+ deviceProfile: ownedAvdDeviceProfile(android.avdName)
1894
+ } : null;
1895
+ }
1820
1896
  function fail(code, message, remedy, { lines = [] } = {}) {
1821
1897
  return {
1822
1898
  ok: false,
@@ -1850,7 +1926,7 @@ function androidCompilerCache({ root, optimizations, settingsContext }) {
1850
1926
  })}`
1851
1927
  };
1852
1928
  }
1853
- function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, variant: variantFlag, systemImage: systemImageFlag, device: deviceFlag, wait: waitFlag, waitConflict, remote: commandRemoteBackend, buildCache: requestedBuildCache }, { warn, resolveCompilerCache = androidCompilerCache, resolveCacheProvider = resolveCacheProviderConfig, validateAvdConfig = androidAvdConfigSettingError, readFlavors = readProductFlavors, detectExpo = detectIsExpo, listSystemImages = listInstalledSystemImages, parkedLimit = parkedMaxSetting }) {
1929
+ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, variant: variantFlag, systemImage: systemImageFlag, deviceProfile: deviceProfileFlag, device: deviceFlag, wait: waitFlag, waitConflict, remote: commandRemoteBackend, buildCache: requestedBuildCache }, { warn, resolveCompilerCache = androidCompilerCache, resolveCacheProvider = resolveCacheProviderConfig, validateAvdConfig = androidAvdConfigSettingError, readFlavors = readProductFlavors, detectExpo = detectIsExpo, listSystemImages = listInstalledSystemImages, listDeviceProfiles = listAvdDeviceProfiles, parkedLimit = parkedMaxSetting, supportsFold = systemImageSupportsFold, ownedAvd = slotOwnedAvd }) {
1854
1930
  const root = settingsContext.projectPath;
1855
1931
  const [shapeError, ...moreShapeErrors] = [parkedLimit("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
1856
1932
  if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
@@ -1878,6 +1954,7 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1878
1954
  const avdConfigError = validateAvdConfig(settings, root);
1879
1955
  if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1880
1956
  const systemImage = resolveSystemImage(systemImageFlag, settings);
1957
+ const deviceProfile = resolveDeviceProfile(deviceProfileFlag, settings);
1881
1958
  const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1882
1959
  const flavorRefusal = productFlavorRefusal({
1883
1960
  flavors: readFlavors(root),
@@ -1907,6 +1984,29 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1907
1984
  listImages: listSystemImages
1908
1985
  });
1909
1986
  if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
1987
+ const profileRefusal = deviceProfileRefusal({
1988
+ flag: deviceProfileFlag,
1989
+ resolved: deviceProfile,
1990
+ physical,
1991
+ remoteBackend,
1992
+ listProfiles: listDeviceProfiles
1993
+ });
1994
+ if (profileRefusal) return fail(profileRefusal.code, profileRefusal.message, profileRefusal.remedy);
1995
+ if (!physical && !remoteBackend) {
1996
+ const flagImage = typeof systemImageFlag === "string" && systemImageFlag.trim() ? systemImageFlag.trim() : null;
1997
+ const existing = ownedAvd(root, slot);
1998
+ const profile = deviceProfile ?? existing?.deviceProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
1999
+ const image = () => flagImage ?? existing?.systemImage ?? systemImage ?? pickDefaultSystemImage(listSystemImages())?.pkg ?? null;
2000
+ const checked = profileNeedsFoldFeature(profile) ? image() : null;
2001
+ const foldRefusal = checked !== null && foldableImageRefusal({
2002
+ profile,
2003
+ image: checked,
2004
+ avdName: existing && existing.systemImage === checked && existing.deviceProfile === profile ? existing.avdName : null,
2005
+ images: listSystemImages,
2006
+ supportsFold
2007
+ });
2008
+ if (foldRefusal) return fail(foldRefusal.code, foldRefusal.message, foldRefusal.remedy);
2009
+ }
1910
2010
  const target = physical ? {
1911
2011
  kind: "physical",
1912
2012
  serial: typeof deviceFlag === "string" ? deviceFlag : null,
@@ -1917,10 +2017,12 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1917
2017
  } : remoteBackend ? {
1918
2018
  kind: "remote",
1919
2019
  backend: remoteBackend,
1920
- systemImage
2020
+ systemImage,
2021
+ deviceProfile
1921
2022
  } : {
1922
2023
  kind: "emulator",
1923
- systemImage
2024
+ systemImage,
2025
+ deviceProfile
1924
2026
  };
1925
2027
  return {
1926
2028
  ok: true,
@@ -1944,21 +2046,213 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1944
2046
  };
1945
2047
  }
1946
2048
  //#endregion
2049
+ //#region src/commands/android/next-build.ts
2050
+ const DEFAULT_PLAN_DEPS = {
2051
+ findRoot: findProjectRoot,
2052
+ fingerprint: fingerprintProject,
2053
+ listSystemImages: listInstalledSystemImages,
2054
+ avdSystemImage: ownedAvdSystemImage,
2055
+ avdDirectory: ownedAvdDirectory,
2056
+ listAvds,
2057
+ loadCacheProvider,
2058
+ loadProjectProvider,
2059
+ checkEasAuth,
2060
+ resolveRemote,
2061
+ planPrebuild
2062
+ };
2063
+ function note(line) {
2064
+ console.error(line);
2065
+ }
2066
+ function runOnlyFlag(opts) {
2067
+ if (isPhysicalDeviceRequest(opts.device)) return "--device";
2068
+ if (opts.remote) return "--remote";
2069
+ if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
2070
+ if (opts.metroCheck === false) return "--no-metro-check";
2071
+ return null;
2072
+ }
2073
+ function emulatorImage(root, slot, requested, deps) {
2074
+ const record = deviceSlotPlatforms(getProject(root), slot)?.android;
2075
+ if (record?.avdName && !record.setupIncomplete) {
2076
+ if (record.owned && deps.avdDirectory(record.avdName)) return { systemImage: deps.avdSystemImage(record.avdName) };
2077
+ if (!record.owned && avdListed(record.avdName, deps.listAvds)) return { systemImage: null };
2078
+ }
2079
+ let images;
2080
+ try {
2081
+ images = deps.listSystemImages();
2082
+ } catch (error) {
2083
+ return { refusal: {
2084
+ code: NO_DEVICE,
2085
+ message: `Could not read the installed Android system images: ${error?.message || error}`,
2086
+ remedy: "Check that ANDROID_HOME points at a readable SDK, then try again."
2087
+ } };
2088
+ }
2089
+ const picked = pickDefaultSystemImage(images, requested ? { systemImage: requested } : {});
2090
+ if (picked) return { systemImage: picked.pkg };
2091
+ const arch = hostSystemImageArch();
2092
+ return { refusal: {
2093
+ code: NO_DEVICE,
2094
+ message: `No ${arch} Android system image is installed, so the emulator build's ABI and cache key are unknown.`,
2095
+ remedy: `Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}", then try again.`
2096
+ } };
2097
+ }
2098
+ function avdListed(avdName, list) {
2099
+ try {
2100
+ return list({ timeoutMs: 1e4 }).includes(avdName);
2101
+ } catch {
2102
+ return false;
2103
+ }
2104
+ }
2105
+ async function planAndroid(opts, overrides = {}) {
2106
+ const deps = {
2107
+ ...DEFAULT_PLAN_DEPS,
2108
+ ...overrides
2109
+ };
2110
+ const json = Boolean(opts.json);
2111
+ const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
2112
+ const flag = runOnlyFlag(opts);
2113
+ if (flag) return refuse(planFlagRefusal(flag));
2114
+ const root = deps.findRoot(process.cwd());
2115
+ if (!root) return refuse(NO_PROJECT_REFUSAL);
2116
+ const projectProblem = appProjectProblem(root);
2117
+ if (projectProblem) return refuse({
2118
+ code: "STIM_NO_PROJECT",
2119
+ ...projectProblem
2120
+ });
2121
+ const slot = validateDeviceSlot(opts.slot);
2122
+ const settingsContext = {
2123
+ projectPath: root,
2124
+ gitCommonDir: gitCommonDir(root),
2125
+ repoRoot: repoRoot(root)
2126
+ };
2127
+ const planned = resolveAndroidRunPlan({
2128
+ settings: resolveSettings(settingsContext),
2129
+ settingsContext,
2130
+ slot,
2131
+ easProfile: opts.easProfile,
2132
+ variant: opts.variant ?? null,
2133
+ systemImage: opts.systemImage ?? null,
2134
+ device: null,
2135
+ wait: void 0,
2136
+ waitConflict: false,
2137
+ remote: null,
2138
+ buildCache: opts.buildCache !== false
2139
+ }, {
2140
+ warn: (label, message) => note(phaseLine(label, chalk.yellow(message))),
2141
+ resolveCompilerCache: ({ optimizations }) => ({
2142
+ cas: null,
2143
+ optimizations,
2144
+ warning: null
2145
+ }),
2146
+ listSystemImages: deps.listSystemImages
2147
+ });
2148
+ if (!planned.ok) return refuse(planned, planned.lines);
2149
+ const { build, target, isExpo, cacheProviderConfig } = planned.plan;
2150
+ const planTarget = {
2151
+ platform: PLATFORM,
2152
+ slot,
2153
+ root,
2154
+ projectKey: statsProjectKey({
2155
+ root,
2156
+ commonDir: settingsContext.gitCommonDir,
2157
+ repoRoot: settingsContext.repoRoot
2158
+ })
2159
+ };
2160
+ if (opts.easProfile !== void 0) {
2161
+ const eas = await planEasDevelopmentBuild({
2162
+ root,
2163
+ platform: PLATFORM,
2164
+ profile: opts.easProfile,
2165
+ note,
2166
+ isExpo,
2167
+ selectors: [opts.variant],
2168
+ buildCache: opts.buildCache
2169
+ });
2170
+ if (!eas.ok) return refuse(eas);
2171
+ return printPlan(planPayload(planTarget, {
2172
+ fingerprint: eas.fingerprint,
2173
+ cacheKey: eas.cacheKey,
2174
+ cacheHit: eas.buildId ? "remote" : false,
2175
+ provider: "eas",
2176
+ cacheSkipped: false,
2177
+ prebuild: null,
2178
+ refusal: eas.missing
2179
+ }), json);
2180
+ }
2181
+ if (target.kind !== "emulator") {
2182
+ if (target.kind === "physical") return refuse(planFlagRefusal("--device"));
2183
+ return refuse({
2184
+ code: "STIM_BAD_ARG",
2185
+ message: `android.remote routes this workspace's runs to a ${target.backend} device, whose ABI --plan cannot read without a session.`,
2186
+ remedy: "Run `stim android` to build for the remote device, or unset android.remote to plan the owned emulator."
2187
+ });
2188
+ }
2189
+ if (build.compilerCache === "cas") return refuse({
2190
+ code: "STIM_BAD_ARG",
2191
+ message: "The experimental Android compiler CAS keys builds by a toolchain Stim sets up, which --plan does not.",
2192
+ remedy: "Run `stim android` to build with the CAS, or set optimizations.android.compilerCache to ccache."
2193
+ });
2194
+ const image = build.targetAbiOnly && !build.release ? emulatorImage(root, slot, target.systemImage, deps) : { systemImage: null };
2195
+ if ("refusal" in image) return refuse(image.refusal);
2196
+ const { abi, runOptions, remoteRunOptions } = androidBuildOptions({
2197
+ release: build.release,
2198
+ physical: false,
2199
+ device: { systemImage: image.systemImage },
2200
+ variant: build.variant,
2201
+ deviceAbi: () => null,
2202
+ buildProfile: build.profile,
2203
+ targetAbiOnly: build.targetAbiOnly
2204
+ });
2205
+ let fingerprint;
2206
+ try {
2207
+ fingerprint = await deps.fingerprint(root, { platform: PLATFORM });
2208
+ } catch (error) {
2209
+ return refuse({
2210
+ code: NO_FINGERPRINT,
2211
+ message: `@expo/fingerprint could not fingerprint ${root}: ${error?.message || error}`,
2212
+ remedy: "Fix the @expo/fingerprint error above, then retry."
2213
+ });
2214
+ }
2215
+ if (!fingerprint?.hash) return refuse({
2216
+ code: NO_FINGERPRINT,
2217
+ message: `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`,
2218
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
2219
+ });
2220
+ const plan = await planCachedBuild({
2221
+ ...planTarget,
2222
+ isExpo,
2223
+ fingerprint: fingerprint.hash,
2224
+ sources: fingerprint.sources ?? [],
2225
+ cacheKey: buildCacheKey(PLATFORM, fingerprint.hash, runOptions),
2226
+ cachePolicy: build.cache,
2227
+ providerConfig: cacheProviderConfig,
2228
+ expoRemote: abi || build.profile || !build.cache.remote ? null : { runOptions: remoteRunOptions }
2229
+ }, {
2230
+ ...deps,
2231
+ note
2232
+ });
2233
+ printPlan(plan, json);
2234
+ }
2235
+ //#endregion
1947
2236
  //#region src/commands/android.ts
1948
2237
  function androidCommand(program) {
1949
2238
  registerAndroid(program);
1950
2239
  }
1951
2240
  function registerAndroid(program) {
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) => {
2241
+ 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) => {
1953
2242
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1954
2243
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1955
2244
  }).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) => {
2245
+ if (opts.plan) return planAndroid(opts);
1956
2246
  const root = findProjectRoot(process.cwd());
1957
2247
  if (!root) {
1958
2248
  refuseNoProject({ json: Boolean(opts.json) });
1959
2249
  return;
1960
2250
  }
1961
- if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", async (claim) => {
2251
+ if (!(await withNativeBuildRun(root, {
2252
+ command: "android",
2253
+ platform: "android",
2254
+ slot: opts.slot ?? "default"
2255
+ }, async (claim) => {
1962
2256
  recordWorkspaceUse(root);
1963
2257
  const progress = startBuildProgress({
1964
2258
  root,
@@ -1977,6 +2271,7 @@ function registerAndroid(program) {
1977
2271
  useBuildCache: opts.buildCache !== false,
1978
2272
  variant: opts.variant ?? null,
1979
2273
  systemImage: opts.systemImage ?? null,
2274
+ deviceProfile: opts.deviceProfile ?? null,
1980
2275
  remoteDevice: opts.remote ?? null,
1981
2276
  device: opts.device ?? null,
1982
2277
  wait: opts.wait,
@@ -1986,14 +2281,10 @@ function registerAndroid(program) {
1986
2281
  } finally {
1987
2282
  progress.clear();
1988
2283
  }
1989
- }, {
1990
- external: true,
1991
- waitMs: 18e5,
1992
- declareSpawns: true
1993
- })).ok) process.exit(1);
2284
+ }, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) })).ok) process.exit(runCancellation() ? 130 : 1);
1994
2285
  });
1995
2286
  }
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) } = {}) {
2287
+ 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, recordStats = recordRunStats, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
1997
2288
  return {
1998
2289
  root,
1999
2290
  json,
@@ -2011,6 +2302,7 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2011
2302
  resolveEasBuild,
2012
2303
  variantFlag,
2013
2304
  systemImageFlag,
2305
+ deviceProfileFlag,
2014
2306
  deviceFlag,
2015
2307
  waitFlag,
2016
2308
  waitConflict,
@@ -2029,6 +2321,7 @@ function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRem
2029
2321
  releaseSlot,
2030
2322
  ensureDevice,
2031
2323
  listSystemImages,
2324
+ listDeviceProfiles,
2032
2325
  ensureDeviceBooted,
2033
2326
  resolveAvdSerial,
2034
2327
  waitForDeviceBoot,
@@ -2130,7 +2423,7 @@ function avdSetupFailure(error, root, logFile) {
2130
2423
  };
2131
2424
  }
2132
2425
  async function runAndroid(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);
2426
+ 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, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2134
2427
  const progress = options.progress ?? NO_BUILD_PROGRESS;
2135
2428
  const slot = validateDeviceSlot(options.slot);
2136
2429
  const started = now();
@@ -2203,6 +2496,11 @@ async function runAndroid(options = {}) {
2203
2496
  let ccacheActivity = CCACHE_NOT_RUN;
2204
2497
  let reclaimed = [];
2205
2498
  const fail = (code, message, remedy, { lastBuildStatus = false, diagnostics = [], lines = [], logPath = null, lease } = {}) => {
2499
+ const cancellation = cancelledFailure(PLATFORM, {
2500
+ code,
2501
+ message
2502
+ });
2503
+ if (cancellation) ({code, message, remedy, lines} = cancellation);
2206
2504
  if (lastBuildStatus) persistLastBuild({
2207
2505
  writeState,
2208
2506
  root,
@@ -2266,6 +2564,7 @@ async function runAndroid(options = {}) {
2266
2564
  easProfile,
2267
2565
  variant: variantFlag,
2268
2566
  systemImage: systemImageFlag,
2567
+ deviceProfile: deviceProfileFlag,
2269
2568
  device: deviceFlag,
2270
2569
  wait: waitFlag,
2271
2570
  waitConflict,
@@ -2274,6 +2573,7 @@ async function runAndroid(options = {}) {
2274
2573
  }, {
2275
2574
  resolveCacheProvider,
2276
2575
  listSystemImages,
2576
+ listDeviceProfiles,
2277
2577
  warn: (label, message) => out(phaseLine(label, chalk.yellow(message)))
2278
2578
  });
2279
2579
  if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
@@ -2307,10 +2607,11 @@ async function runAndroid(options = {}) {
2307
2607
  buildCache: requestedBuildCache
2308
2608
  });
2309
2609
  if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
2310
- let androidPackage = detectAndroidPackage(root);
2610
+ const appIds = detectAppIds(root);
2611
+ let androidPackage = appIds.androidPackage;
2311
2612
  record.bundleId = androidPackage;
2312
2613
  const registerProject = () => upsertProject(root, {
2313
- bundleId: detectBundleId(root) ?? void 0,
2614
+ bundleId: appIds.bundleId ?? void 0,
2314
2615
  androidPackage: androidPackage ?? void 0,
2315
2616
  isExpo
2316
2617
  });
@@ -2373,6 +2674,7 @@ async function runAndroid(options = {}) {
2373
2674
  let device;
2374
2675
  let bootDuration = "";
2375
2676
  let bootPromise;
2677
+ let startRemoteBoot = null;
2376
2678
  if (target.kind === "physical" && !target.serial) {
2377
2679
  const pooled = await pooledAndroidDevice({
2378
2680
  root,
@@ -2404,6 +2706,11 @@ async function runAndroid(options = {}) {
2404
2706
  serial: resolved.serial
2405
2707
  });
2406
2708
  } else {
2709
+ if (!remoteDevice) try {
2710
+ configuredAndroidEmulatorApp();
2711
+ } catch (err) {
2712
+ return fail("STIM_BAD_ARG", err.message, "Run `stim settings set androidEmulatorApp emulator` or `stim settings set androidEmulatorApp stim-desktop`.");
2713
+ }
2407
2714
  const capacity = checkCapacity({
2408
2715
  platform: PLATFORM,
2409
2716
  project,
@@ -2418,7 +2725,11 @@ async function runAndroid(options = {}) {
2418
2725
  projectPath: root,
2419
2726
  settingsRoot,
2420
2727
  settings,
2421
- flags: { systemImage: target.systemImage },
2728
+ flags: {
2729
+ systemImage: target.systemImage,
2730
+ systemImageFlag: systemImageFlag?.trim() || null,
2731
+ deviceProfile: target.deviceProfile
2732
+ },
2422
2733
  note: out,
2423
2734
  out,
2424
2735
  logFile: emuLog
@@ -2429,7 +2740,6 @@ async function runAndroid(options = {}) {
2429
2740
  }
2430
2741
  const prepareMs = prepare();
2431
2742
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
2432
- const bootTimer = stepTimer(now);
2433
2743
  const boot = () => Promise.resolve(ensureDeviceBooted({
2434
2744
  platform: PLATFORM,
2435
2745
  device,
@@ -2441,41 +2751,32 @@ async function runAndroid(options = {}) {
2441
2751
  reason: String(e?.message || e),
2442
2752
  serial: void 0
2443
2753
  }));
2444
- bootPromise = (remoteDevice?.ctx.backend === "eas" ? ensureRemoteOwned({
2445
- root,
2446
- platform: PLATFORM,
2447
- sessionName: ownedSessionName(remoteDevice.ctx.label),
2448
- startedAt,
2449
- boot,
2450
- createdSessionId: remoteDevice.createdSessionId,
2451
- abandonCreatedSession: remoteDevice.abandonCreatedSession,
2452
- webPreviewUrl: remoteDevice.webPreviewUrl,
2453
- writeState,
2454
- register: registerProject
2455
- }) : boot()).then((result) => {
2456
- bootDuration = bootTimer();
2457
- return result;
2458
- });
2459
- }
2460
- if (remoteDevice) {
2461
- const booted = await bootPromise;
2462
- if (booted.failed) {
2463
- if (booted.code) return fail(booted.code, booted.reason ?? "The remote device did not boot.", booted.remedy ?? null);
2464
- const diag = noDeviceDiagnostic({
2465
- reason: booted.reason ?? "The remote device did not boot.",
2466
- logFile: emuLog,
2467
- remedy: "Run `stim status` to inspect the remote device, then retry the command.",
2468
- localEmulator: false
2469
- });
2470
- return fail(NO_DEVICE, diag.message, diag.remedy, {
2471
- lines: diag.lines,
2472
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
2754
+ const startBoot = () => {
2755
+ const bootTimer = stepTimer(now);
2756
+ return (remoteDevice?.ctx.backend === "eas" ? ensureRemoteOwned({
2757
+ root,
2758
+ platform: PLATFORM,
2759
+ sessionName: ownedSessionName(remoteDevice.ctx.label),
2760
+ startedAt: new Date(now()).toISOString(),
2761
+ boot,
2762
+ createdSessionId: remoteDevice.createdSessionId,
2763
+ abandonCreatedSession: remoteDevice.abandonCreatedSession,
2764
+ webPreviewUrl: remoteDevice.webPreviewUrl,
2765
+ writeState,
2766
+ register: registerProject,
2767
+ notice: (line) => out(chalk.dim(phaseLine("lock", line)))
2768
+ }) : boot()).then((result) => {
2769
+ bootDuration = bootTimer();
2770
+ return result;
2473
2771
  });
2474
- }
2772
+ };
2773
+ if (remoteDevice) startRemoteBoot = startBoot;
2774
+ else bootPromise = startBoot();
2475
2775
  }
2476
2776
  record.avdName = device.avdName ?? null;
2477
2777
  record.deviceName = device.deviceName ?? device.avdName ?? null;
2478
2778
  record.systemImage = device.systemImage;
2779
+ record.deviceProfile = device.deviceProfile;
2479
2780
  const runFromFingerprint = async () => {
2480
2781
  if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
2481
2782
  port: metroPort,
@@ -2542,6 +2843,25 @@ async function runAndroid(options = {}) {
2542
2843
  ccacheActivity = artifact.ccache;
2543
2844
  androidPackage = artifact.androidPackage;
2544
2845
  record.appPath = apkPath;
2846
+ if (runCancellation()) return fail("STIM_CANCELLED", "before install");
2847
+ if (startRemoteBoot) {
2848
+ progress.step("install");
2849
+ bootPromise = startRemoteBoot();
2850
+ const booted = await bootPromise;
2851
+ if (booted.failed) {
2852
+ if (booted.code) return fail(booted.code, booted.reason ?? "The remote device did not boot.", booted.remedy ?? null);
2853
+ const diag = noDeviceDiagnostic({
2854
+ reason: booted.reason ?? "The remote device did not boot.",
2855
+ logFile: emuLog,
2856
+ remedy: "Run `stim status` to inspect the remote device, then retry the command.",
2857
+ localEmulator: false
2858
+ });
2859
+ return fail(NO_DEVICE, diag.message, diag.remedy, {
2860
+ lines: diag.lines,
2861
+ logPath: diag.logPath ? displayPath(root, diag.logPath) : null
2862
+ });
2863
+ }
2864
+ }
2545
2865
  let leaseHandle = null;
2546
2866
  let stopLeaseSignals = null;
2547
2867
  const releaseLease = () => {