stim 1.2.0 → 1.3.1

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 (59) hide show
  1. package/README.md +34 -3
  2. package/dist/{android-Bcmev2s2.mjs → android-CT-PkXUh.mjs} +157 -62
  3. package/dist/{android-cas-D6HPZ6UQ.mjs → android-cas-DXorPCi_.mjs} +5 -5
  4. package/dist/android-hMj83VpN.mjs +1374 -0
  5. package/dist/{app-install-NFxUahnH.mjs → app-install-DIp9riNx.mjs} +6 -6
  6. package/dist/{build-slots-D0b6sJyo.mjs → build-slots-C4AouyNz.mjs} +2 -2
  7. package/dist/{cache-manifest-Cu4_Znw9.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +13 -13
  10. package/dist/collector-run.d.mts +3 -1
  11. package/dist/collector-run.mjs +33 -8
  12. package/dist/{config-CMtIaTk9.mjs → config-CcefMY2Q.mjs} +93 -12
  13. package/dist/{deps-BLBsTNDS.mjs → deps-0Yt0Inly.mjs} +1 -1
  14. package/dist/{dev-client-RpvXxCXV.mjs → dev-client-VfSZNLT4.mjs} +186 -9
  15. package/dist/{device-CoCPmazS.mjs → device-Bbv45Z9Z.mjs} +19 -11
  16. package/dist/{device-lease-Dpik41RY.mjs → device-lease-CJwK7OZr.mjs} +107 -83
  17. package/dist/{device-pool-B5pEVa3t.mjs → device-pool-CcBNTvM7.mjs} +18 -12
  18. package/dist/{device-remote-E81KAdXf.mjs → device-remote-DAA0Zm6Z.mjs} +7 -7
  19. package/dist/{doctor-CqPeMsSm.mjs → doctor-B4EITgQB.mjs} +11 -12
  20. package/dist/{doctor-D_CX7ib5.mjs → doctor-BueDfLYG.mjs} +4 -4
  21. package/dist/{error-diagnostics-Dw8Gn3yw.mjs → error-diagnostics-DwhNBFDU.mjs} +62 -52
  22. package/dist/{gc-B7KkDinF.mjs → gc-BRu62v4o.mjs} +66 -57
  23. package/dist/{guide-BS2Xc24o.mjs → guide-D95E0u3i.mjs} +307 -74
  24. package/dist/{ios-B7KLgIp5.mjs → ios-DAju3TVy.mjs} +183 -54
  25. package/dist/{ios-D1h1Di39.mjs → ios-Deh5js5V.mjs} +78 -40
  26. package/dist/{ios-device-xNt0Lg6v.mjs → ios-device-CWOM_S44.mjs} +1 -1
  27. package/dist/{logs-DcX9AvRF.mjs → logs-CAdwW6c2.mjs} +34 -6
  28. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-Cdp5aaCH.mjs} +23 -6
  29. package/dist/{metro-COslRK_F.mjs → metro-CORqXyTL.mjs} +2 -2
  30. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  31. package/dist/{ownership-CGPart6T.mjs → ownership-8O073GIC.mjs} +3 -3
  32. package/dist/{ownership-CYOyqxUo.mjs → ownership-DXcol4oU.mjs} +3 -3
  33. package/dist/{ownership-claim-7zRxxTfZ.mjs → ownership-claim-BtPtJVmq.mjs} +38 -23
  34. package/dist/{project-BBTVABUz.mjs → project-fKmuIaMI.mjs} +2 -2
  35. package/dist/{reclaim-C5Jmuahx.mjs → reclaim-CUmwucWG.mjs} +80 -74
  36. package/dist/{reload-Z8nKifAk.mjs → reload-CWpXoOgN.mjs} +32 -24
  37. package/dist/{server-bare-DEk37Mz2.mjs → server-bare-DEzPZLvf.mjs} +2 -2
  38. package/dist/server-expo-44em5Pmg.mjs +2 -0
  39. package/dist/{server-expo-C3ktAwUG.mjs → server-expo-DizT4OMk.mjs} +4 -4
  40. package/dist/{settings-DwU1_U_q.mjs → settings-DEJf6_kJ.mjs} +2 -2
  41. package/dist/slot-launch-CZklomj6.mjs +19 -0
  42. package/dist/{start-BrY4EuIg.mjs → start-B5G4NjiA.mjs} +11 -11
  43. package/dist/{state-CZ-ER-8d.mjs → state-B3XA2wp2.mjs} +9 -9
  44. package/dist/{stats-BCWEVLrM.mjs → stats-BR-kwj0B.mjs} +1 -1
  45. package/dist/{stats-BsF15Bti.mjs → stats-CaaQr4yN.mjs} +3 -3
  46. package/dist/{status-DFI1Hfpg.mjs → status-CI9clRSb.mjs} +55 -24
  47. package/dist/{stop-B7DR4awK.mjs → stop-CQhffpmX.mjs} +2 -2
  48. package/dist/{stop-Btg770vV.mjs → stop-CjlgDk0a.mjs} +93 -42
  49. package/dist/supervisor-run.d.mts +1 -2
  50. package/dist/supervisor-run.mjs +5 -5
  51. package/dist/{remote-cache-DDmJ1Roq.mjs → teardown--gESs9ck.mjs} +985 -887
  52. package/dist/{workspace-process-lock-BKfdys1q.mjs → workspace-process-lock-IBj1Towr.mjs} +1 -1
  53. package/dist/{worktree-BPTbI-LN.mjs → worktree-DtQGO9-s.mjs} +121 -111
  54. package/dist/{worktree-C79xx_kM.mjs → worktree-Eq6TLH8N.mjs} +1 -1
  55. package/dist/{xcode-DeKCribl.mjs → xcode-xnxyvJkD.mjs} +159 -96
  56. package/package.json +5 -5
  57. package/dist/android-K63_0UfF.mjs +0 -727
  58. package/dist/build-lock-BsVoLDaq.mjs +0 -645
  59. package/dist/server-expo-Drd7ksHu.mjs +0 -2
@@ -1,35 +1,37 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { S as upsertProject, f as getProject, l as getConcurrencyLimits } from "./config-CMtIaTk9.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-DcAtEx_K.mjs";
4
- import { D as workspaceLogsDir, T as workspaceDir, _ as writeWorkspaceState, g as writeWorkspaceLaunch, n as MODE_EXPO, p as readWorkspaceState, t as MODE_BARE } from "./state-CZ-ER-8d.mjs";
5
- import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-C79xx_kM.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, r as detectBundleId, t as appProjectProblem } from "./project-BBTVABUz.mjs";
7
- import { i as resolveProjectMetro, r as pidExists } from "./metro-COslRK_F.mjs";
8
- import { n as verifyCollectorOwnership } from "./ownership-CGPart6T.mjs";
9
- import { a as easAuthNote, c as loadProjectProvider, d as resolveEasCliBin, f as resolveRemote, i as checkEasAuth, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as uploadRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS, v as POOL_SETTING_REMEDY, x as parkedMaxSetting } from "./remote-cache-DDmJ1Roq.mjs";
10
- import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, O as remoteIosSetting, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, k as resolveCacheProviderConfig, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, x as metroWarmupUrlSetting } from "./settings-DwU1_U_q.mjs";
2
+ import { I as deviceSlotKey, S as upsertProject, V as validateDeviceSlot, f as getProject, l as getConcurrencyLimits } from "./config-CcefMY2Q.mjs";
3
+ import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
+ import { D as workspaceLogsDir, T as workspaceDir, _ as writeWorkspaceState, g as writeWorkspaceLaunch, n as MODE_EXPO, p as readWorkspaceState, t as MODE_BARE } from "./state-B3XA2wp2.mjs";
5
+ import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
6
+ import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, r as detectBundleId, t as appProjectProblem } from "./project-fKmuIaMI.mjs";
7
+ import { i as resolveProjectMetro, r as pidExists } from "./metro-CORqXyTL.mjs";
8
+ import { n as verifyCollectorOwnership } from "./ownership-8O073GIC.mjs";
11
9
  import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, m as shortUdid, n as appReadinessMessage, p as shortHash } from "./command-output-8wHMEde0.mjs";
12
- import { C as launchIosApp, E as unverifiedLaunchLines, O as verifyLaunch, T as readMetroRecords, b as isBundleProof, c as RELEASE_VERIFY_WAIT_MS, f as clearOtherUserApps, i as LAUNCH_BUNDLING, k as verifyReleaseLaunch, m as devClientUrl, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, v as installIosApp, w as readCollectorRecords, y as iosAppProcess } from "./app-install-NFxUahnH.mjs";
13
- import { f as listIosRuntimes } from "./ios-D1h1Di39.mjs";
14
- import { C as untrackedNativeFiles, S as untrackedMissLine, _ as providerUploadOutcome, b as storeBuild, c as buildCacheKey, d as filesystemBuildCapability, f as fingerprintDiffRecord, g as providerDownloadPath, h as prepareProviderDownloadDir, i as releaseBuildLock, l as describeFingerprintMiss, m as fingerprintProject, n as acquireBuildLock, o as takeoverLine, p as fingerprintDiffSuffix, s as waitForBuild, t as WAIT_CEILING_MS, v as refingerprintAfterMutation, y as resolveBuild } from "./build-lock-BsVoLDaq.mjs";
15
- import { r as claimFailure } from "./ownership-claim-7zRxxTfZ.mjs";
16
- 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-B5pEVa3t.mjs";
17
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-D0b6sJyo.mjs";
18
- import { a as swapJsBundle, c as ensureWorkspaceStorageSafely, d as noMetroMessage, f as noMetroRemedy, h as warmMetro, l as gateShouldRetry, m as sleep, n as pickDevClientScheme, o as needsPrebuild, p as resolveMetroWithRetry, r as schemesFromInfoPlist, s as runPrebuild, t as devClientScheme, u as launchOutcomeRecord } from "./dev-client-RpvXxCXV.mjs";
19
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BCWEVLrM.mjs";
20
- import { T as unknownIosRuntimeRefusal, b as ensureBooted, c as discoverXcodeProject, d as resolveScheme, i as buildIos, l as readBundleExecutable, m as describeDiagnostic, n as COMPILATION_CACHE_UNAVAILABLE, o as compilationCacheActivityLine, t as COMPILATION_CACHE_NOT_RUN, u as readBundleId, v as checkDeviceCapacity, w as unknownIosDeviceTypeRefusal, x as ensureOwnedDevice, y as clearIosAdoptionPending } from "./xcode-DeKCribl.mjs";
21
- import { a as ensureMetroReachable, b as ownedSessionName, c as remoteIosDeps, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, w as gateMetroOrigin } from "./device-remote-E81KAdXf.mjs";
10
+ import { r as claimFailure } from "./ownership-claim-BtPtJVmq.mjs";
11
+ import { $ as providerDownloadPath, G as buildCacheKey, J as filesystemBuildCapability, K as describeFingerprintMiss, Q as prepareProviderDownloadDir, R as WAIT_CEILING_MS, U as takeoverLine, V as releaseBuildLock, W as waitForBuild, X as fingerprintDiffSuffix, Y as fingerprintDiffRecord, Z as fingerprintProject, at as untrackedMissLine, et as providerUploadOutcome, nt as resolveBuild, ot as untrackedNativeFiles, rt as storeBuild, tt as refingerprintAfterMutation, z as acquireBuildLock } from "./android-hMj83VpN.mjs";
12
+ import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, O as remoteIosSetting, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, k as resolveCacheProviderConfig, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, x as metroWarmupUrlSetting } from "./settings-DEJf6_kJ.mjs";
13
+ import { C as uploadRemote, S as resolveRemote, _ as isEasAuthFailureText, a as POOL_SETTING_REMEDY, c as parkedMaxSetting, d as RESOLVE_TIMEOUT_MS, f as UPLOAD_TIMEOUT_MS, g as exitAfterFlush, h as easAuthNote, m as checkEasAuth, p as cacheLevel, v as loadProjectProvider, x as resolveEasCliBin } from "./teardown--gESs9ck.mjs";
14
+ import { _ as easDeviceBuildRemedy, a as swapJsBundle, c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, h as sleep, l as gateShouldRetry, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, r as schemesFromInfoPlist, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-VfSZNLT4.mjs";
15
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-IBj1Towr.mjs";
16
+ import { C as launchIosApp, E as unverifiedLaunchLines, O as verifyLaunch, T as readMetroRecords, b as isBundleProof, c as RELEASE_VERIFY_WAIT_MS, f as clearOtherUserApps, i as LAUNCH_BUNDLING, k as verifyReleaseLaunch, m as devClientUrl, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, v as installIosApp, w as readCollectorRecords, y as iosAppProcess } from "./app-install-DIp9riNx.mjs";
17
+ import { f as listIosRuntimes } from "./ios-Deh5js5V.mjs";
18
+ 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-CcBNTvM7.mjs";
19
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C4AouyNz.mjs";
20
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BR-kwj0B.mjs";
21
+ import { E as unknownIosRuntimeRefusal, S as ensureOwnedDevice, T as unknownIosDeviceTypeRefusal, b as clearIosAdoptionPending, c as discoverXcodeProject, d as resolveScheme, i as buildIos, l as readBundleExecutable, m as describeDiagnostic, n as COMPILATION_CACHE_UNAVAILABLE, o as compilationCacheActivityLine, t as COMPILATION_CACHE_NOT_RUN, u as readBundleId, x as ensureBooted, y as checkDeviceCapacity } from "./xcode-xnxyvJkD.mjs";
22
+ import { a as ensureMetroReachable, b as ownedSessionName, c as remoteIosDeps, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, w as gateMetroOrigin } from "./device-remote-DAA0Zm6Z.mjs";
23
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-CZklomj6.mjs";
22
24
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
23
- import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Dw8Gn3yw.mjs";
24
- import { a as iosDeviceProcess, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as installIosDeviceApp, l as localNetworkPending, n as LAUNCH_PROBE_TIMEOUT_MS, o as iosPoolCandidates, r as awaitIosDeviceLaunch, s as iosPoolNoCandidatesRefusal, t as DEVICECTL_INSTALL_TIMEOUT_MS, u as resolveIosPhysicalDevice } from "./ios-device-xNt0Lg6v.mjs";
25
- import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-BLBsTNDS.mjs";
25
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-DwhNBFDU.mjs";
26
+ import { a as iosDeviceProcess, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as installIosDeviceApp, l as localNetworkPending, n as LAUNCH_PROBE_TIMEOUT_MS, o as iosPoolCandidates, r as awaitIosDeviceLaunch, s as iosPoolNoCandidatesRefusal, t as DEVICECTL_INSTALL_TIMEOUT_MS, u as resolveIosPhysicalDevice } from "./ios-device-CWOM_S44.mjs";
27
+ import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-0Yt0Inly.mjs";
26
28
  import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
27
29
  import { InvalidArgumentError } from "commander";
28
30
  import { X509Certificate } from "node:crypto";
29
31
  import { basename, join } from "node:path";
30
32
  import { networkInterfaces, tmpdir } from "node:os";
31
- import chalk from "chalk";
32
33
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
34
+ import chalk from "chalk";
33
35
  //#region src/engine/ios-profile.ts
34
36
  const TAG = /<(\/?)\s*([A-Za-z0-9_]+)(?:\s[^>]*?)?(\/?)>/g;
35
37
  const ENTITIES = {
@@ -633,7 +635,12 @@ function resolveDeviceType(flag, settings) {
633
635
  function resolveRuntime(flag, settings) {
634
636
  return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || iosStringSetting(settings, "runtime");
635
637
  }
636
- function deviceModelRefusal({ deviceTypeFlag, runtimeFlag, deviceType, runtime, physical, remoteBackend, listRuntimes }) {
638
+ function deviceModelRefusal({ slot, deviceTypeFlag, runtimeFlag, deviceType, runtime, physical, remoteBackend, listRuntimes }) {
639
+ if (slot && slot !== "default" && remoteBackend) return {
640
+ code: "STIM_BAD_ARG",
641
+ message: "Named slots currently support local simulators and physical devices.",
642
+ remedy: "Use the default slot for a remote session."
643
+ };
637
644
  if (typeof deviceTypeFlag === "string" && deviceTypeFlag.trim() === "") return {
638
645
  code: "STIM_BAD_ARG",
639
646
  message: "--device-type was given an empty name.",
@@ -699,22 +706,22 @@ function printDiagnostics(note, result) {
699
706
  }
700
707
  //#endregion
701
708
  //#region src/commands/ios/collector.ts
702
- function collectorLogFile(root) {
703
- return join(workspaceLogsDir(root), `collector-ios.log`);
709
+ function collectorLogFile(root, slot) {
710
+ return join(workspaceLogsDir(root), `collector-${deviceSlotKey("ios", slot)}.log`);
704
711
  }
705
712
  function collectorEntry() {
706
713
  return spawnEntry("collector-run");
707
714
  }
708
715
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
709
716
  const COLLECTOR_POLL_MS = 25;
710
- async function stopPreviousCollector({ root, kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
711
- const previous = (readState(root)?.collectors)?.["ios"] || null;
717
+ async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
718
+ const previous = (readState(root)?.collectors)?.[deviceSlotKey("ios", slot)] || null;
712
719
  const previousPid = Number(previous?.pid) || null;
713
720
  let killed = null;
714
721
  if (previousPid) {
715
722
  const ownership = verify({
716
723
  pid: previousPid,
717
- platform: "ios",
724
+ platform: deviceSlotKey("ios", slot),
718
725
  root,
719
726
  isAlive: alive,
720
727
  expected: previous
@@ -732,9 +739,10 @@ async function stopPreviousCollector({ root, kill = (pid, signal) => process.kil
732
739
  }
733
740
  return { killed };
734
741
  }
735
- async function replaceCollector({ root, 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) => {} }) {
742
+ 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) => {} }) {
736
743
  const { killed } = await stopPreviousCollector({
737
744
  root,
745
+ slot,
738
746
  kill,
739
747
  alive,
740
748
  readState,
@@ -753,6 +761,7 @@ async function replaceCollector({ root, udid, bundleId, appName, appExecutable,
753
761
  "--bundle",
754
762
  bundleId
755
763
  ];
764
+ if (slot !== "default") args.push("--slot", slot);
756
765
  if (appName) args.push("--app-name", appName);
757
766
  if (appExecutable) args.push("--app-executable", appExecutable);
758
767
  if (physical) args.push("--physical");
@@ -760,7 +769,7 @@ async function replaceCollector({ root, udid, bundleId, appName, appExecutable,
760
769
  let stdio = "ignore";
761
770
  try {
762
771
  mkdirSync(workspaceLogsDir(root), { recursive: true });
763
- const fd = openSync(collectorLogFile(root), "a");
772
+ const fd = openSync(collectorLogFile(root, slot), "a");
764
773
  stdio = [
765
774
  "ignore",
766
775
  fd,
@@ -791,6 +800,7 @@ async function replaceCollector({ root, udid, bundleId, appName, appExecutable,
791
800
  //#endregion
792
801
  //#region src/commands/ios/dependencies.ts
793
802
  const DEFAULT_DEPS = {
803
+ resolveEasDevelopmentBuild,
794
804
  findProjectRoot,
795
805
  resolveSettings,
796
806
  gitCommonDir,
@@ -890,9 +900,10 @@ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false
890
900
  if (errorCode) record.errorCode = errorCode;
891
901
  return record;
892
902
  }
893
- function iosFacts({ 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 }) {
903
+ 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 }) {
894
904
  return {
895
905
  platform: "ios",
906
+ ...slot && slot !== "default" ? { slot } : {},
896
907
  udid,
897
908
  deviceName: deviceName ?? null,
898
909
  deviceType: deviceType ?? null,
@@ -942,7 +953,7 @@ async function finishIosUpload(uploadPending, remote, phase, note) {
942
953
  }
943
954
  return false;
944
955
  }
945
- function reportIosResult({ 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, remote, providerName, closeWriter, webPreviewUrl, lease, recordRun }) {
956
+ 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, remote, providerName, closeWriter, webPreviewUrl, lease, recordRun }) {
946
957
  const durationMs = elapsed();
947
958
  recordRun({
948
959
  failed: false,
@@ -963,6 +974,7 @@ function reportIosResult({ d, root, json, release, configuration, buildScheme =
963
974
  }), { write: d.writeWorkspaceState });
964
975
  closeWriter();
965
976
  const facts = iosFacts({
977
+ slot,
966
978
  udid,
967
979
  deviceName: device?.deviceName ?? null,
968
980
  deviceType: device?.deviceType,
@@ -1015,9 +1027,14 @@ function cacheDescription(cacheHit, providerName = null) {
1015
1027
  function missingCrashReportHint({ physical, remote, crashed }) {
1016
1028
  return crashed && !physical && !remote ? `No attributable native crash report captured yet. ${IOS_CRASH_REPORT_RETRY}` : "No attributable native crash report captured. Read `stim logs --source device` for available output.";
1017
1029
  }
1018
- async function verifyIosRun({ root, appPath, d, release, launched, configuration, phase, note, metroCheck, logsDir, launchedAt, metroPort, isExpo, bundleId, udid, scheme, physical, appName, lanAddress, lanOrigin, remoteDevice, metroOrigin }) {
1030
+ async function verifyIosRun({ root, slot, appPath, d, release, launched, configuration, phase, note, metroCheck, logsDir, launchedAt, metroPort, isExpo, bundleId, udid, scheme, physical, appName, lanAddress, lanOrigin, remoteDevice, metroOrigin }) {
1031
+ const runCommand = nativeRunCommand("ios", slot, {
1032
+ physical,
1033
+ deviceId: udid
1034
+ });
1019
1035
  const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1020
1036
  root,
1037
+ slot,
1021
1038
  platform: "ios",
1022
1039
  deviceId: udid,
1023
1040
  appId: bundleId,
@@ -1054,6 +1071,7 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1054
1071
  }
1055
1072
  const verification = metroCheck ? await d.verifyLaunch({
1056
1073
  requireBundleResponse: true,
1074
+ slot: launchSlotScope(root, slot),
1057
1075
  onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
1058
1076
  logsDir,
1059
1077
  since: launchedAt,
@@ -1090,10 +1108,10 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1090
1108
  remote: Boolean(remoteDevice),
1091
1109
  crashed: true
1092
1110
  })));
1093
- if (nativeFatal || verification.processAlive === false) note(chalk.yellow(phaseLine("remedy", "Fix the crash, then run `stim ios` again. A Metro reload cannot restart an exited app.")));
1111
+ if (nativeFatal || verification.processAlive === false) note(chalk.yellow(phaseLine("remedy", `Fix the crash, then run \`${runCommand}\` again. A Metro reload cannot restart an exited app.`)));
1094
1112
  else if (verification.processAlive === true && metroPort !== null) {
1095
1113
  const reloadRemedy = `press Reload on the app's own error screen -- run \`agent-device snapshot -i --platform ios --udid ${udid}\` in your existing automation session to reach it. Neither \`stim reload ios\` nor \`agent-device metro reload\` can: this app never connected to Metro.`;
1096
- note(chalk.yellow(phaseLine("remedy", `The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`stim ios\` unless native inputs changed or the app process exits.`)));
1114
+ note(chalk.yellow(phaseLine("remedy", `The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`)));
1097
1115
  }
1098
1116
  return { state: LAUNCH_FATAL };
1099
1117
  }
@@ -1102,7 +1120,7 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1102
1120
  const hasAppErrors = reportLaunchErrors(verification.errors ?? [], note, root);
1103
1121
  if (hasAppErrors && verification.processAlive === true && metroPort !== null) {
1104
1122
  const reloadRemedy = physical || remoteDevice ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload ios`.";
1105
- note(chalk.yellow(phaseLine("remedy", `The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`stim ios\` unless native inputs changed or the app process exits.`)));
1123
+ note(chalk.yellow(phaseLine("remedy", `The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`)));
1106
1124
  }
1107
1125
  return {
1108
1126
  state: true,
@@ -1146,14 +1164,14 @@ function reportLaunchErrors(errors, note, root) {
1146
1164
  for (const line of report.lines) note(chalk.yellow(phaseLine("launch", line)));
1147
1165
  return report.lines.length > 0;
1148
1166
  }
1149
- function cleanAdoptedIosApps({ d, root, udid, bundleId, phase, note }) {
1167
+ function cleanAdoptedIosApps({ d, root, slot, udid, bundleId, phase, note }) {
1150
1168
  const swept = d.clearOtherUserApps({
1151
1169
  udid,
1152
1170
  keep: bundleId
1153
1171
  });
1154
1172
  if (swept.removed.length) phase("install", `removed ${swept.removed.join(", ")}, left by the previous workspace`);
1155
1173
  if (swept.listed && swept.failed.length === 0) {
1156
- d.clearIosAdoptionPending(root);
1174
+ d.clearIosAdoptionPending(root, slot);
1157
1175
  return null;
1158
1176
  }
1159
1177
  if (!swept.listed) return "Could not list apps left by the previous workspace.";
@@ -1195,7 +1213,11 @@ function recordIosReloadTarget({ d, root, physical, remoteDevice, bundleId, udid
1195
1213
  function simulatorLaunchFailureRemedy(remote, udid, bundleId, logFile) {
1196
1214
  return `${remote ? "Run" : "If the simulator timed out, run `stim doctor --platform ios` and resolve any reported host memory pressure before retrying. Otherwise run"} \`xcrun simctl launch --console ${udid} ${bundleId}\` to see what the app reports, and check ${logFile}.`;
1197
1215
  }
1198
- async function finishIosRun({ d, root, json, release, configuration, buildScheme, isExpo, metroCheck, metroPort, logsDir, logFile, device, udid, physical, lanAddress, lanOriginUrl, remoteDevice, bootPromise, bootDuration, appPath, bundleId: initialBundleId, swapDir, buildFailure, fail, phase, note, logWriter, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, elapsed, startedAt, storeHash, storeKey, cacheHit, compilationCache, useBuildCache, waitedForBuild, closeWriter, lease, releaseLease, recordRun }) {
1216
+ async function finishIosRun({ d, root, slot, json, release, configuration, buildScheme, isExpo, metroCheck, metroPort, logsDir, logFile, device, udid, physical, lanAddress, lanOriginUrl, remoteDevice, bootPromise, bootDuration, appPath, bundleId: initialBundleId, swapDir, buildFailure, fail, phase, note, logWriter, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, elapsed, startedAt, storeHash, storeKey, cacheHit, compilationCache, useBuildCache, waitedForBuild, closeWriter, lease, releaseLease, recordRun }) {
1217
+ const runCommand = nativeRunCommand("ios", slot, {
1218
+ physical,
1219
+ deviceId: udid
1220
+ });
1199
1221
  let bundleId = initialBundleId;
1200
1222
  let leaseWarned = false;
1201
1223
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1231,7 +1253,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1231
1253
  if (!booted?.ok) return fail({
1232
1254
  code: booted?.code || "STIM_NO_DEVICE",
1233
1255
  message: booted?.reason || "The owned simulator could not be booted.",
1234
- remedy: booted?.remedy || "Run `stim ios` again to re-establish an owned simulator for this workspace."
1256
+ remedy: booted?.remedy || `Run \`${runCommand}\` again to re-establish an owned simulator for this workspace.`
1235
1257
  });
1236
1258
  const deviceOutcome = physical ? "connected" : `${device?.adopted ? "adopted" : "booted"} ${bootDuration()}`;
1237
1259
  phase("device", `${deviceLabel(device, udid)} ${deviceOutcome}`);
@@ -1298,6 +1320,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1298
1320
  });
1299
1321
  const collector = await d.replaceCollector({
1300
1322
  root,
1323
+ slot,
1301
1324
  udid,
1302
1325
  bundleId,
1303
1326
  appName,
@@ -1320,7 +1343,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1320
1343
  bundleId,
1321
1344
  appName: appName ?? bundleId,
1322
1345
  collectorPid: collector.pid,
1323
- readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt)
1346
+ readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt && (entry.slot ?? "default") === (slot ?? "default"))
1324
1347
  });
1325
1348
  if (started.failed || !started.pid) return fail({
1326
1349
  code: "STIM_LAUNCH_FAILED",
@@ -1356,7 +1379,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1356
1379
  if (cleanupFailure) return fail({
1357
1380
  code: "STIM_INSTALL_FAILED",
1358
1381
  message: `${cleanupFailure} Stim kept the adoption cleanup pending and did not install or launch the app.`,
1359
- remedy: "Run `stim ios` again after simulator tooling is responsive.",
1382
+ remedy: `Run \`${runCommand}\` again after simulator tooling is responsive.`,
1360
1383
  build: {
1361
1384
  ...buildFailure,
1362
1385
  appPath,
@@ -1389,6 +1412,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1389
1412
  dropSwapDir();
1390
1413
  if (!remoteDevice) await d.replaceCollector({
1391
1414
  root,
1415
+ slot,
1392
1416
  udid,
1393
1417
  bundleId,
1394
1418
  appName,
@@ -1424,6 +1448,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1424
1448
  if (launched?.failed) {
1425
1449
  printNativeCrashReport({
1426
1450
  root,
1451
+ slot,
1427
1452
  platform: "ios",
1428
1453
  deviceId: udid,
1429
1454
  appId: bundleId,
@@ -1446,6 +1471,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1446
1471
  recordIosReloadTarget({
1447
1472
  d,
1448
1473
  root,
1474
+ slot,
1449
1475
  physical,
1450
1476
  remoteDevice: Boolean(remoteDevice),
1451
1477
  bundleId,
@@ -1463,6 +1489,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1463
1489
  });
1464
1490
  if (remoteDevice) await d.replaceCollector({
1465
1491
  root,
1492
+ slot,
1466
1493
  udid,
1467
1494
  bundleId,
1468
1495
  appName,
@@ -1472,6 +1499,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1472
1499
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
1473
1500
  const { state: launchState, warning: launchWarning } = await verifyIosRun({
1474
1501
  root,
1502
+ slot,
1475
1503
  appPath,
1476
1504
  d,
1477
1505
  release,
@@ -1523,6 +1551,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1523
1551
  const facts = reportIosResult({
1524
1552
  d,
1525
1553
  root,
1554
+ slot,
1526
1555
  json,
1527
1556
  release,
1528
1557
  configuration,
@@ -1570,14 +1599,20 @@ function iosCommand(program) {
1570
1599
  registerIos(program);
1571
1600
  }
1572
1601
  function registerIos(program, deps = {}) {
1573
- 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. Requires a running dev server (`stim start`).").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?\" gate 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 (M4)\"). 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("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. With no UDID, the first connected device this workspace can lease is used. 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. The build still happens here.", (value) => {
1602
+ 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. Requires a running dev server (`stim start`).").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", validateDeviceSlot).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--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?\" gate 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 (M4)\"). 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("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. With no UDID, the first connected device this workspace can lease is used. 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) => {
1574
1603
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1575
1604
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1576
1605
  }).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) => {
1577
- await runIos({
1606
+ const root = (deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd());
1607
+ const run = () => runIos({
1578
1608
  ...opts,
1579
1609
  waitConflict: waitFlagConflict(process.argv)
1580
1610
  }, deps);
1611
+ if (!root) await run();
1612
+ else await withWorkspaceProcessLock(workspaceDir(root), "native-run", run, {
1613
+ external: true,
1614
+ waitMs: 18e5
1615
+ });
1581
1616
  });
1582
1617
  }
1583
1618
  function explicitSchemeRefusal(root, scheme, isExpo, d) {
@@ -1592,11 +1627,58 @@ function explicitSchemeRefusal(root, scheme, isExpo, d) {
1592
1627
  if (project.error) return project.error;
1593
1628
  return d.resolveScheme(project, { scheme }).error ?? null;
1594
1629
  }
1630
+ function iosSlotLogFile(root, slot) {
1631
+ return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir(root), `build-${deviceSlotKey("ios", slot)}.ndjson`);
1632
+ }
1633
+ function iosSlotDeps(d, slot) {
1634
+ if (slot !== "default") {
1635
+ const base = d;
1636
+ d = {
1637
+ ...base,
1638
+ ensureOwnedDevice: (args) => base.ensureOwnedDevice({
1639
+ ...args,
1640
+ slot
1641
+ }),
1642
+ checkDeviceCapacity: (args) => base.checkDeviceCapacity({
1643
+ ...args,
1644
+ slot
1645
+ }),
1646
+ selectFromPool: (args) => base.selectFromPool({
1647
+ ...args,
1648
+ slot
1649
+ }),
1650
+ acquireRunLease: (args) => base.acquireRunLease({
1651
+ ...args,
1652
+ slot
1653
+ }),
1654
+ runLease: (args) => base.runLease({
1655
+ ...args,
1656
+ slot
1657
+ }),
1658
+ replaceCollector: (args) => base.replaceCollector({
1659
+ ...args,
1660
+ slot
1661
+ }),
1662
+ stopPreviousCollector: (args) => base.stopPreviousCollector({
1663
+ ...args,
1664
+ slot
1665
+ }),
1666
+ clearIosAdoptionPending: (root) => base.clearIosAdoptionPending(root, slot),
1667
+ writeWorkspaceLaunch: (root, platform, record) => base.writeWorkspaceLaunch(root, platform, record, slot),
1668
+ createWriter: (file, options) => base.createWriter(file, {
1669
+ ...options,
1670
+ fields: { slot }
1671
+ })
1672
+ };
1673
+ }
1674
+ return d;
1675
+ }
1595
1676
  async function runIos(opts = {}, overrides = {}) {
1596
- let d = {
1677
+ const slot = validateDeviceSlot(opts.slot);
1678
+ let d = iosSlotDeps({
1597
1679
  ...DEFAULT_DEPS,
1598
1680
  ...overrides
1599
- };
1681
+ }, slot);
1600
1682
  const json = Boolean(opts.json);
1601
1683
  const metroCheck = opts.metroCheck !== false;
1602
1684
  let useBuildCache = opts.buildCache !== false;
@@ -1642,7 +1724,7 @@ async function runIos(opts = {}, overrides = {}) {
1642
1724
  return null;
1643
1725
  }
1644
1726
  const logsDir = workspaceLogsDir(root);
1645
- const logFile = buildLogFile(root);
1727
+ const logFile = iosSlotLogFile(root, slot);
1646
1728
  let writer = null;
1647
1729
  const logWriter = () => writer ||= d.createWriter(logFile, { truncate: true });
1648
1730
  let buildLock = null;
@@ -1781,7 +1863,7 @@ async function runIos(opts = {}, overrides = {}) {
1781
1863
  message: settingError,
1782
1864
  remedy: SETTING_SHAPE_REMEDY
1783
1865
  });
1784
- const configuration = resolveConfiguration(opts.configuration, settings);
1866
+ const configuration = opts.easProfile !== void 0 ? null : resolveConfiguration(opts.configuration, settings);
1785
1867
  const buildScheme = opts.scheme;
1786
1868
  const release = isReleaseConfiguration(configuration);
1787
1869
  const cachePolicy = artifactCachePolicy(optimizations, useBuildCache, release);
@@ -1789,7 +1871,7 @@ async function runIos(opts = {}, overrides = {}) {
1789
1871
  const deviceType = resolveDeviceType(opts.deviceType, settings);
1790
1872
  const runtime = resolveRuntime(opts.runtime, settings);
1791
1873
  const deviceFlag = opts.device;
1792
- const physical = deviceFlag !== null && deviceFlag !== void 0 && deviceFlag !== false;
1874
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1793
1875
  if (physical && deviceFlag === "") return fail({
1794
1876
  code: "STIM_BAD_ARG",
1795
1877
  message: "--device was given an empty UDID.",
@@ -1824,6 +1906,7 @@ async function runIos(opts = {}, overrides = {}) {
1824
1906
  if (schemeRefusal) return fail(schemeRefusal);
1825
1907
  const remoteBackend = physical ? null : opts.remote ?? remoteIosSetting(settings);
1826
1908
  const modelRefusal = deviceModelRefusal({
1909
+ slot,
1827
1910
  deviceTypeFlag: opts.deviceType,
1828
1911
  runtimeFlag: opts.runtime,
1829
1912
  deviceType,
@@ -1833,6 +1916,17 @@ async function runIos(opts = {}, overrides = {}) {
1833
1916
  listRuntimes: d.listIosRuntimes
1834
1917
  });
1835
1918
  if (modelRefusal) return fail(modelRefusal);
1919
+ const easBuild = await d.resolveEasDevelopmentBuild({
1920
+ root,
1921
+ platform: "ios",
1922
+ profile: opts.easProfile,
1923
+ note,
1924
+ isExpo,
1925
+ physical,
1926
+ selectors: [opts.scheme, opts.configuration],
1927
+ buildCache: opts.buildCache
1928
+ });
1929
+ if (isEasBuildFailure(easBuild)) return fail(easBuild);
1836
1930
  const registerProject = () => d.upsertProject(root, {
1837
1931
  bundleId: d.detectBundleId(root) ?? void 0,
1838
1932
  isExpo
@@ -2112,6 +2206,38 @@ async function runIos(opts = {}, overrides = {}) {
2112
2206
  return result;
2113
2207
  });
2114
2208
  udid = device.deviceUdid ?? (await bootPromise)?.udid ?? "";
2209
+ if (easBuild?.ok) {
2210
+ fingerprint = storeHash = easBuild.fingerprint;
2211
+ cacheKey = storeKey = easBuild.cacheKey;
2212
+ appPath = easBuild.path;
2213
+ cacheHit = easBuild.cacheHit;
2214
+ providerName = "eas";
2215
+ stats.setCacheKey(cacheKey);
2216
+ if (physical) {
2217
+ const gate = d.gateProfileForDevice({
2218
+ appPath,
2219
+ udid,
2220
+ configuration
2221
+ });
2222
+ if (!gate.ok) {
2223
+ fail({
2224
+ code: gate.code,
2225
+ message: gate.reason,
2226
+ remedy: easDeviceBuildRemedy(opts.easProfile)
2227
+ });
2228
+ return false;
2229
+ }
2230
+ if (!d.devClientScheme(root, appPath)) {
2231
+ fail({
2232
+ code: "STIM_BAD_ARG",
2233
+ message: "The EAS device app has no development-client URL scheme.",
2234
+ remedy: "Install expo-dev-client with npx expo install expo-dev-client, then rebuild the EAS profile and retry."
2235
+ });
2236
+ return false;
2237
+ }
2238
+ }
2239
+ return true;
2240
+ }
2115
2241
  const fingerprintTimer = stepTimer(d.now);
2116
2242
  let computedFingerprint;
2117
2243
  try {
@@ -2635,10 +2761,12 @@ async function runIos(opts = {}, overrides = {}) {
2635
2761
  }
2636
2762
  try {
2637
2763
  if (!await resolveInitialFingerprint()) return null;
2638
- await resolveRemoteArtifact();
2639
- if (!await waitForSharedBuild()) return null;
2640
- await prepareCachedArtifact();
2641
- if (!await buildArtifact()) return null;
2764
+ if (!easBuild) {
2765
+ await resolveRemoteArtifact();
2766
+ if (!await waitForSharedBuild()) return null;
2767
+ await prepareCachedArtifact();
2768
+ if (!await buildArtifact()) return null;
2769
+ }
2642
2770
  if (physicalDevice) {
2643
2771
  const acquired = await d.acquireRunLease({
2644
2772
  root,
@@ -2673,6 +2801,7 @@ async function runIos(opts = {}, overrides = {}) {
2673
2801
  }
2674
2802
  try {
2675
2803
  return await finishIosRun({
2804
+ slot,
2676
2805
  d,
2677
2806
  root,
2678
2807
  json,