stim 1.14.0 → 1.16.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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,39 +1,52 @@
1
- import { A as ownership_claim_exports, G as validateDeviceSlot, H as parseDeviceSlotOption, R as deviceSlotFileKey, T as upsertProject, p as getProject, u as getConcurrencyLimits, z as deviceSlotKey } from "./config-lvc7d3C2.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-CDY9IkVC.mjs";
4
- import { M as workspaceLogsDir, a as pidExists, b as writeWorkspaceState, c as resolveProjectMetro, g as recordWorkspaceUse, h as readWorkspaceState$1, k as workspaceDir, l as signalProcessTree } from "./metro-CcEo8Fl8.mjs";
5
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-BVrHsYZG.mjs";
6
- import { a as findProjectRoot, n as appProjectProblem, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-CWlvEaeN.mjs";
7
- import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DHqvsbqC.mjs";
8
- import { _ as stepTimer, c as launchErrorReport, g as stepClock, h as shortUdid, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-DVcpw6eE.mjs";
9
- import { A as providerDownloadPath, D as fingerprintProject, E as fingerprintDiffRecord, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine } from "./device-lease-5c3WrVRm.mjs";
10
- import { ct as parkedMaxSetting, rt as POOL_SETTING_REMEDY } from "./android-d6rGq1pA.mjs";
11
- import { A as resolveCacheProviderConfig, B as detectProviders, E as publicUrlSetting, G as resolveOptimizations, H as artifactCachePolicy, I as tunnelModeSetting, L as unknownSettingKeys, P as settingShapeErrors, S as metroWarmupUrlSetting, W as optimizationBuildProfile, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, j as resolveSettings, k as remoteIosSetting, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS } from "./settings-D61iNOsB.mjs";
12
- import { D as parseIosSimulatorApp, f as listIosRuntimes } from "./ios-BwDPrnYQ.mjs";
13
- import { c as recordFinishedBuild, l as startBuildProgress, n as NO_BUILD_PROGRESS } from "./build-progress-DxKpwnaS.mjs";
14
- import { a as acquireBuildSlot, s as releaseBuildSlot } from "./in-use-DlqBDKqY.mjs";
15
- import { n as verifyCollectorOwnership } from "./ownership-DyYDF5ww.mjs";
16
- import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-CcR6o6lI.mjs";
17
- import { F as ownedSessionName, S as withNativeBuildRun, a as ensureMetroReachable, l as remoteIosDeps, m as cancelledFailure, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, u as resolveRemoteContext, x as runCancellation, z as gateMetroOrigin } from "./device-remote-D87zEzaq.mjs";
18
- import { c as clearOtherUserApps, h as iosAppProcess, m as installIosApp, r as DEFAULT_METRO_PORT, u as devClientUrl, v as launchIosApp, y as restartedAppNote } from "./app-install-BpgSgkJj.mjs";
19
- import { c as readMetroRecords, f as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, o as isBundleProof, p as verifyReleaseLaunch, r as LAUNCH_UNVERIFIED, s as readCollectorRecords, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-Deg4tB3Z.mjs";
20
- import { f as easDeviceBuildRemedy, h as resolveEasDevelopmentBuild, l as unknownIosDeviceTypeRefusal, m as planEasDevelopmentBuild, p as isEasBuildFailure, r as checkDeviceCapacity, u as unknownIosRuntimeRefusal } from "./simslim-DrDN4WuV.mjs";
21
- import { f as spawnEntry, h as detectBundleId, s as startDevServer } from "./start-s4RXp1wy.mjs";
22
- 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-BiyDyPP-.mjs";
23
- import { C as clearIosAdoptionPending, S as IosDeviceMismatchError, _ as staleNativeDirRefusal, a as refusePlan, c as skippedMissReason, d as schemesFromInfoPlist, g as runPrebuild, h as recordPrebuild, i as printPlan, l as devClientScheme, m as planPrebuild, n as planFlagRefusal, o as explainBuildMiss, p as swapJsBundle, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, u as pickDevClientScheme, v as ensureBooted, w as warmMetro, y as ensureOwnedDevice } from "./build-plan-Dcr-3ie0.mjs";
24
- import { t as budgetGate } from "./budget-CGDkhvv_.mjs";
25
- import { a as sleep, i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-DjBHRg5m.mjs";
26
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-CX1Z8Tca.mjs";
27
- import { a as compilationCacheActivityLine, c as readBundleExecutable, l as readBundleId, p as describeDiagnostic, r as buildIos, s as discoverXcodeProject, t as COMPILATION_CACHE_NOT_RUN, u as resolveScheme } from "./xcode-Cp5KvK9x.mjs";
28
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-D5t8EiMf.mjs";
29
- import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-C5HzcwCW.mjs";
30
- import { a as iosPoolCandidates, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as iosDeviceProcess, l as localNetworkPending, n as installIosDeviceApp, o as iosPoolNoCandidatesRefusal, r as iosDeviceBounds, s as isWirelessIosDevice, t as awaitIosDeviceLaunch, u as resolveIosPhysicalDevice } from "./ios-device-DGglU3E_.mjs";
31
- import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-D9IcKqXS.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, V as parseDeviceSlotOption, W as validateDeviceSlot, p as getProject, u as getConcurrencyLimits, v as recordIosSchemeApprovals, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
+ import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
4
+ import { _ as stepTimer, c as launchErrorReport, g as stepClock, h as shortUdid, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-DnJMdEY-.mjs";
5
+ import { B as settingsLayers, E as parseIosRemote, G as detectProviders, H as unknownSettingKeys, J as artifactCachePolicy, L as settingOriginScope, M as remoteIosSetting, N as resolveCacheProviderConfig, P as resolveSettings, R as settingShapeErrors, S as metroPortSetting, Tt as removeTemporaryEntry, V as tunnelModeSetting, X as optimizationBuildProfile, Z as resolveOptimizations, _ as iosSigningIdentitySha1Setting, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, g as iosSigningIdentitySettingError, gt as repoRoot, h as iosSigningIdentitySetting, it as gitCommonDir, k as publicUrlSetting, m as iosLanHostSettingError, p as iosLanHostSetting, v as iosSigningIdentitySha1SettingError, w as metroWarmupUrlSetting, wt as makeTemporaryDirectory } from "./settings-oTSkkNrM.mjs";
6
+ import { n as appProjectProblem, o as findProjectRoot$1, r as detectIsExpo, t as NO_PROJECT_REFUSAL } from "./project-Du91Ilvc.mjs";
7
+ import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-BeK1ZPz1.mjs";
8
+ import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
9
+ import { t as budgetGate } from "./budget-DjrURfQn.mjs";
10
+ import { A as providerDownloadPath, D as fingerprintProject, E as fingerprintDiffRecord, L as untrackedMissLine, M as refingerprintAfterMutation, N as resolveBuild, O as inputsChangedDuringBuild, P as storeBuild, R as untrackedNativeFiles, S as configInputsChanged, T as filesystemBuildCapability, _ as waitForBuild, b as buildCacheKey, g as releaseBuildLock, j as providerUploadOutcome, k as prepareProviderDownloadDir, m as acquireBuildLock, v as waitForSharedBuild, x as changedDuringBuildLine } from "./device-lease-D-hLqx1b.mjs";
11
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-rUk77xqh.mjs";
12
+ import { m as writeWorkspaceLaunch, n as MODE_EXPO, t as MODE_BARE } from "./state-DcW6vOd_.mjs";
13
+ import { S as statsProjectKey$1, _ as tapBuildLog, g as startBuildProgress, h as recordFinishedBuild, l as NO_BUILD_PROGRESS, v as createRunRecorder, x as recordRunStats, y as readRunEstimates } from "./idle-DYVymG92.mjs";
14
+ import { ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY } from "./android-B_NumVyQ.mjs";
15
+ import { p as listIosRuntimes, r as clearIosAppData, u as listAllIosSims } from "./ios-BaInn3sW.mjs";
16
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
17
+ import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
18
+ import { S as bundlerPin, _ as readBundleId, c as COMPILATION_CACHE_NOT_RUN, d as buildIos, g as readBundleExecutable, h as discoverXcodeProject, p as compilationCacheActivityLine, r as iosToolchain, v as resolveScheme } from "./toolchain-DkUflduf.mjs";
19
+ import { a as offloadBuild, c as placementLoad, d as OffloadRefusal, f as namedBuildMachine, i as closeOffload, l as remotePhaseText, m as resolveBuildPlacement, n as buildPlacementCandidates, p as parseBuildMachineOption, r as chooseBuildMachine, s as offloadPlacement, u as simulatorRuntime } from "./client-BTY686R6.mjs";
20
+ import { n as verifyCollectorOwnership } from "./ownership-DiA16AsN.mjs";
21
+ import { C as pickDevClientScheme, S as restartedAppNote, T as schemesFromInfoPlist, _ as installIosApp, a as IOS_DEV_MENU_OFF_DEFAULTS_PLIST, d as devClientUrl, l as clearOtherUserApps, r as DEFAULT_METRO_PORT, v as iosAppProcess, x as launchIosApp } from "./app-install-CKWdbXIX.mjs";
22
+ import { t as checkDeviceCapacity } from "./device-capacity-BHwxNBlu.mjs";
23
+ import { n as devClientTakesDevMenuParams, t as devClientScheme } from "./dev-client-CroAIjjj.mjs";
24
+ import { t as spawnEntry } from "./spawn-entry-jw19m8Nl.mjs";
25
+ import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-urbmfXig.mjs";
26
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-D2fsca_Z.mjs";
27
+ import { c as readMetroRecords, f as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unattributedLaunchLines, n as LAUNCH_FATAL, o as isBundleProof, p as verifyReleaseLaunch, r as LAUNCH_UNVERIFIED, s as readCollectorRecords, t as LAUNCH_BUNDLING, u as unverifiedLaunchLines } from "./launch-verify-2DBRizjo.mjs";
28
+ import { a as acquireRunLease, c as lostRefusal, d as runLease, f as waitFlagConflict, o as leaseExpiryText, s as lostLine, t as DEBUG_VERIFY_STEP_MS, u as releaseLeaseOnSignal } from "./device-lease-run-CqjShqWW.mjs";
29
+ import { n as clearIosAdoptionPending } from "./device-ios-BadmeL2V.mjs";
30
+ import { _ as simulatorBuildArch, a as deviceShortName, c as isReleaseConfiguration, d as printDiagnostics, f as resolveConfiguration, g as resolveSimulatorAppFlag, h as resolveRuntime, i as deviceModelRefusal, l as ownedSimFailure, m as resolveIosWait, n as buildLogFile, o as iosConfigurationSetting, p as resolveDeviceType, r as deviceLabel, s as iosProviderRunOptions, t as appNameFromPath, u as podAction, v as xcodeFailureReport } from "./support-D4aYJniO.mjs";
31
+ import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, o as exitAfterFlush, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-C8-V13XN.mjs";
32
+ import { O as readRemoteSimulatorArch, a as ensureMetroReachable, k as gateMetroOrigin, l as remoteIosDeps, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, u as resolveRemoteContext, w as ownedSessionName } from "./device-remote-D5WVWqOq.mjs";
33
+ import { d as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-NaBrRFtQ.mjs";
34
+ import { a as isEasBuildFailure, i as easDeviceBuildRemedy, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-B8-7AoTI.mjs";
35
+ import { c as startDevServer, h as detectBundleId } from "./start-ClDfwXa4.mjs";
36
+ import { a as refusePlan, c as skippedMissReason, f as swapJsBundle, h as ensureOwnedDevice, i as printPlan, m as ensureBooted, n as planFlagRefusal, o as explainBuildMiss, p as didSetUpDevice, r as planPayload, s as fingerprintErrorMissReason, t as planCachedBuild, v as warmMetro } from "./build-plan-DjeSb2Xl.mjs";
37
+ import { a as sleep, i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-DBzRnEKS.mjs";
38
+ import { t as selectFromPool } from "./device-pool-CUaEWMMm.mjs";
39
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-B8VrHxn1.mjs";
40
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-B9L4Ae0x.mjs";
41
+ import { a as iosPoolCandidates, c as isWirelessIosDevice, f as resolveIosPhysicalDevice, i as iosDeviceProcess, l as listIosDevices, n as installIosDeviceApp, o as iosPoolNoCandidatesRefusal, p as verifyIosDeviceReleaseLaunch, r as iosDeviceBounds, t as awaitIosDeviceLaunch, u as localNetworkPending } from "./ios-device-FfCN_C8p.mjs";
42
+ import { n as writeHostedIos, t as readHostedIos } from "./ios-state-DS5UpYTH.mjs";
43
+ import { n as prepareHostedIos, t as placeHostedIos } from "./hosted-ios-CIWelIqf.mjs";
44
+ import { X509Certificate } from "node:crypto";
32
45
  import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
33
- import { InvalidArgumentError } from "commander";
34
- import { buildDiagnostics } from "@stim-cli/core/state";
35
46
  import { basename, join } from "node:path";
36
- import { X509Certificate } from "node:crypto";
47
+ import { hostSimulatorArch } from "@stim-cli/core";
48
+ import { InvalidArgumentError } from "commander";
49
+ import { buildDiagnostics, hostedIosStatus, isJsonObject, machineCapacity, parseMachine } from "@stim-cli/core/state";
37
50
  import { networkInterfaces, tmpdir } from "node:os";
38
51
  import chalk from "chalk";
39
52
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
@@ -111,149 +124,9 @@ function copyAppAside(appPath, { exec = null, mkdtemp = () => makeTemporaryDirec
111
124
  appPath: copy
112
125
  };
113
126
  }
114
- function buildLogFile(root) {
115
- return join(workspaceLogsDir(root), `build-ios.ndjson`);
116
- }
117
- const MAX_PRINTED_DIAGNOSTICS = 6;
118
- function deviceLabel(device, udid) {
119
- const name = device?.deviceName || device?.name || null;
120
- return name ? `${name} (${shortUdid(udid)})` : shortUdid(udid);
121
- }
122
- function deviceShortName(device, udid) {
123
- return device?.deviceName || device?.name || shortUdid(udid);
124
- }
125
- function appNameFromPath(appPath) {
126
- if (typeof appPath !== "string" || appPath.trim() === "") return null;
127
- const name = basename(appPath).replace(/\.app$/i, "");
128
- return name === "" ? null : name;
129
- }
130
- function iosConfigurationSetting(settings) {
131
- const ios = settings?.["ios"];
132
- if (!ios || typeof ios !== "object" || Array.isArray(ios)) return null;
133
- const raw = ios["configuration"];
134
- return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
135
- }
136
- function resolveConfiguration(flag, settings) {
137
- return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || iosConfigurationSetting(settings);
138
- }
139
- function iosStringSetting(settings, key) {
140
- const ios = settings?.["ios"];
141
- if (!ios || typeof ios !== "object" || Array.isArray(ios)) return null;
142
- const raw = ios[key];
143
- return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
144
- }
145
- function resolveDeviceType(flag, settings) {
146
- return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || iosStringSetting(settings, "deviceType");
147
- }
148
- function resolveRuntime(flag, settings) {
149
- return (typeof flag === "string" && flag.trim() !== "" ? flag.trim() : null) || iosStringSetting(settings, "runtime");
150
- }
151
- function resolveSimulatorAppFlag(flag, physical, remoteBackend) {
152
- if (flag === void 0) return {};
153
- let simulatorApp;
154
- try {
155
- simulatorApp = parseIosSimulatorApp(flag, "--simulator-app");
156
- } catch (error) {
157
- return { refusal: {
158
- code: "STIM_BAD_ARG",
159
- message: error.message
160
- } };
161
- }
162
- if (physical || remoteBackend) return { refusal: {
163
- code: "STIM_BAD_ARG",
164
- message: "--simulator-app only applies to a local owned iOS simulator.",
165
- remedy: "Drop --simulator-app when using a physical or remote device."
166
- } };
167
- return { simulatorApp };
168
- }
169
- function deviceModelRefusal({ slot, deviceTypeFlag, runtimeFlag, deviceType, runtime, physical, remoteBackend, listRuntimes }) {
170
- if (slot && slot !== "default" && remoteBackend) return {
171
- code: "STIM_BAD_ARG",
172
- message: "Named slots currently support local simulators and physical devices.",
173
- remedy: "Use the default slot for a remote session."
174
- };
175
- if (typeof deviceTypeFlag === "string" && deviceTypeFlag.trim() === "") return {
176
- code: "STIM_BAD_ARG",
177
- message: "--device-type was given an empty name.",
178
- remedy: "Pass `--device-type <name>` with a model `xcrun simctl list devicetypes` names, e.g. \"iPad Pro 13-inch (M5)\"."
179
- };
180
- if (typeof runtimeFlag === "string" && runtimeFlag.trim() === "") return {
181
- code: "STIM_BAD_ARG",
182
- message: "--runtime was given an empty version.",
183
- remedy: "Pass `--runtime <version>` with a runtime `xcrun simctl list runtimes` reports, e.g. \"18.5\"."
184
- };
185
- if (remoteBackend) {
186
- const given = [deviceTypeFlag && "--device-type", runtimeFlag && "--runtime"].filter(Boolean).join(" and ");
187
- if (given) return {
188
- code: "STIM_BAD_ARG",
189
- message: `${given} ${given.includes(" and ") ? "apply" : "applies"} only to a local owned iOS simulator; the ${remoteBackend} remote backend chooses its own device.`,
190
- remedy: `Drop ${given} for a remote run. For a local owned simulator, drop --remote and unset ios.remote with \`stim settings unset ios.remote --scope <workspace|repo|committed>\`.`
191
- };
192
- return null;
193
- }
194
- if (physical) return null;
195
- if (!deviceType && !runtime) return null;
196
- let runtimes;
197
- try {
198
- runtimes = listRuntimes();
199
- } catch (error) {
200
- return {
201
- code: "STIM_NO_DEVICE",
202
- message: `Could not read the installed simulator runtimes: ${error?.message || error}`,
203
- remedy: "Run `stim doctor` to check the simulator toolchain, then try again."
204
- };
205
- }
206
- const refusal = unknownIosRuntimeRefusal(runtime, runtimes) ?? unknownIosDeviceTypeRefusal(deviceType, runtimes, runtime);
207
- return refusal ? {
208
- code: "STIM_BAD_ARG",
209
- message: refusal.message,
210
- remedy: refusal.remedy
211
- } : null;
212
- }
213
- function ownedSimFailure(error) {
214
- return {
215
- code: "STIM_NO_DEVICE",
216
- message: `Could not ensure an owned iOS simulator: ${error?.message || error}`,
217
- remedy: error instanceof IosDeviceMismatchError ? error.remedy : "Run `stim doctor` to check the simulator toolchain, then try again."
218
- };
219
- }
220
- function isReleaseConfiguration(configuration) {
221
- return typeof configuration === "string" && configuration.trim() !== "" && configuration.trim().toLowerCase() !== "debug";
222
- }
223
- function podAction(podState, verdict) {
224
- if (verdict?.stale) return {
225
- install: true,
226
- reason: verdict.reason
227
- };
228
- if (verdict?.noPods && podState?.hasPodfile) return {
229
- install: true,
230
- reason: "ios/Podfile exists but no pods are installed"
231
- };
232
- return { install: false };
233
- }
234
- function xcodeFailureReport(result, logPath) {
235
- const diagnostics = result.diagnostics;
236
- const code = result.exitCode;
237
- const how = code === null ? "" : ` (exit code ${code})`;
238
- return {
239
- message: diagnostics.length ? `\`xcodebuild\` failed${how} with ${diagnostics.length} diagnostic${diagnostics.length === 1 ? "" : "s"}.` : `\`xcodebuild\` failed${how} with no recognizable diagnostic.`,
240
- remedy: diagnostics.find((d) => d?.remedy)?.remedy || `See ${logPath} for the transcript.`
241
- };
242
- }
243
- function printDiagnostics(note, result) {
244
- const diagnostics = result.diagnostics;
245
- const shown = diagnostics.slice(0, MAX_PRINTED_DIAGNOSTICS);
246
- for (const diagnostic of shown) note(chalk.red(phaseLine("error", describeDiagnostic(diagnostic))));
247
- const hidden = diagnostics.length - shown.length + result.truncated;
248
- if (hidden > 0) note(chalk.dim(phaseLine("error", `... and ${hidden} more diagnostic${hidden === 1 ? "" : "s"} in the log`)));
249
- if (diagnostics.length === 0) {
250
- note(chalk.red(phaseLine("error", "xcodebuild failed with no recognizable diagnostic; last lines:")));
251
- for (const line of result.tail.slice(-5)) note(chalk.dim(phaseLine("", line)));
252
- }
253
- }
254
127
  //#endregion
255
128
  //#region src/commands/ios/result.ts
256
- function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false, cacheSkipped = false, durationMs = 0, appPath = null, bundleId = null, startedAt, status, errorCode = null, missReason = null, diagnostics = null, configuration = null }) {
129
+ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false, cacheSkipped = false, durationMs = 0, appPath = null, bundleId = null, startedAt, status, errorCode = null, missReason = null, diagnostics = null, configuration = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
257
130
  const record = {
258
131
  platform: "ios",
259
132
  fingerprint,
@@ -268,12 +141,16 @@ function lastBuildRecord({ fingerprint = null, cacheKey = null, cacheHit = false
268
141
  configuration
269
142
  };
270
143
  if (errorCode) record.errorCode = errorCode;
144
+ record.buildMachine = buildMachine;
145
+ if (builtOn) record.builtOn = builtOn;
146
+ if (offloadedTo) record.offloadedTo = offloadedTo;
147
+ if (offloadFallback) record.offloadFallback = offloadFallback;
271
148
  if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
272
149
  const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
273
150
  if (recorded.length) record.diagnostics = recorded;
274
151
  return record;
275
152
  }
276
- function iosFacts({ slot, udid, deviceName, deviceType = null, runtime = null, fingerprint, configuration = null, scheme = null, cacheKey, cacheHit, cacheSkipped = false, compilationCache = COMPILATION_CACHE_NOT_RUN, waitedForBuild = null, appPath, bundleId, installSkipped = false, metroPort, logsDir, durationMs, launched = true, webPreviewUrl = null, lease, devServer = null }) {
153
+ function iosFacts({ root, slot, udid, deviceName, deviceType = null, runtime = null, fingerprint, configuration = null, scheme = null, cacheKey, cacheHit, cacheSkipped = false, compilationCache = COMPILATION_CACHE_NOT_RUN, waitedForBuild = null, appPath, bundleId, installSkipped = false, metroPort, logsDir, durationMs, launched = true, webPreviewUrl = null, lease, devServer = null, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null }) {
277
154
  return {
278
155
  platform: "ios",
279
156
  ...slot && slot !== "default" ? { slot } : {},
@@ -287,6 +164,10 @@ function iosFacts({ slot, udid, deviceName, deviceType = null, runtime = null, f
287
164
  cacheKey,
288
165
  cacheHit: cacheLevel(cacheHit),
289
166
  cacheSkipped: Boolean(cacheSkipped),
167
+ buildMachine,
168
+ ...builtOn ? { builtOn } : {},
169
+ ...offloadedTo ? { offloadedTo } : {},
170
+ ...offloadFallback ? { offloadFallback } : {},
290
171
  compilationCache,
291
172
  waitedForBuild: waitedForBuild ? {
292
173
  pid: waitedForBuild.pid ?? null,
@@ -298,6 +179,7 @@ function iosFacts({ slot, udid, deviceName, deviceType = null, runtime = null, f
298
179
  launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
299
180
  metroPort,
300
181
  logs: { dir: logsDir },
182
+ ...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
301
183
  durationMs,
302
184
  ...webPreviewUrl ? { webPreviewUrl } : {},
303
185
  ...lease === void 0 ? {} : { lease },
@@ -327,13 +209,14 @@ async function finishIosUpload(uploadPending, remote, phase, note) {
327
209
  }
328
210
  return false;
329
211
  }
330
- function reportIosResult({ slot, root, json, release, configuration, buildScheme = null, metroCheck, metroPort, logsDir, device, udid, appPath, bundleId, installSkipped, elapsed, startedAt, storeHash, storeKey, cacheHit, missReason, compilationCache, useBuildCache, waitedForBuild, launchState, launchWarning, providerName, closeWriter, webPreviewUrl, lease, recordRun, reclaimed = [], devServer = null }) {
212
+ function reportIosResult({ host, slot, root, json, release, configuration, buildScheme = null, metroCheck, metroPort, logsDir, device, udid, appPath, bundleId, installSkipped, elapsed, startedAt, storeHash, storeKey, cacheHit, missReason, compilationCache, useBuildCache, waitedForBuild, launchState, launchWarning, providerName, buildMachine = "auto", builtOn, offloadedTo = null, offloadFallback = null, closeWriter, webPreviewUrl, lease, recordRun, reclaimed = [], devServer = null, links }) {
331
213
  const durationMs = elapsed();
332
214
  recordRun({
333
215
  failed: false,
334
216
  cacheHit,
335
217
  waited: waitedForBuild,
336
- durationMs
218
+ durationMs,
219
+ offloadedTo
337
220
  });
338
221
  writeLastBuild(root, lastBuildRecord({
339
222
  fingerprint: storeHash,
@@ -346,10 +229,15 @@ function reportIosResult({ slot, root, json, release, configuration, buildScheme
346
229
  bundleId,
347
230
  startedAt,
348
231
  status: "ok",
349
- configuration: configuration ?? "Debug"
232
+ configuration: configuration ?? "Debug",
233
+ buildMachine,
234
+ builtOn,
235
+ offloadedTo,
236
+ offloadFallback
350
237
  }));
351
238
  closeWriter();
352
239
  const facts = iosFacts({
240
+ root,
353
241
  slot,
354
242
  udid,
355
243
  deviceName: device?.deviceName ?? null,
@@ -372,35 +260,43 @@ function reportIosResult({ slot, root, json, release, configuration, buildScheme
372
260
  launched: launchState,
373
261
  webPreviewUrl,
374
262
  lease,
375
- devServer
263
+ devServer,
264
+ buildMachine,
265
+ builtOn,
266
+ offloadedTo,
267
+ offloadFallback
376
268
  });
377
- if (json) console.log(JSON.stringify(reclaimed.length ? {
269
+ if (host) facts.host = host;
270
+ if (json) console.log(JSON.stringify({
378
271
  ...facts,
379
- reclaimed
380
- } : facts));
272
+ ...links ? { links } : {},
273
+ ...reclaimed.length ? { reclaimed } : {}
274
+ }));
381
275
  else {
382
- const summary = `${launchWarning ? "WARNING" : "OK"}: ${bundleId} on ${deviceLabel(device, udid)}, ` + (release ? `${configuration} (embedded JS, no Metro)` : `Metro port ${metroPort}`) + ` (${cacheDescription(cacheHit, providerName)}, ${formatDuration(durationMs)})`;
276
+ const summary = `${launchWarning ? "WARNING" : "OK"}: ${bundleId} on ${host ? `${device.deviceName} (iOS ${device.runtime?.replace(/^iOS /, "")}) on ${host.machine}` : deviceLabel(device, udid)}, ` + (release ? `${configuration} (embedded JS, no Metro)` : `Metro port ${metroPort}`) + ` (${cacheDescription(cacheHit, providerName, offloadedTo)}, ${formatDuration(durationMs)})`;
383
277
  const outcome = launchWarning ? chalk.yellow(`${summary} -- ${launchWarning}`) : launchState === "unverified" ? chalk.yellow(`${summary} -- launch UNVERIFIED`) : launchState === "bundling" ? chalk.green(`${summary} -- bundle requested, still building`) : chalk.green(summary);
384
278
  const deviceName = device?.deviceName ?? device?.name ?? udid;
385
- const cacheResult = useBuildCache ? cacheDescription(cacheHit, providerName) : "bypassed; built";
279
+ const cacheResult = useBuildCache ? cacheDescription(cacheHit, providerName, offloadedTo) : `bypassed; ${cacheDescription(false, null, offloadedTo)}`;
386
280
  const metroResult = release ? `embedded (${configuration})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
387
281
  console.log([
388
282
  outcome,
389
- phaseLine("device", `${deviceName} (${udid})`),
283
+ phaseLine("device", host ? `${deviceName} (iOS ${device.runtime?.replace(/^iOS /, "")}) on ${host.machine}` : `${deviceName} (${udid})`),
390
284
  phaseLine("app", bundleId),
391
285
  phaseLine("metro", metroResult),
392
286
  phaseLine("cache", cacheResult),
393
287
  ...compilationCache.status === "not-run" ? [phaseLine("compilation cache", compilationCacheActivityLine(compilationCache))] : [],
394
- phaseLine("logs", logsDir)
288
+ phaseLine("logs", logsDir),
289
+ phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)
395
290
  ].join("\n"));
396
291
  if (facts.webPreviewUrl) console.error(chalk.dim(`Watch this device: ${facts.webPreviewUrl}`));
292
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
397
293
  }
398
294
  return facts;
399
295
  }
400
- function cacheDescription(cacheHit, providerName = null) {
296
+ function cacheDescription(cacheHit, providerName = null, offloadedTo = null) {
401
297
  if (cacheHit === "remote") return `from ${providerName || "the remote cache"}`;
402
298
  if (cacheHit === "local") return "from cache";
403
- return "built";
299
+ return offloadedTo ? `built on ${offloadedTo}` : "built";
404
300
  }
405
301
  //#endregion
406
302
  //#region src/commands/ios/artifact.ts
@@ -414,15 +310,17 @@ var ArtifactRefusal = class extends Error {
414
310
  this.failure = failure;
415
311
  }
416
312
  };
417
- async function acquireIosArtifact({ root, logFile, udid, remoteDestination, device, configuration, buildScheme, buildProfile, isExpo, optimizations, cache, easBuild, easProfile, maxBuilds, progress }, d) {
418
- const { phase, note, logWriter, estimates, stats, step } = progress;
313
+ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, hostedDestination, simulatorArch, device, configuration, buildScheme, buildProfile, buildMachine = "auto", isExpo, optimizations, cache, easBuild, easProfile, maxBuilds, progress }, d) {
314
+ const { phase, note, logWriter, estimates, stats, step, miss, hit: lateHit, place, waitingOn } = progress;
419
315
  const physical = device !== null;
420
316
  const keyOptions = {
421
317
  scheme: buildScheme,
422
318
  ...configuration ? { configuration } : {},
423
319
  isSimulator: !physical,
320
+ ...simulatorArch ? { arch: simulatorArch } : {},
424
321
  ...buildProfile ? { buildProfile } : {}
425
322
  };
323
+ const providerRunOptions = iosProviderRunOptions(configuration, simulatorArch);
426
324
  const lanAddress = device?.lanAddress ?? null;
427
325
  const metroPort = device?.metroPort ?? null;
428
326
  const release = isReleaseConfiguration(configuration);
@@ -476,6 +374,23 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
476
374
  let appPath = null;
477
375
  let bundleId = null;
478
376
  let cacheHit = false;
377
+ let offloadedTo = null;
378
+ let handoff = null;
379
+ let offloadFallback = null;
380
+ let fallbackMachine = null;
381
+ let hereReason = "no build machine is paired";
382
+ let slotWaitMs;
383
+ let builtOn;
384
+ const fallBack = (reason, line = reason) => {
385
+ if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
386
+ offloadFallback = reason;
387
+ buildFailure = {
388
+ ...buildFailure,
389
+ offloadFallback: reason
390
+ };
391
+ phase("build", `${line} -> building here`);
392
+ };
393
+ const openOffload = { choice: null };
479
394
  let remote = null;
480
395
  let abandonedRemote = false;
481
396
  let uploadPending = null;
@@ -599,7 +514,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
599
514
  platform: "ios",
600
515
  projectRoot: root,
601
516
  fingerprintHash: fingerprint,
602
- runOptions: configuration ? { configuration } : null
517
+ runOptions: providerRunOptions
603
518
  });
604
519
  if (hit?.appPath) {
605
520
  let stored = null;
@@ -640,6 +555,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
640
555
  step("wait");
641
556
  phase("build", text);
642
557
  },
558
+ waitingOn,
643
559
  warn: (text) => note(chalk.yellow(phaseLine("build", text))),
644
560
  out: note
645
561
  });
@@ -752,32 +668,48 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
752
668
  }
753
669
  }
754
670
  }
755
- function explainMiss(rekeyedBy) {
756
- if (swapFellBack) missReason = skippedMissReason("the cached app could not be reused, so this run built it fresh");
757
- else if (!useBuildCache) missReason = skippedMissReason(cache.disabledByFlag ? "cache reuse turned off by --no-build-cache" : "cache reuse off in config");
758
- else {
759
- const current = {
760
- hash: storeHash ?? fingerprint,
761
- sources: storeSources
762
- };
763
- const explained = explainBuildMiss({
764
- root,
765
- platform: "ios",
766
- current,
767
- rekeyedBy,
768
- baselineDeps: { readState: d.readWorkspaceState }
769
- });
770
- missReason = explained.reason;
771
- if (explained.previousHash && explained.changedNames.length) logWriter().write(fingerprintDiffRecord({
671
+ function reasonForMiss(rekeyedBy) {
672
+ if (swapFellBack) return {
673
+ reason: skippedMissReason("the cached app could not be reused, so this run built it fresh"),
674
+ diff: null
675
+ };
676
+ if (!useBuildCache) return {
677
+ reason: skippedMissReason(cache.disabledByFlag ? "cache reuse turned off by --no-build-cache" : "cache reuse off in config"),
678
+ diff: null
679
+ };
680
+ const current = {
681
+ hash: storeHash ?? fingerprint,
682
+ sources: storeSources
683
+ };
684
+ const explained = explainBuildMiss({
685
+ root,
686
+ platform: "ios",
687
+ current,
688
+ rekeyedBy,
689
+ baselineDeps: { readState: d.readWorkspaceState }
690
+ });
691
+ return {
692
+ reason: explained.reason,
693
+ diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
772
694
  changed: explained.changedNames,
773
695
  previousHash: explained.previousHash,
774
696
  hash: current.hash
775
- }));
776
- }
697
+ }) : null
698
+ };
699
+ }
700
+ function reportPendingRecheck(prebuild) {
701
+ const pods = d.readPodState(root);
702
+ if ((prebuild === "generate" || prebuild === "regenerate" || podAction(pods, d.podsAreStale(pods.lockText, pods.manifestText)).install) && useBuildCache) miss(reasonForMiss([]).reason, true);
703
+ }
704
+ function explainMiss(rekeyedBy) {
705
+ const explained = reasonForMiss(rekeyedBy);
706
+ missReason = explained.reason;
707
+ if (explained.diff) logWriter().write(explained.diff);
777
708
  buildFailure = {
778
709
  ...buildFailure,
779
710
  missReason
780
711
  };
712
+ miss(missReason);
781
713
  phase("cache", `miss: ${missReason.summary}`);
782
714
  if (missReason.kind === "no-baseline") {
783
715
  const untracked = untrackedMissLine(d.untrackedNativeFiles({ projectRoot: root }));
@@ -821,31 +753,199 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
821
753
  note(chalk.dim(phaseLine("fingerprint", `${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after xcodebuild)`)));
822
754
  }
823
755
  }
756
+ async function takeBuildSlot() {
757
+ if (!maxBuilds) return;
758
+ try {
759
+ buildSlot = await d.acquireBuildSlot({
760
+ max: maxBuilds,
761
+ root,
762
+ logFile,
763
+ out: note
764
+ });
765
+ slotWaitMs = buildSlot.slotWaitMs;
766
+ } catch (e) {
767
+ const refusal = (0, ownership_claim_exports.claimFailure)(e, "stim ios");
768
+ if (refusal) fail({
769
+ code: refusal.code,
770
+ message: refusal.message,
771
+ remedy: refusal.remedy,
772
+ build: buildFailure
773
+ });
774
+ note(chalk.yellow(phaseLine("build", `could not take a build slot: ${e?.message || e}; building anyway`)));
775
+ }
776
+ }
777
+ /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
778
+ function placeBuild() {
779
+ const { mode, machines } = buildPlacementCandidates(buildMachine);
780
+ if (machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
781
+ const runtime = physical || release ? null : hostedDestination?.runtime ?? (remoteDestination ? null : simulatorRuntime(udid));
782
+ const unsupported = physical ? "device builds build here" : remoteDestination && !hostedDestination ? "--remote builds build here" : release ? `${configuration} builds build here` : !cachePolicy.write ? "the build cache is off" : !runtime ? `the runtime of simulator ${udid} is unknown` : null;
783
+ const here = machineCapacity();
784
+ const placement = offloadPlacement({
785
+ mode,
786
+ machines: machines.length,
787
+ here,
788
+ unsupported,
789
+ selected: buildMachine
790
+ });
791
+ if (!placement.offload) {
792
+ hereReason = placement.reason;
793
+ if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
794
+ return null;
795
+ }
796
+ return {
797
+ mode,
798
+ here,
799
+ reason: placement.reason,
800
+ runtime,
801
+ machines
802
+ };
803
+ }
804
+ /** Asks the paired machines once the post-mutation key is known; null builds here. */
805
+ async function chooseMachine(candidate) {
806
+ const choice = await chooseBuildMachine({
807
+ projectRoot: root,
808
+ target: {
809
+ platform: "ios",
810
+ local: {
811
+ ...iosToolchain(),
812
+ ...hostedDestination ? { arch: hostedDestination.architecture === "x86_64" ? "x64" : "arm64" } : {}
813
+ },
814
+ runtime: candidate.runtime,
815
+ cocoapodsPinned: bundlerPin(root) !== null
816
+ },
817
+ mode: candidate.mode,
818
+ here: candidate.here,
819
+ note: (line) => note(chalk.dim(phaseLine("build", `offload: ${line}`))),
820
+ machines: candidate.machines,
821
+ selected: buildMachine
822
+ });
823
+ if (typeof choice === "string") {
824
+ const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
825
+ if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
826
+ fallBack(choice);
827
+ return null;
828
+ }
829
+ openOffload.choice = choice;
830
+ phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
831
+ return choice;
832
+ }
833
+ /** Builds on the chosen machine and stores the app under the post-mutation key; false builds here instead unless a machine was named. */
834
+ async function compileElsewhere({ choice, candidate }) {
835
+ const runtime = candidate.runtime;
836
+ if (!storeKey || !storeHash) {
837
+ if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
838
+ return false;
839
+ }
840
+ const stagingDir = join(workspaceDir(root), "offload", "ios");
841
+ const outcome = await offloadBuild({
842
+ choice,
843
+ expectedFingerprint: storeHash,
844
+ request: {
845
+ platform: "ios",
846
+ runtime,
847
+ configuration,
848
+ scheme: buildScheme ?? null,
849
+ isExpo,
850
+ optimizations
851
+ },
852
+ stagingDir,
853
+ onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
854
+ onEnter: (name) => {
855
+ if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") {
856
+ builtOn = choice.machine;
857
+ buildFailure = {
858
+ ...buildFailure,
859
+ builtOn
860
+ };
861
+ }
862
+ place({
863
+ host: choice.machine,
864
+ phase: name
865
+ });
866
+ step(name === "prebuild" || name === "pods" ? name : "compile");
867
+ },
868
+ onRecord: (record) => logWriter().write({
869
+ ...record,
870
+ offloadedTo: choice.machine
871
+ }),
872
+ note: (line) => phase("build", line)
873
+ });
874
+ place(null);
875
+ let stored = null;
876
+ let reason = outcome.ok ? null : outcome.reason;
877
+ if (outcome.ok) {
878
+ const settled = await refingerprintAfterMutation({
879
+ projectRoot: root,
880
+ platform: "ios",
881
+ previousHash: storeHash,
882
+ fingerprint: d.fingerprintProject
883
+ });
884
+ if (!settled || settled.moved) reason = "the checkout here changed while it built";
885
+ else try {
886
+ stored = d.storeBuild("ios", storeKey, outcome.artifactPath, {
887
+ sources: storeSources,
888
+ overwrite: !useBuildCache
889
+ });
890
+ } catch (e) {
891
+ reason = `could not store the app: ${e?.message || e}`;
892
+ }
893
+ }
894
+ try {
895
+ rmSync(stagingDir, {
896
+ recursive: true,
897
+ force: true
898
+ });
899
+ } catch {}
900
+ step("compile");
901
+ const prepared = stored ? await installableCachedApp(stored) : null;
902
+ if (!outcome.ok || !prepared) {
903
+ const why = reason ?? "the stored app is not installable";
904
+ fallbackMachine = choice.machine;
905
+ fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`);
906
+ logWriter().write({
907
+ src: "build",
908
+ level: "warn",
909
+ event: "offload_failed",
910
+ msg: reason,
911
+ machine: choice.machine
912
+ });
913
+ return false;
914
+ }
915
+ const { timings } = outcome;
916
+ appPath = prepared;
917
+ builtOn = outcome.machine;
918
+ offloadedTo = outcome.machine;
919
+ handoff = outcome.handoff ?? null;
920
+ stats.setPlacement({
921
+ decision: "offloaded",
922
+ machine: outcome.machine,
923
+ reason: `${candidate.reason}${placementLoad(choice)}`,
924
+ buildMs: timings.totalMs
925
+ });
926
+ phase("build", `built on ${outcome.machine} in ${formatDuration(timings.totalMs)}: offer ${formatDuration(timings.offerMs)}, sync ${formatDuration(timings.syncMs)}, build ${formatDuration(timings.workerMs)}, fetch ${formatDuration(timings.fetchMs)}`);
927
+ compilationCache = outcome.compilationCache;
928
+ phase("cache", `compilation cache on ${outcome.machine} ${compilationCacheActivityLine(compilationCache)}`);
929
+ logWriter().write({
930
+ src: "build",
931
+ level: "info",
932
+ event: "offload_done",
933
+ msg: `built on ${outcome.machine}`,
934
+ timings
935
+ });
936
+ return true;
937
+ }
824
938
  async function buildArtifact() {
825
939
  buildFailure = {
826
940
  fingerprint,
827
941
  cacheKey,
828
942
  cacheHit,
829
- cacheSkipped: !useBuildCache
943
+ cacheSkipped: !useBuildCache,
944
+ buildMachine
830
945
  };
831
946
  if (!appPath) {
832
- if (maxBuilds) try {
833
- buildSlot = await d.acquireBuildSlot({
834
- max: maxBuilds,
835
- root,
836
- logFile,
837
- out: note
838
- });
839
- } catch (e) {
840
- const refusal = (0, ownership_claim_exports.claimFailure)(e, "stim ios");
841
- if (refusal) fail({
842
- code: refusal.code,
843
- message: refusal.message,
844
- remedy: refusal.remedy,
845
- build: buildFailure
846
- });
847
- note(chalk.yellow(phaseLine("build", `could not take a build slot: ${e?.message || e}; building anyway`)));
848
- }
947
+ const offload = placeBuild();
948
+ if (!offload) await takeBuildSlot();
849
949
  const mutatingSteps = [];
850
950
  const rekeyedBy = [];
851
951
  const prebuild = d.planPrebuild(root, "ios", {
@@ -857,6 +957,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
857
957
  ...staleNativeDirRefusal("ios"),
858
958
  build: buildFailure
859
959
  });
960
+ reportPendingRecheck(prebuild);
860
961
  if (prebuild === "generate" || prebuild === "regenerate") {
861
962
  step("prebuild");
862
963
  recordPrebuild(root, "ios", null);
@@ -887,7 +988,8 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
887
988
  });
888
989
  }
889
990
  const podState = d.readPodState(root);
890
- const action = podAction(podState, d.podsAreStale(podState.lockText, podState.manifestText));
991
+ const verdict = d.podsAreStale(podState.lockText, podState.manifestText);
992
+ const action = podAction(podState, verdict);
891
993
  if (action.install) {
892
994
  step("pods");
893
995
  const result = await d.runPodInstall(root, logWriter(), { estimateMs: estimates().podsMs });
@@ -943,6 +1045,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
943
1045
  if (prepared) {
944
1046
  appPath = prepared;
945
1047
  cacheHit = "local";
1048
+ lateHit();
946
1049
  phase("cache", `hit ${shortHash(storeHash)} (post-${mutatingSteps.join("/")} key)`);
947
1050
  if (releasedWait) {
948
1051
  waitedForBuild = releasedWait.facts;
@@ -952,15 +1055,42 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
952
1055
  }
953
1056
  }
954
1057
  }
955
- if (!appPath) {
1058
+ if (offload && !appPath) {
956
1059
  explainMiss(rekeyedBy);
957
1060
  step("compile");
1061
+ if (!storeKey) fallBack("no cache key to store the app under", "offload failed: no cache key to store the app under");
1062
+ const choice = storeKey ? await chooseMachine(offload) : null;
1063
+ if (!choice || !await compileElsewhere({
1064
+ choice,
1065
+ candidate: offload
1066
+ })) await takeBuildSlot();
1067
+ }
1068
+ if (!appPath) {
1069
+ if (!offload) explainMiss(rekeyedBy);
1070
+ step("compile");
1071
+ if (offload) stats.setPlacement({
1072
+ decision: "fell-back",
1073
+ slotWaitMs,
1074
+ reason: offloadFallback ?? "offload failed",
1075
+ ...fallbackMachine ? { machine: fallbackMachine } : {}
1076
+ });
1077
+ else stats.setPlacement({
1078
+ decision: "here",
1079
+ reason: hereReason,
1080
+ slotWaitMs
1081
+ });
1082
+ builtOn = "here";
1083
+ buildFailure = {
1084
+ ...buildFailure,
1085
+ builtOn
1086
+ };
958
1087
  phase("build", `compiling ${configuration || "Debug"} with xcodebuild`);
959
1088
  const result = await d.buildIos({
960
1089
  root,
961
1090
  scheme: buildScheme,
962
1091
  udid,
963
1092
  destination: remoteDestination ? GENERIC_SIM_DESTINATION : null,
1093
+ arch: remoteDestination ? simulatorArch : null,
964
1094
  ...physical ? { sdk: IPHONEOS_SDK } : {},
965
1095
  logWriter: logWriter(),
966
1096
  ...configuration ? { configuration } : {},
@@ -1023,7 +1153,7 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1023
1153
  projectRoot: root,
1024
1154
  fingerprintHash: storeHash,
1025
1155
  buildPath: appPath,
1026
- runOptions: configuration ? { configuration } : null
1156
+ runOptions: providerRunOptions
1027
1157
  });
1028
1158
  }
1029
1159
  }
@@ -1033,11 +1163,15 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1033
1163
  await resolveInitialFingerprint();
1034
1164
  if (!easBuild) {
1035
1165
  remote = await resolveRemoteArtifact();
1166
+ if (!appPath) miss(reasonForMiss([]).reason);
1036
1167
  await awaitSharedBuild();
1037
1168
  await prepareCachedArtifact();
1169
+ if (appPath) lateHit();
1170
+ else if (swapFellBack) miss(reasonForMiss([]).reason);
1038
1171
  await buildArtifact();
1039
1172
  }
1040
1173
  const artifact = {
1174
+ handoff,
1041
1175
  path: appPath,
1042
1176
  bundleId,
1043
1177
  cache: {
@@ -1047,6 +1181,10 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1047
1181
  } : null,
1048
1182
  hit: cacheHit,
1049
1183
  providerName: remote?.name ?? providerName,
1184
+ buildMachine,
1185
+ ...builtOn ? { builtOn } : {},
1186
+ offloadedTo,
1187
+ offloadFallback: offloadedTo ? null : offloadFallback,
1050
1188
  readEnabled: useBuildCache,
1051
1189
  missReason: cacheHit ? null : missReason,
1052
1190
  waitedForBuild,
@@ -1054,6 +1192,8 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1054
1192
  },
1055
1193
  failureFields: {
1056
1194
  ...buildFailure,
1195
+ buildMachine,
1196
+ ...builtOn ? { builtOn } : {},
1057
1197
  fingerprint: storeHash,
1058
1198
  cacheKey: storeKey,
1059
1199
  cacheHit,
@@ -1076,6 +1216,23 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1076
1216
  artifact
1077
1217
  };
1078
1218
  } catch (error) {
1219
+ if (error instanceof OffloadRefusal) {
1220
+ const refusal = error;
1221
+ return {
1222
+ ok: false,
1223
+ failure: {
1224
+ code: refusal.code,
1225
+ message: refusal.message,
1226
+ remedy: refusal.remedy,
1227
+ build: {
1228
+ ...buildFailure,
1229
+ buildMachine,
1230
+ ...builtOn ? { builtOn } : {}
1231
+ }
1232
+ },
1233
+ compilationCache
1234
+ };
1235
+ }
1079
1236
  if (error instanceof ArtifactRefusal) return {
1080
1237
  ok: false,
1081
1238
  failure: error.failure,
@@ -1083,12 +1240,248 @@ async function acquireIosArtifact({ root, logFile, udid, remoteDestination, devi
1083
1240
  };
1084
1241
  throw error;
1085
1242
  } finally {
1243
+ if (openOffload.choice) closeOffload(openOffload.choice);
1086
1244
  releaseLock();
1087
1245
  releaseSlot();
1088
1246
  if (!transferred) releaseArtifact();
1089
1247
  }
1090
1248
  }
1091
1249
  //#endregion
1250
+ //#region src/commands/ios/remote.ts
1251
+ function resolveIosRemote({ opts, settings, physical, recorded }) {
1252
+ const target = opts.remote !== void 0 ? parseIosRemote(opts.remote) : remoteIosSetting(settings);
1253
+ if (target?.kind === "machine" && target.machine === "auto") return { failure: {
1254
+ code: "STIM_BAD_ARG",
1255
+ message: "stim ios --remote auto is not available yet: automatic placement has not shipped. Name a hosting Mac from hosting.machines."
1256
+ } };
1257
+ if (physical && target?.kind === "machine") return { failure: {
1258
+ code: "STIM_BAD_ARG",
1259
+ message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
1260
+ remedy: "Pass only one of --device and --remote; unset ios.remote for a local phone."
1261
+ } };
1262
+ const machine = target?.kind === "machine" ? target.machine : null;
1263
+ if (recorded && recorded.machine !== machine) return { failure: {
1264
+ code: "STIM_BAD_ARG",
1265
+ message: `This workspace's iOS simulator runs on ${recorded.machine}; run stim stop first.`
1266
+ } };
1267
+ return {
1268
+ machine,
1269
+ backend: !physical && target?.kind === "backend" ? target.backend : null,
1270
+ recorded
1271
+ };
1272
+ }
1273
+ function selectIosTarget({ root, slot, opts, settings, physical, release, metroCheck, d }) {
1274
+ let recorded;
1275
+ try {
1276
+ recorded = d.readHostedIos(root, slot)[slot];
1277
+ } catch (error) {
1278
+ return { failure: {
1279
+ code: "STIM_HOSTING_REFUSED",
1280
+ message: error.message,
1281
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
1282
+ } };
1283
+ }
1284
+ const selection = resolveIosRemote({
1285
+ opts,
1286
+ settings,
1287
+ physical,
1288
+ recorded
1289
+ });
1290
+ if ("failure" in selection) return selection;
1291
+ const localIos = selection.machine ? deviceSlotPlatforms(d.getProject(root), slot)?.ios : void 0;
1292
+ if (selection.machine && localIos?.owned && localIos.deviceUdid) {
1293
+ let localRunning = true;
1294
+ try {
1295
+ localRunning = d.listAllIosSims({ timeoutMs: 5e3 }).some((sim) => sim.udid === localIos.deviceUdid && sim.state !== "Shutdown");
1296
+ } catch {}
1297
+ if (localRunning) return { failure: {
1298
+ code: "STIM_BAD_ARG",
1299
+ message: `This workspace's iOS simulator for slot ${slot} runs on this Mac; run stim stop first.`
1300
+ } };
1301
+ }
1302
+ if (selection.machine && !release && !metroCheck) return { failure: {
1303
+ code: "STIM_BAD_ARG",
1304
+ message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
1305
+ remedy: "Run stim stop; stim start, then retry without --no-metro-check."
1306
+ } };
1307
+ return selection;
1308
+ }
1309
+ function hostedIosSelectors(deviceType, runtime) {
1310
+ return {
1311
+ ...deviceType ? { deviceType } : {},
1312
+ ...runtime ? { runtime } : {}
1313
+ };
1314
+ }
1315
+ async function connectIosTarget(selection, selectors, d) {
1316
+ if (!selection.machine) return { target: null };
1317
+ try {
1318
+ return { target: await d.prepareHostedIos(selection.machine, selectors, selection.recorded) };
1319
+ } catch (error) {
1320
+ return { failure: {
1321
+ code: error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "STIM_HOSTING_REFUSED",
1322
+ message: error instanceof Error ? error.message : String(error),
1323
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
1324
+ } };
1325
+ }
1326
+ }
1327
+ function hostedIosBuildTarget(target) {
1328
+ return target ? { hostedDestination: {
1329
+ runtime: target.choice.runtimeId,
1330
+ architecture: target.choice.architecture
1331
+ } } : {};
1332
+ }
1333
+ async function iosPlacementBudget(d, root, note, hosted) {
1334
+ return hosted ? {
1335
+ reclaimed: [],
1336
+ refusal: null
1337
+ } : d.budgetGate({
1338
+ root,
1339
+ note
1340
+ });
1341
+ }
1342
+ function hostedIosMetroNote(hosted, settings, note) {
1343
+ if (hosted && settings.iosSimulatorApp !== void 0) note(chalk.dim(phaseLine("device", "iosSimulatorApp is ignored on a hosting Mac; the simulator boots headless.")));
1344
+ if (hosted && (publicUrlSetting(settings) || tunnelModeSetting(settings))) note(chalk.dim(phaseLine("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.")));
1345
+ }
1346
+ async function connectIosBackend(root, backend, deviceType, d) {
1347
+ if (!backend) return {
1348
+ remote: null,
1349
+ arch: null
1350
+ };
1351
+ const resolved = await d.resolveRemoteContext({
1352
+ root,
1353
+ backend,
1354
+ easBin: d.resolveEasCliBin(root)?.file ?? null,
1355
+ deviceType: deviceType?.trim() || null
1356
+ });
1357
+ if ("failed" in resolved) return { failure: {
1358
+ code: resolved.code ?? "STIM_NO_REMOTE_SESSION",
1359
+ message: resolved.failed,
1360
+ remedy: resolved.remedy
1361
+ } };
1362
+ return {
1363
+ remote: d.remoteIosDeps(resolved.ctx),
1364
+ arch: await d.readRemoteSimulatorArch(resolved.ctx.existingDaemon)
1365
+ };
1366
+ }
1367
+ function iosMetroSettings(settings, hosted) {
1368
+ return hosted ? {
1369
+ ...settings,
1370
+ ios: {
1371
+ ...isJsonObject(settings.ios) ? settings.ios : {},
1372
+ remote: void 0
1373
+ },
1374
+ android: {
1375
+ ...isJsonObject(settings.android) ? settings.android : {},
1376
+ remote: void 0
1377
+ },
1378
+ metro: {
1379
+ ...isJsonObject(settings.metro) ? settings.metro : {},
1380
+ tunnel: void 0,
1381
+ publicUrl: void 0,
1382
+ ngrokUrl: void 0
1383
+ }
1384
+ } : settings;
1385
+ }
1386
+ //#endregion
1387
+ //#region src/commands/ios/hosted.ts
1388
+ async function finishHostedIosRun({ target, d, artifact, root, slot, release, isExpo, metroCheck, metroPort, logsDir, fail, note, selectors, ...report }) {
1389
+ const bundleId = artifact.bundleId ?? (d.readBundleId(artifact.path) || d.detectBundleId(root));
1390
+ if (!bundleId) return fail({
1391
+ code: "STIM_INSTALL_FAILED",
1392
+ message: "The built app has no bundle identifier."
1393
+ });
1394
+ const launchedAt = d.now();
1395
+ let run;
1396
+ try {
1397
+ run = await d.placeHostedIos(target, {
1398
+ root,
1399
+ slot,
1400
+ bundle: artifact.path,
1401
+ handoff: artifact.handoff,
1402
+ bundleId,
1403
+ selectors,
1404
+ release,
1405
+ ...!release && isExpo ? { devClientScheme: d.devClientScheme(root, artifact.path) ?? void 0 } : {},
1406
+ reserved: (placement) => d.writeHostedIos(root, slot, placement),
1407
+ note
1408
+ });
1409
+ } catch (error) {
1410
+ return fail({
1411
+ code: error.code ?? "STIM_HOSTING_REFUSED",
1412
+ message: error.message,
1413
+ remedy: error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? error.remedy : `Retry stim ios --remote ${target.host.machine}, or run stim stop to reconcile any recorded placement.`
1414
+ });
1415
+ }
1416
+ d.writeHostedIos(root, slot, run.placement);
1417
+ d.writeWorkspaceLaunch(root, "ios", {
1418
+ appId: bundleId,
1419
+ deviceId: run.placement.session,
1420
+ metroPort,
1421
+ release,
1422
+ launchedAt: new Date(launchedAt).toISOString()
1423
+ }, slot);
1424
+ let launched = run.launched;
1425
+ if (!release && metroCheck) {
1426
+ const evidence = await d.verifyLaunch({
1427
+ requireBundleResponse: true,
1428
+ platform: "ios",
1429
+ logsDir,
1430
+ since: launchedAt,
1431
+ metroPort,
1432
+ mode: isExpo ? MODE_EXPO : MODE_BARE,
1433
+ slot: launchSlotScope(root, slot),
1434
+ platformShared: siblingPlatformSlots(root, "ios", slot).length > 0
1435
+ });
1436
+ if (evidence.fatal) return fail({
1437
+ code: "STIM_LAUNCH_FAILED",
1438
+ message: "Metro could not deliver the hosted app bundle.",
1439
+ remedy: "Run stim logs --errors and fix the bundle error."
1440
+ });
1441
+ launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
1442
+ }
1443
+ if (launched === "unverified") note(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim ios --remote ${target.host.machine}.`);
1444
+ const cache = artifact.cache;
1445
+ const device = run.placement.device;
1446
+ const uploadsAbandoned = await artifact.completeUploads();
1447
+ return {
1448
+ facts: reportIosResult({
1449
+ ...report,
1450
+ root,
1451
+ slot,
1452
+ release,
1453
+ metroCheck,
1454
+ metroPort,
1455
+ logsDir,
1456
+ device: {
1457
+ deviceName: device.deviceType,
1458
+ deviceType: device.deviceType,
1459
+ runtime: device.runtime
1460
+ },
1461
+ udid: "",
1462
+ host: hostedIosStatus(run.placement),
1463
+ appPath: artifact.path,
1464
+ bundleId,
1465
+ installSkipped: false,
1466
+ storeHash: cache.identity?.fingerprint ?? null,
1467
+ storeKey: cache.identity?.key ?? null,
1468
+ cacheHit: cache.hit,
1469
+ missReason: cache.missReason,
1470
+ compilationCache: cache.compilation,
1471
+ useBuildCache: cache.readEnabled,
1472
+ waitedForBuild: cache.waitedForBuild,
1473
+ launchState: launched,
1474
+ providerName: cache.providerName,
1475
+ buildMachine: cache.buildMachine,
1476
+ builtOn: cache.builtOn,
1477
+ offloadedTo: cache.offloadedTo,
1478
+ offloadFallback: cache.offloadFallback,
1479
+ webPreviewUrl: null
1480
+ }),
1481
+ uploadsAbandoned
1482
+ };
1483
+ }
1484
+ //#endregion
1092
1485
  //#region src/engine/ios-profile.ts
1093
1486
  const TAG = /<(\/?)\s*([A-Za-z0-9_]+)(?:\s[^>]*?)?(\/?)>/g;
1094
1487
  const ENTITIES = {
@@ -1587,7 +1980,7 @@ function hostLanCandidates(interfaces = networkInterfaces) {
1587
1980
  //#endregion
1588
1981
  //#region src/commands/ios/collector.ts
1589
1982
  function collectorLogFile(root, slot) {
1590
- return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey("ios", slot)}.log`);
1983
+ return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey("ios", slot)}.log`);
1591
1984
  }
1592
1985
  function collectorEntry() {
1593
1986
  return spawnEntry("collector-run");
@@ -1648,7 +2041,7 @@ async function replaceCollector({ root, slot = "default", udid, bundleId, appNam
1648
2041
  if (payloadUrl) args.push("--payload-url", payloadUrl);
1649
2042
  let stdio = "ignore";
1650
2043
  try {
1651
- mkdirSync(workspaceLogsDir(root), { recursive: true });
2044
+ mkdirSync(workspaceLogsDir$1(root), { recursive: true });
1652
2045
  const fd = openSync(collectorLogFile(root, slot), "a");
1653
2046
  stdio = [
1654
2047
  "ignore",
@@ -1680,23 +2073,34 @@ async function replaceCollector({ root, slot = "default", udid, bundleId, appNam
1680
2073
  //#endregion
1681
2074
  //#region src/commands/ios/dependencies.ts
1682
2075
  const DEFAULT_DEPS = {
2076
+ prepareHostedIos,
2077
+ placeHostedIos,
2078
+ readHostedIos,
2079
+ writeHostedIos,
1683
2080
  resolveEasDevelopmentBuild,
1684
- findProjectRoot,
2081
+ findProjectRoot: findProjectRoot$1,
1685
2082
  resolveSettings,
2083
+ settingsLayers,
2084
+ settingOriginScope,
1686
2085
  gitCommonDir,
1687
2086
  repoRoot,
1688
2087
  detectBundleId,
1689
2088
  detectIsExpo,
1690
2089
  devClientScheme,
2090
+ devClientTakesDevMenuParams,
1691
2091
  getProject,
1692
2092
  upsertProject,
2093
+ recordIosSchemeApprovals,
1693
2094
  checkDeviceCapacity,
1694
2095
  budgetGate,
1695
2096
  ensureOwnedDevice,
1696
2097
  listIosRuntimes,
2098
+ listAllIosSims,
1697
2099
  ensureBooted,
1698
2100
  resolveRemoteContext,
1699
2101
  remoteIosDeps,
2102
+ readRemoteSimulatorArch,
2103
+ hostSimulatorArch,
1700
2104
  ensureMetroReachable,
1701
2105
  ensureRemoteBootOwned,
1702
2106
  detectProviders,
@@ -1748,6 +2152,7 @@ const DEFAULT_DEPS = {
1748
2152
  swapJsBundle,
1749
2153
  installIosApp,
1750
2154
  clearOtherUserApps,
2155
+ clearIosAppData,
1751
2156
  clearIosAdoptionPending,
1752
2157
  launchIosApp,
1753
2158
  verifyLaunch,
@@ -1922,7 +2327,7 @@ function reportUnverified({ verification, siblings, slot, phase, note, metroPort
1922
2327
  waitedMs: verification.waitedMs,
1923
2328
  bundleId,
1924
2329
  udid,
1925
- devClientUrl: scheme ? devClientUrl(scheme, metroPort ?? 8081, lanAddress ?? void 0) : null,
2330
+ devClientUrl: scheme ? launched?.url ?? devClientUrl(scheme, metroPort ?? 8081, lanAddress ?? void 0) : null,
1926
2331
  mode: isExpo ? MODE_EXPO : MODE_BARE,
1927
2332
  remote: remoteDevice,
1928
2333
  physical,
@@ -1949,20 +2354,30 @@ function installsOverWifi(d, udid, selectedWireless, note) {
1949
2354
  if (wireless) note(phaseLine("device", `${current?.name ?? udid} is paired over Wi-Fi, so the install and launch go over the network and take longer than over a cable`));
1950
2355
  return wireless;
1951
2356
  }
1952
- function cleanAdoptedIosApps({ d, root, slot, udid, bundleId, phase, note }) {
2357
+ function cleanAdoptedIosApps({ d, udid, bundleId, devClient, phase, note }) {
1953
2358
  const swept = d.clearOtherUserApps({
1954
2359
  udid,
1955
2360
  keep: bundleId
1956
2361
  });
1957
2362
  if (swept.removed.length) phase("install", `removed ${swept.removed.join(", ")}, left by the previous workspace`);
1958
2363
  if (swept.listed && swept.failed.length === 0) {
1959
- d.clearIosAdoptionPending(root, slot);
2364
+ if (swept.kept && bundleId) {
2365
+ try {
2366
+ d.clearIosAppData(udid, bundleId, { defaultsPlist: devClient ? IOS_DEV_MENU_OFF_DEFAULTS_PLIST : null });
2367
+ } catch (error) {
2368
+ return `Could not clear the data ${bundleId} kept from the previous workspace: ${String(error?.message || error)}`;
2369
+ }
2370
+ phase("install", `cleared ${bundleId} data left by the previous workspace`);
2371
+ }
1960
2372
  return null;
1961
2373
  }
1962
2374
  if (!swept.listed) return "Could not list apps left by the previous workspace.";
1963
2375
  for (const failure of swept.failed) note(chalk.yellow(phaseLine("install", `could not remove ${failure}, left by the previous workspace`)));
1964
2376
  return `Could not remove ${swept.failed.join(", ")}, left by the previous workspace.`;
1965
2377
  }
2378
+ function installMayBeProven(adopting, parkedCacheKey, storeKey) {
2379
+ return !adopting || !parkedCacheKey || parkedCacheKey === storeKey;
2380
+ }
1966
2381
  function resolveRunBundleId(d, root, appPath, bundleId) {
1967
2382
  if (!appPath || bundleId) return bundleId;
1968
2383
  return d.readBundleId(appPath) || d.detectBundleId(root);
@@ -1995,7 +2410,7 @@ function launchFailureRemedy(remoteDevice, udid, bundleId, logFile) {
1995
2410
  }
1996
2411
  async function finishIosRun({ artifact, d, root, slot, json, release, configuration, buildScheme, isExpo, metroCheck, metroPort, logsDir, logFile, device, udid, physical, wireless, lanAddress, lanOriginUrl, remoteDevice, bootPromise, bootDuration, fail, phase, note, logWriter, elapsed, startedAt, closeWriter, lease, releaseLease, recordRun, reclaimed, devServer, enterPhase }) {
1997
2412
  const { path: appPath, bundleId: initialBundleId, failureFields: buildFailure, cache } = artifact;
1998
- const { hit: cacheHit, providerName, readEnabled: useBuildCache, missReason, waitedForBuild, compilation: compilationCache } = cache;
2413
+ const { hit: cacheHit, providerName, buildMachine, builtOn, offloadedTo, offloadFallback, readEnabled: useBuildCache, missReason, waitedForBuild, compilation: compilationCache } = cache;
1999
2414
  const storeHash = cache.identity?.fingerprint ?? null;
2000
2415
  const storeKey = cache.identity?.key ?? null;
2001
2416
  const runCommand = nativeRunCommand("ios", slot, {
@@ -2033,7 +2448,7 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2033
2448
  }
2034
2449
  });
2035
2450
  if (bundleId) d.upsertProject(root, { bundleId });
2036
- enterPhase("install");
2451
+ enterPhase("device");
2037
2452
  const booted = await bootPromise;
2038
2453
  if (!booted?.ok) return fail({
2039
2454
  code: booted?.code || "STIM_NO_DEVICE",
@@ -2043,6 +2458,7 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2043
2458
  const deviceOutcome = physical ? "connected" : `${device?.adopted ? "adopted" : "booted"} ${bootDuration()}`;
2044
2459
  phase("device", `${deviceLabel(device, udid)} ${deviceOutcome}`);
2045
2460
  const scheme = release ? void 0 : d.devClientScheme(root, appPath);
2461
+ const devMenuParams = d.devClientTakesDevMenuParams(root);
2046
2462
  const appName = appNameFromPath(appPath);
2047
2463
  const appExecutable = readRunExecutable(d, appPath, note);
2048
2464
  const dropSwapDir = artifact.release;
@@ -2058,6 +2474,7 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2058
2474
  root,
2059
2475
  note
2060
2476
  });
2477
+ enterPhase("install");
2061
2478
  const installTimer = stepTimer(d.now);
2062
2479
  const installed = d.installIosDeviceApp({
2063
2480
  udid,
@@ -2160,9 +2577,9 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2160
2577
  if (adopting) {
2161
2578
  const cleanupFailure = cleanAdoptedIosApps({
2162
2579
  d,
2163
- root,
2164
2580
  udid,
2165
2581
  bundleId,
2582
+ devClient: Boolean(scheme),
2166
2583
  phase,
2167
2584
  note
2168
2585
  });
@@ -2177,13 +2594,15 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2177
2594
  }
2178
2595
  });
2179
2596
  }
2597
+ enterPhase("install");
2180
2598
  const installTimer = stepTimer(d.now);
2181
2599
  const installed = d.installIosApp({
2182
2600
  udid,
2183
2601
  appPath,
2184
2602
  bundleId,
2185
2603
  devClientScheme: scheme,
2186
- proveInstalled: !adopting || device?.parkedCacheKey === storeKey
2604
+ schemeApprovals: device.schemeApprovals,
2605
+ proveInstalled: installMayBeProven(adopting, device?.parkedCacheKey, storeKey)
2187
2606
  }, { now: d.now });
2188
2607
  if (installed?.failed) return fail({
2189
2608
  code: installed.code || "STIM_INSTALL_FAILED",
@@ -2195,10 +2614,12 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2195
2614
  bundleId
2196
2615
  }
2197
2616
  });
2617
+ if (adopting) d.clearIosAdoptionPending(root, slot);
2198
2618
  installSkipped = Boolean(installed?.skipped);
2199
2619
  const artifactDuration = installed?.artifactDurationMs === void 0 ? installTimer() : `(${formatDuration(installed.artifactDurationMs)})`;
2200
2620
  phase("install", installSkipped ? `unchanged (${deviceShortName(device, udid)} already has this build) ${artifactDuration}` : `${basename(appPath)} -> ${deviceLabel(device, udid)} ${artifactDuration}`);
2201
2621
  if (!remoteDevice && installed?.devClientPreparationDurationMs !== void 0) phase("install", `dev client prepared (${formatDuration(installed.devClientPreparationDurationMs)})`);
2622
+ if (installed.schemeApprovals) d.recordIosSchemeApprovals(root, udid, installed.schemeApprovals, slot);
2202
2623
  dropSwapDir();
2203
2624
  if (!remoteDevice) await d.replaceCollector({
2204
2625
  root,
@@ -2230,6 +2651,7 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2230
2651
  bundleId,
2231
2652
  metroPort,
2232
2653
  devClientScheme: scheme,
2654
+ devMenuParams,
2233
2655
  consolePaths: simulatorConsolePaths({
2234
2656
  deviceId: udid,
2235
2657
  appId: bundleId,
@@ -2360,12 +2782,20 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2360
2782
  launchState,
2361
2783
  launchWarning,
2362
2784
  providerName,
2785
+ buildMachine,
2786
+ builtOn,
2787
+ offloadedTo,
2788
+ offloadFallback,
2363
2789
  closeWriter,
2364
2790
  webPreviewUrl: remoteDevice?.webPreviewUrl() ?? null,
2365
2791
  lease: physical ? leaseFacts : void 0,
2366
2792
  recordRun,
2367
2793
  reclaimed,
2368
- devServer
2794
+ devServer,
2795
+ links: workspaceLinks(root, {
2796
+ platform: "ios",
2797
+ slot
2798
+ })
2369
2799
  }),
2370
2800
  uploadsAbandoned
2371
2801
  };
@@ -2424,21 +2854,26 @@ async function planIos(opts, d, schemeRefusal) {
2424
2854
  platform: "ios",
2425
2855
  slot,
2426
2856
  root,
2427
- projectKey: statsProjectKey({
2857
+ projectKey: statsProjectKey$1({
2428
2858
  root,
2429
2859
  commonDir: settingsContext.gitCommonDir,
2430
2860
  repoRoot: settingsContext.repoRoot
2431
2861
  })
2432
2862
  };
2433
2863
  const isExpo = d.detectIsExpo(root);
2864
+ const settingsLayersForOrigin = d.settingsLayers(settingsContext);
2865
+ const iosRemote = remoteIosSetting(settings);
2434
2866
  const modelRefusal = deviceModelRefusal({
2435
2867
  slot,
2436
2868
  deviceTypeFlag: opts.deviceType,
2437
2869
  runtimeFlag: opts.runtime,
2438
2870
  deviceType: resolveDeviceType(opts.deviceType, settings),
2439
2871
  runtime: resolveRuntime(opts.runtime, settings),
2872
+ deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
2873
+ runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
2440
2874
  physical: false,
2441
- remoteBackend: remoteIosSetting(settings),
2875
+ remoteBackend: iosRemote?.kind === "backend" ? iosRemote.backend : null,
2876
+ hosted: iosRemote?.kind === "machine",
2442
2877
  listRuntimes: d.listIosRuntimes
2443
2878
  });
2444
2879
  if (modelRefusal) return refuse(modelRefusal);
@@ -2463,6 +2898,12 @@ async function planIos(opts, d, schemeRefusal) {
2463
2898
  refusal: eas.missing
2464
2899
  }), json);
2465
2900
  }
2901
+ const remoteBackend = remoteIosSetting(settings);
2902
+ if (remoteBackend) return refuse({
2903
+ code: "STIM_BAD_ARG",
2904
+ message: `ios.remote routes this workspace's runs to a ${remoteBackend.kind === "backend" ? remoteBackend.backend : remoteBackend.machine} device, whose architecture --plan cannot read without a session.`,
2905
+ remedy: "Run `stim ios` to build for the remote device, or unset ios.remote to plan the owned simulator."
2906
+ });
2466
2907
  const refusal = schemeRefusal(root, opts.scheme, isExpo);
2467
2908
  if (refusal) return refuse({
2468
2909
  code: refusal.code,
@@ -2483,10 +2924,17 @@ async function planIos(opts, d, schemeRefusal) {
2483
2924
  message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
2484
2925
  remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
2485
2926
  });
2927
+ const arch = simulatorBuildArch({
2928
+ physical: false,
2929
+ remoteArch: null,
2930
+ hostArch: d.hostSimulatorArch(),
2931
+ configuration
2932
+ });
2486
2933
  const cacheKey = buildCacheKey("ios", fingerprint.hash, {
2487
2934
  scheme: opts.scheme,
2488
2935
  ...configuration ? { configuration } : {},
2489
2936
  isSimulator: true,
2937
+ ...arch ? { arch } : {},
2490
2938
  ...buildProfile ? { buildProfile } : {}
2491
2939
  });
2492
2940
  const plan = await planCachedBuild({
@@ -2497,7 +2945,7 @@ async function planIos(opts, d, schemeRefusal) {
2497
2945
  cacheKey,
2498
2946
  cachePolicy,
2499
2947
  providerConfig: d.resolveCacheProviderConfig(settingsContext),
2500
- expoRemote: cachePolicy.remote && !buildProfile && !opts.scheme ? { runOptions: configuration ? { configuration } : null } : null
2948
+ expoRemote: cachePolicy.remote && !buildProfile && !opts.scheme ? { runOptions: iosProviderRunOptions(configuration, arch) } : null
2501
2949
  }, {
2502
2950
  loadCacheProvider: d.loadCacheProvider,
2503
2951
  loadProjectProvider: d.loadProjectProvider,
@@ -2520,9 +2968,9 @@ function iosCommand(program) {
2520
2968
  registerIos(program);
2521
2969
  }
2522
2970
  function registerIos(program, deps = {}) {
2523
- program.command("ios").description("Build (or restore from the fingerprint cache), install and launch this workspace's app on its owned simulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--scheme <name>", "Shared Xcode app scheme to build; overrides automatic scheme selection, not the app URL scheme").option("--no-metro-check", "Skip the \"is this workspace's dev server running?\" check: do not start it, and build anyway").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--configuration <name>", "Xcode configuration to build (e.g. Release). A non-Debug configuration embeds the JS bundle and skips Metro entirely. Overrides the ios.configuration setting. Default: Debug").option("--device-type <name>", "Simulator model to create this workspace's owned sim as, exactly as `xcrun simctl list devicetypes` names it (e.g. \"iPad Pro 13-inch (M5)\"). Overrides the ios.deviceType setting for this invocation. A model no installed runtime can create refuses with STIM_BAD_ARG and prints the models they do offer.").option("--runtime <version>", "Simulator runtime to create this workspace's owned sim on, as a version (\"18.5\") or a runtime's full name (\"iOS 18.5\"); nothing else matches. Overrides the ios.runtime setting for this invocation. An unknown version refuses with STIM_BAD_ARG and prints the installed runtimes.").option("--simulator-app <app>", "Open the owned simulator in xcode, siniulator, or stim-desktop for this run, overriding the machine iosSimulatorApp setting. Also opens an already running simulator; local simulators only.").option("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. The phone can be cabled or paired over Wi-Fi. With no UDID, the first cabled device this workspace can lease is used, waiting for one that is busy; a Wi-Fi device is used only when no cabled one is connected and ready. In Debug the app is wired to this workspace's Metro over the LAN. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
2524
- if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
2525
- throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
2971
+ program.command("ios").description("Build (or restore from the fingerprint cache), install and launch this workspace's app on its owned simulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--build-machine <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--scheme <name>", "Shared Xcode app scheme to build; overrides automatic scheme selection, not the app URL scheme").option("--no-metro-check", "Skip the \"is this workspace's dev server running?\" check: do not start it, and build anyway").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--configuration <name>", "Xcode configuration to build (e.g. Release). A non-Debug configuration embeds the JS bundle and skips Metro entirely. Overrides the ios.configuration setting. Default: Debug").option("--device-type <name>", "Simulator model to create this workspace's owned sim as, exactly as `xcrun simctl list devicetypes` names it (e.g. \"iPad Pro 13-inch (M5)\"). Overrides the ios.deviceType setting for this invocation. A model no installed runtime can create refuses with STIM_BAD_ARG and prints the models they do offer.").option("--runtime <version>", "Simulator runtime to create this workspace's owned sim on, as a version (\"18.5\") or a runtime's full name (\"iOS 18.5\"); nothing else matches. Overrides the ios.runtime setting for this invocation. An unknown version refuses with STIM_BAD_ARG and prints the installed runtimes.").option("--simulator-app <app>", "Open the owned simulator in xcode, siniulator, or stim-desktop for this run, overriding the machine iosSimulatorApp setting. Also opens an already running simulator; local simulators only.").option("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. The phone can be cabled or paired over Wi-Fi. With no UDID, the first cabled device this workspace can lease is used, waiting for one that is busy; a Wi-Fi device is used only when no cabled one is connected and ready. In Debug the app is wired to this workspace's Metro over the LAN. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or an approved Mac in hosting.machines; named Macs never fall back locally. auto is not available yet.", (value) => {
2972
+ if (parseMachine(value)) return value.trim();
2973
+ throw new InvalidArgumentError("expected eas, proxy, auto, or a hosting Mac name");
2526
2974
  }).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) => {
2527
2975
  if (opts.plan) {
2528
2976
  const d = {
@@ -2583,8 +3031,34 @@ function explicitSchemeRefusal(root, scheme, isExpo, d) {
2583
3031
  if (project.error) return project.error;
2584
3032
  return d.resolveScheme(project, { scheme }).error ?? null;
2585
3033
  }
3034
+ function resolveIosBuildSetup(flag, settings) {
3035
+ const placement = resolveBuildPlacement(flag);
3036
+ if (placement.failure) return {
3037
+ ok: false,
3038
+ failure: {
3039
+ ...placement.failure,
3040
+ setup: true
3041
+ }
3042
+ };
3043
+ try {
3044
+ return {
3045
+ ok: true,
3046
+ buildMachine: placement.selected,
3047
+ optimizations: resolveOptimizations(settings)
3048
+ };
3049
+ } catch (error) {
3050
+ return {
3051
+ ok: false,
3052
+ failure: {
3053
+ code: "STIM_BAD_ARG",
3054
+ message: error.message,
3055
+ remedy: SETTING_SHAPE_REMEDY
3056
+ }
3057
+ };
3058
+ }
3059
+ }
2586
3060
  function iosSlotLogFile(root, slot) {
2587
- return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir(root), `build-${deviceSlotFileKey("ios", slot)}.ndjson`);
3061
+ return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir$1(root), `build-${deviceSlotFileKey("ios", slot)}.ndjson`);
2588
3062
  }
2589
3063
  function iosSlotDeps(d, slot) {
2590
3064
  if (slot !== "default") {
@@ -2675,10 +3149,16 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2675
3149
  process.exitCode = 1;
2676
3150
  return null;
2677
3151
  }
2678
- const logsDir = workspaceLogsDir(root);
3152
+ const logsDir = workspaceLogsDir$1(root);
2679
3153
  const logFile = iosSlotLogFile(root, slot);
2680
3154
  let writer = null;
2681
- const logWriter = () => writer ||= d.createWriter(logFile, { truncate: true });
3155
+ const logWriter = () => writer ||= tapBuildLog(d.createWriter(logFile, {
3156
+ truncate: true,
3157
+ fields: {
3158
+ platform: "ios",
3159
+ slot
3160
+ }
3161
+ }), progress);
2682
3162
  let leaseHandle = null;
2683
3163
  let stopLeaseSignals = null;
2684
3164
  const releaseLease = () => {
@@ -2698,13 +3178,15 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2698
3178
  write: (statsRun, at) => d.recordStats(statsRun, at),
2699
3179
  now: () => d.now(),
2700
3180
  note: (line) => note(chalk.dim(line)),
2701
- phases: () => progress.durations()
3181
+ phases: () => progress.durations(),
3182
+ deviceSetup: () => progress.deviceSetupKnown()
2702
3183
  });
2703
3184
  const recordRun = stats.record;
2704
3185
  let compilationCache = COMPILATION_CACHE_NOT_RUN;
2705
3186
  let reclaimed = [];
3187
+ let buildMachine = "auto";
2706
3188
  let builtConfiguration = null;
2707
- const fail = ({ code, message, remedy = null, lines = [], logPath = null, build = null, lease }) => {
3189
+ const fail = ({ code, message, remedy = null, lines = [], logPath = null, build = null, lease, setup = false }) => {
2708
3190
  ({code, message, remedy, lines} = cancelledFailure("ios", {
2709
3191
  code,
2710
3192
  message
@@ -2720,6 +3202,7 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2720
3202
  if (remedy) note(chalk.dim(phaseLine("remedy", remedy)));
2721
3203
  if (logPath) note(chalk.dim(phaseLine("log", logPath)));
2722
3204
  if (build) writeLastBuild(root, lastBuildRecord({
3205
+ buildMachine,
2723
3206
  ...build,
2724
3207
  configuration: builtConfiguration,
2725
3208
  startedAt,
@@ -2728,7 +3211,7 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2728
3211
  durationMs: elapsed()
2729
3212
  }));
2730
3213
  note(chalk.red(phaseLine("failed", code)));
2731
- recordRun({
3214
+ if (!setup) recordRun({
2732
3215
  failed: true,
2733
3216
  durationMs: elapsed()
2734
3217
  });
@@ -2750,12 +3233,13 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2750
3233
  gitCommonDir: d.gitCommonDir(root),
2751
3234
  repoRoot: settingsRepoRoot
2752
3235
  };
2753
- const projectKey = statsProjectKey({
3236
+ const projectKey = statsProjectKey$1({
2754
3237
  root,
2755
3238
  commonDir: settingsContext.gitCommonDir,
2756
3239
  repoRoot: settingsRepoRoot
2757
3240
  });
2758
3241
  stats.setProject(projectKey);
3242
+ progress.estimate(projectKey);
2759
3243
  let estimatesRead = null;
2760
3244
  const estimates = () => estimatesRead ??= d.readEstimates({
2761
3245
  projectKey,
@@ -2769,16 +3253,10 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2769
3253
  lines: moreShapeErrors,
2770
3254
  remedy: SETTING_SHAPE_REMEDY
2771
3255
  });
2772
- let optimizations;
2773
- try {
2774
- optimizations = resolveOptimizations(settings);
2775
- } catch (error) {
2776
- return fail({
2777
- code: "STIM_BAD_ARG",
2778
- message: error.message,
2779
- remedy: SETTING_SHAPE_REMEDY
2780
- });
2781
- }
3256
+ const setup = resolveIosBuildSetup(opts.buildMachine, settings);
3257
+ if (!setup.ok) return fail(setup.failure);
3258
+ buildMachine = setup.buildMachine;
3259
+ const { optimizations } = setup;
2782
3260
  const buildProfile = optimizationBuildProfile("ios", optimizations);
2783
3261
  const cacheProviderConfig = d.resolveCacheProviderConfig(settingsContext);
2784
3262
  for (const key of unknownSettingKeys(settings)) note(phaseLine("setting", chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`)));
@@ -2819,63 +3297,55 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2819
3297
  message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
2820
3298
  remedy: "Pass only one of --device and --remote."
2821
3299
  });
2822
- const noWait = opts.wait === false;
2823
- const waitFlagged = opts.wait !== void 0;
2824
- if (opts.waitConflict) return fail({
2825
- code: "STIM_BAD_ARG",
2826
- message: "--wait and --no-wait ask for opposite things.",
2827
- remedy: "Pass `--wait <seconds>` to wait for the lease, or `--no-wait` to install without one."
2828
- });
2829
- if (waitFlagged && !physical) return fail({
2830
- code: "STIM_BAD_ARG",
2831
- message: "--wait and --no-wait only apply to a `--device` run.",
2832
- remedy: "This workspace owns its simulator, so nothing contends for it. Drop the flag, or pass `--device`."
2833
- });
2834
- const waitParsed = parseDeviceWait(noWait ? void 0 : opts.wait);
2835
- if ("error" in waitParsed) return fail({
2836
- code: "STIM_BAD_ARG",
2837
- message: waitParsed.error,
2838
- remedy: "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once."
2839
- });
2840
- const waitSeconds = waitParsed.seconds;
3300
+ const wait = resolveIosWait(opts, physical);
3301
+ if ("failure" in wait) return fail(wait.failure);
3302
+ const { waitSeconds, noWait } = wait;
2841
3303
  const isExpo = d.detectIsExpo(root);
2842
3304
  const schemeRefusal = explicitSchemeRefusal(root, buildScheme, isExpo, d);
2843
3305
  if (schemeRefusal) return fail(schemeRefusal);
2844
- const remoteBackend = physical ? null : opts.remote ?? remoteIosSetting(settings);
2845
- const viewer = resolveSimulatorAppFlag(opts.simulatorApp, physical, remoteBackend);
3306
+ const remoteSelection = selectIosTarget({
3307
+ root,
3308
+ slot,
3309
+ opts,
3310
+ settings,
3311
+ physical,
3312
+ release,
3313
+ metroCheck,
3314
+ d
3315
+ });
3316
+ if ("failure" in remoteSelection) return fail(remoteSelection.failure);
3317
+ const { machine: hostedMachine, backend: remoteBackend } = remoteSelection;
3318
+ const viewer = resolveSimulatorAppFlag(opts.simulatorApp, physical, hostedMachine ?? remoteBackend);
2846
3319
  if ("refusal" in viewer) return fail(viewer.refusal);
2847
3320
  const { simulatorApp } = viewer;
3321
+ const settingsLayersForOrigin = d.settingsLayers(settingsContext);
2848
3322
  const modelRefusal = deviceModelRefusal({
2849
3323
  slot,
2850
3324
  deviceTypeFlag: opts.deviceType,
2851
3325
  runtimeFlag: opts.runtime,
2852
3326
  deviceType,
2853
3327
  runtime,
3328
+ deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
3329
+ runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
2854
3330
  physical,
2855
3331
  remoteBackend,
3332
+ hosted: Boolean(hostedMachine),
2856
3333
  listRuntimes: d.listIosRuntimes
2857
3334
  });
2858
3335
  if (modelRefusal) return fail(modelRefusal);
2859
- const budget = await d.budgetGate({
2860
- root,
2861
- note
2862
- });
2863
- reclaimed = budget.reclaimed;
2864
- if (budget.refusal) return fail(budget.refusal);
2865
- let remoteDevice = null;
2866
- if (remoteBackend) {
2867
- const resolved = await d.resolveRemoteContext({
2868
- root,
2869
- backend: remoteBackend,
2870
- easBin: d.resolveEasCliBin(root)?.file ?? null
2871
- });
2872
- if ("failed" in resolved) return fail({
2873
- code: resolved.code ?? "STIM_NO_REMOTE_SESSION",
2874
- message: resolved.failed,
2875
- remedy: resolved.remedy
2876
- });
2877
- remoteDevice = d.remoteIosDeps(resolved.ctx);
2878
- d = {
3336
+ const selectors = hostedIosSelectors(deviceType, runtime);
3337
+ const connected = await connectIosTarget(remoteSelection, selectors, d);
3338
+ if ("failure" in connected) return fail(connected.failure);
3339
+ const hostedTarget = connected.target;
3340
+ try {
3341
+ const budget = await iosPlacementBudget(d, root, note, Boolean(hostedTarget));
3342
+ reclaimed = budget.reclaimed;
3343
+ if (budget.refusal) return fail(budget.refusal);
3344
+ const backendConnection = await connectIosBackend(root, remoteBackend, opts.deviceType, d);
3345
+ if ("failure" in backendConnection) return fail(backendConnection.failure);
3346
+ const remoteDevice = backendConnection.remote;
3347
+ const remoteArch = hostedTarget ? hostedTarget.choice.architecture : backendConnection.arch;
3348
+ if (remoteDevice) d = {
2879
3349
  ...d,
2880
3350
  checkDeviceCapacity: remoteDevice.checkDeviceCapacity,
2881
3351
  ensureOwnedDevice: remoteDevice.ensureOwnedDevice,
@@ -2883,376 +3353,432 @@ async function runIos(opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS) {
2883
3353
  installIosApp: remoteDevice.installIosApp,
2884
3354
  launchIosApp: remoteDevice.launchIosApp
2885
3355
  };
2886
- }
2887
- const easBuild = await d.resolveEasDevelopmentBuild({
2888
- root,
2889
- platform: "ios",
2890
- profile: opts.easProfile,
2891
- note,
2892
- isExpo,
2893
- physical,
2894
- selectors: [opts.scheme, opts.configuration],
2895
- buildCache: opts.buildCache
2896
- });
2897
- if (isEasBuildFailure(easBuild)) return fail(easBuild);
2898
- const registerProject = () => d.upsertProject(root, {
2899
- bundleId: d.detectBundleId(root) ?? void 0,
2900
- isExpo
2901
- });
2902
- if (remoteBackend !== "eas") registerProject();
2903
- const proj = d.getProject(root);
2904
- const limits = d.getConcurrencyLimits();
2905
- let physicalDevice = null;
2906
- let wireless = false;
2907
- if (physical && typeof deviceFlag !== "string") {
2908
- const pooled = await d.selectFromPool({
3356
+ const easBuild = await d.resolveEasDevelopmentBuild({
2909
3357
  root,
2910
3358
  platform: "ios",
2911
- idLabel: "udid",
2912
- list: () => iosPoolCandidates(d.listIosDevices()).map((entry) => ({
2913
- id: entry.udid,
2914
- name: entry.name,
2915
- fallback: isWirelessIosDevice(entry)
2916
- })),
2917
- noCandidates: () => {
2918
- const resolved = iosPoolNoCandidatesRefusal(d.listIosDevices());
2919
- return {
2920
- message: resolved.error,
2921
- remedy: resolved.remedy
2922
- };
2923
- },
2924
- waitSeconds,
2925
- noWait,
2926
- now: d.now,
2927
- warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
2928
- });
2929
- if (pooled.status === "refused") return fail({
2930
- code: pooled.refusal.code,
2931
- message: pooled.refusal.message,
2932
- remedy: pooled.refusal.remedy,
2933
- ...pooled.refusal.lease === null ? {} : { lease: pooled.refusal.lease }
2934
- });
2935
- physicalDevice = {
2936
- udid: pooled.candidate.id,
2937
- name: pooled.candidate.name ?? pooled.candidate.id
2938
- };
2939
- wireless = pooled.candidate.fallback === true;
2940
- } else if (physical) {
2941
- const resolved = resolveIosPhysicalDevice(typeof deviceFlag === "string" ? deviceFlag : null, d.listIosDevices());
2942
- if (!resolved.udid) return fail({
2943
- code: "STIM_NO_DEVICE",
2944
- message: resolved.error,
2945
- remedy: resolved.remedy
3359
+ profile: opts.easProfile,
3360
+ note,
3361
+ isExpo,
3362
+ physical,
3363
+ selectors: [opts.scheme, opts.configuration],
3364
+ buildCache: opts.buildCache
2946
3365
  });
2947
- physicalDevice = {
2948
- udid: resolved.udid,
2949
- name: resolved.name ?? resolved.udid
2950
- };
2951
- wireless = resolved.wireless === true;
2952
- }
2953
- if (!physical) {
2954
- const capacity = d.checkDeviceCapacity({
2955
- platform: "ios",
2956
- project: proj,
2957
- max: limits.maxDevices
3366
+ if (isEasBuildFailure(easBuild)) return fail(easBuild);
3367
+ const registerProject = () => d.upsertProject(root, {
3368
+ bundleId: d.detectBundleId(root) ?? void 0,
3369
+ isExpo
2958
3370
  });
2959
- if (capacity) return fail(capacity);
2960
- }
2961
- let metroPort = proj?.metroPort ?? null;
2962
- let lanAddress = null;
2963
- let lanOriginUrl = null;
2964
- let devServer = null;
2965
- if (!await resolveMetroPort()) return null;
2966
- let device;
2967
- if (physicalDevice) device = {
2968
- deviceUdid: physicalDevice.udid,
2969
- deviceName: physicalDevice.name,
2970
- owned: false
2971
- };
2972
- else {
2973
- const prepare = stepClock(d.now);
2974
- try {
2975
- device = await d.ensureOwnedDevice({
3371
+ if (remoteBackend !== "eas") registerProject();
3372
+ const proj = d.getProject(root);
3373
+ const limits = d.getConcurrencyLimits();
3374
+ let physicalDevice = null;
3375
+ let wireless = false;
3376
+ if (physical && typeof deviceFlag !== "string") {
3377
+ const pooled = await d.selectFromPool({
3378
+ root,
2976
3379
  platform: "ios",
2977
- project: proj,
2978
- projectPath: root,
2979
- settingsRoot: root,
2980
- settings,
2981
- flags: {
2982
- deviceType,
2983
- runtime,
2984
- runtimeFlag: resolveRuntime(opts.runtime, null),
2985
- simulatorApp
3380
+ idLabel: "udid",
3381
+ list: () => iosPoolCandidates(d.listIosDevices()).map((entry) => ({
3382
+ id: entry.udid,
3383
+ name: entry.name,
3384
+ fallback: isWirelessIosDevice(entry)
3385
+ })),
3386
+ noCandidates: () => {
3387
+ const resolved = iosPoolNoCandidatesRefusal(d.listIosDevices());
3388
+ return {
3389
+ message: resolved.error,
3390
+ remedy: resolved.remedy
3391
+ };
2986
3392
  },
2987
- note,
2988
- out: note
3393
+ waitSeconds,
3394
+ noWait,
3395
+ now: d.now,
3396
+ warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
2989
3397
  });
2990
- } catch (e) {
2991
- return fail(ownedSimFailure(e));
3398
+ if (pooled.status === "refused") return fail({
3399
+ code: pooled.refusal.code,
3400
+ message: pooled.refusal.message,
3401
+ remedy: pooled.refusal.remedy,
3402
+ ...pooled.refusal.lease === null ? {} : { lease: pooled.refusal.lease }
3403
+ });
3404
+ physicalDevice = {
3405
+ udid: pooled.candidate.id,
3406
+ name: pooled.candidate.name ?? pooled.candidate.id
3407
+ };
3408
+ wireless = pooled.candidate.fallback === true;
3409
+ } else if (physical) {
3410
+ const resolved = resolveIosPhysicalDevice(typeof deviceFlag === "string" ? deviceFlag : null, d.listIosDevices());
3411
+ if (!resolved.udid) return fail({
3412
+ code: "STIM_NO_DEVICE",
3413
+ message: resolved.error,
3414
+ remedy: resolved.remedy
3415
+ });
3416
+ physicalDevice = {
3417
+ udid: resolved.udid,
3418
+ name: resolved.name ?? resolved.udid
3419
+ };
3420
+ wireless = resolved.wireless === true;
2992
3421
  }
2993
- const prepareMs = prepare();
2994
- if (device.created || prepareMs >= 2e3) phase("device", `${deviceLabel(device, device.deviceUdid)} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
2995
- }
2996
- let bootDuration = "";
2997
- let bootPromise;
2998
- let udid = "";
2999
- async function resolveMetroPort() {
3000
- if (release) {
3001
- metroPort = null;
3002
- phase("metro", `skipped (${configuration}: the JS bundle is embedded, no dev server is used)`);
3003
- } else if (metroCheck) {
3004
- const gate = await ensureDevServer({
3005
- root,
3422
+ if (!physical && !hostedTarget) {
3423
+ const capacity = d.checkDeviceCapacity({
3424
+ platform: "ios",
3425
+ project: proj,
3426
+ max: limits.maxDevices
3427
+ });
3428
+ if (capacity) return fail(capacity);
3429
+ }
3430
+ let metroPort = proj?.metroPort ?? null;
3431
+ let lanAddress = null;
3432
+ let lanOriginUrl = null;
3433
+ let devServer = null;
3434
+ if (!await resolveMetroPort()) return null;
3435
+ let device;
3436
+ if (physicalDevice) device = {
3437
+ deviceUdid: physicalDevice.udid,
3438
+ deviceName: physicalDevice.name,
3439
+ owned: false
3440
+ };
3441
+ else if (hostedTarget) device = {
3442
+ owned: false,
3443
+ deviceName: hostedTarget.choice.deviceType,
3444
+ runtime: hostedTarget.choice.runtime
3445
+ };
3446
+ else {
3447
+ const prepare = stepClock(d.now);
3448
+ try {
3449
+ device = await d.ensureOwnedDevice({
3450
+ platform: "ios",
3451
+ project: proj,
3452
+ projectPath: root,
3453
+ settingsRoot: root,
3454
+ settings,
3455
+ flags: {
3456
+ deviceType,
3457
+ runtime,
3458
+ runtimeFlag: resolveRuntime(opts.runtime, null),
3459
+ simulatorApp
3460
+ },
3461
+ note,
3462
+ out: note
3463
+ });
3464
+ } catch (e) {
3465
+ return fail(ownedSimFailure(e));
3466
+ }
3467
+ progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
3468
+ const prepareMs = prepare();
3469
+ if (device.created || prepareMs >= 2e3) phase("device", `${deviceLabel(device, device.deviceUdid)} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
3470
+ }
3471
+ let bootDuration = "";
3472
+ let bootPromise;
3473
+ let udid = "";
3474
+ async function resolveMetroPort() {
3475
+ if (release) {
3476
+ metroPort = null;
3477
+ phase("metro", `skipped (${configuration}: the JS bundle is embedded, no dev server is used)`);
3478
+ } else if (metroCheck) {
3479
+ const gate = await ensureDevServer({
3480
+ root,
3481
+ port: metroPort,
3482
+ settings: iosMetroSettings(settings, Boolean(hostedTarget)),
3483
+ remote: Boolean(remoteDevice),
3484
+ note,
3485
+ resolve: d.resolveProjectMetro,
3486
+ start: d.startDevServer,
3487
+ readState: d.readWorkspaceState
3488
+ });
3489
+ reclaimed = [...reclaimed, ...gate.reclaimed];
3490
+ if (!gate.ok) {
3491
+ fail({
3492
+ code: gate.code,
3493
+ message: gate.message,
3494
+ remedy: gate.remedy,
3495
+ lines: gate.lines
3496
+ });
3497
+ return false;
3498
+ }
3499
+ metroPort = gate.port;
3500
+ devServer = gate.devServer;
3501
+ } else {
3502
+ const pin = metroPortSetting(root);
3503
+ if (pin.error) {
3504
+ fail({
3505
+ code: "STIM_BAD_ARG",
3506
+ message: pin.error,
3507
+ remedy: SETTING_SHAPE_REMEDY
3508
+ });
3509
+ return false;
3510
+ }
3511
+ if (pin.port === null && !metroPort) note(chalk.yellow(`No Metro port is reserved for this workspace; wiring the app to ${DEFAULT_METRO_PORT}.`));
3512
+ metroPort = pin.port ?? metroPort ?? 8081;
3513
+ }
3514
+ hostedIosMetroNote(Boolean(hostedTarget), settings, note);
3515
+ if (physical && metroPort !== null && !await resolveLanOrigin()) return false;
3516
+ if (remoteDevice && metroPort !== null) {
3517
+ const reachable = await d.ensureMetroReachable({
3518
+ ctx: remoteDevice.ctx,
3519
+ metroPort,
3520
+ isExpo,
3521
+ tunnelMode: tunnelModeSetting(settings) ?? void 0,
3522
+ publicUrl: publicUrlSetting(settings),
3523
+ available: d.detectProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
3524
+ });
3525
+ if ("failed" in reachable) {
3526
+ fail({
3527
+ code: reachable.code ?? "STIM_NO_REMOTE_SESSION",
3528
+ message: reachable.failed,
3529
+ remedy: reachable.remedy
3530
+ });
3531
+ return false;
3532
+ }
3533
+ }
3534
+ if (!release && metroCheck && optimizations.metroWarmup) d.warmMetro({
3006
3535
  port: metroPort,
3007
- settings,
3008
- remote: Boolean(remoteDevice),
3009
- note,
3010
- resolve: d.resolveProjectMetro,
3011
- start: d.startDevServer,
3012
- readState: d.readWorkspaceState
3536
+ platform: "ios",
3537
+ isExpo,
3538
+ appId: proj?.bundleId,
3539
+ bundleUrl: metroWarmupUrlSetting(settings, "ios")
3540
+ });
3541
+ return true;
3542
+ }
3543
+ async function resolveLanOrigin() {
3544
+ const port = metroPort;
3545
+ const chosen = chooseLanAddress({
3546
+ pinned: iosLanHostSetting(settings),
3547
+ candidates: d.hostLanCandidates()
3013
3548
  });
3014
- reclaimed = [...reclaimed, ...gate.reclaimed];
3015
- if (!gate.ok) {
3549
+ if (!chosen) {
3016
3550
  fail({
3017
- code: gate.code,
3018
- message: gate.message,
3019
- remedy: gate.remedy,
3020
- lines: gate.lines
3551
+ code: "STIM_NO_LAN_ADDRESS",
3552
+ message: "A Debug run on a phone needs an address the phone can reach, and this Mac has no non-internal IPv4 interface.",
3553
+ remedy: "The phone reaches Metro over the network you share, because USB carries no reverse forward. Join a Wi-Fi or Ethernet network, or connect this Mac by cable, then run the command again."
3021
3554
  });
3022
3555
  return false;
3023
3556
  }
3024
- metroPort = gate.port;
3025
- devServer = gate.devServer;
3026
- } else if (!metroPort) {
3027
- metroPort = DEFAULT_METRO_PORT;
3028
- note(chalk.yellow(`No Metro port is reserved for this workspace; wiring the app to ${metroPort}.`));
3029
- }
3030
- if (physical && metroPort !== null && !await resolveLanOrigin()) return false;
3031
- if (remoteDevice && metroPort !== null) {
3032
- const reachable = await d.ensureMetroReachable({
3033
- ctx: remoteDevice.ctx,
3034
- metroPort,
3557
+ lanAddress = chosen.address;
3558
+ lanOriginUrl = lanOriginUrlFor(chosen.address, port);
3559
+ const source = chosen.pinned ? "ios.lanHost" : `${chosen.interfaceName ?? "interface"}${chosen.candidates > 1 ? ` of ${chosen.candidates} candidates` : ""}`;
3560
+ phase("lan", `${lanOriginUrl} (${source})`);
3561
+ if (publicUrlSetting(settings) || tunnelModeSetting(settings)) note(chalk.dim(phaseLine("lan", "metro.publicUrl and metro.tunnel are ignored on --device: neither channel to a phone carries a URL, only a host and a port. They still apply to --remote.")));
3562
+ if (!metroCheck) return true;
3563
+ const reachable = await d.ensureLanReachable({
3564
+ origin: lanOriginUrl,
3565
+ metroPort: port,
3566
+ root,
3035
3567
  isExpo,
3036
- tunnelMode: tunnelModeSetting(settings) ?? void 0,
3037
- publicUrl: publicUrlSetting(settings),
3038
- available: d.detectProviders(binOnPath)
3568
+ logsDir
3039
3569
  });
3040
3570
  if ("failed" in reachable) {
3041
3571
  fail({
3042
- code: reachable.code ?? "STIM_NO_REMOTE_SESSION",
3572
+ code: "STIM_LAN_METRO_UNREACHABLE",
3043
3573
  message: reachable.failed,
3044
3574
  remedy: reachable.remedy
3045
3575
  });
3046
3576
  return false;
3047
3577
  }
3578
+ phase("lan", `gated: ${lanOriginUrl} answered as this workspace's Metro`);
3579
+ return true;
3048
3580
  }
3049
- if (!release && metroCheck && optimizations.metroWarmup) d.warmMetro({
3050
- port: metroPort,
3051
- platform: "ios",
3052
- isExpo,
3053
- appId: proj?.bundleId,
3054
- bundleUrl: metroWarmupUrlSetting(settings, "ios")
3055
- });
3056
- return true;
3057
- }
3058
- async function resolveLanOrigin() {
3059
- const port = metroPort;
3060
- const chosen = chooseLanAddress({
3061
- pinned: iosLanHostSetting(settings),
3062
- candidates: d.hostLanCandidates()
3063
- });
3064
- if (!chosen) {
3065
- fail({
3066
- code: "STIM_NO_LAN_ADDRESS",
3067
- message: "A Debug run on a phone needs an address the phone can reach, and this Mac has no non-internal IPv4 interface.",
3068
- remedy: "The phone reaches Metro over the network you share, because USB carries no reverse forward. Join a Wi-Fi or Ethernet network, or connect this Mac by cable, then run the command again."
3069
- });
3070
- return false;
3071
- }
3072
- lanAddress = chosen.address;
3073
- lanOriginUrl = lanOriginUrlFor(chosen.address, port);
3074
- const source = chosen.pinned ? "ios.lanHost" : `${chosen.interfaceName ?? "interface"}${chosen.candidates > 1 ? ` of ${chosen.candidates} candidates` : ""}`;
3075
- phase("lan", `${lanOriginUrl} (${source})`);
3076
- if (publicUrlSetting(settings) || tunnelModeSetting(settings)) note(chalk.dim(phaseLine("lan", "metro.publicUrl and metro.tunnel are ignored on --device: neither channel to a phone carries a URL, only a host and a port. They still apply to --remote.")));
3077
- if (!metroCheck) return true;
3078
- const reachable = await d.ensureLanReachable({
3079
- origin: lanOriginUrl,
3080
- metroPort: port,
3081
- root,
3082
- isExpo,
3083
- logsDir
3084
- });
3085
- if ("failed" in reachable) {
3086
- fail({
3087
- code: "STIM_LAN_METRO_UNREACHABLE",
3088
- message: reachable.failed,
3089
- remedy: reachable.remedy
3090
- });
3091
- return false;
3092
- }
3093
- phase("lan", `gated: ${lanOriginUrl} answered as this workspace's Metro`);
3094
- return true;
3095
- }
3096
- let artifact = null;
3097
- try {
3098
- const boot = () => physicalDevice ? Promise.resolve({
3099
- ok: true,
3100
- udid: physicalDevice.udid
3101
- }) : Promise.resolve(d.ensureBooted({
3102
- platform: "ios",
3103
- device,
3104
- simulatorApp,
3105
- out: note
3106
- })).catch((e) => ({
3107
- ok: false,
3108
- reason: String(e?.message || e)
3109
- }));
3110
- const startBoot = () => {
3111
- const bootTimer = stepTimer(d.now);
3112
- bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
3113
- root,
3114
- platform: "ios",
3115
- sessionName: ownedSessionName(remoteDevice.ctx.label),
3116
- startedAt: new Date(d.now()).toISOString(),
3117
- boot,
3118
- createdSessionId: remoteDevice.createdSessionId,
3119
- abandonCreatedSession: remoteDevice.abandonCreatedSession,
3120
- webPreviewUrl: remoteDevice.webPreviewUrl,
3121
- writeState: d.writeWorkspaceState,
3122
- register: registerProject,
3123
- notice: (line) => note(chalk.dim(phaseLine("lock", line)))
3124
- }) : boot()).then((result) => {
3125
- bootDuration = bootTimer();
3126
- return result;
3127
- });
3128
- return bootPromise.then((result) => result?.udid ?? "");
3129
- };
3130
- const localBoot = remoteDevice ? null : startBoot();
3131
- udid = device.deviceUdid ?? await localBoot ?? "";
3132
- const acquiredArtifact = unlessCancelled(await acquireIosArtifact({
3133
- root,
3134
- logFile,
3135
- udid,
3136
- configuration,
3137
- buildScheme,
3138
- buildProfile,
3139
- isExpo,
3140
- remoteDestination: Boolean(remoteDevice),
3141
- device: physical ? {
3142
- lanAddress,
3143
- metroPort,
3144
- signingName: iosSigningIdentitySetting(settings),
3145
- signingSha1: iosSigningIdentitySha1Setting(settings)
3146
- } : null,
3147
- optimizations: optimizations.ios,
3148
- cache: {
3149
- policy: cachePolicy,
3150
- providerConfig: cacheProviderConfig,
3151
- disabledByFlag: opts.buildCache === false
3152
- },
3153
- easBuild,
3154
- easProfile: opts.easProfile,
3155
- maxBuilds: limits.maxBuilds,
3156
- progress: {
3157
- phase,
3158
- note,
3159
- logWriter,
3160
- estimates,
3161
- stats,
3162
- step: progress.step
3163
- }
3164
- }, d));
3165
- if (!acquiredArtifact.ok) {
3166
- compilationCache = acquiredArtifact.compilationCache;
3167
- return fail(acquiredArtifact.failure);
3168
- }
3169
- artifact = acquiredArtifact.artifact;
3170
- compilationCache = artifact.cache.compilation;
3171
- if (!localBoot) {
3172
- progress.step("install");
3173
- udid = await startBoot();
3174
- }
3175
- if (physicalDevice) {
3176
- const acquired = await d.acquireRunLease({
3177
- root,
3581
+ let artifact = null;
3582
+ try {
3583
+ const boot = () => physicalDevice ? Promise.resolve({
3584
+ ok: true,
3585
+ udid: physicalDevice.udid
3586
+ }) : Promise.resolve(d.ensureBooted({
3178
3587
  platform: "ios",
3179
- id: physicalDevice.udid,
3180
- deviceName: physicalDevice.name,
3181
- idLabel: "udid",
3182
- waitSeconds,
3183
- noWait,
3184
- installBoundMs: iosDeviceBounds(wireless).installMs,
3185
- appId: artifact.bundleId ?? proj?.bundleId ?? null,
3186
- holderAppId: (holder) => d.getProject(holder)?.bundleId ?? null,
3187
- now: d.now,
3188
- warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
3189
- });
3190
- if (acquired.status === "refused") return fail({
3191
- code: acquired.refusal.code,
3192
- message: acquired.refusal.message,
3193
- remedy: acquired.refusal.remedy,
3194
- lease: acquired.refusal.lease
3195
- });
3196
- leaseHandle = d.runLease({
3588
+ device,
3589
+ simulatorApp,
3590
+ out: note
3591
+ })).catch((e) => ({
3592
+ ok: false,
3593
+ reason: String(e?.message || e)
3594
+ }));
3595
+ const startBoot = () => {
3596
+ const bootTimer = stepTimer(d.now);
3597
+ bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
3598
+ root,
3599
+ platform: "ios",
3600
+ sessionName: ownedSessionName(remoteDevice.ctx.label),
3601
+ startedAt: new Date(d.now()).toISOString(),
3602
+ deviceType: remoteDevice.ctx.deviceType ?? null,
3603
+ boot,
3604
+ createdSessionId: remoteDevice.createdSessionId,
3605
+ abandonCreatedSession: remoteDevice.abandonCreatedSession,
3606
+ webPreviewUrl: remoteDevice.webPreviewUrl,
3607
+ writeState: d.writeWorkspaceState,
3608
+ register: registerProject,
3609
+ notice: (line) => note(chalk.dim(phaseLine("lock", line)))
3610
+ }) : boot()).then((result) => {
3611
+ bootDuration = bootTimer();
3612
+ return result;
3613
+ });
3614
+ return bootPromise.then((result) => result?.udid ?? "");
3615
+ };
3616
+ const localBoot = remoteDevice || hostedTarget ? null : startBoot();
3617
+ udid = device.deviceUdid ?? await localBoot ?? "";
3618
+ const acquiredArtifact = unlessCancelled(await acquireIosArtifact({
3197
3619
  root,
3198
- platform: "ios",
3199
- kind: acquired.status === "leased" ? acquired.kind : null,
3200
- expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
3201
- });
3202
- if (acquired.status === "leased") {
3203
- stopLeaseSignals = d.releaseLeaseOnSignal(releaseLease);
3204
- phase("lease", `${acquired.kind} lease on ${physicalDevice.udid} until ${leaseExpiryText(acquired.expiresAt, d.now())}`);
3620
+ logFile,
3621
+ udid,
3622
+ configuration,
3623
+ buildScheme,
3624
+ buildProfile,
3625
+ buildMachine,
3626
+ isExpo,
3627
+ remoteDestination: Boolean(remoteDevice || hostedTarget),
3628
+ ...hostedIosBuildTarget(hostedTarget),
3629
+ simulatorArch: simulatorBuildArch({
3630
+ physical,
3631
+ remoteArch,
3632
+ hostArch: d.hostSimulatorArch(),
3633
+ configuration
3634
+ }),
3635
+ device: physical ? {
3636
+ lanAddress,
3637
+ metroPort,
3638
+ signingName: iosSigningIdentitySetting(settings),
3639
+ signingSha1: iosSigningIdentitySha1Setting(settings)
3640
+ } : null,
3641
+ optimizations: optimizations.ios,
3642
+ cache: {
3643
+ policy: cachePolicy,
3644
+ providerConfig: cacheProviderConfig,
3645
+ disabledByFlag: opts.buildCache === false
3646
+ },
3647
+ easBuild,
3648
+ easProfile: opts.easProfile,
3649
+ maxBuilds: limits.maxBuilds,
3650
+ progress: {
3651
+ phase,
3652
+ note,
3653
+ logWriter,
3654
+ estimates,
3655
+ stats,
3656
+ step: progress.step,
3657
+ miss: progress.miss,
3658
+ hit: progress.hit,
3659
+ place: progress.place,
3660
+ waitingOn: progress.waitingOn
3661
+ }
3662
+ }, d));
3663
+ if (!acquiredArtifact.ok) {
3664
+ compilationCache = acquiredArtifact.compilationCache;
3665
+ return fail(acquiredArtifact.failure);
3205
3666
  }
3206
- }
3207
- try {
3208
- return await finishIosRun({
3667
+ artifact = acquiredArtifact.artifact;
3668
+ compilationCache = artifact.cache.compilation;
3669
+ if (hostedTarget) return await finishHostedIosRun({
3670
+ target: hostedTarget,
3671
+ root,
3209
3672
  slot,
3210
3673
  d,
3211
- root,
3212
- json,
3213
- release,
3674
+ artifact,
3214
3675
  configuration,
3215
3676
  buildScheme,
3677
+ release,
3216
3678
  isExpo,
3217
3679
  metroCheck,
3218
3680
  metroPort,
3219
3681
  logsDir,
3220
- logFile,
3221
- device,
3222
- udid,
3223
- physical,
3224
- wireless,
3225
- lanAddress,
3226
- lanOriginUrl,
3227
- remoteDevice,
3228
- bootPromise,
3229
- bootDuration: () => bootDuration,
3230
- artifact,
3231
- fail,
3232
- phase,
3233
- note,
3234
- logWriter,
3682
+ json,
3235
3683
  elapsed,
3236
3684
  startedAt,
3237
- closeWriter: () => writer?.close?.(),
3238
- lease: leaseHandle,
3239
- releaseLease,
3685
+ closeWriter: () => writer?.close(),
3240
3686
  recordRun,
3241
3687
  reclaimed,
3242
3688
  devServer,
3243
- enterPhase: progress.step
3689
+ fail,
3690
+ note,
3691
+ selectors
3692
+ });
3693
+ if (!localBoot) {
3694
+ progress.step("device");
3695
+ udid = await startBoot();
3696
+ }
3697
+ if (physicalDevice) {
3698
+ progress.step("device");
3699
+ const acquired = await d.acquireRunLease({
3700
+ root,
3701
+ platform: "ios",
3702
+ id: physicalDevice.udid,
3703
+ deviceName: physicalDevice.name,
3704
+ idLabel: "udid",
3705
+ waitSeconds,
3706
+ noWait,
3707
+ installBoundMs: iosDeviceBounds(wireless).installMs,
3708
+ appId: artifact.bundleId ?? proj?.bundleId ?? null,
3709
+ holderAppId: (holder) => d.getProject(holder)?.bundleId ?? null,
3710
+ now: d.now,
3711
+ warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
3712
+ });
3713
+ if (acquired.status === "refused") return fail({
3714
+ code: acquired.refusal.code,
3715
+ message: acquired.refusal.message,
3716
+ remedy: acquired.refusal.remedy,
3717
+ lease: acquired.refusal.lease
3718
+ });
3719
+ leaseHandle = d.runLease({
3720
+ root,
3721
+ platform: "ios",
3722
+ kind: acquired.status === "leased" ? acquired.kind : null,
3723
+ expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
3724
+ });
3725
+ if (acquired.status === "leased") {
3726
+ stopLeaseSignals = d.releaseLeaseOnSignal(releaseLease);
3727
+ phase("lease", `${acquired.kind} lease on ${physicalDevice.udid} until ${leaseExpiryText(acquired.expiresAt, d.now())}`);
3728
+ }
3729
+ }
3730
+ try {
3731
+ return await finishIosRun({
3732
+ slot,
3733
+ d,
3734
+ root,
3735
+ json,
3736
+ release,
3737
+ configuration,
3738
+ buildScheme,
3739
+ isExpo,
3740
+ metroCheck,
3741
+ metroPort,
3742
+ logsDir,
3743
+ logFile,
3744
+ device,
3745
+ udid,
3746
+ physical,
3747
+ wireless,
3748
+ lanAddress,
3749
+ lanOriginUrl,
3750
+ remoteDevice,
3751
+ bootPromise,
3752
+ bootDuration: () => bootDuration,
3753
+ artifact,
3754
+ fail,
3755
+ phase,
3756
+ note,
3757
+ logWriter,
3758
+ elapsed,
3759
+ startedAt,
3760
+ closeWriter: () => writer?.close?.(),
3761
+ lease: leaseHandle,
3762
+ releaseLease,
3763
+ recordRun,
3764
+ reclaimed,
3765
+ devServer,
3766
+ enterPhase: progress.step
3767
+ });
3768
+ } finally {
3769
+ releaseLease();
3770
+ }
3771
+ } catch (error) {
3772
+ recordRun({
3773
+ failed: true,
3774
+ durationMs: elapsed()
3244
3775
  });
3776
+ throw error;
3245
3777
  } finally {
3246
- releaseLease();
3778
+ artifact?.release();
3247
3779
  }
3248
- } catch (error) {
3249
- recordRun({
3250
- failed: true,
3251
- durationMs: elapsed()
3252
- });
3253
- throw error;
3254
3780
  } finally {
3255
- artifact?.release();
3781
+ hostedTarget?.host.connection.close();
3256
3782
  }
3257
3783
  }
3258
3784
  //#endregion