stim 1.10.1 → 1.11.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 (58) hide show
  1. package/dist/{build-progress-BXx-ZBe4.mjs → activity-BtG4Va9l.mjs} +8 -182
  2. package/dist/{android-DPov-fv2.mjs → android-BA2i_KAV.mjs} +1 -1
  3. package/dist/{android-Bu4mbDzc.mjs → android-C1q4l523.mjs} +376 -99
  4. package/dist/{android-787HhP5w.mjs → android-C4zG2Tuc.mjs} +63 -18
  5. package/dist/{android-cas-CITmIeWY.mjs → android-cas-DfJkwCXW.mjs} +3 -3
  6. package/dist/{app-install-Cfmqr3TQ.mjs → app-install-DHut6JnV.mjs} +1 -1
  7. package/dist/{budget-nDCEwtJv.mjs → budget-De3P2rq_.mjs} +9 -9
  8. package/dist/{dev-client-DExzPiOa.mjs → build-plan-CeAYgKAx.mjs} +415 -24
  9. package/dist/build-progress-CDwogz6R.mjs +179 -0
  10. package/dist/cli.mjs +14 -14
  11. package/dist/collector-run.mjs +1 -1
  12. package/dist/{deps-DP4eWqea.mjs → deps-OF7CD5Dx.mjs} +2 -2
  13. package/dist/{device-Czh5kO8A.mjs → device-BwH2iI3O.mjs} +47 -9
  14. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-D83nqeMX.mjs} +51 -51
  15. package/dist/{device-pool-BBWLAr55.mjs → device-pool-DHj7cdPy.mjs} +2 -2
  16. package/dist/{device-remote-powUHdbK.mjs → device-remote-BQZSnnmE.mjs} +7 -7
  17. package/dist/{doctor-CmqK1LcQ.mjs → doctor-D8QdMRMf.mjs} +12 -12
  18. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-UYFSQwRy.mjs} +3 -3
  19. package/dist/{gc-zCDpmdHU.mjs → gc-COZteyiq.mjs} +27 -253
  20. package/dist/{guide-DwmaD_Wa.mjs → guide-NDvTmMi-.mjs} +336 -48
  21. package/dist/{idle--jn7pBNe.mjs → idle-NETbeNiQ.mjs} +4 -3
  22. package/dist/{in-use-CNIWU-r-.mjs → in-use-DVhlSoyn.mjs} +4 -4
  23. package/dist/{ios-CbdqTqOc.mjs → ios-CQLKpNfr.mjs} +294 -81
  24. package/dist/{ios-COGcGEcN.mjs → ios-DArbmwer.mjs} +2 -1
  25. package/dist/{ios-device-DNjPXBnb.mjs → ios-device-CwDZo0X4.mjs} +1 -1
  26. package/dist/{launch-verify-BhgkYAws.mjs → launch-verify-CuPDGXdr.mjs} +1 -1
  27. package/dist/logs-B44a1UeU.mjs +485 -0
  28. package/dist/merge-state-DDxS-XIZ.mjs +232 -0
  29. package/dist/{named-ports-DiTfSz6p.mjs → named-ports-Dc7neAlc.mjs} +1 -1
  30. package/dist/native-run-D7yB_AsB.mjs +143 -0
  31. package/dist/{ownership-BfPNRf3-.mjs → ownership-BiojdtAp.mjs} +3 -3
  32. package/dist/{ports-CLSSFOuz.mjs → ports-DwBaRFJs.mjs} +1 -1
  33. package/dist/{reload-CymyF6Kk.mjs → reload-By9Dwsrt.mjs} +4 -4
  34. package/dist/{server-bare-DVbb7kXH.mjs → server-bare-D_RK1RCj.mjs} +1 -1
  35. package/dist/server-expo-B-CCUJZj.mjs +2 -0
  36. package/dist/{server-expo-BvOCeakp.mjs → server-expo-o6H5gcBc.mjs} +1 -1
  37. package/dist/{settings-DOugPlPz.mjs → settings-B5oFZEri.mjs} +12 -3
  38. package/dist/{settings-EWYNwe72.mjs → settings-C0ugALSC.mjs} +1 -1
  39. package/dist/settings.schema.json +52 -0
  40. package/dist/{simslim-DZUxzsjq.mjs → simslim-CIR3IYJO.mjs} +150 -95
  41. package/dist/{slot-launch-Dk4oTREM.mjs → slot-launch-rggO1Esw.mjs} +2 -2
  42. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-BJwXTMPt.mjs} +24 -1
  43. package/dist/{start-RbmFPeZ7.mjs → start-BSsf2tUs.mjs} +4 -4
  44. package/dist/{start-qWapeB4W.mjs → start-i8zc9VmS.mjs} +1 -1
  45. package/dist/{status-Zp8kvexe.mjs → status-CitcBRsC.mjs} +227 -22
  46. package/dist/{status-Dr4fRkym.mjs → status-Q-WDEGCB.mjs} +20 -6
  47. package/dist/{stop-DcEv5sk-.mjs → stop-DIhrvu0B.mjs} +188 -27
  48. package/dist/stop-rst5mL14.mjs +3 -0
  49. package/dist/supervisor-run.mjs +7 -7
  50. package/dist/{workspace-process-lock-BXA96myD.mjs → workspace-process-lock-Cx74RSDZ.mjs} +9 -4
  51. package/dist/{workspaces-wrH6dXfu.mjs → workspaces-COD2xCz-.mjs} +15 -5
  52. package/dist/{worktree-DEG2o9F8.mjs → worktree-DhxDTK44.mjs} +1 -1
  53. package/dist/{worktree-Bd4dRvx7.mjs → worktree-KOjMX3zA.mjs} +8 -8
  54. package/dist/{xcode-B2tBkf0m.mjs → xcode-B5SPD--o.mjs} +3 -3
  55. package/package.json +5 -5
  56. package/dist/logs-MARIy6m9.mjs +0 -209
  57. package/dist/server-expo-DBVTdult.mjs +0 -2
  58. package/dist/stop-DI4BG4Os.mjs +0 -3
@@ -1,29 +1,35 @@
1
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";
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";
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-BWLOJ_Wr.mjs";
4
+ import { D as makeTemporaryDirectory, O as removeTemporaryEntry, c as gitCommonDirOnDisk } from "./worktree-BlizKDpZ.mjs";
5
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";
6
+ import { a as spawnDeclared } from "./spawn-claims-BJwXTMPt.mjs";
7
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Cx74RSDZ.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-D5z2_bcF.mjs";
10
+ import { N as storedSources, S as entryDir, x as diffFingerprintSources, y as artifactIn } from "./device-lease-D83nqeMX.mjs";
11
+ import { D as listAvds, F as ownedAvdName, H as resolveOwnedAvdSerial, L as ownedAvdSystemImage, M as ownedAvdDeviceProfile, O as listInstalledSystemImages, P as ownedAvdMatchesConfiguration, S as getAvdNameForSerial, T as listAdbDevices, f as avdPoolConfiguration, g as createOwnedAvd, h as configureNewOwnedAvd, j as nextConsolePort, m as bootAndroidEmulator, q as waitForBoot, t as DEFAULT_AVD_DEVICE_PROFILE, u as assertOwnedAvdStopped, z as pickDefaultSystemImage } from "./android-C4zG2Tuc.mjs";
12
+ import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-B5oFZEri.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-BiojdtAp.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-DArbmwer.mjs";
15
+ import { o as estimateBuild } from "./build-progress-CDwogz6R.mjs";
16
+ import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-o6H5gcBc.mjs";
17
+ import { N as easAuthNote } from "./device-remote-BQZSnnmE.mjs";
18
+ import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-CuPDGXdr.mjs";
19
+ import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, t as reconcileSimSlim } from "./simslim-CIR3IYJO.mjs";
20
+ import { s as startDevServer } from "./start-BSsf2tUs.mjs";
21
+ import { r as readStats } from "./stats-BAgJaLA4.mjs";
22
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B5SPD--o.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-/, ""))
@@ -669,7 +677,9 @@ const ANDROID_FRESH_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 12e4
669
677
  const ANDROID_BOOT_TIMEOUT_MS = process.platform === "win32" ? 12e5 : 24e4;
670
678
  async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
671
679
  const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
672
- const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
680
+ const requestedProfile = flags.deviceProfile || settings.android?.deviceProfile || null;
681
+ const deviceProfile = requestedProfile ?? DEFAULT_AVD_DEVICE_PROFILE;
682
+ const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig, deviceProfile);
673
683
  if (record?.setupIncomplete && record.avdName) {
674
684
  const avdName = record.avdName;
675
685
  const cleanup = teardownAvd(avdName, {
@@ -689,6 +699,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
689
699
  if (record?.avdName) {
690
700
  if (record.owned) {
691
701
  const resolved = resolveOwnedAvdSerial(record.avdName);
702
+ const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
703
+ 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
704
  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
705
  else if (resolved.serial) {
694
706
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
@@ -703,7 +715,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
703
715
  setDevice(projectPath, "android", updated, slot);
704
716
  return {
705
717
  ...updated,
706
- systemImage: ownedAvdSystemImage(record.avdName)
718
+ systemImage: ownedAvdSystemImage(record.avdName),
719
+ deviceProfile: currentProfile
707
720
  };
708
721
  } else if (!resolved.missing) {
709
722
  out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
@@ -718,7 +731,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
718
731
  logFile,
719
732
  alive
720
733
  }),
721
- systemImage: ownedAvdSystemImage(record.avdName)
734
+ systemImage: ownedAvdSystemImage(record.avdName),
735
+ deviceProfile: currentProfile
722
736
  };
723
737
  }
724
738
  } else if (listAvds().includes(record.avdName)) {
@@ -780,7 +794,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
780
794
  alive
781
795
  }),
782
796
  adopted: true,
783
- systemImage
797
+ systemImage,
798
+ deviceProfile
784
799
  };
785
800
  }
786
801
  }
@@ -790,6 +805,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
790
805
  label,
791
806
  previousAvdName: record?.avdName,
792
807
  systemImage: flags.systemImage || settings.android?.systemImage || void 0,
808
+ deviceProfile,
793
809
  configuration,
794
810
  configure: (avdName) => configureAvd(avdName, {
795
811
  dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
@@ -817,7 +833,8 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
817
833
  alive
818
834
  }),
819
835
  created: created.created,
820
- systemImage: created.systemImage
836
+ systemImage: created.systemImage,
837
+ deviceProfile: created.deviceProfile
821
838
  };
822
839
  }
823
840
  function claimAndroidConsolePort({ projectPath, slot, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock$1, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
@@ -1572,4 +1589,378 @@ function hasDevClient(root) {
1572
1589
  return isPackageResolvable(root, "expo-dev-client");
1573
1590
  }
1574
1591
  //#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 };
1592
+ //#region src/cache/miss-reason.ts
1593
+ const MISS_CHANGE_CAP = 20;
1594
+ function recordedBuilds(state, platform) {
1595
+ const builds = [];
1596
+ for (const value of [state?.[LAST_BUILD_KEYS[platform]], state?.lastBuild]) {
1597
+ const record = value;
1598
+ if (record?.platform !== platform) continue;
1599
+ const startedAt = typeof record.startedAt === "string" ? record.startedAt : "";
1600
+ if (typeof record.fingerprint === "string" && typeof record.cacheKey === "string") builds.push({
1601
+ fingerprint: record.fingerprint,
1602
+ cacheKey: record.cacheKey,
1603
+ startedAt
1604
+ });
1605
+ const carried = record.missReason?.baseline;
1606
+ if (typeof carried?.fingerprint === "string" && typeof carried.cacheKey === "string") builds.push({
1607
+ fingerprint: carried.fingerprint,
1608
+ cacheKey: carried.cacheKey,
1609
+ startedAt
1610
+ });
1611
+ }
1612
+ return builds;
1613
+ }
1614
+ function projectIdentity(root) {
1615
+ let real;
1616
+ try {
1617
+ real = realpathSync.native(root);
1618
+ } catch {
1619
+ return null;
1620
+ }
1621
+ for (let dir = real;; dir = dirname(dir)) {
1622
+ if (existsSync(join(dir, ".git"))) {
1623
+ const common = gitCommonDirOnDisk(dir);
1624
+ return common ? `${common}\n${relative(dir, real)}` : null;
1625
+ }
1626
+ if (dirname(dir) === dir) return null;
1627
+ }
1628
+ }
1629
+ function findMissBaseline(root, platform, { readState = readWorkspaceState, projectRoots = () => Object.keys(loadConfig()?.projects ?? {}), identity = projectIdentity, sourcesOf = (p, key) => storedSources(p, key, sharedBuildCache()) } = {}) {
1630
+ for (const build of recordedBuilds(readState(root), platform)) {
1631
+ const sources = sourcesOf(platform, build.cacheKey);
1632
+ if (sources) return {
1633
+ fingerprint: build.fingerprint,
1634
+ cacheKey: build.cacheKey,
1635
+ sources,
1636
+ from: "workspace"
1637
+ };
1638
+ }
1639
+ const project = identity(root);
1640
+ if (!project) return null;
1641
+ const others = [];
1642
+ for (const other of projectRoots()) {
1643
+ if (other === root || identity(other) !== project) continue;
1644
+ others.push(...recordedBuilds(readState(other), platform));
1645
+ }
1646
+ for (const build of others.toSorted((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0))) {
1647
+ const sources = sourcesOf(platform, build.cacheKey);
1648
+ if (sources) return {
1649
+ fingerprint: build.fingerprint,
1650
+ cacheKey: build.cacheKey,
1651
+ sources,
1652
+ from: "project"
1653
+ };
1654
+ }
1655
+ return null;
1656
+ }
1657
+ const CATEGORY_RANK = {
1658
+ "native-dependency": 0,
1659
+ "app-config": 1,
1660
+ "config-plugin": 2,
1661
+ package: 3,
1662
+ "native-dir": 4,
1663
+ file: 5,
1664
+ "app-asset": 6,
1665
+ other: 7,
1666
+ "package-scripts": 8,
1667
+ autolinking: 9
1668
+ };
1669
+ function packageIn(path) {
1670
+ return [...path.matchAll(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g)].at(-1)?.[1] ?? null;
1671
+ }
1672
+ function describeChange({ name, change, reasons }) {
1673
+ const pkg = packageIn(name);
1674
+ if (name === "expoConfig" || reasons.includes("expoConfig")) return {
1675
+ category: "app-config",
1676
+ label: "app config changed"
1677
+ };
1678
+ if (pkg && reasons.some((reason) => /^(expo|rncore)Autolinking(Ios|Android)?$/.test(reason))) return {
1679
+ category: "native-dependency",
1680
+ label: `native dependency ${change}: ${pkg}`
1681
+ };
1682
+ if (pkg && reasons.includes("expoConfigPlugins")) return {
1683
+ category: "config-plugin",
1684
+ label: `config plugin ${change}: ${pkg}`
1685
+ };
1686
+ if (/^(expoAutolinkingConfig|rncoreAutolinkingConfig)(:|$)/.test(name)) return {
1687
+ category: "autolinking",
1688
+ label: "autolinking config changed"
1689
+ };
1690
+ if (name.startsWith("package:")) return {
1691
+ category: "package",
1692
+ label: `${name.slice(8)} ${change}`
1693
+ };
1694
+ if (name === "packageJson:scripts") return {
1695
+ category: "package-scripts",
1696
+ label: "package.json scripts changed"
1697
+ };
1698
+ if (reasons.includes("expoConfigExternalFile")) return {
1699
+ category: "app-asset",
1700
+ label: `app asset ${change}: ${name}`
1701
+ };
1702
+ if (reasons.includes("bareNativeDir")) return {
1703
+ category: "native-dir",
1704
+ label: `${name}/ ${change}`
1705
+ };
1706
+ if (reasons.length || name.includes("/") || name.includes(".")) return {
1707
+ category: "file",
1708
+ label: `${name} ${change}`
1709
+ };
1710
+ return {
1711
+ category: "other",
1712
+ label: `${name} ${change}`
1713
+ };
1714
+ }
1715
+ function rekeyPrefix(rekeyedBy) {
1716
+ return rekeyedBy.length ? `${rekeyedBy.join(" and ")} changed native inputs; ` : "";
1717
+ }
1718
+ function missReasonFromChanges({ changes, baseline, rekeyedBy = [] }) {
1719
+ const prefix = rekeyPrefix(rekeyedBy);
1720
+ if (!baseline) return {
1721
+ kind: "no-baseline",
1722
+ summary: `${prefix}no earlier build of this project in the cache to compare with`,
1723
+ changes: [],
1724
+ changeCount: 0,
1725
+ baseline: null,
1726
+ rekeyedBy
1727
+ };
1728
+ if (!changes.length) return {
1729
+ kind: "same-sources",
1730
+ summary: `${prefix}native inputs match the last build; no cached app for this configuration or target`,
1731
+ changes: [],
1732
+ changeCount: 0,
1733
+ baseline,
1734
+ rekeyedBy
1735
+ };
1736
+ const described = changes.map((change, index) => ({
1737
+ change,
1738
+ index,
1739
+ ...describeChange(change)
1740
+ })).toSorted((a, b) => CATEGORY_RANK[a.category] - CATEGORY_RANK[b.category] || a.index - b.index);
1741
+ const dependencyMoved = described.some((entry) => entry.category === "native-dependency");
1742
+ const labels = [...new Set(described.filter((entry) => !(dependencyMoved && entry.category === "autolinking")).map((entry) => entry.label))];
1743
+ const more = labels.length > 1 ? ` (+${labels.length - 1} more)` : "";
1744
+ return {
1745
+ kind: "changed",
1746
+ summary: `${prefix}${labels[0]}${more}`,
1747
+ changes: described.slice(0, MISS_CHANGE_CAP).map(({ change, category }) => ({
1748
+ source: change.name,
1749
+ change: change.change,
1750
+ category
1751
+ })),
1752
+ changeCount: changes.length,
1753
+ baseline,
1754
+ rekeyedBy
1755
+ };
1756
+ }
1757
+ function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDeps, differ = expoFingerprint.diffFingerprints }) {
1758
+ const baseline = findMissBaseline(root, platform, baselineDeps);
1759
+ const changes = baseline ? diffFingerprintSources({
1760
+ previous: baseline.sources,
1761
+ previousHash: baseline.fingerprint,
1762
+ current,
1763
+ differ
1764
+ }) : [];
1765
+ return {
1766
+ reason: missReasonFromChanges({
1767
+ changes,
1768
+ baseline: baseline ? {
1769
+ fingerprint: baseline.fingerprint,
1770
+ cacheKey: baseline.cacheKey,
1771
+ from: baseline.from
1772
+ } : null,
1773
+ rekeyedBy
1774
+ }),
1775
+ changedNames: changes.map((change) => change.name),
1776
+ previousHash: baseline?.fingerprint ?? null
1777
+ };
1778
+ }
1779
+ function skippedMissReason(summary) {
1780
+ return {
1781
+ kind: "cache-skipped",
1782
+ summary,
1783
+ changes: [],
1784
+ changeCount: 0,
1785
+ baseline: null,
1786
+ rekeyedBy: []
1787
+ };
1788
+ }
1789
+ function fingerprintErrorMissReason(message) {
1790
+ return {
1791
+ kind: "fingerprint-error",
1792
+ summary: `fingerprinting failed: ${message}`,
1793
+ changes: [],
1794
+ changeCount: 0,
1795
+ baseline: null,
1796
+ rekeyedBy: []
1797
+ };
1798
+ }
1799
+ //#endregion
1800
+ //#region src/commands/build-plan.ts
1801
+ const DISCARD = {
1802
+ file: "",
1803
+ written: 0,
1804
+ dropped: 0,
1805
+ lastError: null,
1806
+ write: () => true,
1807
+ close: () => ({
1808
+ file: "",
1809
+ written: 0,
1810
+ dropped: 0,
1811
+ lastError: null
1812
+ })
1813
+ };
1814
+ async function providerHit({ root, platform, cacheKey, providerConfig }, deps) {
1815
+ if (!providerConfig) return null;
1816
+ let destinationDir;
1817
+ try {
1818
+ destinationDir = makeTemporaryDirectory(root, "stim-plan-");
1819
+ } catch (error) {
1820
+ deps.note(chalk.yellow(phaseLine("cache", `provider not checked: ${error?.message || error}`)));
1821
+ return null;
1822
+ }
1823
+ try {
1824
+ const found = await resolveTieredBuild({
1825
+ local: {
1826
+ resolve: () => null,
1827
+ store: () => null
1828
+ },
1829
+ loadProvider: () => deps.loadCacheProvider({
1830
+ projectRoot: root,
1831
+ config: providerConfig
1832
+ }),
1833
+ target: {
1834
+ projectRoot: root,
1835
+ platform,
1836
+ key: cacheKey
1837
+ },
1838
+ destinationDir,
1839
+ warn: (_code, message) => deps.note(chalk.yellow(phaseLine("cache", message)))
1840
+ });
1841
+ return found ? found.providerName ?? "the cache provider" : null;
1842
+ } finally {
1843
+ removeTemporaryEntry(destinationDir);
1844
+ }
1845
+ }
1846
+ async function expoRemoteHit(lookup, deps) {
1847
+ if (!lookup.expoRemote) return null;
1848
+ const { root, platform, isExpo, fingerprint } = lookup;
1849
+ const loaded = await deps.loadProjectProvider(root, { isExpo });
1850
+ if (loaded?.unavailable) {
1851
+ deps.note(chalk.yellow(phaseLine("cache", `provider not usable: ${loaded.unavailable}`)));
1852
+ return null;
1853
+ }
1854
+ if (!loaded?.provider) return null;
1855
+ const name = loaded.name ?? "the build cache provider";
1856
+ if (name === "eas") {
1857
+ const auth = deps.checkEasAuth({
1858
+ projectRoot: root,
1859
+ owner: loaded.owner || null
1860
+ });
1861
+ const authNote = easAuthNote(auth);
1862
+ if (authNote) deps.note(chalk.yellow(phaseLine("cache", authNote)));
1863
+ if (auth?.code === "logged-out") return null;
1864
+ }
1865
+ const hit = await deps.resolveRemote({
1866
+ provider: loaded.provider,
1867
+ platform,
1868
+ projectRoot: root,
1869
+ fingerprintHash: fingerprint,
1870
+ runOptions: lookup.expoRemote.runOptions,
1871
+ logWriter: DISCARD
1872
+ });
1873
+ if (hit?.timedOut) deps.note(chalk.yellow(phaseLine("cache", `${name} did not answer; counted as a miss`)));
1874
+ else if (hit?.failed) deps.note(chalk.yellow(phaseLine("cache", `${name} could not be used: ${hit.failed}`)));
1875
+ return hit?.appPath ? name : null;
1876
+ }
1877
+ async function planCachedBuild(lookup, deps) {
1878
+ const { root, platform, isExpo, fingerprint, sources, cacheKey, cachePolicy } = lookup;
1879
+ let cacheHit = false;
1880
+ let provider = null;
1881
+ if (cachePolicy.read) {
1882
+ if (artifactIn(entryDir(platform, cacheKey))) cacheHit = "local";
1883
+ else {
1884
+ provider = (cachePolicy.remote ? await providerHit(lookup, deps) : null) ?? await expoRemoteHit(lookup, deps);
1885
+ if (provider) cacheHit = "remote";
1886
+ }
1887
+ }
1888
+ let prebuild = null;
1889
+ let refusal = null;
1890
+ if (!cacheHit) {
1891
+ prebuild = deps.planPrebuild(root, platform, {
1892
+ isExpo,
1893
+ fingerprint,
1894
+ sources
1895
+ });
1896
+ if (prebuild === "refuse") refusal = staleNativeDirRefusal(platform);
1897
+ }
1898
+ return planPayload(lookup, {
1899
+ fingerprint,
1900
+ cacheKey,
1901
+ cacheHit,
1902
+ provider,
1903
+ cacheSkipped: !cachePolicy.read,
1904
+ prebuild,
1905
+ refusal
1906
+ });
1907
+ }
1908
+ function planPayload({ platform, slot, projectKey }, found) {
1909
+ const { refusal, ...rest } = found;
1910
+ const outcome = refusal ? null : found.cacheHit ? "hit" : "cold";
1911
+ const estimate = outcome ? estimateBuild(readStats().record?.history?.[projectKey], platform, outcome, "prepare") : {
1912
+ expectedMs: null,
1913
+ basis: 0
1914
+ };
1915
+ return {
1916
+ platform,
1917
+ ...slot === "default" ? {} : { slot },
1918
+ ...rest,
1919
+ outcome,
1920
+ expectedMs: estimate.expectedMs,
1921
+ basis: estimate.basis,
1922
+ ...refusal ? { refusal } : {}
1923
+ };
1924
+ }
1925
+ function cacheLine(plan) {
1926
+ if (plan.refusal) return `the build would refuse with ${plan.refusal.code}`;
1927
+ if (plan.cacheHit === "local") return "local cache hit";
1928
+ if (plan.cacheHit === "remote") return `remote cache hit (${plan.provider})`;
1929
+ return `${plan.cacheSkipped ? "cache reads are off" : "cache miss"}: compiles${plan.prebuild === "generate" || plan.prebuild === "regenerate" ? `, ${plan.prebuild}s the native dir` : ""}`;
1930
+ }
1931
+ function printPlan(plan, json) {
1932
+ if (json) {
1933
+ console.log(JSON.stringify(plan));
1934
+ return;
1935
+ }
1936
+ const slot = plan.slot ? ` [${plan.slot}]` : "";
1937
+ console.log(phaseLine("plan", `${plan.platform}${slot} ${shortHash(plan.fingerprint)} -> ${cacheLine(plan)}`));
1938
+ if (plan.refusal) {
1939
+ console.log(phaseLine("error", plan.refusal.message));
1940
+ console.log(phaseLine("remedy", plan.refusal.remedy));
1941
+ return;
1942
+ }
1943
+ 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`)})`));
1944
+ }
1945
+ function refusePlan({ code, message, remedy }, json, lines = []) {
1946
+ console.error(chalk.red(phaseLine("error", message)));
1947
+ for (const line of lines) console.error(chalk.dim(phaseLine("", line)));
1948
+ console.error(chalk.dim(phaseLine("remedy", remedy)));
1949
+ console.error(chalk.red(phaseLine("failed", code)));
1950
+ if (json) console.log(JSON.stringify({
1951
+ code,
1952
+ message,
1953
+ remedy
1954
+ }));
1955
+ process.exitCode = 1;
1956
+ }
1957
+ const PLAN_ONLY_REMEDY = "Drop the flag to plan the owned simulator or emulator build, or run the command without --plan.";
1958
+ function planFlagRefusal(flag) {
1959
+ return {
1960
+ code: "STIM_BAD_ARG",
1961
+ message: `${flag} does not apply to --plan, which predicts the next local build without choosing a device.`,
1962
+ remedy: PLAN_ONLY_REMEDY
1963
+ };
1964
+ }
1965
+ //#endregion
1966
+ 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 };