stim 1.1.0 → 1.3.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 (64) hide show
  1. package/README.md +37 -0
  2. package/dist/{android-CC5bUer7.mjs → android-BW8i_YxU.mjs} +1 -1
  3. package/dist/android-BqVIJoG7.mjs +1368 -0
  4. package/dist/{android-DnZlT3aU.mjs → android-CqcW1hrP.mjs} +205 -58
  5. package/dist/{android-cas-DGlTStWu.mjs → android-cas-BR5YJBYv.mjs} +6 -6
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install-oZIQRbdD.mjs → app-install-DIp9riNx.mjs} +29 -21
  8. package/dist/{build-slots-CgacA9zQ.mjs → build-slots-C3-R4XQr.mjs} +3 -3
  9. package/dist/{cache-manifest-oeH18ERr.mjs → cache-manifest-CXR-Y8pH.mjs} +1 -1
  10. package/dist/cache-manifest.mjs +1 -1
  11. package/dist/cli.mjs +13 -13
  12. package/dist/collector-run.d.mts +4 -1
  13. package/dist/collector-run.mjs +36 -11
  14. package/dist/{config-D7uu8Gbq.mjs → config-CcefMY2Q.mjs} +94 -13
  15. package/dist/{deps-64wVnpZc.mjs → deps-Spz8ozgo.mjs} +2 -2
  16. package/dist/{dev-client-Bvky1hya.mjs → dev-client-BjisHDB5.mjs} +250 -10
  17. package/dist/{device-DJKv5Qej.mjs → device-BkiW-gkk.mjs} +19 -11
  18. package/dist/{device-lease-D-G3SgVe.mjs → device-lease-BJYybF-2.mjs} +107 -83
  19. package/dist/{device-pool-BlHXnYYs.mjs → device-pool-CPbLGfrB.mjs} +19 -13
  20. package/dist/{device-remote-CCgkG82J.mjs → device-remote-AkYCKS8U.mjs} +8 -8
  21. package/dist/{doctor-DlVxTtMq.mjs → doctor-Bmv86UUW.mjs} +18 -26
  22. package/dist/{doctor-BRF9aAJ3.mjs → doctor-fAMXC4vl.mjs} +10 -10
  23. package/dist/{error-diagnostics-B5IJwv3-.mjs → error-diagnostics-DHC31rXA.mjs} +65 -55
  24. package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
  25. package/dist/{gc-DhXdgSdt.mjs → gc-GGu-8ULN.mjs} +90 -67
  26. package/dist/{guide-DrhrDBo5.mjs → guide-92CPA21s.mjs} +414 -47
  27. package/dist/{ios-CK1Vrk6Q.mjs → ios-BSXFeO5H.mjs} +200 -59
  28. package/dist/{ios-BZxKPuqP.mjs → ios-Deh5js5V.mjs} +147 -40
  29. package/dist/{ios-device-CdSm0GDw.mjs → ios-device-CWOM_S44.mjs} +2 -2
  30. package/dist/{ios-device-CYbuRNXR.mjs → ios-device-D3pzTKQE.mjs} +1 -1
  31. package/dist/{logs-Dxoy16Qv.mjs → logs-DPNF4C9s.mjs} +9 -6
  32. package/dist/{logs-query-oqn47Ffx.mjs → logs-query-R-Cqc6Qv.mjs} +22 -5
  33. package/dist/{metro-B01H3EuD.mjs → metro-CORqXyTL.mjs} +3 -3
  34. package/dist/{ndjson-DcAtEx_K.mjs → ndjson-BZwI1NBv.mjs} +5 -2
  35. package/dist/{ownership-D8HmSv2E.mjs → ownership-8O073GIC.mjs} +3 -3
  36. package/dist/{ownership-B9QIdQOw.mjs → ownership-BpCKiRlQ.mjs} +4 -4
  37. package/dist/{ownership-claim-CeJLEpjD.mjs → ownership-claim-Bf2CAfw3.mjs} +4 -3
  38. package/dist/{project-COj6nzzh.mjs → project-fKmuIaMI.mjs} +2 -2
  39. package/dist/{reclaim-BPvN2HlP.mjs → reclaim-iW9Cad7k.mjs} +80 -73
  40. package/dist/{reload-DYZEBbZy.mjs → reload-DazHDSFA.mjs} +32 -24
  41. package/dist/{server-bare-BYHqEeyw.mjs → server-bare-DEzPZLvf.mjs} +7 -2
  42. package/dist/server-expo-44em5Pmg.mjs +2 -0
  43. package/dist/{server-expo-BEMr_m6p.mjs → server-expo-DizT4OMk.mjs} +5 -5
  44. package/dist/{settings-BSwrY4FZ.mjs → settings-DEJf6_kJ.mjs} +26 -5
  45. package/dist/slot-launch-cci0xvmC.mjs +19 -0
  46. package/dist/{start-ByCylYtB.mjs → start-mObjQH2W.mjs} +12 -12
  47. package/dist/{state-IYEY-zi8.mjs → state-B3XA2wp2.mjs} +9 -9
  48. package/dist/{stats-wqxPm7Tv.mjs → stats-BR-kwj0B.mjs} +1 -1
  49. package/dist/{stats-DZ4Q5J-7.mjs → stats-CaaQr4yN.mjs} +3 -3
  50. package/dist/{status-Dia0XTqG.mjs → status-BxQNqu9t.mjs} +86 -25
  51. package/dist/{stop-D6o5zAdm.mjs → stop-CrHGcjrV.mjs} +2 -2
  52. package/dist/{stop-BApyaF21.mjs → stop-DimGXEKY.mjs} +93 -42
  53. package/dist/supervisor-run.d.mts +1 -2
  54. package/dist/supervisor-run.mjs +5 -5
  55. package/dist/{remote-cache-DOi5-zLj.mjs → teardown-ClFp1Aax.mjs} +889 -830
  56. package/dist/{workspace-process-lock-CxOO9-pf.mjs → workspace-process-lock-3NcQnZ0X.mjs} +1 -1
  57. package/dist/{worktree-DxEmUI_n.mjs → worktree-DxTgN1CJ.mjs} +16 -16
  58. package/dist/{worktree-Dx3ZC5lS.mjs → worktree-Eq6TLH8N.mjs} +7 -3
  59. package/dist/{xcode-CDB6fytw.mjs → xcode-D7sAp-Zy.mjs} +256 -103
  60. package/package.json +5 -5
  61. package/shim/bundle-response.cjs +5 -0
  62. package/dist/android-xmyGBNy5.mjs +0 -665
  63. package/dist/build-lock-vE45ZzJZ.mjs +0 -645
  64. package/dist/server-expo-rb0k-0w7.mjs +0 -2
@@ -1,35 +1,37 @@
1
- import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
- import { S as upsertProject, f as getProject, l as getConcurrencyLimits } from "./config-D7uu8Gbq.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-IYEY-zi8.mjs";
5
- import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-Dx3ZC5lS.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, r as detectBundleId, t as appProjectProblem } from "./project-COj6nzzh.mjs";
7
- import { i as resolveProjectMetro, r as pidExists } from "./metro-B01H3EuD.mjs";
8
- import { n as verifyCollectorOwnership } from "./ownership-D8HmSv2E.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-DOi5-zLj.mjs";
10
- import { D as remoteIosSetting, E as remoteDeviceSettingError, F as unknownSettingKeys, H as resolveOptimizations, L as detectProviders, M as settingShapeErrors, O as resolveCacheProviderConfig, P as tunnelModeSetting, V as optimizationBuildProfile, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, k as resolveSettings, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, w as publicUrlSetting, z as artifactCachePolicy } from "./settings-BSwrY4FZ.mjs";
1
+ import { t as getExecutor } from "./exec-CyylIdq9.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-oZIQRbdD.mjs";
13
- import { d as listIosRuntimes } from "./ios-BZxKPuqP.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-vE45ZzJZ.mjs";
15
- import { r as claimFailure } from "./ownership-claim-CeJLEpjD.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-BlHXnYYs.mjs";
17
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-CgacA9zQ.mjs";
18
- import { a as swapJsBundle, c as ensureWorkspaceStorageSafely, d as noMetroMessage, f as noMetroRemedy, 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-Bvky1hya.mjs";
19
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-wqxPm7Tv.mjs";
20
- import { C as unknownIosDeviceTypeRefusal, _ as checkDeviceCapacity, b as ensureOwnedDevice, 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 clearIosAdoptionPending, w as unknownIosRuntimeRefusal, y as ensureBooted } from "./xcode-CDB6fytw.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-CCgkG82J.mjs";
10
+ import { r as claimFailure } from "./ownership-claim-Bf2CAfw3.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-BqVIJoG7.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-ClFp1Aax.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-BjisHDB5.mjs";
15
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.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-CPbLGfrB.mjs";
19
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C3-R4XQr.mjs";
20
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BR-kwj0B.mjs";
21
+ 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-D7sAp-Zy.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-AkYCKS8U.mjs";
23
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-cci0xvmC.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-B5IJwv3-.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-CdSm0GDw.mjs";
25
- import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-64wVnpZc.mjs";
25
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-DHC31rXA.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-Spz8ozgo.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,
@@ -812,6 +822,7 @@ const DEFAULT_DEPS = {
812
822
  detectProviders,
813
823
  resolveProjectMetro,
814
824
  resolveMetroWithRetry,
825
+ warmMetro,
815
826
  readWorkspaceState,
816
827
  pidExists,
817
828
  getConcurrencyLimits,
@@ -889,9 +900,10 @@ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false
889
900
  if (errorCode) record.errorCode = errorCode;
890
901
  return record;
891
902
  }
892
- 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 }) {
893
904
  return {
894
905
  platform: "ios",
906
+ ...slot && slot !== "default" ? { slot } : {},
895
907
  udid,
896
908
  deviceName: deviceName ?? null,
897
909
  deviceType: deviceType ?? null,
@@ -941,7 +953,7 @@ async function finishIosUpload(uploadPending, remote, phase, note) {
941
953
  }
942
954
  return false;
943
955
  }
944
- 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 }) {
945
957
  const durationMs = elapsed();
946
958
  recordRun({
947
959
  failed: false,
@@ -962,6 +974,7 @@ function reportIosResult({ d, root, json, release, configuration, buildScheme =
962
974
  }), { write: d.writeWorkspaceState });
963
975
  closeWriter();
964
976
  const facts = iosFacts({
977
+ slot,
965
978
  udid,
966
979
  deviceName: device?.deviceName ?? null,
967
980
  deviceType: device?.deviceType,
@@ -1014,9 +1027,14 @@ function cacheDescription(cacheHit, providerName = null) {
1014
1027
  function missingCrashReportHint({ physical, remote, crashed }) {
1015
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.";
1016
1029
  }
1017
- 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
+ });
1018
1035
  const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1019
1036
  root,
1037
+ slot,
1020
1038
  platform: "ios",
1021
1039
  deviceId: udid,
1022
1040
  appId: bundleId,
@@ -1052,6 +1070,8 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1052
1070
  return { state: crashes.length || processCheck?.reason === "exited" ? LAUNCH_FATAL : LAUNCH_UNVERIFIED };
1053
1071
  }
1054
1072
  const verification = metroCheck ? await d.verifyLaunch({
1073
+ requireBundleResponse: true,
1074
+ slot: launchSlotScope(root, slot),
1055
1075
  onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
1056
1076
  logsDir,
1057
1077
  since: launchedAt,
@@ -1088,10 +1108,10 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1088
1108
  remote: Boolean(remoteDevice),
1089
1109
  crashed: true
1090
1110
  })));
1091
- 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.`)));
1092
1112
  else if (verification.processAlive === true && metroPort !== null) {
1093
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.`;
1094
- 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.`)));
1095
1115
  }
1096
1116
  return { state: LAUNCH_FATAL };
1097
1117
  }
@@ -1100,7 +1120,7 @@ async function verifyIosRun({ root, appPath, d, release, launched, configuration
1100
1120
  const hasAppErrors = reportLaunchErrors(verification.errors ?? [], note, root);
1101
1121
  if (hasAppErrors && verification.processAlive === true && metroPort !== null) {
1102
1122
  const reloadRemedy = physical || remoteDevice ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload ios`.";
1103
- 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.`)));
1104
1124
  }
1105
1125
  return {
1106
1126
  state: true,
@@ -1144,14 +1164,14 @@ function reportLaunchErrors(errors, note, root) {
1144
1164
  for (const line of report.lines) note(chalk.yellow(phaseLine("launch", line)));
1145
1165
  return report.lines.length > 0;
1146
1166
  }
1147
- function cleanAdoptedIosApps({ d, root, udid, bundleId, phase, note }) {
1167
+ function cleanAdoptedIosApps({ d, root, slot, udid, bundleId, phase, note }) {
1148
1168
  const swept = d.clearOtherUserApps({
1149
1169
  udid,
1150
1170
  keep: bundleId
1151
1171
  });
1152
1172
  if (swept.removed.length) phase("install", `removed ${swept.removed.join(", ")}, left by the previous workspace`);
1153
1173
  if (swept.listed && swept.failed.length === 0) {
1154
- d.clearIosAdoptionPending(root);
1174
+ d.clearIosAdoptionPending(root, slot);
1155
1175
  return null;
1156
1176
  }
1157
1177
  if (!swept.listed) return "Could not list apps left by the previous workspace.";
@@ -1190,7 +1210,14 @@ function recordIosReloadTarget({ d, root, physical, remoteDevice, bundleId, udid
1190
1210
  note(chalk.yellow(phaseLine("state", `could not record iOS launch: ${error?.message || error}`)));
1191
1211
  }
1192
1212
  }
1193
- 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 }) {
1213
+ function simulatorLaunchFailureRemedy(remote, udid, bundleId, logFile) {
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}.`;
1215
+ }
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
+ });
1194
1221
  let bundleId = initialBundleId;
1195
1222
  let leaseWarned = false;
1196
1223
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1226,7 +1253,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1226
1253
  if (!booted?.ok) return fail({
1227
1254
  code: booted?.code || "STIM_NO_DEVICE",
1228
1255
  message: booted?.reason || "The owned simulator could not be booted.",
1229
- 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.`
1230
1257
  });
1231
1258
  const deviceOutcome = physical ? "connected" : `${device?.adopted ? "adopted" : "booted"} ${bootDuration()}`;
1232
1259
  phase("device", `${deviceLabel(device, udid)} ${deviceOutcome}`);
@@ -1293,6 +1320,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1293
1320
  });
1294
1321
  const collector = await d.replaceCollector({
1295
1322
  root,
1323
+ slot,
1296
1324
  udid,
1297
1325
  bundleId,
1298
1326
  appName,
@@ -1315,7 +1343,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1315
1343
  bundleId,
1316
1344
  appName: appName ?? bundleId,
1317
1345
  collectorPid: collector.pid,
1318
- readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt)
1346
+ readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt && (entry.slot ?? "default") === (slot ?? "default"))
1319
1347
  });
1320
1348
  if (started.failed || !started.pid) return fail({
1321
1349
  code: "STIM_LAUNCH_FAILED",
@@ -1351,7 +1379,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1351
1379
  if (cleanupFailure) return fail({
1352
1380
  code: "STIM_INSTALL_FAILED",
1353
1381
  message: `${cleanupFailure} Stim kept the adoption cleanup pending and did not install or launch the app.`,
1354
- remedy: "Run `stim ios` again after simulator tooling is responsive.",
1382
+ remedy: `Run \`${runCommand}\` again after simulator tooling is responsive.`,
1355
1383
  build: {
1356
1384
  ...buildFailure,
1357
1385
  appPath,
@@ -1384,6 +1412,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1384
1412
  dropSwapDir();
1385
1413
  if (!remoteDevice) await d.replaceCollector({
1386
1414
  root,
1415
+ slot,
1387
1416
  udid,
1388
1417
  bundleId,
1389
1418
  appName,
@@ -1419,6 +1448,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1419
1448
  if (launched?.failed) {
1420
1449
  printNativeCrashReport({
1421
1450
  root,
1451
+ slot,
1422
1452
  platform: "ios",
1423
1453
  deviceId: udid,
1424
1454
  appId: bundleId,
@@ -1428,7 +1458,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1428
1458
  return fail({
1429
1459
  code: launched.code || "STIM_LAUNCH_FAILED",
1430
1460
  message: launched.reason,
1431
- remedy: `Run \`xcrun simctl launch --console ${udid} ${bundleId}\` to see what the app reports, and check ${logFile}.`,
1461
+ remedy: simulatorLaunchFailureRemedy(Boolean(remoteDevice), udid, bundleId, logFile),
1432
1462
  build: {
1433
1463
  ...buildFailure,
1434
1464
  appPath,
@@ -1441,6 +1471,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1441
1471
  recordIosReloadTarget({
1442
1472
  d,
1443
1473
  root,
1474
+ slot,
1444
1475
  physical,
1445
1476
  remoteDevice: Boolean(remoteDevice),
1446
1477
  bundleId,
@@ -1458,6 +1489,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1458
1489
  });
1459
1490
  if (remoteDevice) await d.replaceCollector({
1460
1491
  root,
1492
+ slot,
1461
1493
  udid,
1462
1494
  bundleId,
1463
1495
  appName,
@@ -1467,6 +1499,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1467
1499
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
1468
1500
  const { state: launchState, warning: launchWarning } = await verifyIosRun({
1469
1501
  root,
1502
+ slot,
1470
1503
  appPath,
1471
1504
  d,
1472
1505
  release,
@@ -1518,6 +1551,7 @@ async function finishIosRun({ d, root, json, release, configuration, buildScheme
1518
1551
  const facts = reportIosResult({
1519
1552
  d,
1520
1553
  root,
1554
+ slot,
1521
1555
  json,
1522
1556
  release,
1523
1557
  configuration,
@@ -1565,14 +1599,20 @@ function iosCommand(program) {
1565
1599
  registerIos(program);
1566
1600
  }
1567
1601
  function registerIos(program, deps = {}) {
1568
- 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) => {
1569
1603
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1570
1604
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1571
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) => {
1572
- await runIos({
1606
+ const root = (deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd());
1607
+ const run = () => runIos({
1573
1608
  ...opts,
1574
1609
  waitConflict: waitFlagConflict(process.argv)
1575
1610
  }, deps);
1611
+ if (!root) await run();
1612
+ else await withWorkspaceProcessLock(workspaceDir(root), "native-run", run, {
1613
+ external: true,
1614
+ waitMs: 18e5
1615
+ });
1576
1616
  });
1577
1617
  }
1578
1618
  function explicitSchemeRefusal(root, scheme, isExpo, d) {
@@ -1587,11 +1627,58 @@ function explicitSchemeRefusal(root, scheme, isExpo, d) {
1587
1627
  if (project.error) return project.error;
1588
1628
  return d.resolveScheme(project, { scheme }).error ?? null;
1589
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
+ }
1590
1676
  async function runIos(opts = {}, overrides = {}) {
1591
- let d = {
1677
+ const slot = validateDeviceSlot(opts.slot);
1678
+ let d = iosSlotDeps({
1592
1679
  ...DEFAULT_DEPS,
1593
1680
  ...overrides
1594
- };
1681
+ }, slot);
1595
1682
  const json = Boolean(opts.json);
1596
1683
  const metroCheck = opts.metroCheck !== false;
1597
1684
  let useBuildCache = opts.buildCache !== false;
@@ -1637,7 +1724,7 @@ async function runIos(opts = {}, overrides = {}) {
1637
1724
  return null;
1638
1725
  }
1639
1726
  const logsDir = workspaceLogsDir(root);
1640
- const logFile = buildLogFile(root);
1727
+ const logFile = iosSlotLogFile(root, slot);
1641
1728
  let writer = null;
1642
1729
  const logWriter = () => writer ||= d.createWriter(logFile, { truncate: true });
1643
1730
  let buildLock = null;
@@ -1776,7 +1863,7 @@ async function runIos(opts = {}, overrides = {}) {
1776
1863
  message: settingError,
1777
1864
  remedy: SETTING_SHAPE_REMEDY
1778
1865
  });
1779
- const configuration = resolveConfiguration(opts.configuration, settings);
1866
+ const configuration = opts.easProfile !== void 0 ? null : resolveConfiguration(opts.configuration, settings);
1780
1867
  const buildScheme = opts.scheme;
1781
1868
  const release = isReleaseConfiguration(configuration);
1782
1869
  const cachePolicy = artifactCachePolicy(optimizations, useBuildCache, release);
@@ -1784,7 +1871,7 @@ async function runIos(opts = {}, overrides = {}) {
1784
1871
  const deviceType = resolveDeviceType(opts.deviceType, settings);
1785
1872
  const runtime = resolveRuntime(opts.runtime, settings);
1786
1873
  const deviceFlag = opts.device;
1787
- const physical = deviceFlag !== null && deviceFlag !== void 0 && deviceFlag !== false;
1874
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1788
1875
  if (physical && deviceFlag === "") return fail({
1789
1876
  code: "STIM_BAD_ARG",
1790
1877
  message: "--device was given an empty UDID.",
@@ -1819,6 +1906,7 @@ async function runIos(opts = {}, overrides = {}) {
1819
1906
  if (schemeRefusal) return fail(schemeRefusal);
1820
1907
  const remoteBackend = physical ? null : opts.remote ?? remoteIosSetting(settings);
1821
1908
  const modelRefusal = deviceModelRefusal({
1909
+ slot,
1822
1910
  deviceTypeFlag: opts.deviceType,
1823
1911
  runtimeFlag: opts.runtime,
1824
1912
  deviceType,
@@ -1828,6 +1916,17 @@ async function runIos(opts = {}, overrides = {}) {
1828
1916
  listRuntimes: d.listIosRuntimes
1829
1917
  });
1830
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);
1831
1930
  const registerProject = () => d.upsertProject(root, {
1832
1931
  bundleId: d.detectBundleId(root) ?? void 0,
1833
1932
  isExpo
@@ -2032,6 +2131,13 @@ async function runIos(opts = {}, overrides = {}) {
2032
2131
  return false;
2033
2132
  }
2034
2133
  }
2134
+ if (!release && metroCheck && optimizations.metroWarmup) d.warmMetro({
2135
+ port: metroPort,
2136
+ platform: "ios",
2137
+ isExpo,
2138
+ appId: proj?.bundleId,
2139
+ bundleUrl: metroWarmupUrlSetting(settings, "ios")
2140
+ });
2035
2141
  return true;
2036
2142
  }
2037
2143
  async function resolveLanOrigin() {
@@ -2100,6 +2206,38 @@ async function runIos(opts = {}, overrides = {}) {
2100
2206
  return result;
2101
2207
  });
2102
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
+ }
2103
2241
  const fingerprintTimer = stepTimer(d.now);
2104
2242
  let computedFingerprint;
2105
2243
  try {
@@ -2267,7 +2405,7 @@ async function runIos(opts = {}, overrides = {}) {
2267
2405
  releasedWait = null;
2268
2406
  const held = attempt.held;
2269
2407
  const who = held.projectRoot || "another workspace";
2270
- phase("build", `${who} is already building ${shortHash(fingerprint)} (pid ${held.pid})${held.logFile ? ` -- tail ${held.logFile}` : ""}`);
2408
+ phase("build", `${who} is already building ${shortHash(fingerprint)} (pid ${held.pid})${held.logFile ? ` -- tail ${held.logFile}` : ""} -- stim guide lifecycle concurrency`);
2271
2409
  let waited = null;
2272
2410
  try {
2273
2411
  const ceilingMs = WAIT_CEILING_MS - (d.now() - waitStarted);
@@ -2319,7 +2457,7 @@ async function runIos(opts = {}, overrides = {}) {
2319
2457
  pid: held.pid,
2320
2458
  ms: waited.waitedMs
2321
2459
  };
2322
- phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache`);
2460
+ phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
2323
2461
  } else if (waited?.lockReleased) {
2324
2462
  failedHolder = void 0;
2325
2463
  releasedWait = {
@@ -2540,7 +2678,7 @@ async function runIos(opts = {}, overrides = {}) {
2540
2678
  phase("cache", `hit ${shortHash(storeHash)} (post-${mutatingSteps.join("/")} key)`);
2541
2679
  if (releasedWait) {
2542
2680
  waitedForBuild = releasedWait.facts;
2543
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache`);
2681
+ phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
2544
2682
  }
2545
2683
  }
2546
2684
  }
@@ -2623,10 +2761,12 @@ async function runIos(opts = {}, overrides = {}) {
2623
2761
  }
2624
2762
  try {
2625
2763
  if (!await resolveInitialFingerprint()) return null;
2626
- await resolveRemoteArtifact();
2627
- if (!await waitForSharedBuild()) return null;
2628
- await prepareCachedArtifact();
2629
- 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
+ }
2630
2770
  if (physicalDevice) {
2631
2771
  const acquired = await d.acquireRunLease({
2632
2772
  root,
@@ -2661,6 +2801,7 @@ async function runIos(opts = {}, overrides = {}) {
2661
2801
  }
2662
2802
  try {
2663
2803
  return await finishIosRun({
2804
+ slot,
2664
2805
  d,
2665
2806
  root,
2666
2807
  json,