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,29 +1,35 @@
1
- import { H as projectDeviceSlots, O as acquireAvdClaim, S as setDevice, T as withConfigLock$1, g as loadConfig$1, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, v as releaseAndroidConsolePort, x as saveConfig, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
1
+ import { B as deviceSlotPlatforms, C as setDevice, E as withConfigLock$1, S as saveConfig, U as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, d as getConfigDir, i as clearAndroidBootPending, k as acquireAvdClaim, t as allConsolePortsAndSerials, y as releaseAndroidConsolePort } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { g as updateWorkspaceState, i as pidExists, k as workspaceId, m as readWorkspaceState$1, s as resolveProjectMetro, x as ensureWorkspaceStorage } from "./metro-BWLOJ_Wr.mjs";
4
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry } from "./worktree-BlizKDpZ.mjs";
5
- import { a as detectIsExpo, c as ownedDeviceLabel, s as isPackageResolvable } from "./project-BXZzOABt.mjs";
6
- import { n as spawnDeclared } from "./spawn-claims-CUleC2cM.mjs";
7
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
3
+ import { S as sharedBuildCache, g as updateWorkspaceState, i as pidExists, k as workspaceId, m as readWorkspaceState$1, s as resolveProjectMetro, x as ensureWorkspaceStorage } from "./metro-Dzo30hvJ.mjs";
4
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, c as gitCommonDirOnDisk } from "./worktree-DeBQSVtR.mjs";
5
+ import { a as isPackageResolvable, o as ownedDeviceLabel, r as detectIsExpo } from "./project-B8rbutVC.mjs";
6
+ import { a as spawnDeclared } from "./spawn-claims-PT8aQpCa.mjs";
7
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BvvJTfo_.mjs";
8
8
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
9
- import { l as phaseLine } from "./command-output-D5z2_bcF.mjs";
10
- import { E as listInstalledSystemImages, I as pickDefaultSystemImage, M as ownedAvdName, P as ownedAvdSystemImage, T as listAvds, W as waitForBoot, d as avdPoolConfiguration, h as createOwnedAvd, j as ownedAvdMatchesConfiguration, k as nextConsolePort, l as assertOwnedAvdStopped, m as configureNewOwnedAvd, p as bootAndroidEmulator, w as listAdbDevices, x as getAvdNameForSerial, z as resolveOwnedAvdSerial } from "./android-787HhP5w.mjs";
11
- import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-DOugPlPz.mjs";
12
- import { _ as dropParked, b as readParked, d as teardownOwnedAvd, g as adoptParked, m as teardownParkedIosSim, p as teardownParkedAvd, v as isLegacyDeletionClaim, x as selectParked, y as parkedMaxSetting } from "./ownership-BfPNRf3-.mjs";
13
- import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-COGcGEcN.mjs";
14
- import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-BvOCeakp.mjs";
15
- import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-BhgkYAws.mjs";
16
- import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, t as reconcileSimSlim } from "./simslim-DZUxzsjq.mjs";
17
- import { s as startDevServer } from "./start-RbmFPeZ7.mjs";
18
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B2tBkf0m.mjs";
19
- import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
20
- import { isJsonObject, readIdleStop, readJsonObject } from "@stim-cli/core/state";
21
- import { basename, join } from "node:path";
9
+ import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-BWCj5Vh-.mjs";
10
+ import { N as storedSources, S as entryDir, x as diffFingerprintSources, y as artifactIn } from "./device-lease-B9LAXIUH.mjs";
11
+ import { B as pickDefaultSystemImage, C as getAvdNameForSerial, E as listAdbDevices, F as ownedAvdMatchesConfiguration, I as ownedAvdName, M as nextConsolePort, N as ownedAvdDeviceProfile, O as listAvds, R as ownedAvdSystemImage, W as resolveOwnedAvdSerial, X as waitForBoot, _ as createOwnedAvd, d as assertOwnedAvdStopped, g as configureNewOwnedAvd, h as bootAndroidEmulator, k as listInstalledSystemImages, n as OWNED_AVD_CONFIG_DEFAULTS, p as avdPoolConfiguration, t as DEFAULT_AVD_DEVICE_PROFILE } from "./android-75Sk2rIR.mjs";
12
+ import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-BlyDn2Oz.mjs";
13
+ import { _ as dropParked, b as readParked, d as teardownOwnedAvd, g as adoptParked, m as teardownParkedIosSim, p as teardownParkedAvd, v as isLegacyDeletionClaim, x as selectParked, y as parkedMaxSetting } from "./ownership-B8d4kXBx.mjs";
14
+ import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-CZWOHTTc.mjs";
15
+ import { o as estimateBuild } from "./build-progress-h60GPV27.mjs";
16
+ import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-CeT1nIIk.mjs";
17
+ import { a as easAuthNote } from "./remote-cache-BRsRZru5.mjs";
18
+ import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-B2oR_kFN.mjs";
19
+ import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, t as reconcileSimSlim } from "./simslim-COtEEP7x.mjs";
20
+ import { s as startDevServer } from "./start-BboE1HIS.mjs";
21
+ import { r as readStats } from "./stats-D8rUTPHd.mjs";
22
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-BzplpQzc.mjs";
23
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync } from "node:fs";
24
+ import { LAST_BUILD_KEYS, isJsonObject, loadConfig, readIdleStop, readJsonObject, readWorkspaceState } from "@stim-cli/core/state";
25
+ import { basename, dirname, join, relative } from "node:path";
22
26
  import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
23
27
  import { captureProcessIdentity } from "@stim-cli/core/process-identity";
24
28
  import { randomUUID } from "node:crypto";
25
29
  import chalk from "chalk";
26
30
  import { isDeepStrictEqual } from "node:util";
31
+ import * as expoFingerprint from "@expo/fingerprint";
32
+ import { resolveTieredBuild } from "@stim-cli/cache";
27
33
  import { request } from "node:http";
28
34
  //#region src/engine/metro-warmup.ts
29
35
  const WARMUP_TIMEOUT_MS = 6e4;
@@ -526,7 +532,7 @@ function findOtherOwner(avdName, projectPath, selectedSlot = "default") {
526
532
  for (const [path, project] of Object.entries(loadConfig$1()?.projects ?? {})) if (projectDeviceSlots(project).some(({ slot, platforms }) => (path !== projectPath || slot !== selectedSlot) && platforms.android?.avdName?.toLowerCase() === avdName.toLowerCase())) return path;
527
533
  return null;
528
534
  }
529
- async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, systemImage, configuration, configure, teardown = teardownOwnedAvd }) {
535
+ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, systemImage, deviceProfile, configuration, configure, teardown = teardownOwnedAvd }) {
530
536
  const { claim, reservation, previous, avdName } = withConfigLock$1(() => {
531
537
  const previous = deviceSlotPlatforms(loadConfig$1()?.projects?.[projectPath], slot)?.android;
532
538
  if (previous?.avdName && previous.avdName !== previousAvdName) throw new Error(`Another Stim run assigned AVD ${previous.avdName} to this workspace. Retry to use it.`);
@@ -572,6 +578,7 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
572
578
  try {
573
579
  const creation = await createOwnedAvd(label, {
574
580
  systemImage,
581
+ deviceProfile,
575
582
  spawn: (...args) => {
576
583
  markClaimChildPending(claim);
577
584
  child = getExecutor().spawn(...args);
@@ -607,6 +614,7 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
607
614
  avdName,
608
615
  created: false,
609
616
  systemImage: ownedAvdSystemImage(avdName),
617
+ deviceProfile: ownedAvdDeviceProfile(avdName),
610
618
  ...resolved.serial ? {
611
619
  serial: resolved.serial,
612
620
  consolePort: Number(resolved.serial.replace(/^emulator-/, ""))
@@ -633,7 +641,10 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
633
641
  avdName,
634
642
  owned: true,
635
643
  deviceName: avdName,
636
- ...prepared.created ? { poolConfiguration: configuration } : {},
644
+ ...prepared.created ? {
645
+ poolConfiguration: configuration,
646
+ bootPending: true
647
+ } : {},
637
648
  ...prepared.consolePort ? { consolePort: prepared.consolePort } : {}
638
649
  }, slot);
639
650
  });
@@ -668,8 +679,13 @@ async function prepareOwnedAvd({ projectPath, slot, label, previousAvdName, syst
668
679
  const ANDROID_FRESH_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 12e4;
669
680
  const ANDROID_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 24e4;
670
681
  async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
671
- const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
672
- const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
682
+ const projectAvdConfig = androidAvdConfigSetting(settings, settingsRoot);
683
+ const avdConfig = {
684
+ ...OWNED_AVD_CONFIG_DEFAULTS,
685
+ ...projectAvdConfig
686
+ };
687
+ const requestedProfile = flags.deviceProfile || settings.android?.deviceProfile || null;
688
+ let replacedProfile = null;
673
689
  if (record?.setupIncomplete && record.avdName) {
674
690
  const avdName = record.avdName;
675
691
  const cleanup = teardownAvd(avdName, {
@@ -686,9 +702,35 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
686
702
  if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`, "Wait for any active AVD operation to finish. If the refusal names an unresolved claim, inspect its creator and native processes before removing only that claim. Retry `stim android` to reconcile the incomplete device.");
687
703
  record = null;
688
704
  }
705
+ const requestedImage = flags.systemImageFlag || null;
706
+ const currentImage = requestedImage && record?.owned && record.avdName ? ownedAvdSystemImage(record.avdName) : null;
707
+ if (record?.avdName && requestedImage && currentImage && currentImage !== requestedImage) {
708
+ const avdName = record.avdName;
709
+ if (!record.bootPending) throw new AvdRecoveryError(`this project's emulator ${avdName} uses system image ${currentImage}, but ${requestedImage} was requested. --system-image replaces only an emulator that never finished a boot, and this one has.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
710
+ replacedProfile = ownedAvdDeviceProfile(avdName);
711
+ const cleanup = teardownAvd(avdName, {
712
+ del: true,
713
+ owner: {
714
+ projectPath,
715
+ slot,
716
+ expectedRecord: record
717
+ },
718
+ onRemoved: () => {
719
+ clearDevice(projectPath, "android", slot, avdName);
720
+ }
721
+ });
722
+ if (cleanup.status === "failed" || cleanup.status === "skipped") throw new AvdRecoveryError(`Owned AVD ${avdName} never finished a boot, but could not be deleted to create one on ${requestedImage} (${cleanup.reason || cleanup.status}). Stim kept the device record.`, "Fix the cause, then retry, or pass `--slot <name>` to create the requested emulator beside it.");
723
+ out(phaseLine("device", `deleted ${avdName}, which never finished a boot, to create one on ${requestedImage}`));
724
+ record = null;
725
+ }
726
+ const deviceProfile = requestedProfile ?? replacedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
727
+ const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
728
+ const configurationBeforeDefaults = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), projectAvdConfig, deviceProfile);
689
729
  if (record?.avdName) {
690
730
  if (record.owned) {
691
731
  const resolved = resolveOwnedAvdSerial(record.avdName);
732
+ const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
733
+ if (requestedProfile && currentProfile && currentProfile !== requestedProfile) throw new AvdRecoveryError(`this project's emulator uses device profile ${currentProfile}, but ${requestedProfile} was requested. Stim will not silently boot a different model.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
692
734
  if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
693
735
  else if (resolved.serial) {
694
736
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
@@ -703,7 +745,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
703
745
  setDevice(projectPath, "android", updated, slot);
704
746
  return {
705
747
  ...updated,
706
- systemImage: ownedAvdSystemImage(record.avdName)
748
+ systemImage: ownedAvdSystemImage(record.avdName),
749
+ deviceProfile: currentProfile
707
750
  };
708
751
  } else if (!resolved.missing) {
709
752
  out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
@@ -718,7 +761,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
718
761
  logFile,
719
762
  alive
720
763
  }),
721
- systemImage: ownedAvdSystemImage(record.avdName)
764
+ systemImage: ownedAvdSystemImage(record.avdName),
765
+ deviceProfile: currentProfile
722
766
  };
723
767
  }
724
768
  } else if (listAvds().includes(record.avdName)) {
@@ -734,7 +778,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
734
778
  }
735
779
  if (parkedMaxSetting("android").max > 0) {
736
780
  const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
737
- const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && entry.configuration === configuration).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
781
+ const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && (entry.configuration === configuration || entry.configuration === configurationBeforeDefaults)).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
738
782
  for (const parked of candidates) {
739
783
  if (isLegacyDeletionClaim(parked.deletionClaim)) continue;
740
784
  const resolved = resolveOwnedAvdSerial(parked.name);
@@ -750,7 +794,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
750
794
  out(phaseLine("device", `kept parked ${parked.name}: ${String(error?.message || error)}`));
751
795
  continue;
752
796
  }
753
- if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, configuration)) {
797
+ if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, parked.configuration)) {
754
798
  const result = teardownParkedAvd(parked.name);
755
799
  if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
756
800
  continue;
@@ -759,7 +803,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
759
803
  avdName: parked.name,
760
804
  deviceName: parked.name,
761
805
  owned: true,
762
- poolConfiguration: configuration,
806
+ poolConfiguration: parked.configuration,
763
807
  adoptionPending: true
764
808
  };
765
809
  if (!adoptParked({
@@ -769,6 +813,15 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
769
813
  udid: parked.udid,
770
814
  device: adopted
771
815
  })) continue;
816
+ if (parked.configuration !== configuration) try {
817
+ configureAvd(parked.name, {
818
+ dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
819
+ avdConfig
820
+ });
821
+ adopted.poolConfiguration = configuration;
822
+ } catch (error) {
823
+ out(phaseLine("device", `${parked.name} keeps its old AVD settings: ${String(error?.message || error)}`));
824
+ }
772
825
  return {
773
826
  ...await bootOwnedAvdOnFreshPort({
774
827
  avdName: parked.name,
@@ -780,7 +833,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
780
833
  alive
781
834
  }),
782
835
  adopted: true,
783
- systemImage
836
+ systemImage,
837
+ deviceProfile
784
838
  };
785
839
  }
786
840
  }
@@ -790,6 +844,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
790
844
  label,
791
845
  previousAvdName: record?.avdName,
792
846
  systemImage: flags.systemImage || settings.android?.systemImage || void 0,
847
+ deviceProfile,
793
848
  configuration,
794
849
  configure: (avdName) => configureAvd(avdName, {
795
850
  dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
@@ -808,7 +863,10 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
808
863
  return {
809
864
  ...await bootOwnedAvdOnFreshPort({
810
865
  avdName: created.avdName,
811
- metadata: created.created ? { poolConfiguration: configuration } : void 0,
866
+ metadata: created.created ? {
867
+ poolConfiguration: configuration,
868
+ bootPending: true
869
+ } : void 0,
812
870
  projectPath,
813
871
  slot,
814
872
  deviceName: created.avdName,
@@ -817,7 +875,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
817
875
  alive
818
876
  }),
819
877
  created: created.created,
820
- systemImage: created.systemImage
878
+ systemImage: created.systemImage,
879
+ deviceProfile: created.deviceProfile
821
880
  };
822
881
  }
823
882
  function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock$1, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
@@ -826,6 +885,7 @@ function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, liveP
826
885
  const claim = {
827
886
  ...metadata?.poolConfiguration ? { poolConfiguration: metadata.poolConfiguration } : {},
828
887
  ...metadata?.adoptionPending ? { adoptionPending: true } : {},
888
+ ...metadata?.bootPending ? { bootPending: true } : {},
829
889
  avdName,
830
890
  consolePort,
831
891
  owned: true,
@@ -863,8 +923,10 @@ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, slot, d
863
923
  if (result.failed) throw new AvdBootError(result.reason, result.remedy);
864
924
  const running = getAvdNameForSerial(serial);
865
925
  if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
926
+ clearAndroidBootPending(projectPath, avdName, slot);
927
+ const { bootPending: _booted, ...booted } = claim;
866
928
  return {
867
- ...claim,
929
+ ...booted,
868
930
  serial
869
931
  };
870
932
  } catch (error) {
@@ -943,17 +1005,22 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
943
1005
  failed: true,
944
1006
  reason: `AVD ${device.avdName} is not Stim-owned by name; refusing to boot it.`
945
1007
  };
946
- if (resolved.serial) return waitForAndroidBoot({
1008
+ const avdName = device.avdName;
1009
+ const booted = (result) => {
1010
+ if (!result.failed) clearAndroidBootPending(projectPath, avdName, slot);
1011
+ return result;
1012
+ };
1013
+ if (resolved.serial) return booted(await waitForAndroidBoot({
947
1014
  serial: resolved.serial,
948
1015
  timeoutMs,
949
1016
  out
950
- });
1017
+ }));
951
1018
  const freshSerial = `emulator-${device.consolePort}`;
952
- if (device.owned && device.serial === freshSerial) return waitForAndroidBoot({
1019
+ if (device.owned && device.serial === freshSerial) return booted(await waitForAndroidBoot({
953
1020
  serial: freshSerial,
954
1021
  timeoutMs,
955
1022
  out
956
- });
1023
+ }));
957
1024
  const claim = claimAndroidConsolePort({
958
1025
  projectPath,
959
1026
  slot,
@@ -983,7 +1050,7 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
983
1050
  out
984
1051
  });
985
1052
  if (result.failed) releaseAndroidConsolePort(projectPath, claim.consolePort, slot);
986
- return result;
1053
+ return booted(result);
987
1054
  }
988
1055
  //#endregion
989
1056
  //#region src/engine/device.ts
@@ -1572,4 +1639,413 @@ function hasDevClient(root) {
1572
1639
  return isPackageResolvable(root, "expo-dev-client");
1573
1640
  }
1574
1641
  //#endregion
1575
- export { isPhysicalDeviceRequest as _, swapJsBundle as a, warmMetro as b, runPrebuild as c, ensureOwnedDevice as d, AvdBootError as f, ensureWorkspaceStorageSafely as g, ensureDevServer as h, detectEntryFile as i, staleNativeDirRefusal as l, clearIosAdoptionPending as m, pickDevClientScheme as n, planPrebuild as o, AvdRecoveryError as p, schemesFromInfoPlist as r, recordPrebuild as s, devClientScheme as t, ensureBooted as u, launchOutcomeRecord as v, sleep$1 as y };
1642
+ //#region src/cache/miss-reason.ts
1643
+ const MISS_CHANGE_CAP = 20;
1644
+ function recordedBuilds(state, platform) {
1645
+ const builds = [];
1646
+ for (const value of [state?.[LAST_BUILD_KEYS[platform]], state?.lastBuild]) {
1647
+ const record = value;
1648
+ if (record?.platform !== platform) continue;
1649
+ const startedAt = typeof record.startedAt === "string" ? record.startedAt : "";
1650
+ if (typeof record.fingerprint === "string" && typeof record.cacheKey === "string") builds.push({
1651
+ fingerprint: record.fingerprint,
1652
+ cacheKey: record.cacheKey,
1653
+ startedAt
1654
+ });
1655
+ const carried = record.missReason?.baseline;
1656
+ if (typeof carried?.fingerprint === "string" && typeof carried.cacheKey === "string") builds.push({
1657
+ fingerprint: carried.fingerprint,
1658
+ cacheKey: carried.cacheKey,
1659
+ startedAt
1660
+ });
1661
+ }
1662
+ return builds;
1663
+ }
1664
+ function projectIdentity(root) {
1665
+ let real;
1666
+ try {
1667
+ real = realpathSync.native(root);
1668
+ } catch {
1669
+ return null;
1670
+ }
1671
+ for (let dir = real;; dir = dirname(dir)) {
1672
+ if (existsSync(join(dir, ".git"))) {
1673
+ const common = gitCommonDirOnDisk(dir);
1674
+ return common ? `${common}\n${relative(dir, real)}` : null;
1675
+ }
1676
+ if (dirname(dir) === dir) return null;
1677
+ }
1678
+ }
1679
+ function findMissBaseline(root, platform, { readState = readWorkspaceState, projectRoots = () => Object.keys(loadConfig()?.projects ?? {}), identity = projectIdentity, sourcesOf = (p, key) => storedSources(p, key, sharedBuildCache()) } = {}) {
1680
+ for (const build of recordedBuilds(readState(root), platform)) {
1681
+ const sources = sourcesOf(platform, build.cacheKey);
1682
+ if (sources) return {
1683
+ fingerprint: build.fingerprint,
1684
+ cacheKey: build.cacheKey,
1685
+ sources,
1686
+ from: "workspace"
1687
+ };
1688
+ }
1689
+ const project = identity(root);
1690
+ if (!project) return null;
1691
+ const others = [];
1692
+ for (const other of projectRoots()) {
1693
+ if (other === root || identity(other) !== project) continue;
1694
+ others.push(...recordedBuilds(readState(other), platform));
1695
+ }
1696
+ for (const build of others.toSorted((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0))) {
1697
+ const sources = sourcesOf(platform, build.cacheKey);
1698
+ if (sources) return {
1699
+ fingerprint: build.fingerprint,
1700
+ cacheKey: build.cacheKey,
1701
+ sources,
1702
+ from: "project"
1703
+ };
1704
+ }
1705
+ return null;
1706
+ }
1707
+ const CATEGORY_RANK = {
1708
+ "native-dependency": 0,
1709
+ "app-config": 1,
1710
+ "config-plugin": 2,
1711
+ package: 3,
1712
+ "native-dir": 4,
1713
+ file: 5,
1714
+ "app-asset": 6,
1715
+ other: 7,
1716
+ "package-scripts": 8,
1717
+ autolinking: 9
1718
+ };
1719
+ function packageIn(path) {
1720
+ return [...path.matchAll(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g)].at(-1)?.[1] ?? null;
1721
+ }
1722
+ function describeChange({ name, change, reasons }) {
1723
+ const pkg = packageIn(name);
1724
+ if (name === "expoConfig" || reasons.includes("expoConfig")) return {
1725
+ category: "app-config",
1726
+ label: "app config changed"
1727
+ };
1728
+ if (pkg && reasons.some((reason) => /^(expo|rncore)Autolinking(Ios|Android)?$/.test(reason))) return {
1729
+ category: "native-dependency",
1730
+ label: `native dependency ${change}: ${pkg}`
1731
+ };
1732
+ if (pkg && reasons.includes("expoConfigPlugins")) return {
1733
+ category: "config-plugin",
1734
+ label: `config plugin ${change}: ${pkg}`
1735
+ };
1736
+ if (/^(expoAutolinkingConfig|rncoreAutolinkingConfig)(:|$)/.test(name)) return {
1737
+ category: "autolinking",
1738
+ label: "autolinking config changed"
1739
+ };
1740
+ if (name.startsWith("package:")) return {
1741
+ category: "package",
1742
+ label: `${name.slice(8)} ${change}`
1743
+ };
1744
+ if (name === "packageJson:scripts") return {
1745
+ category: "package-scripts",
1746
+ label: "package.json scripts changed"
1747
+ };
1748
+ if (reasons.includes("expoConfigExternalFile")) return {
1749
+ category: "app-asset",
1750
+ label: `app asset ${change}: ${name}`
1751
+ };
1752
+ if (reasons.includes("bareNativeDir")) return {
1753
+ category: "native-dir",
1754
+ label: `${name}/ ${change}`
1755
+ };
1756
+ if (reasons.length || name.includes("/") || name.includes(".")) return {
1757
+ category: "file",
1758
+ label: `${name} ${change}`
1759
+ };
1760
+ return {
1761
+ category: "other",
1762
+ label: `${name} ${change}`
1763
+ };
1764
+ }
1765
+ function rekeyPrefix(rekeyedBy) {
1766
+ return rekeyedBy.length ? `${rekeyedBy.join(" and ")} changed native inputs; ` : "";
1767
+ }
1768
+ function missReasonFromChanges({ changes, baseline, rekeyedBy = [] }) {
1769
+ const prefix = rekeyPrefix(rekeyedBy);
1770
+ if (!baseline) return {
1771
+ kind: "no-baseline",
1772
+ summary: `${prefix}no earlier build of this project in the cache to compare with`,
1773
+ changes: [],
1774
+ changeCount: 0,
1775
+ baseline: null,
1776
+ rekeyedBy
1777
+ };
1778
+ if (!changes.length) return {
1779
+ kind: "same-sources",
1780
+ summary: `${prefix}native inputs match the last build; no cached app for this configuration or target`,
1781
+ changes: [],
1782
+ changeCount: 0,
1783
+ baseline,
1784
+ rekeyedBy
1785
+ };
1786
+ const described = changes.map((change, index) => ({
1787
+ change,
1788
+ index,
1789
+ ...describeChange(change)
1790
+ })).toSorted((a, b) => CATEGORY_RANK[a.category] - CATEGORY_RANK[b.category] || a.index - b.index);
1791
+ const dependencyMoved = described.some((entry) => entry.category === "native-dependency");
1792
+ const labels = [...new Set(described.filter((entry) => !(dependencyMoved && entry.category === "autolinking")).map((entry) => entry.label))];
1793
+ const more = labels.length > 1 ? ` (+${labels.length - 1} more)` : "";
1794
+ return {
1795
+ kind: "changed",
1796
+ summary: `${prefix}${labels[0]}${more}`,
1797
+ changes: described.slice(0, MISS_CHANGE_CAP).map(({ change, category }) => ({
1798
+ source: change.name,
1799
+ change: change.change,
1800
+ category
1801
+ })),
1802
+ changeCount: changes.length,
1803
+ baseline,
1804
+ rekeyedBy
1805
+ };
1806
+ }
1807
+ function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDeps, differ = expoFingerprint.diffFingerprints }) {
1808
+ const baseline = findMissBaseline(root, platform, baselineDeps);
1809
+ const changes = baseline ? diffFingerprintSources({
1810
+ previous: baseline.sources,
1811
+ previousHash: baseline.fingerprint,
1812
+ current,
1813
+ differ
1814
+ }) : [];
1815
+ return {
1816
+ reason: missReasonFromChanges({
1817
+ changes,
1818
+ baseline: baseline ? {
1819
+ fingerprint: baseline.fingerprint,
1820
+ cacheKey: baseline.cacheKey,
1821
+ from: baseline.from
1822
+ } : null,
1823
+ rekeyedBy
1824
+ }),
1825
+ changedNames: changes.map((change) => change.name),
1826
+ previousHash: baseline?.fingerprint ?? null
1827
+ };
1828
+ }
1829
+ function predictBuildMiss({ root, platform, current, prebuild }) {
1830
+ const { reason } = explainBuildMiss({
1831
+ root,
1832
+ platform,
1833
+ current
1834
+ });
1835
+ const baseline = reason.baseline && {
1836
+ fingerprint: reason.baseline.fingerprint,
1837
+ from: reason.baseline.from
1838
+ };
1839
+ if (!prebuild || !baseline) return {
1840
+ ...reason,
1841
+ baseline
1842
+ };
1843
+ const dir = platform === "ios" ? "ios/" : "android/";
1844
+ return {
1845
+ ...reason,
1846
+ kind: "prebuild-pending",
1847
+ summary: `${reason.summary} (before prebuild ${prebuild}s ${dir})`,
1848
+ baseline
1849
+ };
1850
+ }
1851
+ function skippedMissReason(summary) {
1852
+ return {
1853
+ kind: "cache-skipped",
1854
+ summary,
1855
+ changes: [],
1856
+ changeCount: 0,
1857
+ baseline: null,
1858
+ rekeyedBy: []
1859
+ };
1860
+ }
1861
+ function fingerprintErrorMissReason(message) {
1862
+ return {
1863
+ kind: "fingerprint-error",
1864
+ summary: `fingerprinting failed: ${message}`,
1865
+ changes: [],
1866
+ changeCount: 0,
1867
+ baseline: null,
1868
+ rekeyedBy: []
1869
+ };
1870
+ }
1871
+ //#endregion
1872
+ //#region src/commands/build-plan.ts
1873
+ const DISCARD = {
1874
+ file: "",
1875
+ written: 0,
1876
+ dropped: 0,
1877
+ lastError: null,
1878
+ write: () => true,
1879
+ close: () => ({
1880
+ file: "",
1881
+ written: 0,
1882
+ dropped: 0,
1883
+ lastError: null
1884
+ })
1885
+ };
1886
+ async function providerHit({ root, platform, cacheKey, providerConfig }, deps) {
1887
+ if (!providerConfig) return null;
1888
+ let destinationDir;
1889
+ try {
1890
+ destinationDir = makeTemporaryDirectory(root, "stim-plan-");
1891
+ } catch (error) {
1892
+ deps.note(chalk.yellow(phaseLine("cache", `provider not checked: ${error?.message || error}`)));
1893
+ return null;
1894
+ }
1895
+ try {
1896
+ const found = await resolveTieredBuild({
1897
+ local: {
1898
+ resolve: () => null,
1899
+ store: () => null
1900
+ },
1901
+ loadProvider: () => deps.loadCacheProvider({
1902
+ projectRoot: root,
1903
+ config: providerConfig
1904
+ }),
1905
+ target: {
1906
+ projectRoot: root,
1907
+ platform,
1908
+ key: cacheKey
1909
+ },
1910
+ destinationDir,
1911
+ warn: (_code, message) => deps.note(chalk.yellow(phaseLine("cache", message)))
1912
+ });
1913
+ return found ? found.providerName ?? "the cache provider" : null;
1914
+ } finally {
1915
+ removeTemporaryEntry(destinationDir);
1916
+ }
1917
+ }
1918
+ async function expoRemoteHit(lookup, deps) {
1919
+ if (!lookup.expoRemote) return null;
1920
+ const { root, platform, isExpo, fingerprint } = lookup;
1921
+ const loaded = await deps.loadProjectProvider(root, { isExpo });
1922
+ if (loaded?.unavailable) {
1923
+ deps.note(chalk.yellow(phaseLine("cache", `provider not usable: ${loaded.unavailable}`)));
1924
+ return null;
1925
+ }
1926
+ if (!loaded?.provider) return null;
1927
+ const name = loaded.name ?? "the build cache provider";
1928
+ if (name === "eas") {
1929
+ const auth = deps.checkEasAuth({
1930
+ projectRoot: root,
1931
+ owner: loaded.owner || null
1932
+ });
1933
+ const authNote = easAuthNote(auth);
1934
+ if (authNote) deps.note(chalk.yellow(phaseLine("cache", authNote)));
1935
+ if (auth?.code === "logged-out") return null;
1936
+ }
1937
+ const hit = await deps.resolveRemote({
1938
+ provider: loaded.provider,
1939
+ platform,
1940
+ projectRoot: root,
1941
+ fingerprintHash: fingerprint,
1942
+ runOptions: lookup.expoRemote.runOptions,
1943
+ logWriter: DISCARD
1944
+ });
1945
+ if (hit?.timedOut) deps.note(chalk.yellow(phaseLine("cache", `${name} did not answer; counted as a miss`)));
1946
+ else if (hit?.failed) deps.note(chalk.yellow(phaseLine("cache", `${name} could not be used: ${hit.failed}`)));
1947
+ return hit?.appPath ? name : null;
1948
+ }
1949
+ async function planCachedBuild(lookup, deps) {
1950
+ const { root, platform, isExpo, fingerprint, sources, cacheKey, cachePolicy } = lookup;
1951
+ let cacheHit = false;
1952
+ let provider = null;
1953
+ if (cachePolicy.read) {
1954
+ if (artifactIn(entryDir(platform, cacheKey))) cacheHit = "local";
1955
+ else {
1956
+ provider = (cachePolicy.remote ? await providerHit(lookup, deps) : null) ?? await expoRemoteHit(lookup, deps);
1957
+ if (provider) cacheHit = "remote";
1958
+ }
1959
+ }
1960
+ let prebuild = null;
1961
+ let refusal = null;
1962
+ let missReason;
1963
+ if (!cacheHit) {
1964
+ prebuild = deps.planPrebuild(root, platform, {
1965
+ isExpo,
1966
+ fingerprint,
1967
+ sources
1968
+ });
1969
+ if (prebuild === "refuse") refusal = staleNativeDirRefusal(platform);
1970
+ else if (cachePolicy.read) missReason = predictBuildMiss({
1971
+ root,
1972
+ platform,
1973
+ current: {
1974
+ hash: fingerprint,
1975
+ sources
1976
+ },
1977
+ prebuild: prebuild === "generate" || prebuild === "regenerate" ? prebuild : null
1978
+ });
1979
+ }
1980
+ return planPayload(lookup, {
1981
+ fingerprint,
1982
+ cacheKey,
1983
+ cacheHit,
1984
+ provider,
1985
+ cacheSkipped: !cachePolicy.read,
1986
+ prebuild,
1987
+ ...missReason ? { missReason } : {},
1988
+ refusal
1989
+ });
1990
+ }
1991
+ function planPayload({ platform, slot, projectKey }, found) {
1992
+ const { refusal, missReason, ...rest } = found;
1993
+ const outcome = refusal ? null : found.cacheHit ? "hit" : "cold";
1994
+ const estimate = outcome ? estimateBuild(readStats().record?.history?.[projectKey], platform, outcome, "prepare") : {
1995
+ expectedMs: null,
1996
+ basis: 0
1997
+ };
1998
+ return {
1999
+ platform,
2000
+ ...slot === "default" ? {} : { slot },
2001
+ ...rest,
2002
+ outcome,
2003
+ expectedMs: estimate.expectedMs,
2004
+ basis: estimate.basis,
2005
+ ...missReason ? { missReason } : {},
2006
+ ...refusal ? { refusal } : {}
2007
+ };
2008
+ }
2009
+ function cacheLine(plan) {
2010
+ if (plan.refusal) return `the build would refuse with ${plan.refusal.code}`;
2011
+ if (plan.cacheHit === "local") return "local cache hit";
2012
+ if (plan.cacheHit === "remote") return `remote cache hit (${plan.provider})`;
2013
+ return `${plan.cacheSkipped ? "cache reads are off" : "cache miss"}: compiles${plan.prebuild === "generate" || plan.prebuild === "regenerate" ? `, ${plan.prebuild}s the native dir` : ""}`;
2014
+ }
2015
+ function printPlan(plan, json) {
2016
+ if (json) {
2017
+ console.log(JSON.stringify(plan));
2018
+ return;
2019
+ }
2020
+ const slot = plan.slot ? ` [${plan.slot}]` : "";
2021
+ console.log(phaseLine("plan", `${plan.platform}${slot} ${shortHash(plan.fingerprint)} -> ${cacheLine(plan)}`));
2022
+ if (plan.missReason) console.log(phaseLine("cache", `miss: ${plan.missReason.summary}`));
2023
+ if (plan.refusal) {
2024
+ console.log(phaseLine("error", plan.refusal.message));
2025
+ console.log(phaseLine("remedy", plan.refusal.remedy));
2026
+ return;
2027
+ }
2028
+ console.log(phaseLine("expect", plan.expectedMs === null ? `unknown: no ${plan.outcome} run of this project is recorded yet` : `~${formatDuration(plan.expectedMs)} (median of ${plural(plan.basis, `${plan.outcome} run`)})`));
2029
+ }
2030
+ function refusePlan({ code, message, remedy }, json, lines = []) {
2031
+ console.error(chalk.red(phaseLine("error", message)));
2032
+ for (const line of lines) console.error(chalk.dim(phaseLine("", line)));
2033
+ console.error(chalk.dim(phaseLine("remedy", remedy)));
2034
+ console.error(chalk.red(phaseLine("failed", code)));
2035
+ if (json) console.log(JSON.stringify({
2036
+ code,
2037
+ message,
2038
+ remedy
2039
+ }));
2040
+ process.exitCode = 1;
2041
+ }
2042
+ const PLAN_ONLY_REMEDY = "Drop the flag to plan the owned simulator or emulator build, or run the command without --plan.";
2043
+ function planFlagRefusal(flag) {
2044
+ return {
2045
+ code: "STIM_BAD_ARG",
2046
+ message: `${flag} does not apply to --plan, which predicts the next local build without choosing a device.`,
2047
+ remedy: PLAN_ONLY_REMEDY
2048
+ };
2049
+ }
2050
+ //#endregion
2051
+ export { ensureDevServer as C, sleep$1 as D, launchOutcomeRecord as E, warmMetro as O, clearIosAdoptionPending as S, isPhysicalDeviceRequest as T, staleNativeDirRefusal as _, refusePlan as a, AvdBootError as b, skippedMissReason as c, schemesFromInfoPlist as d, detectEntryFile as f, runPrebuild as g, recordPrebuild as h, printPlan as i, devClientScheme as l, planPrebuild as m, planFlagRefusal as n, explainBuildMiss as o, swapJsBundle as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, pickDevClientScheme as u, ensureBooted as v, ensureWorkspaceStorageSafely as w, AvdRecoveryError as x, ensureOwnedDevice as y };