stim 1.10.0 → 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-DBkeHwpm.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-CrJ6t_bv.mjs → app-install-DHut6JnV.mjs} +1 -1
  7. package/dist/{budget-BP4jZZL4.mjs → budget-De3P2rq_.mjs} +9 -9
  8. package/dist/{dev-client-92U593KT.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-DhTZe7L4.mjs → device-BwH2iI3O.mjs} +47 -9
  14. package/dist/{device-lease-CbEaIk7b.mjs → device-lease-D83nqeMX.mjs} +51 -51
  15. package/dist/{device-pool-Di8Q-9Ro.mjs → device-pool-DHj7cdPy.mjs} +2 -2
  16. package/dist/{device-remote-Dy1jGj3U.mjs → device-remote-BQZSnnmE.mjs} +7 -7
  17. package/dist/{doctor-B-inAJmt.mjs → doctor-D8QdMRMf.mjs} +12 -12
  18. package/dist/{error-diagnostics-xxWKNO3-.mjs → error-diagnostics-UYFSQwRy.mjs} +3 -3
  19. package/dist/{gc-ChPFZMRC.mjs → gc-COZteyiq.mjs} +27 -253
  20. package/dist/{guide-DwmaD_Wa.mjs → guide-NDvTmMi-.mjs} +336 -48
  21. package/dist/{idle-DpRLp5lj.mjs → idle-NETbeNiQ.mjs} +4 -3
  22. package/dist/{in-use-CjUluqIX.mjs → in-use-DVhlSoyn.mjs} +4 -4
  23. package/dist/{ios-Dd-hYJBh.mjs → ios-CQLKpNfr.mjs} +294 -81
  24. package/dist/{ios-B2Fpqvlb.mjs → ios-DArbmwer.mjs} +16 -7
  25. package/dist/{ios-device-CtRdEVjx.mjs → ios-device-CwDZo0X4.mjs} +1 -1
  26. package/dist/{launch-verify-CQS_Kg-C.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-DZjdx8Gq.mjs → ownership-BiojdtAp.mjs} +3 -3
  32. package/dist/{ports-CLSSFOuz.mjs → ports-DwBaRFJs.mjs} +1 -1
  33. package/dist/{reload-DoETJ3rl.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-Ca3487H6.mjs → simslim-CIR3IYJO.mjs} +150 -95
  41. package/dist/{slot-launch-C5cg0fmk.mjs → slot-launch-rggO1Esw.mjs} +2 -2
  42. package/dist/{spawn-claims-CUleC2cM.mjs → spawn-claims-BJwXTMPt.mjs} +24 -1
  43. package/dist/{start-F9gv5dcC.mjs → start-BSsf2tUs.mjs} +4 -4
  44. package/dist/{start-VoFsPSes.mjs → start-i8zc9VmS.mjs} +1 -1
  45. package/dist/{status-DLCD-o3s.mjs → status-CitcBRsC.mjs} +227 -22
  46. package/dist/{status-Dr4fRkym.mjs → status-Q-WDEGCB.mjs} +20 -6
  47. package/dist/{stop-Dj4pfmyv.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-BTUyFfkv.mjs → workspaces-COD2xCz-.mjs} +15 -5
  52. package/dist/{worktree-4aZg2NnI.mjs → worktree-DhxDTK44.mjs} +1 -1
  53. package/dist/{worktree-BMS7GeqB.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-ZvOeTmKb.mjs +0 -3
@@ -1,35 +1,36 @@
1
1
  import { L as deviceSlotFileKey, R as deviceSlotKey, V as parseDeviceSlotOption, W as validateDeviceSlot, f as getProject, k as ownership_claim_exports, l as getConcurrencyLimits, w as upsertProject } from "./config-BEq0Qw_a.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, 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";
4
+ import { O as workspaceDir, c as signalProcessTree, h as recordWorkspaceUse, i as pidExists, j as workspaceLogsDir, m as readWorkspaceState$1, s as resolveProjectMetro, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
5
5
  import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-BlizKDpZ.mjs";
6
6
  import { a as detectIsExpo, i as detectBundleId, n as appProjectProblem, o as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
7
7
  import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-WGdIbi_d.mjs";
8
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
9
8
  import { _ as stepTimer, c as launchErrorReport, g as stepClock, h as shortUdid, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-D5z2_bcF.mjs";
10
- import { A as resolveBuild, C as fingerprintDiffRecord, D as providerDownloadPath, E as prepareProviderDownloadDir, N as untrackedMissLine, O as providerUploadOutcome, P as untrackedNativeFiles, 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";
11
- import { n as NO_BUILD_PROGRESS, s as startBuildProgress } from "./build-progress-BXx-ZBe4.mjs";
12
- import { A as resolveCacheProviderConfig, E as publicUrlSetting, I as tunnelModeSetting, L as unknownSettingKeys, P as settingShapeErrors, S as metroWarmupUrlSetting, U as optimizationBuildProfile, V as artifactCachePolicy, W as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, j as resolveSettings, k as remoteIosSetting, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, z as detectProviders } from "./settings-DOugPlPz.mjs";
13
- import { h as POOL_SETTING_REMEDY, y as parkedMaxSetting } from "./ownership-DZjdx8Gq.mjs";
14
- import { D as parseIosSimulatorApp, f as listIosRuntimes } from "./ios-B2Fpqvlb.mjs";
15
- import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-CjUluqIX.mjs";
9
+ import { A as resolveBuild, C as filesystemBuildCapability, D as providerDownloadPath, E as prepareProviderDownloadDir, F as untrackedNativeFiles, O as providerUploadOutcome, P as untrackedMissLine, 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 } from "./device-lease-D83nqeMX.mjs";
10
+ import { A as resolveCacheProviderConfig, E as publicUrlSetting, I as tunnelModeSetting, L as unknownSettingKeys, P as settingShapeErrors, S as metroWarmupUrlSetting, U as optimizationBuildProfile, V as artifactCachePolicy, W as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, j as resolveSettings, k as remoteIosSetting, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, z as detectProviders } from "./settings-B5oFZEri.mjs";
11
+ import { h as POOL_SETTING_REMEDY, y as parkedMaxSetting } from "./ownership-BiojdtAp.mjs";
12
+ import { D as parseIosSimulatorApp, f as listIosRuntimes } from "./ios-DArbmwer.mjs";
13
+ import { c as startBuildProgress, n as NO_BUILD_PROGRESS } from "./build-progress-CDwogz6R.mjs";
14
+ import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-DVhlSoyn.mjs";
16
15
  import { n as verifyCollectorOwnership } from "./ownership-D2xbLVoz.mjs";
17
- import { A as UPLOAD_TIMEOUT_MS, B as resolveRemote, F as isEasAuthFailureText, I as loadProjectProvider, M as checkEasAuth, N as easAuthNote, O as gateMetroOrigin, P as exitAfterFlush, V as uploadRemote, a as ensureMetroReachable, j as cacheLevel, k as RESOLVE_TIMEOUT_MS, l as remoteIosDeps, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, u as resolveRemoteContext, w as ownedSessionName, z as resolveEasCliBin } from "./device-remote-Dy1jGj3U.mjs";
18
- import { c as clearOtherUserApps, h as iosAppProcess, m as installIosApp, r as DEFAULT_METRO_PORT, u as devClientUrl, v as launchIosApp } from "./app-install-CrJ6t_bv.mjs";
19
- import { c as readMetroRecords, d as verifyLaunch, f as verifyReleaseLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unverifiedLaunchLines, n as LAUNCH_FATAL, o as isBundleProof, r as LAUNCH_UNVERIFIED, s as readCollectorRecords, t as LAUNCH_BUNDLING } from "./launch-verify-CQS_Kg-C.mjs";
20
- import { c as unknownIosRuntimeRefusal, d as isEasBuildFailure, f as resolveEasDevelopmentBuild, r as checkDeviceCapacity, s as unknownIosDeviceTypeRefusal, u as easDeviceBuildRemedy } from "./simslim-Ca3487H6.mjs";
21
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-Di8Q-9Ro.mjs";
22
- import { _ as isPhysicalDeviceRequest, a as swapJsBundle, b as warmMetro, c as runPrebuild, d as ensureOwnedDevice, g as ensureWorkspaceStorageSafely, h as ensureDevServer, l as staleNativeDirRefusal, m as clearIosAdoptionPending, n as pickDevClientScheme, o as planPrebuild, r as schemesFromInfoPlist, s as recordPrebuild, t as devClientScheme, u as ensureBooted, v as launchOutcomeRecord, y as sleep } from "./dev-client-92U593KT.mjs";
23
- import { t as budgetGate } from "./budget-BP4jZZL4.mjs";
24
- import { f as spawnEntry, s as startDevServer } from "./start-F9gv5dcC.mjs";
16
+ import { A as UPLOAD_TIMEOUT_MS, B as resolveRemote, F as isEasAuthFailureText, I as loadProjectProvider, M as checkEasAuth, N as easAuthNote, O as gateMetroOrigin, P as exitAfterFlush, V as uploadRemote, a as ensureMetroReachable, j as cacheLevel, k as RESOLVE_TIMEOUT_MS, l as remoteIosDeps, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, u as resolveRemoteContext, w as ownedSessionName, z as resolveEasCliBin } from "./device-remote-BQZSnnmE.mjs";
17
+ import { c as clearOtherUserApps, h as iosAppProcess, m as installIosApp, r as DEFAULT_METRO_PORT, u as devClientUrl, v as launchIosApp } from "./app-install-DHut6JnV.mjs";
18
+ import { c as readMetroRecords, d as verifyLaunch, f as verifyReleaseLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unverifiedLaunchLines, n as LAUNCH_FATAL, o as isBundleProof, r as LAUNCH_UNVERIFIED, s as readCollectorRecords, t as LAUNCH_BUNDLING } from "./launch-verify-CuPDGXdr.mjs";
19
+ import { c as unknownIosDeviceTypeRefusal, d as easDeviceBuildRemedy, f as isEasBuildFailure, l as unknownIosRuntimeRefusal, m as resolveEasDevelopmentBuild, p as planEasDevelopmentBuild, r as checkDeviceCapacity } from "./simslim-CIR3IYJO.mjs";
20
+ import { d as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-D7yB_AsB.mjs";
21
+ 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-DHj7cdPy.mjs";
22
+ import { C as ensureDevServer, D as sleep, E as launchOutcomeRecord, O as warmMetro, S as clearIosAdoptionPending, T as isPhysicalDeviceRequest, _ as staleNativeDirRefusal, a as refusePlan, c as skippedMissReason, d as schemesFromInfoPlist, g as runPrebuild, h as recordPrebuild, i as printPlan, l as devClientScheme, m as planPrebuild, n as planFlagRefusal, o as explainBuildMiss, p as swapJsBundle, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as pickDevClientScheme, v as ensureBooted, w as ensureWorkspaceStorageSafely, y as ensureOwnedDevice } from "./build-plan-CeAYgKAx.mjs";
23
+ import { t as budgetGate } from "./budget-De3P2rq_.mjs";
24
+ import { f as spawnEntry, s as startDevServer } from "./start-BSsf2tUs.mjs";
25
25
  import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BAgJaLA4.mjs";
26
- import { a as compilationCacheActivityLine, c as readBundleExecutable, l as readBundleId, p as describeDiagnostic, r as buildIos, s as discoverXcodeProject, t as COMPILATION_CACHE_NOT_RUN, u as resolveScheme } from "./xcode-B2tBkf0m.mjs";
27
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-C5cg0fmk.mjs";
28
- import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-xxWKNO3-.mjs";
29
- import { a as iosPoolCandidates, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as iosDeviceProcess, l as localNetworkPending, n as installIosDeviceApp, o as iosPoolNoCandidatesRefusal, r as iosDeviceBounds, s as isWirelessIosDevice, t as awaitIosDeviceLaunch, u as resolveIosPhysicalDevice } from "./ios-device-CtRdEVjx.mjs";
30
- import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-DP4eWqea.mjs";
26
+ import { a as compilationCacheActivityLine, c as readBundleExecutable, l as readBundleId, p as describeDiagnostic, r as buildIos, s as discoverXcodeProject, t as COMPILATION_CACHE_NOT_RUN, u as resolveScheme } from "./xcode-B5SPD--o.mjs";
27
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-rggO1Esw.mjs";
28
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-UYFSQwRy.mjs";
29
+ import { a as iosPoolCandidates, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as iosDeviceProcess, l as localNetworkPending, n as installIosDeviceApp, o as iosPoolNoCandidatesRefusal, r as iosDeviceBounds, s as isWirelessIosDevice, t as awaitIosDeviceLaunch, u as resolveIosPhysicalDevice } from "./ios-device-CwDZo0X4.mjs";
30
+ import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-OF7CD5Dx.mjs";
31
31
  import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
32
32
  import { InvalidArgumentError } from "commander";
33
+ import { LAST_BUILD_KEYS } from "@stim-cli/core/state";
33
34
  import { basename, join } from "node:path";
34
35
  import { X509Certificate } from "node:crypto";
35
36
  import { networkInterfaces, tmpdir } from "node:os";
@@ -235,7 +236,7 @@ function printDiagnostics(note, result) {
235
236
  }
236
237
  //#endregion
237
238
  //#region src/commands/ios/result.ts
238
- function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false, cacheSkipped = false, durationMs = 0, appPath = null, bundleId = null, startedAt, status, errorCode = null }) {
239
+ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false, cacheSkipped = false, durationMs = 0, appPath = null, bundleId = null, startedAt, status, errorCode = null, missReason = null }) {
239
240
  const record = {
240
241
  platform: "ios",
241
242
  fingerprint,
@@ -249,6 +250,7 @@ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false
249
250
  status
250
251
  };
251
252
  if (errorCode) record.errorCode = errorCode;
253
+ if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
252
254
  return record;
253
255
  }
254
256
  function iosFacts({ slot, udid, deviceName, deviceType = null, runtime = null, fingerprint, configuration = null, scheme = null, cacheKey, cacheHit, cacheSkipped = false, compilationCache = COMPILATION_CACHE_NOT_RUN, waitedForBuild = null, appPath, bundleId, installSkipped = false, metroPort, logsDir, durationMs, launched = true, webPreviewUrl = null, lease, devServer = null }) {
@@ -284,7 +286,10 @@ function iosFacts({ slot, udid, deviceName, deviceType = null, runtime = null, f
284
286
  }
285
287
  function writeLastBuild(root, record, { write = writeWorkspaceState } = {}) {
286
288
  try {
287
- write(root, { lastBuild: record });
289
+ write(root, {
290
+ lastBuild: record,
291
+ [LAST_BUILD_KEYS["ios"]]: record
292
+ });
288
293
  } catch {}
289
294
  return record;
290
295
  }
@@ -305,7 +310,7 @@ async function finishIosUpload(uploadPending, remote, phase, note) {
305
310
  }
306
311
  return false;
307
312
  }
308
- function reportIosResult({ slot, d, root, json, release, configuration, buildScheme = null, metroCheck, metroPort, logsDir, device, udid, appPath, bundleId, installSkipped, elapsed, startedAt, storeHash, storeKey, cacheHit, compilationCache, useBuildCache, waitedForBuild, launchState, launchWarning, providerName, closeWriter, webPreviewUrl, lease, recordRun, reclaimed = [], devServer = null }) {
313
+ function reportIosResult({ slot, d, root, json, release, configuration, buildScheme = null, metroCheck, metroPort, logsDir, device, udid, appPath, bundleId, installSkipped, elapsed, startedAt, storeHash, storeKey, cacheHit, missReason, compilationCache, useBuildCache, waitedForBuild, launchState, launchWarning, providerName, closeWriter, webPreviewUrl, lease, recordRun, reclaimed = [], devServer = null }) {
309
314
  const durationMs = elapsed();
310
315
  recordRun({
311
316
  failed: false,
@@ -318,6 +323,7 @@ function reportIosResult({ slot, d, root, json, release, configuration, buildSch
318
323
  cacheKey: storeKey,
319
324
  cacheHit,
320
325
  cacheSkipped: !useBuildCache,
326
+ missReason,
321
327
  durationMs,
322
328
  appPath,
323
329
  bundleId,
@@ -462,6 +468,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
462
468
  let releasedWait = null;
463
469
  let swapFellBack = false;
464
470
  let buildFailure = {};
471
+ let missReason = null;
465
472
  async function resolveInitialFingerprint() {
466
473
  if (easBuild?.ok) {
467
474
  fingerprint = storeHash = easBuild.fingerprint;
@@ -492,18 +499,24 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
492
499
  step("cache-lookup");
493
500
  const fingerprintTimer = stepTimer(d.now);
494
501
  let computedFingerprint;
502
+ let fingerprintError = "no hash";
495
503
  try {
496
504
  const computed = await d.fingerprintProject(root, { platform: "ios" });
497
505
  computedFingerprint = computed?.hash ?? null;
498
506
  fingerprintSources = computed?.sources ?? [];
499
507
  } catch (e) {
500
508
  computedFingerprint = null;
501
- note(chalk.dim(`Fingerprinting failed: ${e?.message || e}`));
509
+ fingerprintError = String(e?.message || e);
510
+ note(chalk.dim(`Fingerprinting failed: ${fingerprintError}`));
502
511
  }
503
512
  if (!computedFingerprint) fail({
504
513
  code: "STIM_NO_FINGERPRINT",
505
514
  message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
506
- remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
515
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry.",
516
+ build: {
517
+ cacheSkipped: !useBuildCache,
518
+ missReason: fingerprintErrorMissReason(fingerprintError)
519
+ }
507
520
  });
508
521
  fingerprint = computedFingerprint;
509
522
  cacheKey = buildCacheKey("ios", fingerprint, {
@@ -535,29 +548,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
535
548
  });
536
549
  const cached = found?.tier === "local" ? found.path : null;
537
550
  cacheHit = cached ? "local" : false;
538
- let missDiff = "";
539
- let missUntracked = null;
540
- if (!cached) {
541
- const lastBuild = d.readWorkspaceState(root)?.lastBuild ?? null;
542
- const miss = describeFingerprintMiss({
543
- platform: "ios",
544
- current: {
545
- hash: fingerprint,
546
- sources: fingerprintSources
547
- },
548
- lastBuild
549
- });
550
- if (miss) {
551
- missDiff = fingerprintDiffSuffix(miss.changed);
552
- logWriter().write(fingerprintDiffRecord({
553
- changed: miss.changed,
554
- previousHash: miss.previousHash,
555
- hash: fingerprint
556
- }));
557
- } else if (useBuildCache) missUntracked = untrackedMissLine(d.untrackedNativeFiles({ projectRoot: root }));
558
- }
559
- phase("fingerprint", `${shortHash(fingerprint)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : cache.disabledByFlag ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}${missDiff}`);
560
- if (missUntracked) note(chalk.dim(phaseLine("fingerprint", missUntracked)));
551
+ phase("fingerprint", `${shortHash(fingerprint)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : cache.disabledByFlag ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
561
552
  if (found?.tier === "provider") {
562
553
  cacheHit = "remote";
563
554
  providerName = found.providerName ?? null;
@@ -742,6 +733,38 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
742
733
  }
743
734
  }
744
735
  }
736
+ function explainMiss(rekeyedBy) {
737
+ if (swapFellBack) missReason = skippedMissReason("the cached app could not be reused, so this run built it fresh");
738
+ else if (!useBuildCache) missReason = skippedMissReason(cache.disabledByFlag ? "cache reuse turned off by --no-build-cache" : "cache reuse off in config");
739
+ else {
740
+ const current = {
741
+ hash: storeHash ?? fingerprint,
742
+ sources: storeSources
743
+ };
744
+ const explained = explainBuildMiss({
745
+ root,
746
+ platform: "ios",
747
+ current,
748
+ rekeyedBy,
749
+ baselineDeps: { readState: d.readWorkspaceState }
750
+ });
751
+ missReason = explained.reason;
752
+ if (explained.previousHash && explained.changedNames.length) logWriter().write(fingerprintDiffRecord({
753
+ changed: explained.changedNames,
754
+ previousHash: explained.previousHash,
755
+ hash: current.hash
756
+ }));
757
+ }
758
+ buildFailure = {
759
+ ...buildFailure,
760
+ missReason
761
+ };
762
+ phase("cache", `miss: ${missReason.summary}`);
763
+ if (missReason.kind === "no-baseline") {
764
+ const untracked = untrackedMissLine(d.untrackedNativeFiles({ projectRoot: root }));
765
+ if (untracked) note(chalk.dim(phaseLine("fingerprint", untracked)));
766
+ }
767
+ }
745
768
  async function buildArtifact() {
746
769
  buildFailure = {
747
770
  fingerprint,
@@ -768,6 +791,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
768
791
  note(chalk.yellow(phaseLine("build", `could not take a build slot: ${e?.message || e}; building anyway`)));
769
792
  }
770
793
  const mutatingSteps = [];
794
+ const rekeyedBy = [];
771
795
  const prebuild = d.planPrebuild(root, "ios", {
772
796
  isExpo,
773
797
  fingerprint,
@@ -845,6 +869,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
845
869
  };
846
870
  note(chalk.yellow(phaseLine("fingerprint", `unavailable after ${mutatingSteps.join(", ")}; the build will be installed but not cached`)));
847
871
  } else if (after.moved) {
872
+ rekeyedBy.push(...mutatingSteps.map((mutation) => mutation === "prebuild" ? mutation : "pod install"));
848
873
  storeHash = after.hash;
849
874
  storeSources = after.sources;
850
875
  storeKey = buildCacheKey("ios", after.hash, {
@@ -853,6 +878,11 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
853
878
  isSimulator: !physical,
854
879
  ...buildProfile ? { buildProfile } : {}
855
880
  });
881
+ buildFailure = {
882
+ ...buildFailure,
883
+ fingerprint: storeHash,
884
+ cacheKey: storeKey
885
+ };
856
886
  note(chalk.dim(phaseLine("fingerprint", `${shortHash(fingerprint)} -> ${shortHash(storeHash)} (after ${mutatingSteps.join(", ")})`)));
857
887
  const late = useBuildCache ? d.resolveBuild("ios", storeKey) : null;
858
888
  if (late) {
@@ -870,6 +900,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
870
900
  }
871
901
  }
872
902
  if (!appPath) {
903
+ explainMiss(rekeyedBy);
873
904
  step("compile");
874
905
  phase("build", `compiling ${configuration || "Debug"} with xcodebuild`);
875
906
  const result = await d.buildIos({
@@ -960,10 +991,17 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
960
991
  hit: cacheHit,
961
992
  providerName: remote?.name ?? providerName,
962
993
  readEnabled: useBuildCache,
994
+ missReason: cacheHit ? null : missReason,
963
995
  waitedForBuild,
964
996
  compilation: compilationCache
965
997
  },
966
- failureFields: buildFailure,
998
+ failureFields: {
999
+ ...buildFailure,
1000
+ fingerprint: storeHash,
1001
+ cacheKey: storeKey,
1002
+ cacheHit,
1003
+ cacheSkipped: !useBuildCache
1004
+ },
967
1005
  completeUploads: async () => {
968
1006
  const uploadWasAbandoned = await finishIosUpload(uploadPending, remote, phase, note);
969
1007
  const outcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
@@ -1499,7 +1537,7 @@ function collectorEntry() {
1499
1537
  }
1500
1538
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
1501
1539
  const COLLECTOR_POLL_MS = 25;
1502
- async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1540
+ async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), alive = pidExists, readState = readWorkspaceState$1, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1503
1541
  const previous = (readState(root)?.collectors)?.[deviceSlotKey("ios", slot)] || null;
1504
1542
  const previousPid = Number(previous?.pid) || null;
1505
1543
  let killed = null;
@@ -1524,7 +1562,7 @@ async function stopPreviousCollector({ root, slot = "default", kill = (pid, sign
1524
1562
  }
1525
1563
  return { killed };
1526
1564
  }
1527
- async function replaceCollector({ root, slot = "default", udid, bundleId, appName, appExecutable, physical = false, payloadUrl = null, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1565
+ async function replaceCollector({ root, slot = "default", udid, bundleId, appName, appExecutable, physical = false, payloadUrl = null, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState$1, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1528
1566
  const { killed } = await stopPreviousCollector({
1529
1567
  root,
1530
1568
  slot,
@@ -1608,7 +1646,7 @@ const DEFAULT_DEPS = {
1608
1646
  resolveProjectMetro,
1609
1647
  startDevServer,
1610
1648
  warmMetro,
1611
- readWorkspaceState,
1649
+ readWorkspaceState: readWorkspaceState$1,
1612
1650
  pidExists,
1613
1651
  getConcurrencyLimits,
1614
1652
  fingerprintProject,
@@ -1861,7 +1899,7 @@ function launchFailureRemedy(remoteDevice, udid, bundleId, logFile) {
1861
1899
  }
1862
1900
  async function finishIosRun({ artifact, d, root, slot, json, release, configuration, buildScheme, isExpo, metroCheck, metroPort, logsDir, logFile, device, udid, physical, wireless, lanAddress, lanOriginUrl, remoteDevice, bootPromise, bootDuration, fail, phase, note, logWriter, elapsed, startedAt, closeWriter, lease, releaseLease, recordRun, reclaimed, devServer, enterPhase }) {
1863
1901
  const { path: appPath, bundleId: initialBundleId, failureFields: buildFailure, cache } = artifact;
1864
- const { hit: cacheHit, providerName, readEnabled: useBuildCache, waitedForBuild, compilation: compilationCache } = cache;
1902
+ const { hit: cacheHit, providerName, readEnabled: useBuildCache, missReason, waitedForBuild, compilation: compilationCache } = cache;
1865
1903
  const storeHash = cache.identity?.fingerprint ?? null;
1866
1904
  const storeKey = cache.identity?.key ?? null;
1867
1905
  const runCommand = nativeRunCommand("ios", slot, {
@@ -2219,6 +2257,7 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2219
2257
  storeHash,
2220
2258
  storeKey,
2221
2259
  cacheHit,
2260
+ missReason,
2222
2261
  compilationCache,
2223
2262
  useBuildCache,
2224
2263
  waitedForBuild,
@@ -2236,6 +2275,144 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2236
2275
  };
2237
2276
  }
2238
2277
  //#endregion
2278
+ //#region src/commands/ios/next-build.ts
2279
+ function note(line) {
2280
+ console.error(line);
2281
+ }
2282
+ function runOnlyFlag(opts) {
2283
+ if (isPhysicalDeviceRequest(opts.device)) return "--device";
2284
+ if (opts.remote) return "--remote";
2285
+ if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
2286
+ if (opts.simulatorApp !== void 0) return "--simulator-app";
2287
+ if (opts.metroCheck === false) return "--no-metro-check";
2288
+ return null;
2289
+ }
2290
+ async function planIos(opts, d, schemeRefusal) {
2291
+ const json = Boolean(opts.json);
2292
+ const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
2293
+ const flag = runOnlyFlag(opts);
2294
+ if (flag) return refuse(planFlagRefusal(flag));
2295
+ const root = d.findProjectRoot(process.cwd());
2296
+ if (!root) return refuse(NO_PROJECT_REFUSAL);
2297
+ const projectProblem = appProjectProblem(root);
2298
+ if (projectProblem) return refuse({
2299
+ code: "STIM_NO_PROJECT",
2300
+ ...projectProblem
2301
+ });
2302
+ const settingsContext = {
2303
+ projectPath: root,
2304
+ gitCommonDir: d.gitCommonDir(root),
2305
+ repoRoot: d.repoRoot(root)
2306
+ };
2307
+ const settings = d.resolveSettings(settingsContext);
2308
+ const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
2309
+ if (shapeError) return refuse({
2310
+ code: "STIM_BAD_ARG",
2311
+ message: shapeError,
2312
+ remedy: SETTING_SHAPE_REMEDY
2313
+ }, moreShapeErrors);
2314
+ let optimizations;
2315
+ try {
2316
+ optimizations = resolveOptimizations(settings);
2317
+ } catch (error) {
2318
+ return refuse({
2319
+ code: "STIM_BAD_ARG",
2320
+ message: error.message,
2321
+ remedy: SETTING_SHAPE_REMEDY
2322
+ });
2323
+ }
2324
+ const cacheProviderError = cacheProviderSettingError(settings);
2325
+ if (cacheProviderError) note(chalk.yellow(phaseLine("cache", `${cacheProviderError} Using the local cache.`)));
2326
+ const slot = opts.slot ?? "default";
2327
+ const target = {
2328
+ platform: "ios",
2329
+ slot,
2330
+ root,
2331
+ projectKey: statsProjectKey({
2332
+ root,
2333
+ commonDir: settingsContext.gitCommonDir,
2334
+ repoRoot: settingsContext.repoRoot
2335
+ })
2336
+ };
2337
+ const isExpo = d.detectIsExpo(root);
2338
+ const modelRefusal = deviceModelRefusal({
2339
+ slot,
2340
+ deviceTypeFlag: opts.deviceType,
2341
+ runtimeFlag: opts.runtime,
2342
+ deviceType: resolveDeviceType(opts.deviceType, settings),
2343
+ runtime: resolveRuntime(opts.runtime, settings),
2344
+ physical: false,
2345
+ remoteBackend: remoteIosSetting(settings),
2346
+ listRuntimes: d.listIosRuntimes
2347
+ });
2348
+ if (modelRefusal) return refuse(modelRefusal);
2349
+ if (opts.easProfile !== void 0) {
2350
+ const eas = await planEasDevelopmentBuild({
2351
+ root,
2352
+ platform: "ios",
2353
+ profile: opts.easProfile,
2354
+ note,
2355
+ isExpo,
2356
+ selectors: [opts.scheme, opts.configuration],
2357
+ buildCache: opts.buildCache
2358
+ });
2359
+ if (!eas.ok) return refuse(eas);
2360
+ return printPlan(planPayload(target, {
2361
+ fingerprint: eas.fingerprint,
2362
+ cacheKey: eas.cacheKey,
2363
+ cacheHit: eas.buildId ? "remote" : false,
2364
+ provider: "eas",
2365
+ cacheSkipped: false,
2366
+ prebuild: null,
2367
+ refusal: eas.missing
2368
+ }), json);
2369
+ }
2370
+ const refusal = schemeRefusal(root, opts.scheme, isExpo);
2371
+ if (refusal) return refuse({
2372
+ code: refusal.code,
2373
+ message: refusal.message ?? "",
2374
+ remedy: refusal.remedy ?? ""
2375
+ });
2376
+ const configuration = resolveConfiguration(opts.configuration, settings);
2377
+ const buildProfile = optimizationBuildProfile("ios", optimizations);
2378
+ const cachePolicy = artifactCachePolicy(optimizations, opts.buildCache !== false, isReleaseConfiguration(configuration));
2379
+ let fingerprint;
2380
+ try {
2381
+ fingerprint = await d.fingerprintProject(root, { platform: "ios" });
2382
+ } catch (error) {
2383
+ note(chalk.dim(`Fingerprinting failed: ${error?.message || error}`));
2384
+ }
2385
+ if (!fingerprint?.hash) return refuse({
2386
+ code: "STIM_NO_FINGERPRINT",
2387
+ message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
2388
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
2389
+ });
2390
+ const cacheKey = buildCacheKey("ios", fingerprint.hash, {
2391
+ scheme: opts.scheme,
2392
+ ...configuration ? { configuration } : {},
2393
+ isSimulator: true,
2394
+ ...buildProfile ? { buildProfile } : {}
2395
+ });
2396
+ const plan = await planCachedBuild({
2397
+ ...target,
2398
+ isExpo,
2399
+ fingerprint: fingerprint.hash,
2400
+ sources: fingerprint.sources ?? [],
2401
+ cacheKey,
2402
+ cachePolicy,
2403
+ providerConfig: d.resolveCacheProviderConfig(settingsContext),
2404
+ expoRemote: cachePolicy.remote && !buildProfile && !opts.scheme ? { runOptions: configuration ? { configuration } : null } : null
2405
+ }, {
2406
+ loadCacheProvider: d.loadCacheProvider,
2407
+ loadProjectProvider: d.loadProjectProvider,
2408
+ checkEasAuth: d.checkEasAuth,
2409
+ resolveRemote: d.resolveRemote,
2410
+ planPrebuild: d.planPrebuild,
2411
+ note
2412
+ });
2413
+ printPlan(plan, json);
2414
+ }
2415
+ //#endregion
2239
2416
  //#region src/commands/ios.ts
2240
2417
  function writeNote(line) {
2241
2418
  console.error(line);
@@ -2247,16 +2424,28 @@ function iosCommand(program) {
2247
2424
  registerIos(program);
2248
2425
  }
2249
2426
  function registerIos(program, deps = {}) {
2250
- program.command("ios").description("Build (or restore from the fingerprint cache), install and launch this workspace's app on its owned simulator, 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("--scheme <name>", "Shared Xcode app scheme to build; overrides automatic scheme selection, not the app URL scheme").option("--no-metro-check", "Skip the \"is this workspace's dev server running?\" check: do not start it, and build anyway").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("--configuration <name>", "Xcode configuration to build (e.g. Release). A non-Debug configuration embeds the JS bundle and skips Metro entirely. Overrides the ios.configuration setting. Default: Debug").option("--device-type <name>", "Simulator model to create this workspace's owned sim as, exactly as `xcrun simctl list devicetypes` names it (e.g. \"iPad Pro 13-inch (M5)\"). Overrides the ios.deviceType setting for this invocation. A model no installed runtime can create refuses with STIM_BAD_ARG and prints the models they do offer.").option("--runtime <version>", "Simulator runtime to create this workspace's owned sim on, as a version (\"18.5\") or a runtime's full name (\"iOS 18.5\"); nothing else matches. Overrides the ios.runtime setting for this invocation. An unknown version refuses with STIM_BAD_ARG and prints the installed runtimes.").option("--simulator-app <app>", "Open the owned simulator in xcode, siniulator, or stim-desktop for this run, overriding the machine iosSimulatorApp setting. Also opens an already running simulator; local simulators only.").option("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. The phone can be cabled or paired over Wi-Fi. With no UDID, the first cabled device this workspace can lease is used, waiting for one that is busy; a Wi-Fi device is used only when no cabled one is connected and ready. In Debug the app is wired to this workspace's Metro over the LAN. 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) => {
2427
+ program.command("ios").description("Build (or restore from the fingerprint cache), install and launch this workspace's app on its owned simulator, 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("--scheme <name>", "Shared Xcode app scheme to build; overrides automatic scheme selection, not the app URL scheme").option("--no-metro-check", "Skip the \"is this workspace's dev server running?\" check: do not start it, and build anyway").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("--configuration <name>", "Xcode configuration to build (e.g. Release). A non-Debug configuration embeds the JS bundle and skips Metro entirely. Overrides the ios.configuration setting. Default: Debug").option("--device-type <name>", "Simulator model to create this workspace's owned sim as, exactly as `xcrun simctl list devicetypes` names it (e.g. \"iPad Pro 13-inch (M5)\"). Overrides the ios.deviceType setting for this invocation. A model no installed runtime can create refuses with STIM_BAD_ARG and prints the models they do offer.").option("--runtime <version>", "Simulator runtime to create this workspace's owned sim on, as a version (\"18.5\") or a runtime's full name (\"iOS 18.5\"); nothing else matches. Overrides the ios.runtime setting for this invocation. An unknown version refuses with STIM_BAD_ARG and prints the installed runtimes.").option("--simulator-app <app>", "Open the owned simulator in xcode, siniulator, or stim-desktop for this run, overriding the machine iosSimulatorApp setting. Also opens an already running simulator; local simulators only.").option("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. The phone can be cabled or paired over Wi-Fi. With no UDID, the first cabled device this workspace can lease is used, waiting for one that is busy; a Wi-Fi device is used only when no cabled one is connected and ready. In Debug the app is wired to this workspace's Metro over the LAN. 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) => {
2251
2428
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2252
2429
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2253
2430
  }).option("--wait <seconds>", "How long to wait for another workspace to release the phone it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Only with --device.").option("--no-wait", "Install on a phone 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) => {
2431
+ if (opts.plan) {
2432
+ const d = {
2433
+ ...DEFAULT_DEPS,
2434
+ ...deps
2435
+ };
2436
+ await planIos(opts, d, (root, scheme, isExpo) => explicitSchemeRefusal(root, scheme, isExpo, d));
2437
+ return;
2438
+ }
2254
2439
  const root = (deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd());
2255
2440
  const run = (progress) => runIos({
2256
2441
  ...opts,
2257
2442
  waitConflict: waitFlagConflict(process.argv)
2258
2443
  }, deps, progress);
2259
- const completion = root ? await withWorkspaceProcessLock(workspaceDir(root), "native-run", async (claim) => {
2444
+ const completion = root ? await withNativeBuildRun(root, {
2445
+ command: "ios",
2446
+ platform: "ios",
2447
+ slot: opts.slot ?? "default"
2448
+ }, async (claim) => {
2260
2449
  recordWorkspaceUse(root);
2261
2450
  const progress = startBuildProgress({
2262
2451
  root,
@@ -2270,15 +2459,22 @@ function registerIos(program, deps = {}) {
2270
2459
  } finally {
2271
2460
  progress.clear();
2272
2461
  }
2273
- }, {
2274
- external: true,
2275
- waitMs: 18e5,
2276
- declareSpawns: true
2277
- }) : await run();
2278
- if (!completion) process.exit(1);
2462
+ }, { write: (line) => writeNote(chalk.dim(phaseLine("lock", line))) }) : await run();
2463
+ if (!completion) process.exit(runCancellation() ? 130 : 1);
2279
2464
  else if (completion.uploadsAbandoned) exitAfterFlush(0);
2280
2465
  });
2281
2466
  }
2467
+ function unlessCancelled(result) {
2468
+ if (!result.ok || !runCancellation()) return result;
2469
+ return {
2470
+ ok: false,
2471
+ failure: {
2472
+ code: "STIM_CANCELLED",
2473
+ message: "before install"
2474
+ },
2475
+ compilationCache: result.artifact.cache.compilation
2476
+ };
2477
+ }
2282
2478
  function explicitSchemeRefusal(root, scheme, isExpo, d) {
2283
2479
  if (scheme === void 0) return null;
2284
2480
  if (!scheme.trim()) return {
@@ -2412,6 +2608,15 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2412
2608
  let compilationCache = COMPILATION_CACHE_NOT_RUN;
2413
2609
  let reclaimed = [];
2414
2610
  const fail = ({ code, message, remedy = null, lines = [], logPath = null, build = null, lease }) => {
2611
+ ({code, message, remedy, lines} = cancelledFailure("ios", {
2612
+ code,
2613
+ message
2614
+ }) ?? {
2615
+ code,
2616
+ message,
2617
+ remedy,
2618
+ lines
2619
+ });
2415
2620
  releaseLease();
2416
2621
  if (message) note(chalk.red(phaseLine("error", message)));
2417
2622
  for (const line of lines) note(chalk.dim(phaseLine("", line)));
@@ -2794,7 +2999,6 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2794
2999
  }
2795
3000
  let artifact = null;
2796
3001
  try {
2797
- const bootTimer = stepTimer(d.now);
2798
3002
  const boot = () => physicalDevice ? Promise.resolve({
2799
3003
  ok: true,
2800
3004
  udid: physicalDevice.udid
@@ -2807,23 +3011,28 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2807
3011
  ok: false,
2808
3012
  reason: String(e?.message || e)
2809
3013
  }));
2810
- bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
2811
- root,
2812
- platform: "ios",
2813
- sessionName: ownedSessionName(remoteDevice.ctx.label),
2814
- startedAt,
2815
- boot,
2816
- createdSessionId: remoteDevice.createdSessionId,
2817
- abandonCreatedSession: remoteDevice.abandonCreatedSession,
2818
- webPreviewUrl: remoteDevice.webPreviewUrl,
2819
- writeState: d.writeWorkspaceState,
2820
- register: registerProject
2821
- }) : boot()).then((result) => {
2822
- bootDuration = bootTimer();
2823
- return result;
2824
- });
2825
- udid = device.deviceUdid ?? (await bootPromise)?.udid ?? "";
2826
- const acquiredArtifact = await acquireIosArtifact({
3014
+ const startBoot = () => {
3015
+ const bootTimer = stepTimer(d.now);
3016
+ bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
3017
+ root,
3018
+ platform: "ios",
3019
+ sessionName: ownedSessionName(remoteDevice.ctx.label),
3020
+ startedAt: new Date(d.now()).toISOString(),
3021
+ boot,
3022
+ createdSessionId: remoteDevice.createdSessionId,
3023
+ abandonCreatedSession: remoteDevice.abandonCreatedSession,
3024
+ webPreviewUrl: remoteDevice.webPreviewUrl,
3025
+ writeState: d.writeWorkspaceState,
3026
+ register: registerProject
3027
+ }) : boot()).then((result) => {
3028
+ bootDuration = bootTimer();
3029
+ return result;
3030
+ });
3031
+ return bootPromise.then((result) => result?.udid ?? "");
3032
+ };
3033
+ const localBoot = remoteDevice ? null : startBoot();
3034
+ udid = device.deviceUdid ?? await localBoot ?? "";
3035
+ const acquiredArtifact = unlessCancelled(await acquireIosArtifact({
2827
3036
  root,
2828
3037
  logFile,
2829
3038
  udid,
@@ -2855,13 +3064,17 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2855
3064
  stats,
2856
3065
  step: progress.step
2857
3066
  }
2858
- }, d);
3067
+ }, d));
2859
3068
  if (!acquiredArtifact.ok) {
2860
3069
  compilationCache = acquiredArtifact.compilationCache;
2861
3070
  return fail(acquiredArtifact.failure);
2862
3071
  }
2863
3072
  artifact = acquiredArtifact.artifact;
2864
3073
  compilationCache = artifact.cache.compilation;
3074
+ if (!localBoot) {
3075
+ progress.step("install");
3076
+ udid = await startBoot();
3077
+ }
2865
3078
  if (physicalDevice) {
2866
3079
  const acquired = await d.acquireRunLease({
2867
3080
  root,