stim 1.18.0 → 1.20.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 (178) hide show
  1. package/README.md +21 -4
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
  12. package/dist/api.d.mts +104 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
  19. package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/build-plan-CP0tckNU.mjs +553 -0
  22. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/ccache-CjHQ5F8g.mjs +142 -0
  25. package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  26. package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
  27. package/dist/cli.mjs +1 -1
  28. package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
  29. package/dist/collector-run.d.mts +10 -0
  30. package/dist/collector-run.mjs +7 -7
  31. package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
  32. package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
  33. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  34. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  35. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  36. package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
  37. package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
  38. package/dist/device-DVCwAFlX.mjs +727 -0
  39. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
  40. package/dist/device-host-worker.mjs +38 -18
  41. package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
  42. package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  43. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  44. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  45. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
  46. package/dist/doctor-C2MSuMMg.mjs +515 -0
  47. package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
  48. package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
  49. package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  50. package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  51. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  52. package/dist/errors-DzkMYh8Q.mjs +205 -0
  53. package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
  54. package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
  55. package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
  56. package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
  57. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  58. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
  59. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  60. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  61. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  62. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
  63. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  64. package/dist/ios-BZ9dg2Fr.mjs +9 -0
  65. package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
  66. package/dist/ios-CZuv6gRv.mjs +1112 -0
  67. package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
  68. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
  69. package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
  70. package/dist/ios-project-B9drt_aY.mjs +197 -0
  71. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  72. package/dist/js-swap-C8icNYIY.mjs +432 -0
  73. package/dist/launch-DxH0THLP.mjs +745 -0
  74. package/dist/launch-YrbqaIFj.mjs +1530 -0
  75. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  76. package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
  77. package/dist/macos-C0iljhsN.mjs +2 -0
  78. package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
  79. package/dist/macos-run.mjs +1 -1
  80. package/dist/maintenance-run.mjs +13 -10
  81. package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
  82. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  83. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  84. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
  85. package/dist/native-android-lHzaew7r.mjs +226 -0
  86. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  87. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  88. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  89. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  90. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  91. package/dist/offload-worker.d.mts +12 -6
  92. package/dist/offload-worker.mjs +409 -73
  93. package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
  94. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
  95. package/dist/plan-BDk2AeY_.mjs +260 -0
  96. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  97. package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
  98. package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  99. package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
  100. package/dist/project-B8_duAzW.mjs +1067 -0
  101. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  102. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  103. package/dist/pull-requests.mjs +1 -1
  104. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  105. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  106. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  107. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  108. package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
  109. package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
  110. package/dist/remote-BU9UyhCJ.mjs +192 -0
  111. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  112. package/dist/result-BDoRk1k3.mjs +186 -0
  113. package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
  114. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
  115. package/dist/server-command-7XIKlWtO.mjs +69 -0
  116. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  117. package/dist/server-expo-DV43vep4.mjs +2 -0
  118. package/dist/settings-Bbztm4oV.mjs +1503 -0
  119. package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
  120. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  121. package/dist/settings.schema.json +156 -6
  122. package/dist/simslim-ChMfe8rg.mjs +149 -0
  123. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  124. package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
  125. package/dist/start-CbQCSnXQ.mjs +2 -0
  126. package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
  127. package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
  128. package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
  129. package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
  130. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  131. package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
  132. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  133. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  134. package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
  135. package/dist/stop-WQPQHLuy.mjs +72 -0
  136. package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
  137. package/dist/supervisor-run.d.mts +7 -2
  138. package/dist/supervisor-run.mjs +32 -20
  139. package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
  140. package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
  141. package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
  142. package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
  143. package/dist/toolchain-BIupIro1.mjs +2 -0
  144. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
  145. package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
  146. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  147. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
  148. package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
  149. package/dist/web-B3eldHFH.mjs +123 -0
  150. package/dist/web-BLXpOe6b.mjs +2 -0
  151. package/dist/web-run.mjs +10 -10
  152. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  153. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
  154. package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
  155. package/dist/worktree-q7_OZNY0.mjs +2 -0
  156. package/package.json +8 -6
  157. package/shim/bundle-response.cjs +2 -1
  158. package/shim/bundle-response.d.cts +1 -1
  159. package/shim/expo-metro-config.cjs +1 -1
  160. package/shim/native-android.gradle +101 -0
  161. package/dist/android-BKA16DTf.mjs +0 -4
  162. package/dist/android-BvwuMUJ9.mjs +0 -3572
  163. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  164. package/dist/ios-CPdkoKaa.mjs +0 -3953
  165. package/dist/ios-DNpdygiv.mjs +0 -7
  166. package/dist/macos-Ck7OBMNY.mjs +0 -2
  167. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  168. package/dist/plan-placement-CtxkCO54.mjs +0 -1859
  169. package/dist/project-C3ChRLZy.mjs +0 -212
  170. package/dist/projects-DD7dBTCj.mjs +0 -92
  171. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  172. package/dist/settings-CQyMElEP.mjs +0 -701
  173. package/dist/start-NTc1Ifqs.mjs +0 -2
  174. package/dist/stop-BGhKMOuH.mjs +0 -48
  175. package/dist/trigger-DWAq9hkw.mjs +0 -2
  176. package/dist/web-B6_bFDn7.mjs +0 -2
  177. package/dist/worktree-BJfnU6r5.mjs +0 -2
  178. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,3953 +0,0 @@
1
- import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
3
- import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
4
- import { _ as stepTimer, c as launchErrorReport, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-CBtzyIgF.mjs";
5
- 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-CgYMahz7.mjs";
6
- import { D as makeTemporaryDirectory, O as removeTemporaryEntry, s as gitCommonDir, y as repoRoot } from "./worktree-CsKYexJM.mjs";
7
- import { i as detectIsExpo, n as appProjectProblem, s as findProjectRoot$1, t as NO_PROJECT_REFUSAL } from "./project-C3ChRLZy.mjs";
8
- import { l as resolveProjectMetro, o as pidExists, u as signalProcessTree } from "./metro-Dh8T4Ytd.mjs";
9
- import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, i as recordWorkspaceUse, r as readWorkspaceState$1, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-C59dqG-Z.mjs";
10
- import { F as recordFinishedBuild, Ft as MODE_BARE, I as startBuildProgress, It as MODE_EXPO, Jt as writeWorkspaceLaunch, L as tapBuildLog, O as NO_BUILD_PROGRESS, R as createRunRecorder, U as recordRunStats, V as recordCapacityRefusal, W as statsProjectKey$1, t as budgetGate, z as readRunEstimates } from "./budget-DIhL2BKx.mjs";
11
- import { N as releaseBuildSlot, j as acquireBuildSlot } from "./workspaces-xXItD8Dj.mjs";
12
- 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-Co-EwifR.mjs";
13
- import { ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY } from "./android-DC3ZLuw0.mjs";
14
- import { d as listAllIosSims, i as createIosSimSnapshot, m as listIosRuntimes, r as clearIosAppData } from "./ios-B3SbOWdw.mjs";
15
- import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Behws_fi.mjs";
16
- import { A as publicUrlSetting, C as metroWarmupUrlSetting, F as resolveSettings, H as tunnelModeSetting, K as detectProviders, N as remoteIosSetting, P as resolveCacheProviderConfig, Q as resolveOptimizations, R as settingOriginScope, T as parseIosRemote, U as unknownSettingKeys, V as settingsLayers, Y as artifactCachePolicy, Z as optimizationBuildProfile, _ as iosSigningIdentitySha1SettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, i as SETTING_SHAPE_REMEDY, m as iosSigningIdentitySetting, p as iosLanHostSettingError, u as cacheProviderSettingError, x as metroPortSetting, z as settingShapeErrors } from "./settings-CQyMElEP.mjs";
17
- import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
18
- import { _ as resolveBuildPlacement, a as offloadBuild, c as placementLoad, d as setRemoteLogSink, f as OffloadRefusal, h as parseBuildMachineOption, i as closeOffload, l as remotePhaseText, m as namedBuildMachine, n as buildPlacementCandidates, r as chooseBuildMachine, s as offloadPlacement, u as simulatorRuntime } from "./client-DzhapoMB.mjs";
19
- import { r as checkDeviceCapacity } from "./device-capacity-EUOOYr8T.mjs";
20
- import { C as readBundleId, O as bundlerPin, S as readBundleExecutable, _ as buildIos, l as podsAreStale, m as COMPILATION_CACHE_NOT_RUN, p as runPodInstall, r as iosToolchain, u as readPodState, w as resolveScheme, x as discoverXcodeProject, y as compilationCacheActivityLine } from "./toolchain-Bnyd_K3A.mjs";
21
- import { r as hostedMetroSettings } from "./metro-gateway-CEUw9RMH.mjs";
22
- import { i as writeDevicePlacement, n as readHostedIos, o as writeHostedIos } from "./ios-state-B2pES6Vs.mjs";
23
- import { a as printPlan, b as automaticDevicePlacement, c as fingerprintErrorMissReason, g as ensureOwnedDevice, h as ensureBooted, i as planPayload, l as skippedMissReason, m as didSetUpDevice, n as planCachedBuild, o as refusePlan, p as swapJsBundle, r as planFlagRefusal, s as explainBuildMiss, t as planHostedDevice, x as devicePlacementLine, y as warmMetro } from "./plan-placement-CtxkCO54.mjs";
24
- import { n as devicePlacementRecord, t as buildPlacementRecord } from "./placement-log-BcPhRwR4.mjs";
25
- import { S as restartedAppNote, _ 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-D-PUENug.mjs";
26
- import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-C1nqh4mu.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-hskNcWkZ.mjs";
28
- 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-BGg7aGIZ.mjs";
29
- import { n as devClientTakesDevMenuParams, t as devClientScheme } from "./dev-client-CLnIH02x.mjs";
30
- 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-tHbd1fKy.mjs";
31
- import { d as runCancellationSignal, f as withNativeBuildRun, r as cancelledFailure, u as runCancellation } from "./native-run-B06FkmZx.mjs";
32
- import { a as isEasBuildFailure, i as easDeviceBuildRemedy, o as planEasDevelopmentBuild, s as resolveEasDevelopmentBuild } from "./simslim-EHBQ2R4G.mjs";
33
- import { c as startDevServer, h as detectBundleId } from "./start-D-IeOyoK.mjs";
34
- 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-w1xhCqIW.mjs";
35
- import { n as verifyCollectorOwnership } from "./ownership-D6Se7gg_.mjs";
36
- import { a as sleep, i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-D_603JiC.mjs";
37
- import { n as clearIosAdoptionPending } from "./device-ios-6MewKKnc.mjs";
38
- import { t as selectFromPool } from "./device-pool-F5WvRPtD.mjs";
39
- import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-juCyreaR.mjs";
40
- import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-BRXOqDzG.mjs";
41
- import { _ as resolveSimulatorAppFlag, a as deviceModelRefusal, c as iosProviderRunOptions, d as podAction, f as printDiagnostics, g as resolveRuntime, h as resolveIosWait, i as deviceLabel, l as isReleaseConfiguration, m as resolveDeviceType, n as buildLogFile, o as deviceShortName, p as resolveConfiguration, r as createIosDeviceWaitRun, t as appNameFromPath, u as ownedSimFailure, v as simulatorBuildArch, y as xcodeFailureReport } from "./support-BaayeYCf.mjs";
42
- import { n as prepareHostedIos, t as placeHostedIos } from "./hosted-ios-D5mNmtj4.mjs";
43
- 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-vOaByMhw.mjs";
44
- import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
45
- import { basename, join } from "node:path";
46
- import { buildDiagnostics, hostedIosStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
47
- import { networkInterfaces, tmpdir } from "node:os";
48
- import chalk from "chalk";
49
- import { InvalidArgumentError } from "commander";
50
- import { hostSimulatorArch, workspaceId } from "@stim-cli/core";
51
- import { X509Certificate } from "node:crypto";
52
- import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
53
- //#region src/engine/ios-lan.ts
54
- const IP_TXT = "ip.txt";
55
- function chooseLanAddress({ pinned = null, candidates }) {
56
- const list = Array.isArray(candidates) ? candidates : [];
57
- if (pinned) return {
58
- address: pinned,
59
- interfaceName: list.find((candidate) => candidate.address === pinned)?.interfaceName ?? null,
60
- pinned: true,
61
- candidates: list.length
62
- };
63
- const first = list[0];
64
- if (!first) return null;
65
- return {
66
- address: first.address,
67
- interfaceName: first.interfaceName,
68
- pinned: false,
69
- candidates: list.length
70
- };
71
- }
72
- function lanOriginUrlFor(address, port) {
73
- return `http://${address}:${port}`;
74
- }
75
- function ipTxtContents(address, port) {
76
- return `${String(address).trim()}:${String(port).trim()}\n`;
77
- }
78
- function writeIpTxt(appPath, address, port, { write = writeFileSync } = {}) {
79
- const path = join(appPath, IP_TXT);
80
- write(path, ipTxtContents(address, port), "utf-8");
81
- return path;
82
- }
83
- async function ensureLanReachable({ origin, metroPort, root, isExpo, logsDir, gateOrigin = gateMetroOrigin, readRecords = null }) {
84
- const result = await gateOrigin({
85
- origin,
86
- metroPort,
87
- platform: "ios",
88
- entryPoint: bundleEntryPoint(root, isExpo),
89
- readRecords: readRecords ?? (() => readMetroRecords(logsDir)),
90
- isProof: isBundleProof
91
- });
92
- if (result.failed) return {
93
- failed: result.reason ?? `${origin} is not this workspace's Metro.`,
94
- remedy: `The phone reaches Metro at ${origin}, so that address has to serve THIS workspace's dev server. \`stim start\` prints the port it reserved. Set ios.lanHost in .stim.json when this Mac has several network interfaces and the phone shares one that is not the first.`
95
- };
96
- return { ok: true };
97
- }
98
- function copyAppAside(appPath, { exec = null, mkdtemp = () => makeTemporaryDirectory(appPath, "stim-ios-device-") } = {}) {
99
- const e = exec || getExecutor();
100
- const tmpDir = mkdtemp();
101
- const copy = join(tmpDir, basename(appPath));
102
- try {
103
- e.runFile("cp", [
104
- "-c",
105
- "-R",
106
- appPath,
107
- copy
108
- ]);
109
- } catch {
110
- try {
111
- removeTemporaryEntry(copy);
112
- e.runFile("cp", [
113
- "-R",
114
- appPath,
115
- copy
116
- ]);
117
- } catch (error) {
118
- removeTemporaryEntry(tmpDir);
119
- throw error;
120
- }
121
- }
122
- return {
123
- tmpDir,
124
- appPath: copy
125
- };
126
- }
127
- //#endregion
128
- //#region src/commands/ios/result.ts
129
- function lastBuildRecord({ devicePlacement, 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 }) {
130
- const record = {
131
- platform: "ios",
132
- fingerprint,
133
- cacheKey,
134
- cacheHit: cacheLevel(cacheHit),
135
- cacheSkipped: Boolean(cacheSkipped),
136
- durationMs,
137
- appPath,
138
- bundleId,
139
- startedAt,
140
- status,
141
- configuration
142
- };
143
- if (errorCode) record.errorCode = errorCode;
144
- if (devicePlacement) record.devicePlacement = devicePlacement;
145
- record.buildMachine = buildMachine;
146
- if (builtOn) record.builtOn = builtOn;
147
- if (offloadedTo) record.offloadedTo = offloadedTo;
148
- if (offloadFallback) record.offloadFallback = offloadFallback;
149
- if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
150
- const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
151
- if (recorded.length) record.diagnostics = recorded;
152
- return record;
153
- }
154
- 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 }) {
155
- return {
156
- platform: "ios",
157
- ...slot && slot !== "default" ? { slot } : {},
158
- udid,
159
- deviceName: deviceName ?? null,
160
- deviceType: deviceType ?? null,
161
- runtime: runtime ?? null,
162
- fingerprint,
163
- configuration: configuration ?? null,
164
- ...scheme ? { scheme } : {},
165
- cacheKey,
166
- cacheHit: cacheLevel(cacheHit),
167
- cacheSkipped: Boolean(cacheSkipped),
168
- buildMachine,
169
- ...builtOn ? { builtOn } : {},
170
- ...offloadedTo ? { offloadedTo } : {},
171
- ...offloadFallback ? { offloadFallback } : {},
172
- compilationCache,
173
- waitedForBuild: waitedForBuild ? {
174
- pid: waitedForBuild.pid ?? null,
175
- ms: waitedForBuild.ms ?? 0
176
- } : null,
177
- appPath,
178
- bundleId,
179
- installSkipped: Boolean(installSkipped),
180
- launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
181
- metroPort,
182
- logs: { dir: logsDir },
183
- ...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
184
- durationMs,
185
- ...webPreviewUrl ? { webPreviewUrl } : {},
186
- ...lease === void 0 ? {} : { lease },
187
- ...devServer ? { devServer } : {}
188
- };
189
- }
190
- function writeLastBuild(root, record, { record: recordBuild = recordFinishedBuild } = {}) {
191
- try {
192
- recordBuild(root, record);
193
- } catch {}
194
- return record;
195
- }
196
- async function finishIosUpload(uploadPending, remote, phase, note) {
197
- if (!uploadPending) return false;
198
- const upload = await uploadPending;
199
- if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
200
- else if (upload?.timedOut) {
201
- note(chalk.yellow(phaseLine("cache", `${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`)));
202
- return true;
203
- } else if (upload?.failed) {
204
- const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
205
- code: "logged-out",
206
- reason: upload.failed,
207
- phase: "upload"
208
- }) : null;
209
- note(chalk.yellow(phaseLine("cache", authNote || `${remote?.name} upload failed: ${upload.failed}`)));
210
- }
211
- return false;
212
- }
213
- function reportIosResult({ devicePlacement, 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 }) {
214
- const durationMs = elapsed();
215
- recordRun({
216
- failed: false,
217
- cacheHit,
218
- waited: waitedForBuild,
219
- durationMs,
220
- offloadedTo
221
- });
222
- writeLastBuild(root, lastBuildRecord({
223
- devicePlacement,
224
- fingerprint: storeHash,
225
- cacheKey: storeKey,
226
- cacheHit,
227
- cacheSkipped: !useBuildCache,
228
- missReason,
229
- durationMs,
230
- appPath,
231
- bundleId,
232
- startedAt,
233
- status: "ok",
234
- configuration: configuration ?? "Debug",
235
- buildMachine,
236
- builtOn,
237
- offloadedTo,
238
- offloadFallback
239
- }));
240
- closeWriter();
241
- const facts = iosFacts({
242
- root,
243
- slot,
244
- udid,
245
- deviceName: device?.deviceName ?? null,
246
- deviceType: device?.deviceType,
247
- runtime: device?.runtime,
248
- fingerprint: storeHash,
249
- configuration,
250
- scheme: buildScheme,
251
- cacheKey: storeKey,
252
- cacheHit,
253
- compilationCache,
254
- cacheSkipped: !useBuildCache,
255
- waitedForBuild,
256
- appPath,
257
- bundleId,
258
- installSkipped,
259
- metroPort,
260
- logsDir,
261
- durationMs,
262
- launched: launchState,
263
- webPreviewUrl,
264
- lease,
265
- devServer,
266
- buildMachine,
267
- builtOn,
268
- offloadedTo,
269
- offloadFallback
270
- });
271
- if (devicePlacement) facts.devicePlacement = devicePlacement;
272
- if (host) facts.host = host;
273
- if (json) console.log(JSON.stringify({
274
- ...facts,
275
- ...links ? { links } : {},
276
- ...reclaimed.length ? { reclaimed } : {}
277
- }));
278
- else {
279
- 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)})`;
280
- 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);
281
- const deviceName = device?.deviceName ?? device?.name ?? udid;
282
- const cacheResult = useBuildCache ? cacheDescription(cacheHit, providerName, offloadedTo) : `bypassed; ${cacheDescription(false, null, offloadedTo)}`;
283
- 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})` : ""}`;
284
- console.log([
285
- outcome,
286
- phaseLine("device", host ? `${deviceName} (iOS ${device.runtime?.replace(/^iOS /, "")}) on ${host.machine}` : `${deviceName} (${udid})`),
287
- phaseLine("app", bundleId),
288
- phaseLine("metro", metroResult),
289
- phaseLine("cache", cacheResult),
290
- ...compilationCache.status === "not-run" ? [phaseLine("compilation cache", compilationCacheActivityLine(compilationCache))] : [],
291
- phaseLine("logs", logsDir),
292
- phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)
293
- ].join("\n"));
294
- if (facts.webPreviewUrl) console.error(chalk.dim(`Watch this device: ${facts.webPreviewUrl}`));
295
- if (links) console.error(chalk.dim(workspaceLinkLine(links)));
296
- }
297
- return facts;
298
- }
299
- function cacheDescription(cacheHit, providerName = null, offloadedTo = null) {
300
- if (cacheHit === "remote") return `from ${providerName || "the remote cache"}`;
301
- if (cacheHit === "local") return "from cache";
302
- return offloadedTo ? `built on ${offloadedTo}` : "built";
303
- }
304
- //#endregion
305
- //#region src/commands/ios/artifact.ts
306
- const GENERIC_SIM_DESTINATION = "generic/platform=iOS Simulator";
307
- const IPHONEOS_SDK = "iphoneos";
308
- const PROVIDER_SKIPPED_ON_DEVICE = "a device build is local-tier only: its cache key names the iphoneos slice, and a remote or provider entry is keyed for the simulator";
309
- var ArtifactRefusal = class extends Error {
310
- failure;
311
- constructor(failure) {
312
- super(failure.message ?? failure.code);
313
- this.failure = failure;
314
- }
315
- };
316
- async function acquireIosArtifact({ root, logFile, udid, remoteDestination, hostedDestination, simulatorArch, device, configuration, buildScheme, buildProfile, buildMachine = "auto", isExpo, optimizations, cache, easBuild, easProfile, maxBuilds, progress }, d) {
317
- const { phase, note, logWriter, estimates, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
318
- const physical = device !== null;
319
- const keyOptions = {
320
- scheme: buildScheme,
321
- ...configuration ? { configuration } : {},
322
- isSimulator: !physical,
323
- ...simulatorArch ? { arch: simulatorArch } : {},
324
- ...buildProfile ? { buildProfile } : {}
325
- };
326
- const providerRunOptions = iosProviderRunOptions(configuration, simulatorArch);
327
- const lanAddress = device?.lanAddress ?? null;
328
- const metroPort = device?.metroPort ?? null;
329
- const release = isReleaseConfiguration(configuration);
330
- const cachePolicy = cache.policy;
331
- const useBuildCache = cachePolicy.read;
332
- const cacheProviderConfig = cache.providerConfig;
333
- let compilationCache = COMPILATION_CACHE_NOT_RUN;
334
- function fail(failure) {
335
- throw new ArtifactRefusal(failure);
336
- }
337
- const temporaryDirs = /* @__PURE__ */ new Set();
338
- const releaseArtifact = () => {
339
- for (const directory of temporaryDirs) {
340
- try {
341
- rmSync(directory, {
342
- recursive: true,
343
- force: true
344
- });
345
- } catch {}
346
- temporaryDirs.delete(directory);
347
- }
348
- };
349
- let buildLock = null;
350
- const releaseLock = () => {
351
- if (!buildLock) return;
352
- const held = buildLock;
353
- buildLock = null;
354
- try {
355
- d.releaseBuildLock(held);
356
- } catch (e) {
357
- note(chalk.dim(`Could not release the build lock at ${held.path}: ${e?.message || e}`));
358
- }
359
- };
360
- let buildSlot = null;
361
- const releaseSlot = () => {
362
- if (!buildSlot) return;
363
- const held = buildSlot;
364
- buildSlot = null;
365
- try {
366
- d.releaseBuildSlot(held);
367
- } catch (e) {
368
- note(chalk.dim(`Could not release the build slot: ${e?.message || e}`));
369
- }
370
- };
371
- let fingerprint = "";
372
- let fingerprintSources = [];
373
- let cacheKey = "";
374
- let storeHash = null;
375
- let storeKey = null;
376
- let storeSources = [];
377
- let appPath = null;
378
- let bundleId = null;
379
- let cacheHit = false;
380
- let offloadedTo = null;
381
- let handoff = null;
382
- let offloadFallback = null;
383
- let fallbackMachine = null;
384
- let hereReason = "no remote Mac is paired";
385
- let slotWaitMs;
386
- let builtOn;
387
- const fallBack = (reason, line = reason, info = {}) => {
388
- logWriter().write(buildPlacementRecord({
389
- platform: "ios",
390
- buildMachine,
391
- candidates: info.candidates,
392
- event: "placement_fallback",
393
- fallback: {
394
- code: info.code ?? "fallback",
395
- reason,
396
- machine: info.machine
397
- }
398
- }));
399
- if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
400
- offloadFallback = reason;
401
- buildFailure = {
402
- ...buildFailure,
403
- offloadFallback: reason
404
- };
405
- phase("build", `${line} -> building here`);
406
- };
407
- const openOffload = { choice: null };
408
- let remote = null;
409
- let abandonedRemote = false;
410
- let uploadPending = null;
411
- let providerUpload = null;
412
- let providerName = null;
413
- let providerLoad = null;
414
- const cacheWarn = createWarnOnce((line) => note(chalk.yellow(phaseLine("cache", line))));
415
- const loadProvider = cachePolicy.remote && cacheProviderConfig && !physical ? () => providerLoad ??= d.loadCacheProvider({
416
- projectRoot: root,
417
- config: cacheProviderConfig
418
- }) : null;
419
- if (cacheProviderConfig && physical) note(chalk.dim(phaseLine("cache", PROVIDER_SKIPPED_ON_DEVICE)));
420
- let waitedForBuild = null;
421
- let releasedWait = null;
422
- let swapFellBack = false;
423
- let buildFailure = {};
424
- let missReason = null;
425
- async function resolveInitialFingerprint() {
426
- if (easBuild?.ok) {
427
- fingerprint = storeHash = easBuild.fingerprint;
428
- cacheKey = storeKey = easBuild.cacheKey;
429
- appPath = easBuild.path;
430
- cacheHit = easBuild.cacheHit;
431
- providerName = "eas";
432
- stats.setCacheKey(cacheKey);
433
- if (physical) {
434
- const gate = d.gateProfileForDevice({
435
- appPath,
436
- udid,
437
- configuration
438
- });
439
- if (!gate.ok) fail({
440
- code: gate.code,
441
- message: gate.reason,
442
- remedy: easDeviceBuildRemedy(easProfile)
443
- });
444
- if (!d.devClientScheme(root, appPath)) fail({
445
- code: "STIM_BAD_ARG",
446
- message: "The EAS device app has no development-client URL scheme.",
447
- remedy: "Install expo-dev-client with npx expo install expo-dev-client, then rebuild the EAS profile and retry."
448
- });
449
- }
450
- return;
451
- }
452
- if (useBuildCache) step("cache-lookup");
453
- const fingerprintTimer = stepTimer(d.now);
454
- let computedFingerprint;
455
- let fingerprintError = "no hash";
456
- try {
457
- const computed = await d.fingerprintProject(root, { platform: "ios" });
458
- computedFingerprint = computed?.hash ?? null;
459
- fingerprintSources = computed?.sources ?? [];
460
- } catch (e) {
461
- computedFingerprint = null;
462
- fingerprintError = String(e?.message || e);
463
- note(chalk.dim(`Fingerprinting failed: ${fingerprintError}`));
464
- }
465
- if (!computedFingerprint) fail({
466
- code: "STIM_NO_FINGERPRINT",
467
- message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
468
- remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry.",
469
- build: {
470
- cacheSkipped: !useBuildCache,
471
- missReason: fingerprintErrorMissReason(fingerprintError)
472
- }
473
- });
474
- fingerprint = computedFingerprint;
475
- cacheKey = buildCacheKey("ios", fingerprint, keyOptions);
476
- stats.setCacheKey(cacheKey);
477
- storeHash = fingerprint;
478
- storeKey = cacheKey;
479
- storeSources = fingerprintSources;
480
- const found = await resolveTieredBuild({
481
- local: filesystemBuildCapability({
482
- resolve: d.resolveBuild,
483
- store: d.storeBuild,
484
- sources: fingerprintSources
485
- }),
486
- loadProvider,
487
- target: {
488
- projectRoot: root,
489
- platform: "ios",
490
- key: cacheKey
491
- },
492
- destinationDir: providerDownloadPath(workspaceDir(root)),
493
- ensureDestination: prepareProviderDownloadDir,
494
- skipRead: !useBuildCache,
495
- warn: cacheWarn
496
- });
497
- const cached = found?.tier === "local" ? found.path : null;
498
- cacheHit = cached ? "local" : false;
499
- phase("fingerprint", `${shortHash(fingerprint)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : cache.disabledByFlag ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
500
- if (found?.tier === "provider") {
501
- cacheHit = "remote";
502
- providerName = found.providerName ?? null;
503
- phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
504
- }
505
- appPath = found?.path ?? null;
506
- }
507
- async function resolveRemoteArtifact() {
508
- if (physical || !cachePolicy.remote || buildProfile || buildScheme) return null;
509
- if (!appPath) {
510
- const loaded = await d.loadProjectProvider(root, { isExpo });
511
- if (loaded?.unavailable) note(chalk.yellow(phaseLine("cache", `provider not usable: ${loaded.unavailable}`)));
512
- else if (loaded?.provider) remote = loaded;
513
- if (remote?.name === "eas") {
514
- const auth = d.checkEasAuth({
515
- projectRoot: root,
516
- owner: loaded?.owner || null
517
- });
518
- const authNote = easAuthNote(auth);
519
- if (authNote) note(chalk.yellow(phaseLine("cache", authNote)));
520
- if (auth?.code === "logged-out") remote = null;
521
- }
522
- }
523
- if (remote && useBuildCache) {
524
- const remoteTimer = stepTimer(d.now);
525
- const hit = await d.resolveRemote({
526
- logWriter: logWriter(),
527
- provider: remote.provider,
528
- platform: "ios",
529
- projectRoot: root,
530
- fingerprintHash: fingerprint,
531
- runOptions: providerRunOptions
532
- });
533
- if (hit?.appPath) {
534
- let stored = null;
535
- try {
536
- stored = d.storeBuild("ios", cacheKey, hit.appPath, { sources: fingerprintSources });
537
- } catch (e) {
538
- note(chalk.yellow(phaseLine("cache", `remote hit could not be stored locally: ${e?.message || e}`)));
539
- }
540
- appPath = stored || hit.appPath;
541
- cacheHit = "remote";
542
- phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
543
- } else if (hit?.timedOut) {
544
- abandonedRemote = true;
545
- note(chalk.yellow(phaseLine("cache", `${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`)));
546
- } else if (hit?.failed) {
547
- const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
548
- code: "logged-out",
549
- reason: hit.failed
550
- }) : null;
551
- note(chalk.yellow(phaseLine("cache", authNote || `${remote.name} could not be used: ${hit.failed}; building instead`)));
552
- } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
553
- }
554
- return remote;
555
- }
556
- async function awaitSharedBuild() {
557
- if (!useBuildCache || appPath) return;
558
- const shared = await waitForSharedBuild({
559
- platform: "ios",
560
- key: cacheKey,
561
- fingerprint,
562
- root,
563
- logFile,
564
- command: "stim ios",
565
- acquire: d.acquireBuildLock,
566
- wait: d.waitForBuild,
567
- now: d.now,
568
- phase: (text) => {
569
- step("wait");
570
- phase("build", text);
571
- },
572
- waitingOn,
573
- warn: (text) => note(chalk.yellow(phaseLine("build", text))),
574
- out: note
575
- });
576
- if (shared.refusal) fail({
577
- ...shared.refusal,
578
- build: {
579
- fingerprint,
580
- cacheKey,
581
- cacheHit,
582
- cacheSkipped: !useBuildCache
583
- }
584
- });
585
- buildLock = shared.lock;
586
- releasedWait = shared.released;
587
- if (shared.hit) {
588
- appPath = shared.hit.path;
589
- cacheHit = "local";
590
- waitedForBuild = shared.hit.waited;
591
- }
592
- }
593
- const prepareDeviceApp = async (path, { fresh }) => {
594
- const refuse = (code, reason, remedy) => {
595
- if (fresh) fail({
596
- code,
597
- message: reason,
598
- remedy,
599
- build: {
600
- ...buildFailure,
601
- appPath: path
602
- }
603
- });
604
- note(chalk.yellow(phaseLine("cache", `${reason} -- building fresh instead`)));
605
- note(chalk.dim(phaseLine("", remedy)));
606
- swapFellBack = true;
607
- return null;
608
- };
609
- const gateProfile = () => {
610
- const gate = d.gateProfileForDevice({
611
- appPath: path,
612
- udid,
613
- configuration
614
- });
615
- return gate.ok ? path : refuse(gate.code, gate.reason, gate.remedy);
616
- };
617
- if (release) {
618
- if (!fresh) {
619
- note(chalk.yellow(phaseLine("cache", `a cached ${configuration} device app carries its builder's JS, and the device JS swap lands with phase 6 of appandflow/stim#178 -- building fresh instead, which bakes in this workspace's JS`)));
620
- swapFellBack = true;
621
- return null;
622
- }
623
- return gateProfile();
624
- }
625
- if (d.devClientScheme(root, path)) return gateProfile();
626
- let copy;
627
- try {
628
- copy = copyAppAside(path);
629
- } catch (e) {
630
- return refuse("STIM_INSTALL_FAILED", `Could not copy ${path} aside to write its ip.txt: ${e?.message || e}`, "Free space in the temporary directory and run the command again.");
631
- }
632
- temporaryDirs.add(copy.tmpDir);
633
- writeIpTxt(copy.appPath, lanAddress, metroPort);
634
- const sealed = d.sealAppForDevice({
635
- appPath: copy.appPath,
636
- udid,
637
- configuration,
638
- pinnedName: device?.signingName ?? null,
639
- pinnedSha1: device?.signingSha1 ?? null
640
- });
641
- if (!sealed.ok) {
642
- try {
643
- rmSync(copy.tmpDir, {
644
- recursive: true,
645
- force: true
646
- });
647
- } catch {}
648
- for (const line of sealed.lastLines ?? []) note(chalk.dim(phaseLine("", line)));
649
- return refuse(sealed.code, sealed.reason, sealed.remedy);
650
- }
651
- phase("ip.txt", `${lanAddress}:${metroPort} written into the install copy and re-sealed with "${sealed.identity.name}"${sealed.mode === "preserve-metadata" ? "" : ` (${sealed.mode})`}`);
652
- return copy.appPath;
653
- };
654
- const installableCachedApp = async (cachedPath) => {
655
- if (physical) return prepareDeviceApp(cachedPath, { fresh: false });
656
- if (!release) return cachedPath;
657
- phase("swap", `regenerating this workspace's JS for the cached ${configuration} app`);
658
- const swap = await d.swapJsBundle({
659
- root,
660
- isExpo,
661
- cachedAppPath: cachedPath,
662
- logWriter: logWriter()
663
- });
664
- if (swap?.ok && swap.appPath) {
665
- if (swap.note) note(chalk.yellow(phaseLine("swap", swap.note)));
666
- if (swap.tmpDir) temporaryDirs.add(swap.tmpDir);
667
- phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} + assets replaced, re-signed (${formatDuration(swap.durationMs ?? 0)})`);
668
- return swap.appPath;
669
- }
670
- note(chalk.yellow(phaseLine("swap", `failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${configuration} app carries its builder's JS; it is never installed after a failed swap)`)));
671
- for (const line of swap?.lastLines ?? []) note(chalk.dim(phaseLine("", line)));
672
- swapFellBack = true;
673
- return null;
674
- };
675
- async function prepareCachedArtifact() {
676
- if (appPath && cacheHit) {
677
- const prepared = await installableCachedApp(appPath);
678
- appPath = prepared;
679
- if (!prepared) {
680
- cacheHit = false;
681
- waitedForBuild = null;
682
- }
683
- }
684
- }
685
- function reasonForMiss(rekeyedBy) {
686
- if (swapFellBack) return {
687
- reason: skippedMissReason("the cached app could not be reused, so this run built it fresh"),
688
- diff: null
689
- };
690
- if (!useBuildCache) return {
691
- reason: skippedMissReason(cache.disabledByFlag ? "cache reuse turned off by --no-build-cache" : "cache reuse off in config"),
692
- diff: null
693
- };
694
- const current = {
695
- hash: storeHash ?? fingerprint,
696
- sources: storeSources
697
- };
698
- const explained = explainBuildMiss({
699
- root,
700
- platform: "ios",
701
- current,
702
- rekeyedBy,
703
- baselineDeps: { readState: d.readWorkspaceState }
704
- });
705
- return {
706
- reason: explained.reason,
707
- diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
708
- changed: explained.changedNames,
709
- previousHash: explained.previousHash,
710
- hash: current.hash
711
- }) : null
712
- };
713
- }
714
- function reportPendingRecheck(prebuild) {
715
- const pods = d.readPodState(root);
716
- if ((prebuild === "generate" || prebuild === "regenerate" || podAction(pods, d.podsAreStale(pods.lockText, pods.manifestText)).install) && useBuildCache) miss(reasonForMiss([]).reason, true);
717
- }
718
- function explainMiss(rekeyedBy) {
719
- const explained = reasonForMiss(rekeyedBy);
720
- missReason = explained.reason;
721
- if (explained.diff) logWriter().write(explained.diff);
722
- buildFailure = {
723
- ...buildFailure,
724
- missReason
725
- };
726
- miss(missReason);
727
- phase("cache", `miss: ${missReason.summary}`);
728
- if (missReason.kind === "no-baseline") {
729
- const untracked = untrackedMissLine(d.untrackedNativeFiles({ projectRoot: root }));
730
- if (untracked) note(chalk.dim(phaseLine("fingerprint", untracked)));
731
- }
732
- }
733
- async function settleStoreKeyAfterCompile(prebuildRan) {
734
- if (!storeHash || !storeKey) return;
735
- const afterBuild = await refingerprintAfterMutation({
736
- projectRoot: root,
737
- platform: "ios",
738
- previousHash: storeHash,
739
- fingerprint: d.fingerprintProject
740
- });
741
- const changedDuringBuild = afterBuild ? inputsChangedDuringBuild({
742
- platform: "ios",
743
- lookup: fingerprintSources,
744
- prebuildRan,
745
- compiled: storeSources,
746
- current: afterBuild.sources
747
- }) : [];
748
- if (!afterBuild || changedDuringBuild.length) {
749
- storeHash = null;
750
- storeKey = null;
751
- buildFailure = {
752
- ...buildFailure,
753
- fingerprint: null,
754
- cacheKey: null
755
- };
756
- note(chalk.yellow(phaseLine("fingerprint", afterBuild ? changedDuringBuildLine(changedDuringBuild) : "unavailable after xcodebuild; the build will be installed but not cached")));
757
- } else if (afterBuild.moved) {
758
- const beforeBuildHash = storeHash;
759
- storeHash = afterBuild.hash;
760
- storeSources = afterBuild.sources;
761
- storeKey = buildCacheKey("ios", afterBuild.hash, keyOptions);
762
- buildFailure = {
763
- ...buildFailure,
764
- fingerprint: storeHash,
765
- cacheKey: storeKey
766
- };
767
- note(chalk.dim(phaseLine("fingerprint", `${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after xcodebuild)`)));
768
- }
769
- }
770
- async function takeBuildSlot() {
771
- if (!maxBuilds) return;
772
- try {
773
- buildSlot = await d.acquireBuildSlot({
774
- max: maxBuilds,
775
- root,
776
- logFile,
777
- out: note,
778
- waitingFor: (info) => waitingFor?.(info, "build-slot")
779
- });
780
- slotWaitMs = buildSlot.slotWaitMs;
781
- } catch (e) {
782
- const refusal = (0, ownership_claim_exports.claimFailure)(e, "stim ios");
783
- if (refusal) fail({
784
- code: refusal.code,
785
- message: refusal.message,
786
- remedy: refusal.remedy,
787
- build: buildFailure
788
- });
789
- note(chalk.yellow(phaseLine("build", `could not take a build slot: ${e?.message || e}; building anyway`)));
790
- }
791
- }
792
- /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
793
- function placeBuild() {
794
- const { mode, machines } = buildPlacementCandidates(buildMachine);
795
- if (machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
796
- const runtime = physical || release ? null : hostedDestination?.runtime ?? (remoteDestination ? null : simulatorRuntime(udid));
797
- 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;
798
- const here = machineCapacity();
799
- const placement = offloadPlacement({
800
- mode,
801
- machines: machines.length,
802
- here,
803
- unsupported,
804
- selected: buildMachine
805
- });
806
- if (!placement.offload) {
807
- hereReason = placement.reason;
808
- logWriter().write(buildPlacementRecord({
809
- platform: "ios",
810
- buildMachine,
811
- stays: {
812
- code: placement.code,
813
- reason: placement.reason
814
- }
815
- }));
816
- if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
817
- return null;
818
- }
819
- return {
820
- mode,
821
- here,
822
- code: placement.code,
823
- reason: placement.reason,
824
- runtime,
825
- machines
826
- };
827
- }
828
- /** Asks the paired machines once the post-mutation key is known; null builds here. */
829
- async function chooseMachine(candidate) {
830
- let asked = [];
831
- const choice = await chooseBuildMachine({
832
- projectRoot: root,
833
- target: {
834
- platform: "ios",
835
- local: {
836
- ...iosToolchain(root),
837
- ...hostedDestination ? { arch: hostedDestination.architecture === "x86_64" ? "x64" : "arm64" } : {}
838
- },
839
- runtime: candidate.runtime,
840
- cocoapodsPinned: bundlerPin(root) !== null
841
- },
842
- mode: candidate.mode,
843
- here: candidate.here,
844
- note: (line) => note(chalk.dim(phaseLine("build", `offload: ${line}`))),
845
- machines: candidate.machines,
846
- selected: buildMachine,
847
- onCandidates: (each) => asked = each
848
- });
849
- if (typeof choice === "string") {
850
- const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
851
- if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
852
- fallBack(choice, choice, {
853
- code: "no-remote-mac-took-it",
854
- candidates: asked
855
- });
856
- return null;
857
- }
858
- openOffload.choice = choice;
859
- logWriter().write(buildPlacementRecord({
860
- platform: "ios",
861
- buildMachine,
862
- candidates: asked,
863
- chose: {
864
- machine: choice.machine,
865
- reason: `${candidate.reason}${placementLoad(choice)}`
866
- }
867
- }));
868
- phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
869
- return choice;
870
- }
871
- /** Builds on the chosen machine and stores the app under the post-mutation key; false builds here instead unless a machine was named. */
872
- async function compileElsewhere({ choice, candidate }) {
873
- const runtime = candidate.runtime;
874
- if (!storeKey || !storeHash) {
875
- if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
876
- return false;
877
- }
878
- const stagingDir = join(workspaceDir(root), "offload", "ios");
879
- const outcome = await offloadBuild({
880
- choice,
881
- expectedFingerprint: storeHash,
882
- request: {
883
- platform: "ios",
884
- runtime,
885
- configuration,
886
- scheme: buildScheme ?? null,
887
- isExpo,
888
- optimizations
889
- },
890
- stagingDir,
891
- onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
892
- onEnter: (name) => {
893
- if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") {
894
- builtOn = choice.machine;
895
- buildFailure = {
896
- ...buildFailure,
897
- builtOn
898
- };
899
- }
900
- place({
901
- host: choice.machine,
902
- phase: name
903
- });
904
- step(name === "prebuild" || name === "pods" ? name : "compile");
905
- },
906
- onRecord: (record) => logWriter().write({
907
- ...record,
908
- offloadedTo: choice.machine
909
- }),
910
- note: (line) => phase("build", line)
911
- });
912
- place(null);
913
- let stored = null;
914
- let reason = outcome.ok ? null : outcome.reason;
915
- if (outcome.ok) {
916
- const settled = await refingerprintAfterMutation({
917
- projectRoot: root,
918
- platform: "ios",
919
- previousHash: storeHash,
920
- fingerprint: d.fingerprintProject
921
- });
922
- if (!settled || settled.moved) reason = "the checkout here changed while it built";
923
- else try {
924
- stored = d.storeBuild("ios", storeKey, outcome.artifactPath, {
925
- sources: storeSources,
926
- overwrite: !useBuildCache
927
- });
928
- } catch (e) {
929
- reason = `could not store the app: ${e?.message || e}`;
930
- }
931
- }
932
- try {
933
- rmSync(stagingDir, {
934
- recursive: true,
935
- force: true
936
- });
937
- } catch {}
938
- step("compile");
939
- const prepared = stored ? await installableCachedApp(stored) : null;
940
- if (!outcome.ok || !prepared) {
941
- const why = reason ?? "the stored app is not installable";
942
- fallbackMachine = choice.machine;
943
- fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`, {
944
- code: "offload-failed",
945
- machine: choice.machine
946
- });
947
- logWriter().write({
948
- src: "build",
949
- level: "warn",
950
- event: "offload_failed",
951
- msg: reason,
952
- machine: choice.machine
953
- });
954
- return false;
955
- }
956
- const { timings } = outcome;
957
- appPath = prepared;
958
- builtOn = outcome.machine;
959
- offloadedTo = outcome.machine;
960
- handoff = outcome.handoff ?? null;
961
- stats.setPlacement({
962
- decision: "offloaded",
963
- machine: outcome.machine,
964
- reason: `${candidate.reason}${placementLoad(choice)}`,
965
- buildMs: timings.totalMs
966
- });
967
- 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)}`);
968
- compilationCache = outcome.compilationCache;
969
- phase("cache", `compilation cache on ${outcome.machine} ${compilationCacheActivityLine(compilationCache)}`);
970
- logWriter().write({
971
- src: "build",
972
- level: "info",
973
- event: "offload_done",
974
- msg: `built on ${outcome.machine}`,
975
- timings
976
- });
977
- return true;
978
- }
979
- async function buildArtifact() {
980
- buildFailure = {
981
- fingerprint,
982
- cacheKey,
983
- cacheHit,
984
- cacheSkipped: !useBuildCache,
985
- buildMachine
986
- };
987
- if (!appPath) {
988
- const offload = placeBuild();
989
- if (!offload) await takeBuildSlot();
990
- const mutatingSteps = [];
991
- const rekeyedBy = [];
992
- const prebuild = d.planPrebuild(root, "ios", {
993
- isExpo,
994
- fingerprint,
995
- sources: fingerprintSources
996
- });
997
- if (prebuild === "refuse") fail({
998
- ...staleNativeDirRefusal("ios"),
999
- build: buildFailure
1000
- });
1001
- reportPendingRecheck(prebuild);
1002
- if (prebuild === "generate" || prebuild === "regenerate") {
1003
- step("prebuild");
1004
- recordPrebuild(root, "ios", null);
1005
- const result = await d.runPrebuild(root, "ios", logWriter(), { clean: prebuild === "regenerate" });
1006
- if (result?.failed) {
1007
- phase("prebuild", "FAILED");
1008
- fail({
1009
- code: result.code || "STIM_PREBUILD_FAILED",
1010
- message: result.reason || "expo prebuild failed.",
1011
- remedy: result.remedy || `See ${logFile} for the transcript.`,
1012
- lines: (result.lastLines || []).slice(-5),
1013
- build: buildFailure
1014
- });
1015
- }
1016
- phase("prebuild", `${prebuild === "generate" ? "ios/ absent -> generated" : "ios/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(result?.durationMs ?? 0)})`);
1017
- mutatingSteps.push("prebuild");
1018
- }
1019
- if (isExpo && buildScheme !== void 0) {
1020
- const project = d.discoverXcodeProject(root);
1021
- if (project.error) fail({
1022
- ...project.error,
1023
- build: buildFailure
1024
- });
1025
- const schemeError = d.resolveScheme(project, { scheme: buildScheme }).error;
1026
- if (schemeError) fail({
1027
- ...schemeError,
1028
- build: buildFailure
1029
- });
1030
- }
1031
- const podState = d.readPodState(root);
1032
- const verdict = d.podsAreStale(podState.lockText, podState.manifestText);
1033
- const action = podAction(podState, verdict);
1034
- if (action.install) {
1035
- step("pods");
1036
- const result = await d.runPodInstall(root, logWriter(), { estimateMs: estimates().podsMs });
1037
- const podCommand = result?.command || "pod install";
1038
- for (const line of result?.notes || []) note(chalk.dim(phaseLine("pods", line)));
1039
- if (result?.failed) {
1040
- phase("pods", "FAILED");
1041
- fail({
1042
- code: result.code || "STIM_DEPS_FAILED",
1043
- message: result.reason || "`pod install` failed.",
1044
- remedy: result.remedy || `See ${logFile} for the transcript.`,
1045
- lines: result.diagnosticLines?.length ? result.diagnosticLines : (result.lastLines || []).slice(-5),
1046
- build: buildFailure
1047
- });
1048
- }
1049
- stats.setPodsMs(result?.durationMs ?? 0);
1050
- phase("pods", `${action.reason} -> installed with \`${podCommand}\` (${formatDuration(result?.durationMs ?? 0)})`);
1051
- mutatingSteps.push(podCommand);
1052
- }
1053
- if (mutatingSteps.length) {
1054
- const after = await refingerprintAfterMutation({
1055
- projectRoot: root,
1056
- platform: "ios",
1057
- previousHash: fingerprint,
1058
- fingerprint: d.fingerprintProject
1059
- });
1060
- const prebuildRan = mutatingSteps.includes("prebuild");
1061
- const editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
1062
- if (after && !editedConfig.length && prebuildRan) recordPrebuild(root, "ios", after.hash);
1063
- if (!after || editedConfig.length) {
1064
- storeHash = null;
1065
- storeKey = null;
1066
- buildFailure = {
1067
- ...buildFailure,
1068
- fingerprint: null,
1069
- cacheKey: null
1070
- };
1071
- note(chalk.yellow(phaseLine("fingerprint", after ? changedDuringBuildLine(editedConfig) : `unavailable after ${mutatingSteps.join(", ")}; the build will be installed but not cached`)));
1072
- } else if (after.moved) {
1073
- rekeyedBy.push(...mutatingSteps.map((mutation) => mutation === "prebuild" ? mutation : "pod install"));
1074
- storeHash = after.hash;
1075
- storeSources = after.sources;
1076
- storeKey = buildCacheKey("ios", after.hash, keyOptions);
1077
- buildFailure = {
1078
- ...buildFailure,
1079
- fingerprint: storeHash,
1080
- cacheKey: storeKey
1081
- };
1082
- note(chalk.dim(phaseLine("fingerprint", `${shortHash(fingerprint)} -> ${shortHash(storeHash)} (after ${mutatingSteps.join(", ")})`)));
1083
- const late = useBuildCache ? d.resolveBuild("ios", storeKey) : null;
1084
- if (late) {
1085
- const prepared = await installableCachedApp(late);
1086
- if (prepared) {
1087
- appPath = prepared;
1088
- cacheHit = "local";
1089
- lateHit();
1090
- phase("cache", `hit ${shortHash(storeHash)} (post-${mutatingSteps.join("/")} key)`);
1091
- if (releasedWait) {
1092
- waitedForBuild = releasedWait.facts;
1093
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1094
- }
1095
- }
1096
- }
1097
- }
1098
- }
1099
- if (offload && !appPath) {
1100
- explainMiss(rekeyedBy);
1101
- step("compile");
1102
- if (!storeKey) fallBack("no cache key to store the app under", "offload failed: no cache key to store the app under");
1103
- const choice = storeKey ? await chooseMachine(offload) : null;
1104
- if (!choice || !await compileElsewhere({
1105
- choice,
1106
- candidate: offload
1107
- })) await takeBuildSlot();
1108
- }
1109
- if (!appPath) {
1110
- if (!offload) explainMiss(rekeyedBy);
1111
- step("compile");
1112
- if (offload) stats.setPlacement({
1113
- decision: "fell-back",
1114
- slotWaitMs,
1115
- deviceSlotWaitMs: stats.deviceSlotWaitMs(),
1116
- reason: offloadFallback ?? "offload failed",
1117
- ...fallbackMachine ? { machine: fallbackMachine } : {}
1118
- });
1119
- else stats.setPlacement({
1120
- decision: "here",
1121
- reason: hereReason,
1122
- slotWaitMs,
1123
- deviceSlotWaitMs: stats.deviceSlotWaitMs()
1124
- });
1125
- builtOn = "here";
1126
- buildFailure = {
1127
- ...buildFailure,
1128
- builtOn
1129
- };
1130
- phase("build", `compiling ${configuration || "Debug"} with xcodebuild`);
1131
- const result = await d.buildIos({
1132
- root,
1133
- scheme: buildScheme,
1134
- udid,
1135
- destination: remoteDestination ? GENERIC_SIM_DESTINATION : null,
1136
- arch: remoteDestination ? simulatorArch : null,
1137
- ...physical ? { sdk: IPHONEOS_SDK } : {},
1138
- logWriter: logWriter(),
1139
- ...configuration ? { configuration } : {},
1140
- estimateMs: estimates().coldBuildMs,
1141
- optimizations
1142
- });
1143
- compilationCache = result.compilationCache;
1144
- phase("cache", `compilation cache ${compilationCacheActivityLine(compilationCache)}`);
1145
- if (!result.ok) {
1146
- phase("build", `FAILED after ${formatDuration(result.durationMs)}`);
1147
- printDiagnostics(note, result);
1148
- const report = xcodeFailureReport(result, logFile);
1149
- fail({
1150
- code: result.code,
1151
- message: report.message,
1152
- remedy: report.remedy,
1153
- logPath: logFile,
1154
- build: {
1155
- ...buildFailure,
1156
- diagnostics: result.diagnostics
1157
- }
1158
- });
1159
- }
1160
- stats.setBuildMs(result.durationMs);
1161
- phase("build", `ok (${formatDuration(result.durationMs)})`);
1162
- appPath = result.appPath;
1163
- bundleId = result.bundleId;
1164
- await settleStoreKeyAfterCompile(mutatingSteps.includes("prebuild"));
1165
- if (storeKey && cachePolicy.write) try {
1166
- const stored = await storeTieredBuild({
1167
- local: filesystemBuildCapability({
1168
- resolve: d.resolveBuild,
1169
- store: d.storeBuild,
1170
- sources: storeSources
1171
- }),
1172
- loadProvider,
1173
- target: {
1174
- projectRoot: root,
1175
- platform: "ios",
1176
- key: storeKey
1177
- },
1178
- sourcePath: appPath,
1179
- overwrite: !useBuildCache || swapFellBack,
1180
- warn: cacheWarn
1181
- });
1182
- providerUpload = stored.providerUpload;
1183
- providerName = stored.providerName ?? providerName;
1184
- } catch (e) {
1185
- note(chalk.yellow(`Could not store the build in the shared cache: ${e?.message || e}`));
1186
- }
1187
- if (physical) {
1188
- const prepared = await prepareDeviceApp(appPath, { fresh: true });
1189
- if (!prepared) return;
1190
- appPath = prepared;
1191
- }
1192
- if (remote && !physical && storeHash) uploadPending = d.uploadRemote({
1193
- logWriter: logWriter(),
1194
- provider: remote.provider,
1195
- platform: "ios",
1196
- projectRoot: root,
1197
- fingerprintHash: storeHash,
1198
- buildPath: appPath,
1199
- runOptions: providerRunOptions
1200
- });
1201
- }
1202
- }
1203
- }
1204
- let transferred = false;
1205
- try {
1206
- await resolveInitialFingerprint();
1207
- if (!easBuild) {
1208
- remote = await resolveRemoteArtifact();
1209
- if (!appPath) miss(reasonForMiss([]).reason);
1210
- await awaitSharedBuild();
1211
- await prepareCachedArtifact();
1212
- if (appPath) lateHit();
1213
- else if (swapFellBack) miss(reasonForMiss([]).reason);
1214
- await buildArtifact();
1215
- }
1216
- const artifact = {
1217
- handoff,
1218
- path: appPath,
1219
- bundleId,
1220
- cache: {
1221
- identity: storeHash && storeKey ? {
1222
- fingerprint: storeHash,
1223
- key: storeKey
1224
- } : null,
1225
- hit: cacheHit,
1226
- providerName: remote?.name ?? providerName,
1227
- buildMachine,
1228
- ...builtOn ? { builtOn } : {},
1229
- offloadedTo,
1230
- offloadFallback: offloadedTo ? null : offloadFallback,
1231
- readEnabled: useBuildCache,
1232
- missReason: cacheHit ? null : missReason,
1233
- waitedForBuild,
1234
- compilation: compilationCache
1235
- },
1236
- failureFields: {
1237
- ...buildFailure,
1238
- buildMachine,
1239
- ...builtOn ? { builtOn } : {},
1240
- fingerprint: storeHash,
1241
- cacheKey: storeKey,
1242
- cacheHit,
1243
- cacheSkipped: !useBuildCache
1244
- },
1245
- completeUploads: async () => {
1246
- const uploadWasAbandoned = await finishIosUpload(uploadPending, remote, phase, note);
1247
- const outcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
1248
- if (outcome) {
1249
- if (outcome.warn) note(chalk.yellow(phaseLine("cache", outcome.line)));
1250
- else phase("cache", outcome.line);
1251
- }
1252
- return abandonedRemote || uploadWasAbandoned;
1253
- },
1254
- release: releaseArtifact
1255
- };
1256
- transferred = true;
1257
- return {
1258
- ok: true,
1259
- artifact
1260
- };
1261
- } catch (error) {
1262
- if (error instanceof OffloadRefusal) {
1263
- const refusal = error;
1264
- return {
1265
- ok: false,
1266
- failure: {
1267
- code: refusal.code,
1268
- message: refusal.message,
1269
- remedy: refusal.remedy,
1270
- build: {
1271
- ...buildFailure,
1272
- buildMachine,
1273
- ...builtOn ? { builtOn } : {}
1274
- }
1275
- },
1276
- compilationCache
1277
- };
1278
- }
1279
- if (error instanceof ArtifactRefusal) return {
1280
- ok: false,
1281
- failure: error.failure,
1282
- compilationCache
1283
- };
1284
- throw error;
1285
- } finally {
1286
- if (openOffload.choice) closeOffload(openOffload.choice);
1287
- releaseLock();
1288
- releaseSlot();
1289
- if (!transferred) releaseArtifact();
1290
- }
1291
- }
1292
- //#endregion
1293
- //#region src/commands/ios/remote.ts
1294
- function resolveIosRemote({ opts, settings, physical, recorded }) {
1295
- const target = opts.remote !== void 0 ? parseIosRemote(opts.remote) : remoteIosSetting(settings);
1296
- if (physical && target?.kind === "machine") return { failure: {
1297
- code: "STIM_BAD_ARG",
1298
- message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
1299
- remedy: "Pass only one of --device and --remote; unset ios.remote for a local phone."
1300
- } };
1301
- const machine = target?.kind === "machine" ? target.machine : null;
1302
- if (recorded && recorded.machine !== machine && machine !== "auto") return { failure: {
1303
- code: "STIM_BAD_ARG",
1304
- message: `This workspace's iOS simulator runs on ${recorded.machine}; run stim stop first.`
1305
- } };
1306
- return {
1307
- machine,
1308
- backend: !physical && target?.kind === "backend" ? target.backend : null,
1309
- recorded
1310
- };
1311
- }
1312
- function selectIosTarget({ root, slot, opts, settings, physical, release, metroCheck, d }) {
1313
- let recorded;
1314
- try {
1315
- recorded = d.readHostedIos(root, slot)[slot];
1316
- } catch (error) {
1317
- return { failure: {
1318
- code: "STIM_HOSTING_REFUSED",
1319
- message: error.message,
1320
- ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
1321
- } };
1322
- }
1323
- const selection = resolveIosRemote({
1324
- opts,
1325
- settings,
1326
- physical,
1327
- recorded
1328
- });
1329
- if ("failure" in selection) return selection;
1330
- const localIos = selection.machine ? deviceSlotPlatforms(d.getProject(root), slot)?.ios : void 0;
1331
- if (selection.machine !== "auto" && selection.machine && localIos?.owned && localIos.deviceUdid) {
1332
- let localRunning = true;
1333
- try {
1334
- localRunning = d.listAllIosSims({ timeoutMs: 5e3 }).some((sim) => sim.udid === localIos.deviceUdid && sim.state !== "Shutdown");
1335
- } catch {}
1336
- if (localRunning) return { failure: {
1337
- code: "STIM_BAD_ARG",
1338
- message: `This workspace's iOS simulator for slot ${slot} runs on this Mac; run stim stop first.`
1339
- } };
1340
- }
1341
- if (selection.machine !== "auto" && selection.machine && !release && !metroCheck) return { failure: {
1342
- code: "STIM_BAD_ARG",
1343
- message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
1344
- remedy: "Run stim stop; stim start, then retry without --no-metro-check."
1345
- } };
1346
- return selection;
1347
- }
1348
- async function selectIosPlacement(args) {
1349
- const selection = selectIosTarget(args);
1350
- if ("failure" in selection || selection.machine !== "auto") return selection;
1351
- const { root, slot, d, opts, deviceType, runtime, noWait, phase, log, release, metroCheck } = args;
1352
- const viewer = resolveSimulatorAppFlag(opts.simulatorApp, args.physical, null);
1353
- if ("refusal" in viewer) return { failure: viewer.refusal };
1354
- const refusal = args.validateSelectors();
1355
- if (refusal) return { failure: refusal };
1356
- try {
1357
- const placed = await d.automaticDevicePlacement({
1358
- root,
1359
- slot,
1360
- platform: "ios",
1361
- selectors: hostedIosSelectors(deviceType, runtime),
1362
- buildMachine: opts.remoteBuild,
1363
- noWait
1364
- });
1365
- phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
1366
- log?.(devicePlacementRecord({
1367
- platform: "ios",
1368
- fromFlag: opts.remote !== void 0,
1369
- placed
1370
- }));
1371
- if (placed.target && !release && !metroCheck) return { failure: {
1372
- code: "STIM_BAD_ARG",
1373
- message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
1374
- remedy: "Run stim stop; stim start, then retry without --no-metro-check."
1375
- } };
1376
- return {
1377
- ...selection,
1378
- machine: placed.target?.host.machine ?? null,
1379
- recorded: void 0,
1380
- auto: { target: placed.target },
1381
- budget: placed.target ? {
1382
- reclaimed: [],
1383
- refusal: null
1384
- } : void 0,
1385
- devicePlacement: placed.placement
1386
- };
1387
- } catch (error) {
1388
- return { failure: {
1389
- code: error.code ?? "STIM_HOSTING_REFUSED",
1390
- message: error.message,
1391
- ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
1392
- } };
1393
- }
1394
- }
1395
- function hostedIosSelectors(deviceType, runtime) {
1396
- return {
1397
- ...deviceType ? { deviceType } : {},
1398
- ...runtime ? { runtime } : {}
1399
- };
1400
- }
1401
- async function connectIosTarget(selection, selectors, d) {
1402
- if (selection.auto) return { target: selection.auto.target };
1403
- if (!selection.machine) return { target: null };
1404
- try {
1405
- return { target: await d.prepareHostedIos(selection.machine, selectors, selection.recorded) };
1406
- } catch (error) {
1407
- return { failure: {
1408
- code: error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "STIM_HOSTING_REFUSED",
1409
- message: error instanceof Error ? error.message : String(error),
1410
- ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
1411
- } };
1412
- }
1413
- }
1414
- function hostedIosBuildTarget(target) {
1415
- return target ? { hostedDestination: {
1416
- runtime: target.choice.runtimeId,
1417
- architecture: target.choice.architecture
1418
- } } : {};
1419
- }
1420
- async function iosPlacementBudget(d, root, note, hosted, automatic, simSnapshot) {
1421
- if (automatic) return {
1422
- ...automatic,
1423
- refusal: hosted ? null : automatic.refusal
1424
- };
1425
- return hosted ? {
1426
- reclaimed: [],
1427
- refusal: null
1428
- } : d.budgetGate({
1429
- root,
1430
- note,
1431
- simSnapshot
1432
- });
1433
- }
1434
- function hostedIosMetroNote(hosted, settings, note, simulatorApp) {
1435
- if (hosted && (settings.iosSimulatorApp !== void 0 || simulatorApp !== void 0)) note(chalk.dim(phaseLine("device", "iosSimulatorApp is ignored on a hosting Mac; the simulator boots headless.")));
1436
- 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.")));
1437
- }
1438
- async function connectIosBackend(root, backend, deviceType, d) {
1439
- if (!backend) return {
1440
- remote: null,
1441
- arch: null
1442
- };
1443
- const resolved = await d.resolveRemoteContext({
1444
- root,
1445
- backend,
1446
- easBin: d.resolveEasCliBin(root)?.file ?? null,
1447
- deviceType: deviceType?.trim() || null
1448
- });
1449
- if ("failed" in resolved) return { failure: {
1450
- code: resolved.code ?? "STIM_NO_REMOTE_SESSION",
1451
- message: resolved.failed,
1452
- remedy: resolved.remedy
1453
- } };
1454
- return {
1455
- remote: d.remoteIosDeps(resolved.ctx),
1456
- arch: await d.readRemoteSimulatorArch(resolved.ctx.existingDaemon)
1457
- };
1458
- }
1459
- //#endregion
1460
- //#region src/commands/ios/hosted.ts
1461
- async function finishHostedIosRun({ target, d, artifact, root, slot, release, isExpo, metroCheck, metroPort, logsDir, fail, note, selectors, ...report }) {
1462
- const bundleId = artifact.bundleId ?? (d.readBundleId(artifact.path) || d.detectBundleId(root));
1463
- if (!bundleId) return fail({
1464
- code: "STIM_INSTALL_FAILED",
1465
- message: "The built app has no bundle identifier."
1466
- });
1467
- const launchedAt = d.now();
1468
- let run;
1469
- try {
1470
- run = await d.placeHostedIos(target, {
1471
- root,
1472
- slot,
1473
- bundle: artifact.path,
1474
- handoff: artifact.handoff,
1475
- bundleId,
1476
- selectors,
1477
- release,
1478
- ...!release && isExpo ? { devClientScheme: d.devClientScheme(root, artifact.path) ?? void 0 } : {},
1479
- reserved: (placement) => d.writeHostedIos(root, slot, placement),
1480
- note
1481
- });
1482
- } catch (error) {
1483
- return fail({
1484
- code: error.code ?? "STIM_HOSTING_REFUSED",
1485
- message: error.message,
1486
- 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.`
1487
- });
1488
- }
1489
- d.writeHostedIos(root, slot, run.placement);
1490
- d.writeWorkspaceLaunch(root, "ios", {
1491
- appId: bundleId,
1492
- deviceId: run.placement.session,
1493
- metroPort,
1494
- release,
1495
- launchedAt: new Date(launchedAt).toISOString()
1496
- }, slot);
1497
- let launched = run.launched;
1498
- if (!release && metroCheck) {
1499
- const evidence = await d.verifyLaunch({
1500
- requireBundleResponse: true,
1501
- platform: "ios",
1502
- logsDir,
1503
- since: launchedAt,
1504
- metroPort,
1505
- mode: isExpo ? MODE_EXPO : MODE_BARE,
1506
- slot: launchSlotScope(root, slot),
1507
- platformShared: siblingPlatformSlots(root, "ios", slot).length > 0
1508
- });
1509
- if (evidence.fatal) return fail({
1510
- code: "STIM_LAUNCH_FAILED",
1511
- message: "Metro could not deliver the hosted app bundle.",
1512
- remedy: "Run stim logs --errors and fix the bundle error."
1513
- });
1514
- launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
1515
- }
1516
- if (launched === "unverified") note(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim ios --remote ${target.host.machine}.`);
1517
- const cache = artifact.cache;
1518
- const device = run.placement.device;
1519
- const uploadsAbandoned = await artifact.completeUploads();
1520
- return {
1521
- facts: reportIosResult({
1522
- ...report,
1523
- root,
1524
- slot,
1525
- release,
1526
- metroCheck,
1527
- metroPort,
1528
- logsDir,
1529
- device: {
1530
- deviceName: device.deviceType,
1531
- deviceType: device.deviceType,
1532
- runtime: device.runtime
1533
- },
1534
- udid: "",
1535
- host: hostedIosStatus(run.placement),
1536
- appPath: artifact.path,
1537
- bundleId,
1538
- installSkipped: false,
1539
- storeHash: cache.identity?.fingerprint ?? null,
1540
- storeKey: cache.identity?.key ?? null,
1541
- cacheHit: cache.hit,
1542
- missReason: cache.missReason,
1543
- compilationCache: cache.compilation,
1544
- useBuildCache: cache.readEnabled,
1545
- waitedForBuild: cache.waitedForBuild,
1546
- launchState: launched,
1547
- providerName: cache.providerName,
1548
- buildMachine: cache.buildMachine,
1549
- builtOn: cache.builtOn,
1550
- offloadedTo: cache.offloadedTo,
1551
- offloadFallback: cache.offloadFallback,
1552
- webPreviewUrl: null
1553
- }),
1554
- uploadsAbandoned
1555
- };
1556
- }
1557
- //#endregion
1558
- //#region src/engine/ios-profile.ts
1559
- const TAG = /<(\/?)\s*([A-Za-z0-9_]+)(?:\s[^>]*?)?(\/?)>/g;
1560
- const ENTITIES = {
1561
- amp: "&",
1562
- lt: "<",
1563
- gt: ">",
1564
- quot: "\"",
1565
- apos: "'"
1566
- };
1567
- var PlistError = class extends Error {};
1568
- function codePoint(value) {
1569
- if (!Number.isInteger(value) || value < 0 || value > 1114111 || value >= 55296 && value <= 57343) throw new PlistError(`character reference out of range: ${value}`);
1570
- return String.fromCodePoint(value);
1571
- }
1572
- function decodeEntities(raw) {
1573
- return raw.replace(/&(#x?[0-9A-Fa-f]+|[a-z]+);/g, (match, body) => {
1574
- if (body.startsWith("#x") || body.startsWith("#X")) return codePoint(Number.parseInt(body.slice(2), 16));
1575
- if (body.startsWith("#")) return codePoint(Number.parseInt(body.slice(1), 10));
1576
- return ENTITIES[body] ?? match;
1577
- });
1578
- }
1579
- function peekTag(cursor) {
1580
- TAG.lastIndex = cursor.pos;
1581
- const match = TAG.exec(cursor.xml);
1582
- if (!match) return null;
1583
- return {
1584
- name: (match[2] ?? "").toLowerCase(),
1585
- closing: match[1] === "/",
1586
- selfClosing: match[3] === "/",
1587
- end: TAG.lastIndex
1588
- };
1589
- }
1590
- function takeTag(cursor) {
1591
- const tag = peekTag(cursor);
1592
- if (tag) cursor.pos = tag.end;
1593
- return tag;
1594
- }
1595
- function takeText(cursor, name) {
1596
- const close = cursor.xml.indexOf(`</${name}>`, cursor.pos);
1597
- if (close < 0) {
1598
- cursor.pos = cursor.xml.length;
1599
- return "";
1600
- }
1601
- const raw = cursor.xml.slice(cursor.pos, close);
1602
- cursor.pos = close + name.length + 3;
1603
- return raw;
1604
- }
1605
- function parseValue(cursor) {
1606
- const peeked = peekTag(cursor);
1607
- if (!peeked || peeked.closing) return void 0;
1608
- const tag = takeTag(cursor);
1609
- if (tag.selfClosing) {
1610
- if (tag.name === "true") return true;
1611
- if (tag.name === "false") return false;
1612
- if (tag.name === "array") return [];
1613
- if (tag.name === "dict") return {};
1614
- return "";
1615
- }
1616
- switch (tag.name) {
1617
- case "plist": return parseValue(cursor);
1618
- case "dict": {
1619
- const out = {};
1620
- for (;;) {
1621
- const next = peekTag(cursor);
1622
- if (!next) break;
1623
- if (next.closing) {
1624
- cursor.pos = next.end;
1625
- break;
1626
- }
1627
- cursor.pos = next.end;
1628
- if (next.name !== "key") continue;
1629
- const key = decodeEntities(next.selfClosing ? "" : takeText(cursor, "key"));
1630
- const value = parseValue(cursor);
1631
- if (value !== void 0) out[key] = value;
1632
- }
1633
- return out;
1634
- }
1635
- case "array": {
1636
- const out = [];
1637
- for (;;) {
1638
- const next = peekTag(cursor);
1639
- if (!next) break;
1640
- if (next.closing) {
1641
- cursor.pos = next.end;
1642
- break;
1643
- }
1644
- const value = parseValue(cursor);
1645
- if (value === void 0) break;
1646
- out.push(value);
1647
- }
1648
- return out;
1649
- }
1650
- case "string":
1651
- case "key": return decodeEntities(takeText(cursor, tag.name));
1652
- case "integer": return Number.parseInt(takeText(cursor, "integer").trim(), 10);
1653
- case "real": return Number.parseFloat(takeText(cursor, "real").trim());
1654
- case "date": return new Date(takeText(cursor, "date").trim());
1655
- case "data": return Buffer.from(takeText(cursor, "data").replace(/\s+/g, ""), "base64");
1656
- case "true":
1657
- takeText(cursor, "true");
1658
- return true;
1659
- case "false":
1660
- takeText(cursor, "false");
1661
- return false;
1662
- default:
1663
- takeText(cursor, tag.name);
1664
- return "";
1665
- }
1666
- }
1667
- function parsePlist(xml) {
1668
- if (typeof xml !== "string" || xml.trim() === "") return null;
1669
- const stripped = xml.replace(/<!--[\s\S]*?-->/g, "");
1670
- const start = stripped.indexOf("<plist");
1671
- const cursor = {
1672
- xml: stripped,
1673
- pos: start < 0 ? 0 : start
1674
- };
1675
- try {
1676
- const value = parseValue(cursor);
1677
- return value === void 0 ? null : value;
1678
- } catch {
1679
- return null;
1680
- }
1681
- }
1682
- function isDict(value) {
1683
- return value !== null && typeof value === "object" && !Array.isArray(value) && !Buffer.isBuffer(value) && !(value instanceof Date);
1684
- }
1685
- function stringsOf(value) {
1686
- if (!Array.isArray(value)) return null;
1687
- return value.filter((v) => typeof v === "string");
1688
- }
1689
- function parseProvisioningProfilePlist(xml) {
1690
- const root = parsePlist(xml);
1691
- if (!isDict(root)) return null;
1692
- const entitlements = isDict(root.Entitlements ?? null) ? root.Entitlements : {};
1693
- const teamIdentifier = stringsOf(root.TeamIdentifier);
1694
- const expiration = root.ExpirationDate;
1695
- const certificates = Array.isArray(root.DeveloperCertificates) ? root.DeveloperCertificates.filter((v) => Buffer.isBuffer(v)) : [];
1696
- return {
1697
- name: typeof root.Name === "string" ? root.Name : null,
1698
- uuid: typeof root.UUID === "string" ? root.UUID : null,
1699
- teamIdentifier: teamIdentifier?.[0] ?? null,
1700
- expirationDate: expiration instanceof Date && !Number.isNaN(expiration.getTime()) ? expiration : null,
1701
- provisionedDevices: stringsOf(root.ProvisionedDevices),
1702
- provisionsAllDevices: root.ProvisionsAllDevices === true,
1703
- getTaskAllow: entitlements["get-task-allow"] === true,
1704
- certificates
1705
- };
1706
- }
1707
- function provisioningProfileKind(profile) {
1708
- if (profile.provisionedDevices) return profile.getTaskAllow ? "development" : "ad hoc";
1709
- return profile.provisionsAllDevices ? "enterprise" : "App Store";
1710
- }
1711
- function certificateCommonName(der) {
1712
- if (!Buffer.isBuffer(der) || der.length === 0) return null;
1713
- let subject;
1714
- try {
1715
- subject = new X509Certificate(der).subject;
1716
- } catch {
1717
- return null;
1718
- }
1719
- for (const part of String(subject).split(/\r?\n/)) {
1720
- const trimmed = part.trim();
1721
- if (trimmed.startsWith("CN=")) return trimmed.slice(3).trim() || null;
1722
- }
1723
- return /(?:^|,)\s*CN=([^,]+)/.exec(String(subject))?.[1]?.trim() || null;
1724
- }
1725
- const IDENTITY_LINE = /^\s*\d+\)\s+([0-9A-Fa-f]{40})\s+"(.+)"\s*$/;
1726
- function parseSigningIdentities(text) {
1727
- if (typeof text !== "string") return [];
1728
- const out = [];
1729
- for (const line of text.split(/\r?\n/)) {
1730
- const match = IDENTITY_LINE.exec(line);
1731
- if (!match) continue;
1732
- out.push({
1733
- sha1: match[1].toUpperCase(),
1734
- name: match[2]
1735
- });
1736
- }
1737
- return out;
1738
- }
1739
- const SIGNING_CODES = {
1740
- noProfile: "STIM_NO_PROFILE",
1741
- profileMismatch: "STIM_PROFILE_MISMATCH",
1742
- noIdentity: "STIM_NO_SIGNING_IDENTITY",
1743
- codesignFailed: "STIM_CODESIGN_FAILED"
1744
- };
1745
- const XCODE_STEP = "build once from Xcode to install it -- Stim will not, because registering a device or minting a profile changes your Apple Developer account.";
1746
- const KEYCHAIN_REMEDY = "Open Xcode > Settings > Accounts and download your certificates, or unlock the login keychain with `security unlock-keychain`. `security find-identity -v -p codesigning` should list `Apple Development: ...`.";
1747
- function signingConfiguration(input) {
1748
- return input.configuration ?? "Debug";
1749
- }
1750
- function profileGate(input) {
1751
- const now = input.now ?? Date.now();
1752
- if (!input.profilePresent) return {
1753
- ok: false,
1754
- code: SIGNING_CODES.noProfile,
1755
- reason: "The app bundle has no embedded.mobileprovision, so it was built unsigned or for the simulator.",
1756
- remedy: `Set a team and profile for the target's ${signingConfiguration(input)} configuration in Xcode > Signing & Capabilities, then ${XCODE_STEP}`
1757
- };
1758
- if (!input.profile) return {
1759
- ok: false,
1760
- code: SIGNING_CODES.noProfile,
1761
- reason: "`openssl smime` could not decode the app bundle's embedded.mobileprovision.",
1762
- remedy: `The profile inside the app is corrupt. Delete the build cache entry with \`stim gc --delete\`, then ${XCODE_STEP}`
1763
- };
1764
- const profile = input.profile;
1765
- const named = profile.name ? `"${profile.name}"` : "the embedded profile";
1766
- if (!profile.expirationDate || profile.expirationDate.getTime() <= now) return {
1767
- ok: false,
1768
- code: SIGNING_CODES.profileMismatch,
1769
- reason: profile.expirationDate ? `The ${provisioningProfileKind(profile)} profile ${named} expired on ${profile.expirationDate.toISOString()}.` : `The ${provisioningProfileKind(profile)} profile ${named} carries no ExpirationDate, so Stim cannot prove it is still valid.`,
1770
- remedy: `Renew the profile under Signing & Capabilities, then ${XCODE_STEP}`
1771
- };
1772
- if (!profile.provisionedDevices) {
1773
- const kind = provisioningProfileKind(profile);
1774
- return {
1775
- ok: false,
1776
- code: SIGNING_CODES.profileMismatch,
1777
- reason: `${named} is an ${kind} profile, which carries no ProvisionedDevices list, so Stim cannot prove ${input.udid ?? "the target device"} is admitted.`,
1778
- remedy: `Local device runs need a development profile. Select one under Signing & Capabilities, then ${XCODE_STEP}`
1779
- };
1780
- }
1781
- const udid = input.udid;
1782
- if (!udid || !profile.provisionedDevices.some((d) => d.toLowerCase() === udid.toLowerCase())) return {
1783
- ok: false,
1784
- code: SIGNING_CODES.profileMismatch,
1785
- reason: `The ${provisioningProfileKind(profile)} profile ${named} lists ${profile.provisionedDevices.length} device(s) and ${udid ?? "the target device"} is not one of them.`,
1786
- remedy: `Register ${udid ?? "the device"} at developer.apple.com, regenerate the profile, then ${XCODE_STEP}`
1787
- };
1788
- return {
1789
- ok: true,
1790
- profile
1791
- };
1792
- }
1793
- function signingGate(input) {
1794
- const gated = profileGate(input);
1795
- if (!gated.ok) return gated;
1796
- const profile = gated.profile;
1797
- const named = profile.name ? `"${profile.name}"` : "the embedded profile";
1798
- const identities = Array.isArray(input.identities) ? input.identities : [];
1799
- if (identities.length === 0) return {
1800
- ok: false,
1801
- code: SIGNING_CODES.noIdentity,
1802
- reason: "`security find-identity -v -p codesigning` lists no codesigning identity on this machine.",
1803
- remedy: KEYCHAIN_REMEDY
1804
- };
1805
- if (input.pinnedSha1) {
1806
- const wanted = input.pinnedSha1.trim().toUpperCase();
1807
- const match = identities.find((i) => i.sha1 === wanted);
1808
- if (match) return {
1809
- ok: true,
1810
- identity: match
1811
- };
1812
- return {
1813
- ok: false,
1814
- code: SIGNING_CODES.noIdentity,
1815
- reason: `ios.signingIdentitySha1 is ${wanted}, which is not in the keychain. Available: ${identities.map((i) => `${i.sha1} "${i.name}"`).join(", ")}.`,
1816
- remedy: KEYCHAIN_REMEDY
1817
- };
1818
- }
1819
- const wantedName = input.pinnedName?.trim() || certificateCommonName(profile.certificates[0] ?? null);
1820
- if (!wantedName) return {
1821
- ok: false,
1822
- code: SIGNING_CODES.noIdentity,
1823
- reason: `Stim could not read a signing-identity name out of ${named}: its DeveloperCertificates entry has no readable subject CN.`,
1824
- remedy: "Set ios.signingIdentity in .stim.json to the identity name to re-sign with, or ios.signingIdentitySha1 to its SHA-1 hash."
1825
- };
1826
- const matches = identities.filter((i) => i.name === wantedName);
1827
- if (matches.length === 1) return {
1828
- ok: true,
1829
- identity: matches[0]
1830
- };
1831
- if (matches.length === 0) return {
1832
- ok: false,
1833
- code: SIGNING_CODES.noIdentity,
1834
- reason: `The app was signed by "${wantedName}", which is not in this machine's keychain. Available: ${identities.map((i) => `"${i.name}"`).join(", ")}.`,
1835
- remedy: `${KEYCHAIN_REMEDY} Or set ios.signingIdentity in .stim.json to one of the identities listed above.`
1836
- };
1837
- return {
1838
- ok: false,
1839
- code: SIGNING_CODES.noIdentity,
1840
- reason: `${matches.length} keychain identities share the name "${wantedName}", so Stim cannot pick one: ${matches.map((i) => i.sha1).join(", ")}.`,
1841
- remedy: "Set ios.signingIdentitySha1 in .stim.json to the SHA-1 hash of the certificate to sign with."
1842
- };
1843
- }
1844
- //#endregion
1845
- //#region src/engine/ios-signing.ts
1846
- const EMBEDDED_PROFILE = "embedded.mobileprovision";
1847
- const SIGNING_TIMEOUT_MS = 12e4;
1848
- function lastLines(text, count = 5) {
1849
- return String(text ?? "").split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.trim() !== "").slice(-count);
1850
- }
1851
- function errorText(error) {
1852
- const withOutput = error;
1853
- const stderr = String(withOutput?.stderr ?? "").trim();
1854
- const stdout = String(withOutput?.stdout ?? "").trim();
1855
- return stderr || stdout || String(withOutput?.message ?? error);
1856
- }
1857
- function readEmbeddedProfile(appPath, { exec = null } = {}) {
1858
- const executor = exec || getExecutor();
1859
- const path = join(appPath, EMBEDDED_PROFILE);
1860
- if (!existsSync(path)) return {
1861
- present: false,
1862
- profile: null
1863
- };
1864
- try {
1865
- return {
1866
- present: true,
1867
- profile: parseProvisioningProfilePlist(executor.runFile("/usr/bin/openssl", [
1868
- "smime",
1869
- "-verify",
1870
- "-noverify",
1871
- "-inform",
1872
- "DER",
1873
- "-in",
1874
- path
1875
- ], { timeoutMs: SIGNING_TIMEOUT_MS }))
1876
- };
1877
- } catch {
1878
- return {
1879
- present: true,
1880
- profile: null
1881
- };
1882
- }
1883
- }
1884
- function findSigningIdentities({ exec = null } = {}) {
1885
- const executor = exec || getExecutor();
1886
- try {
1887
- return parseSigningIdentities(executor.runFile("security", [
1888
- "find-identity",
1889
- "-v",
1890
- "-p",
1891
- "codesigning"
1892
- ], { timeoutMs: SIGNING_TIMEOUT_MS }));
1893
- } catch {
1894
- return [];
1895
- }
1896
- }
1897
- function gateProfileForDevice(options) {
1898
- const read = readEmbeddedProfile(options.appPath, { exec: options.exec ?? null });
1899
- return profileGate({
1900
- profilePresent: read.present,
1901
- profile: read.profile,
1902
- identities: [],
1903
- udid: options.udid,
1904
- configuration: options.configuration ?? null,
1905
- ...options.now === void 0 ? {} : { now: options.now }
1906
- });
1907
- }
1908
- function gateAppForDevice(options) {
1909
- const read = readEmbeddedProfile(options.appPath, { exec: options.exec ?? null });
1910
- return signingGate({
1911
- profilePresent: read.present,
1912
- profile: read.profile,
1913
- identities: read.present ? findSigningIdentities({ exec: options.exec ?? null }) : [],
1914
- udid: options.udid,
1915
- configuration: options.configuration ?? null,
1916
- pinnedName: options.pinnedName ?? null,
1917
- pinnedSha1: options.pinnedSha1 ?? null,
1918
- ...options.now === void 0 ? {} : { now: options.now }
1919
- });
1920
- }
1921
- function extractEntitlements(appPath, executor) {
1922
- try {
1923
- return String(executor.runFile("codesign", [
1924
- "-d",
1925
- "--entitlements",
1926
- "-",
1927
- "--xml",
1928
- appPath
1929
- ], { timeoutMs: SIGNING_TIMEOUT_MS })).trim();
1930
- } catch {
1931
- return "";
1932
- }
1933
- }
1934
- function resealBundle({ appPath, identity, exec = null }) {
1935
- const executor = exec || getExecutor();
1936
- const signAs = identity.sha1 || identity.name;
1937
- let mode = "preserve-metadata";
1938
- let preserveError = null;
1939
- try {
1940
- executor.runFile("codesign", [
1941
- "--force",
1942
- "--sign",
1943
- signAs,
1944
- "--preserve-metadata=identifier,entitlements,flags,runtime",
1945
- appPath
1946
- ], { timeoutMs: SIGNING_TIMEOUT_MS });
1947
- } catch (error) {
1948
- preserveError = error;
1949
- }
1950
- let tempDir = null;
1951
- if (preserveError) {
1952
- const entitlements = extractEntitlements(appPath, executor);
1953
- const args = [
1954
- "--force",
1955
- "--sign",
1956
- signAs
1957
- ];
1958
- if (entitlements !== "") {
1959
- tempDir = mkdtempSync(join(tmpdir(), "stim-entitlements-"));
1960
- const plist = join(tempDir, "entitlements.plist");
1961
- writeFileSync(plist, `${entitlements}\n`, "utf-8");
1962
- args.push("--entitlements", plist);
1963
- mode = "entitlements";
1964
- } else mode = "no-entitlements";
1965
- args.push(appPath);
1966
- try {
1967
- executor.runFile("codesign", args, { timeoutMs: SIGNING_TIMEOUT_MS });
1968
- } catch (error) {
1969
- return {
1970
- ok: false,
1971
- code: SIGNING_CODES.codesignFailed,
1972
- reason: `codesign could not re-seal ${appPath} with "${identity.name}": ${errorText(error)}`,
1973
- remedy: `Unlock the login keychain (\`security unlock-keychain\`) and confirm exactly one identity matches "${identity.name}".`,
1974
- lastLines: [...lastLines(errorText(preserveError)), ...lastLines(errorText(error))]
1975
- };
1976
- } finally {
1977
- if (tempDir) rmSync(tempDir, {
1978
- recursive: true,
1979
- force: true
1980
- });
1981
- }
1982
- }
1983
- try {
1984
- executor.runFile("codesign", [
1985
- "--verify",
1986
- "--strict",
1987
- appPath
1988
- ], { timeoutMs: SIGNING_TIMEOUT_MS });
1989
- } catch (error) {
1990
- return {
1991
- ok: false,
1992
- code: SIGNING_CODES.codesignFailed,
1993
- reason: `codesign --verify --strict rejected ${appPath} after re-sealing it with "${identity.name}": ${errorText(error)}`,
1994
- remedy: "The bundle signed but did not seal. Delete the cached artifact with `stim gc --delete` and build fresh, then retry.",
1995
- lastLines: lastLines(errorText(error))
1996
- };
1997
- }
1998
- return {
1999
- ok: true,
2000
- identity,
2001
- mode
2002
- };
2003
- }
2004
- function sealAppForDevice(options) {
2005
- const gate = gateAppForDevice(options);
2006
- if (!gate.ok) return {
2007
- ...gate,
2008
- lastLines: []
2009
- };
2010
- return resealBundle({
2011
- appPath: options.appPath,
2012
- identity: gate.identity,
2013
- exec: options.exec ?? null
2014
- });
2015
- }
2016
- //#endregion
2017
- //#region src/engine/lan-address.ts
2018
- const EN_INTERFACE = /^en(\d+)$/;
2019
- const EXCLUDED_INTERFACE = /^(?:utun|bridge|awdl)/;
2020
- const LINK_LOCAL = /^169\.254\./;
2021
- function isIpv4(entry) {
2022
- return entry.family === "IPv4" || entry.family === 4;
2023
- }
2024
- function lanCandidates(interfaces) {
2025
- const ranked = [];
2026
- const seen = /* @__PURE__ */ new Set();
2027
- for (const [interfaceName, entries] of Object.entries(interfaces ?? {})) {
2028
- if (EXCLUDED_INTERFACE.test(interfaceName)) continue;
2029
- const en = EN_INTERFACE.exec(interfaceName);
2030
- for (const entry of entries ?? []) {
2031
- if (!entry || typeof entry !== "object" || !isIpv4(entry) || entry.internal === true) continue;
2032
- const address = typeof entry.address === "string" ? entry.address.trim() : "";
2033
- if (address === "" || LINK_LOCAL.test(address)) continue;
2034
- if (seen.has(address)) continue;
2035
- seen.add(address);
2036
- ranked.push({
2037
- interfaceName,
2038
- address,
2039
- rank: en ? 0 : 1,
2040
- index: en ? Number(en[1]) : 0
2041
- });
2042
- }
2043
- }
2044
- ranked.sort((a, b) => a.rank - b.rank || a.index - b.index || a.interfaceName.localeCompare(b.interfaceName) || a.address.localeCompare(b.address));
2045
- return ranked.map(({ interfaceName, address }) => ({
2046
- interfaceName,
2047
- address
2048
- }));
2049
- }
2050
- function hostLanCandidates(interfaces = networkInterfaces) {
2051
- return lanCandidates(interfaces());
2052
- }
2053
- //#endregion
2054
- //#region src/commands/ios/collector.ts
2055
- function collectorLogFile(root, slot) {
2056
- return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey("ios", slot)}.log`);
2057
- }
2058
- function collectorEntry() {
2059
- return spawnEntry("collector-run");
2060
- }
2061
- const COLLECTOR_EXIT_WAIT_MS = 2e3;
2062
- const COLLECTOR_POLL_MS = 25;
2063
- async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), alive = pidExists, readState = readWorkspaceState$1, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
2064
- const previous = (readState(root)?.collectors)?.[deviceSlotKey("ios", slot)] || null;
2065
- const previousPid = Number(previous?.pid) || null;
2066
- let killed = null;
2067
- if (previousPid) {
2068
- const ownership = verify({
2069
- pid: previousPid,
2070
- platform: deviceSlotKey("ios", slot),
2071
- root,
2072
- isAlive: alive,
2073
- expected: previous
2074
- });
2075
- if (ownership.status === "ours") {
2076
- try {
2077
- kill(previousPid, "SIGTERM");
2078
- killed = previousPid;
2079
- } catch (err) {
2080
- if (err?.code !== "ESRCH") note(chalk.yellow(`Could not stop the previous ios log collector (pid ${previousPid}): ${err?.message || err}`));
2081
- }
2082
- const deadline = Date.now() + waitMs;
2083
- while (Date.now() < deadline && alive(previousPid)) await sleep(COLLECTOR_POLL_MS);
2084
- } else if (ownership.status === "unverified") note(chalk.dim(`Previous ios log collector (pid ${previousPid}): ${ownership.reason}, so it was not signalled -- starting a replacement anyway`));
2085
- }
2086
- return { killed };
2087
- }
2088
- async function replaceCollector({ root, slot = "default", udid, bundleId, appName, appExecutable, physical = false, payloadUrl = null, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState$1, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
2089
- const { killed } = await stopPreviousCollector({
2090
- root,
2091
- slot,
2092
- kill,
2093
- alive,
2094
- readState,
2095
- verify,
2096
- waitMs,
2097
- note
2098
- });
2099
- const args = [
2100
- collectorEntry(),
2101
- "--platform",
2102
- "ios",
2103
- "--root",
2104
- root,
2105
- "--udid",
2106
- udid,
2107
- "--bundle",
2108
- bundleId
2109
- ];
2110
- if (slot !== "default") args.push("--slot", slot);
2111
- if (appName) args.push("--app-name", appName);
2112
- if (appExecutable) args.push("--app-executable", appExecutable);
2113
- if (physical) args.push("--physical");
2114
- if (payloadUrl) args.push("--payload-url", payloadUrl);
2115
- let stdio = "ignore";
2116
- try {
2117
- mkdirSync(workspaceLogsDir$1(root), { recursive: true });
2118
- const fd = openSync(collectorLogFile(root, slot), "a");
2119
- stdio = [
2120
- "ignore",
2121
- fd,
2122
- fd
2123
- ];
2124
- } catch {}
2125
- let child;
2126
- try {
2127
- child = spawn(process.execPath, args, {
2128
- cwd: root,
2129
- detached: true,
2130
- stdio,
2131
- env: process.env
2132
- });
2133
- child?.unref?.();
2134
- } catch (err) {
2135
- note(chalk.yellow(`Could not start the ios log collector: ${err?.message || err}`));
2136
- return {
2137
- killed,
2138
- pid: null
2139
- };
2140
- }
2141
- return {
2142
- killed,
2143
- pid: child?.pid ?? null
2144
- };
2145
- }
2146
- //#endregion
2147
- //#region src/commands/ios/dependencies.ts
2148
- const DEFAULT_DEPS = {
2149
- automaticDevicePlacement,
2150
- prepareHostedIos,
2151
- placeHostedIos,
2152
- readHostedIos,
2153
- writeHostedIos,
2154
- resolveEasDevelopmentBuild,
2155
- findProjectRoot: findProjectRoot$1,
2156
- resolveSettings,
2157
- settingsLayers,
2158
- settingOriginScope,
2159
- gitCommonDir,
2160
- repoRoot,
2161
- detectBundleId,
2162
- detectIsExpo,
2163
- devClientScheme,
2164
- devClientTakesDevMenuParams,
2165
- getProject,
2166
- upsertProject,
2167
- recordIosSchemeApprovals,
2168
- checkDeviceCapacity,
2169
- budgetGate,
2170
- ensureOwnedDevice,
2171
- listIosRuntimes,
2172
- listAllIosSims,
2173
- ensureBooted,
2174
- resolveRemoteContext,
2175
- remoteIosDeps,
2176
- readRemoteSimulatorArch,
2177
- hostSimulatorArch,
2178
- ensureMetroReachable,
2179
- ensureRemoteBootOwned,
2180
- detectProviders,
2181
- resolveProjectMetro,
2182
- startDevServer,
2183
- warmMetro,
2184
- readWorkspaceState: readWorkspaceState$1,
2185
- pidExists,
2186
- getConcurrencyLimits,
2187
- fingerprintProject,
2188
- untrackedNativeFiles,
2189
- resolveBuild,
2190
- storeBuild,
2191
- resolveCacheProviderConfig,
2192
- loadCacheProvider,
2193
- acquireBuildLock,
2194
- releaseBuildLock,
2195
- waitForBuild,
2196
- acquireBuildSlot,
2197
- releaseBuildSlot,
2198
- loadProjectProvider,
2199
- checkEasAuth,
2200
- resolveEasCliBin,
2201
- resolveRemote,
2202
- uploadRemote,
2203
- planPrebuild,
2204
- runPrebuild,
2205
- readPodState,
2206
- podsAreStale,
2207
- runPodInstall,
2208
- buildIos,
2209
- discoverXcodeProject,
2210
- resolveScheme,
2211
- listIosDevices,
2212
- hostLanCandidates,
2213
- ensureLanReachable,
2214
- gateProfileForDevice,
2215
- sealAppForDevice,
2216
- installIosDeviceApp,
2217
- awaitIosDeviceLaunch,
2218
- acquireRunLease,
2219
- runLease,
2220
- selectFromPool,
2221
- releaseLeaseOnSignal,
2222
- iosDeviceProcess,
2223
- verifyIosDeviceReleaseLaunch,
2224
- readBundleId,
2225
- readBundleExecutable,
2226
- swapJsBundle,
2227
- installIosApp,
2228
- clearOtherUserApps,
2229
- clearIosAppData,
2230
- clearIosAdoptionPending,
2231
- launchIosApp,
2232
- verifyLaunch,
2233
- verifyReleaseLaunch,
2234
- ensureWorkspaceStorage: ensureWorkspaceStorageSafely,
2235
- replaceCollector,
2236
- stopPreviousCollector,
2237
- writeWorkspaceLaunch,
2238
- writeWorkspaceState,
2239
- createWriter: createNdjsonWriter,
2240
- recordStats: recordRunStats,
2241
- recordCapacityRefusal,
2242
- readEstimates: readRunEstimates,
2243
- now: () => Date.now()
2244
- };
2245
- //#endregion
2246
- //#region src/commands/ios/launch.ts
2247
- function missingCrashReportHint({ physical, remote, crashed }) {
2248
- return crashed && !physical && !remote ? `No attributable native crash report captured yet. ${IOS_CRASH_REPORT_RETRY}` : "No attributable native crash report captured. Read `stim logs --source device` for available output.";
2249
- }
2250
- async function verifyIosRun({ root, slot, appPath, d, release, launched, configuration, phase, note, metroCheck, logsDir, launchedAt, metroPort, isExpo, bundleId, udid, scheme, physical, appName, lanAddress, lanOrigin, remoteDevice, metroOrigin }) {
2251
- const runCommand = nativeRunCommand("ios", slot, {
2252
- physical,
2253
- deviceId: udid
2254
- });
2255
- const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
2256
- root,
2257
- slot,
2258
- platform: "ios",
2259
- deviceId: udid,
2260
- appId: bundleId,
2261
- since: launchedAt,
2262
- appPath,
2263
- physical
2264
- }, logsDir);
2265
- const deviceProcess = () => {
2266
- const pid = d.iosDeviceProcess({
2267
- udid,
2268
- appName: appName ?? bundleId
2269
- });
2270
- return pid === void 0 ? null : pid !== null;
2271
- };
2272
- if (release) {
2273
- const processCheck = physical ? await d.verifyIosDeviceReleaseLaunch({
2274
- udid,
2275
- appName: appName ?? bundleId
2276
- }) : await d.verifyReleaseLaunch({ pid: launched?.pid ?? null });
2277
- const crashes = readNativeCrashes();
2278
- if (processCheck?.verified && !crashes.length) {
2279
- phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${configuration}: no bundle fetch to observe)`);
2280
- return { state: true };
2281
- }
2282
- for (const line of launchErrorPreview(crashes, root)) note(chalk.red(phaseLine("launch", line)));
2283
- if (!crashes.length) note(phaseLine("logs", missingCrashReportHint({
2284
- physical,
2285
- remote: Boolean(remoteDevice),
2286
- crashed: processCheck?.reason === "exited"
2287
- })));
2288
- phase("verify", chalk.yellow(crashes.length ? "FATAL: the app reported a native crash" : processCheck?.reason === "exited" ? `FATAL: the app process exited within ${formatDuration(processCheck.waitedMs ?? 0)} of launch` : physical ? `UNVERIFIED: devicectl could not read ${udid}'s process list` : "UNVERIFIED: simctl launch reported no process id to check"));
2289
- note(chalk.yellow(phaseLine("", "Release readiness was not established. Run `stim logs --errors` for captured crash reports, or `stim logs --source device` for the full device output.")));
2290
- return { state: crashes.length || processCheck?.reason === "exited" ? LAUNCH_FATAL : LAUNCH_UNVERIFIED };
2291
- }
2292
- const siblings = metroCheck ? siblingPlatformSlots(root, "ios", slot) : [];
2293
- const verification = metroCheck ? await d.verifyLaunch({
2294
- requireBundleResponse: true,
2295
- slot: launchSlotScope(root, slot),
2296
- appPid: physical || remoteDevice ? null : launched?.pid,
2297
- platformShared: siblings.length > 0,
2298
- onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
2299
- logsDir,
2300
- since: launchedAt,
2301
- metroPort,
2302
- platform: "ios",
2303
- mode: isExpo ? MODE_EXPO : MODE_BARE,
2304
- readNativeCrashes,
2305
- processAlive: remoteDevice ? null : physical ? deviceProcess : () => {
2306
- if (launched?.pid) return d.pidExists(launched.pid);
2307
- const pid = iosAppProcess(udid, bundleId);
2308
- return pid === void 0 ? null : pid !== null;
2309
- }
2310
- }) : {
2311
- verified: false,
2312
- skipped: true
2313
- };
2314
- const nativeCrashes = verification.errors?.some((record) => record.event === "native_crash") ? [] : readNativeCrashes();
2315
- if (nativeCrashes.length) verification.fatal = true;
2316
- verification.errors = await errorDiagnostics([...verification.errors ?? [], ...nativeCrashes], {
2317
- root,
2318
- logsDir,
2319
- port: metroPort,
2320
- allowRequest: true
2321
- });
2322
- if (verification.readiness) phase("readiness", appReadinessMessage(verification.readiness, verification.waitedMs ?? 0));
2323
- if (verification?.fatal) {
2324
- const nativeFatal = verification.errors?.some((record) => record.event === "native_crash");
2325
- const deliveryFailed = verification.record?.event === "bundle_response_failed";
2326
- const reason = nativeFatal ? "the app reported a native crash" : verification.processAlive === false ? "the app process exited" : deliveryFailed ? "Metro bundle delivery failed" : "Metro could not build the bundle";
2327
- phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
2328
- for (const line of launchErrorPreview(verification.errors ?? [], root)) note(chalk.red(phaseLine("", line)));
2329
- if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) note(phaseLine("logs", missingCrashReportHint({
2330
- physical,
2331
- remote: Boolean(remoteDevice),
2332
- crashed: true
2333
- })));
2334
- if (nativeFatal || verification.processAlive === false) note(chalk.yellow(phaseLine("remedy", `Fix the crash, then run \`${runCommand}\` again. A Metro reload cannot restart an exited app.`)));
2335
- else if (verification.processAlive === true && metroPort !== null) {
2336
- const reloadRemedy = `press Reload on the app's own error screen -- run \`agent-device snapshot -i --platform ios --udid ${udid}\` in your existing automation session to reach it. Neither \`stim reload ios\` nor \`agent-device metro reload\` can: this app never connected to Metro.`;
2337
- note(chalk.yellow(phaseLine("remedy", `The native app is still running. ${deliveryFailed ? "Check the Metro logs and device connection, then" : "Fix the JavaScript or TypeScript error, then"} ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`)));
2338
- }
2339
- return { state: LAUNCH_FATAL };
2340
- }
2341
- if (verification?.verified) {
2342
- phase("verify", `bundle loaded` + (verification.processAlive === true ? ", process alive" : "") + (verification.readiness ? "" : ", stable for 3s -- the first screen may still be rendering") + ` (${formatDuration(verification.waitedMs ?? 0)} total)`);
2343
- const hasAppErrors = reportLaunchErrors(verification.errors ?? [], note, root);
2344
- if (hasAppErrors && verification.processAlive === true && metroPort !== null) {
2345
- const reloadRemedy = physical || remoteDevice ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload ios`.";
2346
- note(chalk.yellow(phaseLine("remedy", `The native app is still running. Fix the JavaScript or TypeScript error; Fast Refresh should apply the edit. If the error screen remains, ${reloadRemedy} Do not run \`${runCommand}\` unless native inputs changed or the app process exits.`)));
2347
- }
2348
- return {
2349
- state: true,
2350
- warning: hasAppErrors ? "app errors detected; inspect the error above" : verification.readiness === "timed-out" || verification.readiness === "error" ? "app readiness not confirmed; inspect the UI and logs" : void 0
2351
- };
2352
- }
2353
- if (verification?.skipped) {
2354
- phase("verify", "skipped (--no-metro-check): the launch is reported as unverified");
2355
- return { state: LAUNCH_UNVERIFIED };
2356
- }
2357
- if (verification?.requested) {
2358
- phase("verify", `BUNDLING: the app asked port ${metroPort} for its bundle; build or delivery was still pending after ${formatDuration(verification.waitedMs ?? 0)} (a cold bundle on a large graph outlasts this window)`);
2359
- note(chalk.dim(phaseLine("", "Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute.")));
2360
- return { state: LAUNCH_BUNDLING };
2361
- }
2362
- return reportUnverified({
2363
- verification,
2364
- siblings,
2365
- slot,
2366
- phase,
2367
- note,
2368
- metroPort,
2369
- bundleId,
2370
- udid,
2371
- scheme,
2372
- lanAddress,
2373
- isExpo,
2374
- remoteDevice,
2375
- physical,
2376
- lanOrigin,
2377
- metroOrigin,
2378
- logsDir,
2379
- launchedAt,
2380
- launched
2381
- });
2382
- }
2383
- function reportUnverified({ verification, siblings, slot, phase, note, metroPort, bundleId, udid, scheme, lanAddress, isExpo, remoteDevice, physical, lanOrigin, metroOrigin, logsDir, launchedAt, launched }) {
2384
- if (verification.unattributed) {
2385
- const [headline, ...lines] = unattributedLaunchLines({
2386
- platform: "ios",
2387
- metroPort,
2388
- slot,
2389
- siblings
2390
- });
2391
- phase("verify", chalk.yellow(headline));
2392
- for (const line of lines) note(chalk.yellow(phaseLine("", line)));
2393
- return {
2394
- state: LAUNCH_UNVERIFIED,
2395
- unattributed: true
2396
- };
2397
- }
2398
- phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
2399
- for (const line of unverifiedLaunchLines({
2400
- platform: "ios",
2401
- metroPort: metroPort ?? 8081,
2402
- waitedMs: verification.waitedMs,
2403
- bundleId,
2404
- udid,
2405
- devClientUrl: scheme ? launched?.url ?? devClientUrl(scheme, metroPort ?? 8081, lanAddress ?? void 0) : null,
2406
- mode: isExpo ? MODE_EXPO : MODE_BARE,
2407
- remote: remoteDevice,
2408
- physical,
2409
- devClient: Boolean(scheme),
2410
- lanOrigin,
2411
- metroOrigin,
2412
- localNetworkPending: physical && localNetworkPending(readCollectorRecords(logsDir), {
2413
- since: launchedAt,
2414
- pid: launched?.pid ?? null,
2415
- lanOrigin
2416
- })
2417
- })) note(chalk.yellow(phaseLine("", line)));
2418
- return { state: LAUNCH_UNVERIFIED };
2419
- }
2420
- function reportLaunchErrors(errors, note, root) {
2421
- const report = launchErrorReport(errors, root);
2422
- if (report.summary) note(chalk.dim(phaseLine("launch", report.summary)));
2423
- for (const line of report.lines) note(chalk.yellow(phaseLine("launch", line)));
2424
- return report.lines.length > 0;
2425
- }
2426
- function installsOverWifi(d, udid, selectedWireless, note) {
2427
- const current = d.listIosDevices().find((entry) => entry.udid === udid);
2428
- const wireless = current ? isWirelessIosDevice(current) : selectedWireless;
2429
- 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`));
2430
- return wireless;
2431
- }
2432
- function cleanAdoptedIosApps({ d, udid, bundleId, devClient, phase, note }) {
2433
- const swept = d.clearOtherUserApps({
2434
- udid,
2435
- keep: bundleId
2436
- });
2437
- if (swept.removed.length) phase("install", `removed ${swept.removed.join(", ")}, left by the previous workspace`);
2438
- if (swept.listed && swept.failed.length === 0) {
2439
- if (swept.kept && bundleId) {
2440
- try {
2441
- d.clearIosAppData(udid, bundleId, { defaultsPlist: devClient ? IOS_DEV_MENU_OFF_DEFAULTS_PLIST : null });
2442
- } catch (error) {
2443
- return `Could not clear the data ${bundleId} kept from the previous workspace: ${String(error?.message || error)}`;
2444
- }
2445
- phase("install", `cleared ${bundleId} data left by the previous workspace`);
2446
- }
2447
- return null;
2448
- }
2449
- if (!swept.listed) return "Could not list apps left by the previous workspace.";
2450
- for (const failure of swept.failed) note(chalk.yellow(phaseLine("install", `could not remove ${failure}, left by the previous workspace`)));
2451
- return `Could not remove ${swept.failed.join(", ")}, left by the previous workspace.`;
2452
- }
2453
- function installMayBeProven(adopting, parkedCacheKey, storeKey) {
2454
- return !adopting || !parkedCacheKey || parkedCacheKey === storeKey;
2455
- }
2456
- function resolveRunBundleId(d, root, appPath, bundleId) {
2457
- if (!appPath || bundleId) return bundleId;
2458
- return d.readBundleId(appPath) || d.detectBundleId(root);
2459
- }
2460
- function readRunExecutable(d, appPath, note) {
2461
- const executable = appPath ? d.readBundleExecutable(appPath) : null;
2462
- if (appPath && !executable) note(chalk.dim(`Could not read CFBundleExecutable from ${appPath}; the device log predicate falls back to the .app basename.`));
2463
- return executable;
2464
- }
2465
- function recordIosReloadTarget({ d, root, physical, remoteDevice, bundleId, udid, metroPort, release, launchedAt, note }) {
2466
- if (physical || remoteDevice) return;
2467
- try {
2468
- d.writeWorkspaceLaunch(root, "ios", {
2469
- appId: bundleId,
2470
- deviceId: udid,
2471
- metroPort,
2472
- release,
2473
- launchedAt: new Date(launchedAt).toISOString()
2474
- });
2475
- } catch (error) {
2476
- note(chalk.yellow(phaseLine("state", `could not record iOS launch: ${error?.message || error}`)));
2477
- }
2478
- }
2479
- function installFailureRemedy(remoteDevice) {
2480
- return remoteDevice?.failureRemedy() ?? "Check that the simulator is booted and that the app was built for the simulator SDK.";
2481
- }
2482
- function launchFailureRemedy(remoteDevice, udid, bundleId, logFile) {
2483
- if (remoteDevice) return remoteDevice.failureRemedy();
2484
- return `If the simulator timed out, run \`stim doctor --platform ios\` and resolve any reported host memory pressure before retrying. Otherwise run \`xcrun simctl launch --terminate-running-process --console ${udid} ${bundleId}\` to see what the app reports, and check ${logFile}.`;
2485
- }
2486
- async function finishIosRun({ devicePlacement, 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 }) {
2487
- const { path: appPath, bundleId: initialBundleId, failureFields: buildFailure, cache } = artifact;
2488
- const { hit: cacheHit, providerName, buildMachine, builtOn, offloadedTo, offloadFallback, readEnabled: useBuildCache, missReason, waitedForBuild, compilation: compilationCache } = cache;
2489
- const storeHash = cache.identity?.fingerprint ?? null;
2490
- const storeKey = cache.identity?.key ?? null;
2491
- const runCommand = nativeRunCommand("ios", slot, {
2492
- physical,
2493
- deviceId: udid
2494
- });
2495
- let bundleId = initialBundleId;
2496
- let leaseWarned = false;
2497
- const raiseLeaseFor = (boundMs, beforeInstall) => {
2498
- const step = lease?.raise(boundMs);
2499
- if (!step || step.ok) return null;
2500
- if (beforeInstall) {
2501
- const refusal = lostRefusal(step.holder, step.expiresAt, d.now());
2502
- return {
2503
- code: refusal.code,
2504
- message: refusal.message,
2505
- remedy: refusal.remedy,
2506
- lease: refusal.lease
2507
- };
2508
- }
2509
- if (!leaseWarned) {
2510
- leaseWarned = true;
2511
- note(chalk.yellow(phaseLine("lease", lostLine(step.holder, step.expiresAt, d.now()))));
2512
- }
2513
- return null;
2514
- };
2515
- bundleId = resolveRunBundleId(d, root, appPath, bundleId);
2516
- if (appPath && !bundleId) return fail({
2517
- code: "STIM_INSTALL_FAILED",
2518
- message: `Could not read a bundle identifier from the cached app at ${appPath}.`,
2519
- remedy: "Remove the cache entry (`stim gc`) and run again to rebuild it.",
2520
- build: {
2521
- ...buildFailure,
2522
- appPath
2523
- }
2524
- });
2525
- if (bundleId) d.upsertProject(root, { bundleId });
2526
- enterPhase("device");
2527
- const booted = await bootPromise;
2528
- if (!booted?.ok) return fail({
2529
- code: booted?.code || "STIM_NO_DEVICE",
2530
- message: booted?.reason || "The owned simulator could not be booted.",
2531
- remedy: booted?.remedy || `Run \`${runCommand}\` again to re-establish an owned simulator for this workspace.`
2532
- });
2533
- const deviceOutcome = physical ? "connected" : `${device?.adopted ? "adopted" : "booted"} ${bootDuration()}`;
2534
- phase("device", `${deviceLabel(device, udid)} ${deviceOutcome}`);
2535
- const scheme = release ? void 0 : d.devClientScheme(root, appPath);
2536
- const devMenuParams = d.devClientTakesDevMenuParams(root);
2537
- const appName = appNameFromPath(appPath);
2538
- const appExecutable = readRunExecutable(d, appPath, note);
2539
- const dropSwapDir = artifact.release;
2540
- let installSkipped = false;
2541
- let launched;
2542
- let launchedAt = d.now();
2543
- if (physical) {
2544
- const overWifi = installsOverWifi(d, udid, wireless, note);
2545
- const bounds = iosDeviceBounds(overWifi);
2546
- const lostBeforeInstall = raiseLeaseFor(bounds.installMs, true);
2547
- if (lostBeforeInstall) return fail(lostBeforeInstall);
2548
- await d.stopPreviousCollector({
2549
- root,
2550
- note
2551
- });
2552
- enterPhase("install");
2553
- const installTimer = stepTimer(d.now);
2554
- const installed = d.installIosDeviceApp({
2555
- udid,
2556
- appPath,
2557
- bundleId,
2558
- wireless: overWifi
2559
- }, { beforeStep: () => raiseLeaseFor(bounds.installMs, false) });
2560
- if (installed?.failed) {
2561
- dropSwapDir();
2562
- return fail({
2563
- code: installed.code || "STIM_INSTALL_FAILED",
2564
- message: installed.reason ?? `devicectl could not install ${appPath} on ${udid}.`,
2565
- remedy: installed.remedy ?? null,
2566
- build: {
2567
- ...buildFailure,
2568
- appPath,
2569
- bundleId
2570
- }
2571
- });
2572
- }
2573
- phase("install", `${basename(appPath)} -> ${deviceLabel(device, udid)} ${installTimer()}`);
2574
- if (installed?.note) {
2575
- note(chalk.yellow(phaseLine("install", installed.note)));
2576
- logWriter().write({
2577
- src: "build",
2578
- level: "warn",
2579
- event: "install_uninstalled_first",
2580
- msg: installed.note
2581
- });
2582
- }
2583
- dropSwapDir();
2584
- raiseLeaseFor(COLLECTOR_EXIT_WAIT_MS + bounds.launchMs, false);
2585
- const payloadUrl = scheme && metroPort !== null && lanAddress ? devClientUrl(scheme, metroPort, lanAddress) : null;
2586
- enterPhase("launch");
2587
- const launchTimer = stepTimer(d.now);
2588
- launchedAt = d.now();
2589
- logWriter().write({
2590
- src: "build",
2591
- level: "info",
2592
- marker: true,
2593
- event: "launch_attempt",
2594
- ts: launchedAt,
2595
- platform: "ios",
2596
- appId: bundleId,
2597
- deviceId: udid,
2598
- appPath,
2599
- physical: true,
2600
- msg: `launching ${bundleId} on ${udid}`
2601
- });
2602
- const collector = await d.replaceCollector({
2603
- root,
2604
- slot,
2605
- udid,
2606
- bundleId,
2607
- appName,
2608
- physical: true,
2609
- payloadUrl,
2610
- note
2611
- });
2612
- if (!collector?.pid) return fail({
2613
- code: "STIM_LAUNCH_FAILED",
2614
- message: `The device log collector, which is what launches ${bundleId} on a phone, could not be started.`,
2615
- remedy: `Check ${logFile} and the workspace collector log, then run the command again.`,
2616
- build: {
2617
- ...buildFailure,
2618
- appPath,
2619
- bundleId
2620
- }
2621
- });
2622
- const started = await d.awaitIosDeviceLaunch({
2623
- udid,
2624
- bundleId,
2625
- appName: appName ?? bundleId,
2626
- collectorPid: collector.pid,
2627
- wireless: overWifi,
2628
- readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt && (entry.slot ?? "default") === (slot ?? "default"))
2629
- });
2630
- if (started.failed || !started.pid) return fail({
2631
- code: started.code ?? "STIM_LAUNCH_FAILED",
2632
- message: started.reason ?? `${bundleId} did not start on ${udid}.`,
2633
- remedy: started.remedy ?? null,
2634
- lines: started.lines ?? [],
2635
- logPath: logsDir,
2636
- build: {
2637
- ...buildFailure,
2638
- appPath,
2639
- bundleId
2640
- }
2641
- });
2642
- launched = {
2643
- ok: true,
2644
- mode: payloadUrl ? "payload-url" : "launch",
2645
- pid: started.pid,
2646
- ...payloadUrl ? { url: payloadUrl } : {},
2647
- ...lanOriginUrl ? { jsLocation: lanOriginUrl } : {}
2648
- };
2649
- phase("launch", `${bundleId} pid ${started.pid} ${launchTimer()}`);
2650
- } else {
2651
- const adopting = Boolean(device?.adoptionPending);
2652
- if (adopting) {
2653
- const cleanupFailure = cleanAdoptedIosApps({
2654
- d,
2655
- udid,
2656
- bundleId,
2657
- devClient: Boolean(scheme),
2658
- phase,
2659
- note
2660
- });
2661
- if (cleanupFailure) return fail({
2662
- code: "STIM_INSTALL_FAILED",
2663
- message: `${cleanupFailure} Stim kept the adoption cleanup pending and did not install or launch the app.`,
2664
- remedy: `Run \`${runCommand}\` again after simulator tooling is responsive.`,
2665
- build: {
2666
- ...buildFailure,
2667
- appPath,
2668
- bundleId
2669
- }
2670
- });
2671
- }
2672
- enterPhase("install");
2673
- const installTimer = stepTimer(d.now);
2674
- const installed = d.installIosApp({
2675
- udid,
2676
- appPath,
2677
- bundleId,
2678
- devClientScheme: scheme,
2679
- schemeApprovals: device.schemeApprovals,
2680
- proveInstalled: installMayBeProven(adopting, device?.parkedCacheKey, storeKey)
2681
- }, { now: d.now });
2682
- if (installed?.failed) return fail({
2683
- code: installed.code || "STIM_INSTALL_FAILED",
2684
- message: installed.reason,
2685
- remedy: installFailureRemedy(remoteDevice),
2686
- build: {
2687
- ...buildFailure,
2688
- appPath,
2689
- bundleId
2690
- }
2691
- });
2692
- if (adopting) d.clearIosAdoptionPending(root, slot);
2693
- installSkipped = Boolean(installed?.skipped);
2694
- const artifactDuration = installed?.artifactDurationMs === void 0 ? installTimer() : `(${formatDuration(installed.artifactDurationMs)})`;
2695
- phase("install", installSkipped ? `unchanged (${deviceShortName(device, udid)} already has this build) ${artifactDuration}` : `${basename(appPath)} -> ${deviceLabel(device, udid)} ${artifactDuration}`);
2696
- if (!remoteDevice && installed?.devClientPreparationDurationMs !== void 0) phase("install", `dev client prepared (${formatDuration(installed.devClientPreparationDurationMs)})`);
2697
- if (installed.schemeApprovals) d.recordIosSchemeApprovals(root, udid, installed.schemeApprovals, slot);
2698
- dropSwapDir();
2699
- if (!remoteDevice) await d.replaceCollector({
2700
- root,
2701
- slot,
2702
- udid,
2703
- bundleId,
2704
- appName,
2705
- appExecutable,
2706
- note
2707
- });
2708
- enterPhase("launch");
2709
- const launchTimer = stepTimer(d.now);
2710
- launchedAt = d.now();
2711
- logWriter().write({
2712
- src: "build",
2713
- level: "info",
2714
- marker: true,
2715
- event: "launch_attempt",
2716
- ts: launchedAt,
2717
- platform: "ios",
2718
- appId: bundleId,
2719
- deviceId: udid,
2720
- appPath,
2721
- remote: Boolean(remoteDevice),
2722
- msg: `launching ${bundleId} on ${udid}`
2723
- });
2724
- launched = d.launchIosApp({
2725
- udid,
2726
- bundleId,
2727
- metroPort,
2728
- devClientScheme: scheme,
2729
- devMenuParams,
2730
- consolePaths: simulatorConsolePaths({
2731
- deviceId: udid,
2732
- appId: bundleId,
2733
- since: launchedAt
2734
- }, true, Boolean(remoteDevice))
2735
- });
2736
- if (launched.failed) {
2737
- printNativeCrashReport({
2738
- root,
2739
- slot,
2740
- platform: "ios",
2741
- deviceId: udid,
2742
- appId: bundleId,
2743
- since: launchedAt,
2744
- appPath
2745
- }, logsDir, (line) => note(chalk.red(phaseLine("launch", line))), Boolean(remoteDevice));
2746
- return fail({
2747
- code: launched.code || "STIM_LAUNCH_FAILED",
2748
- message: launched.reason,
2749
- remedy: launchFailureRemedy(remoteDevice, udid, bundleId, logFile),
2750
- build: {
2751
- ...buildFailure,
2752
- appPath,
2753
- bundleId
2754
- }
2755
- });
2756
- }
2757
- phase("launch", `${bundleId}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
2758
- }
2759
- recordIosReloadTarget({
2760
- d,
2761
- root,
2762
- slot,
2763
- physical,
2764
- remoteDevice: Boolean(remoteDevice),
2765
- bundleId,
2766
- udid,
2767
- metroPort,
2768
- release,
2769
- launchedAt,
2770
- note
2771
- });
2772
- logWriter().write({
2773
- src: "build",
2774
- level: "info",
2775
- event: "launch",
2776
- msg: release ? `launched ${bundleId} on ${udid} (${configuration}, embedded JS bundle, no Metro)` : `launched ${bundleId} on ${udid} against Metro ${lanOriginUrl ?? `port ${metroPort}`}` + (launched?.mode === "openurl" || launched?.mode === "payload-url" ? " (expo-dev-client)" : "") + restartedAppNote(launched.restartedPid, "; ")
2777
- });
2778
- if (remoteDevice) logWriter().write({
2779
- src: "build",
2780
- level: "info",
2781
- event: "collector_skipped",
2782
- msg: `remote session ${udid}: device logs come from agent-device/EAS, so no simctl log stream is attached`
2783
- });
2784
- if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
2785
- const { state: launchState, warning: launchWarning, unattributed } = await verifyIosRun({
2786
- root,
2787
- slot,
2788
- appPath,
2789
- d,
2790
- release,
2791
- launched,
2792
- configuration,
2793
- phase,
2794
- note,
2795
- metroCheck,
2796
- logsDir,
2797
- launchedAt,
2798
- metroPort,
2799
- isExpo,
2800
- bundleId,
2801
- udid,
2802
- scheme,
2803
- physical,
2804
- appName,
2805
- lanAddress,
2806
- lanOrigin: lanOriginUrl,
2807
- remoteDevice: Boolean(remoteDevice),
2808
- metroOrigin: typeof launched?.jsLocation === "string" ? launched.jsLocation : null
2809
- });
2810
- if (launchState === "fatal") return fail({
2811
- code: "STIM_LAUNCH_FAILED",
2812
- message: "The app failed its launch readiness check.",
2813
- remedy: `Read the launch error above or run \`stim logs --errors\`. The full timeline is in ${logsDir}.`,
2814
- logPath: logsDir,
2815
- build: {
2816
- ...buildFailure,
2817
- appPath,
2818
- bundleId
2819
- }
2820
- });
2821
- logWriter().write(launchOutcomeRecord({
2822
- launchState,
2823
- release,
2824
- bundleId,
2825
- configuration,
2826
- metroPort,
2827
- unattributed
2828
- }));
2829
- const leaseFacts = lease?.facts() ?? null;
2830
- releaseLease();
2831
- const uploadsAbandoned = await artifact.completeUploads();
2832
- return {
2833
- facts: reportIosResult({
2834
- devicePlacement,
2835
- root,
2836
- slot,
2837
- json,
2838
- release,
2839
- configuration,
2840
- buildScheme,
2841
- metroCheck,
2842
- metroPort,
2843
- logsDir,
2844
- device,
2845
- udid,
2846
- appPath,
2847
- bundleId,
2848
- installSkipped,
2849
- elapsed,
2850
- startedAt,
2851
- storeHash,
2852
- storeKey,
2853
- cacheHit,
2854
- missReason,
2855
- compilationCache,
2856
- useBuildCache,
2857
- waitedForBuild,
2858
- launchState,
2859
- launchWarning,
2860
- providerName,
2861
- buildMachine,
2862
- builtOn,
2863
- offloadedTo,
2864
- offloadFallback,
2865
- closeWriter,
2866
- webPreviewUrl: remoteDevice?.webPreviewUrl() ?? null,
2867
- lease: physical ? leaseFacts : void 0,
2868
- recordRun,
2869
- reclaimed,
2870
- devServer,
2871
- links: workspaceLinks(root, {
2872
- platform: "ios",
2873
- slot
2874
- })
2875
- }),
2876
- uploadsAbandoned
2877
- };
2878
- }
2879
- //#endregion
2880
- //#region src/commands/ios/next-build.ts
2881
- function note(line) {
2882
- console.error(line);
2883
- }
2884
- function runOnlyFlag(opts) {
2885
- if (isPhysicalDeviceRequest(opts.device)) return "--device";
2886
- if (opts.remote) return "--remote";
2887
- if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
2888
- if (opts.simulatorApp !== void 0) return "--simulator-app";
2889
- if (opts.metroCheck === false) return "--no-metro-check";
2890
- return null;
2891
- }
2892
- async function planIos(opts, d, schemeRefusal) {
2893
- const json = Boolean(opts.json);
2894
- const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
2895
- const flag = runOnlyFlag(opts);
2896
- if (flag) return refuse(planFlagRefusal(flag));
2897
- const root = d.findProjectRoot(process.cwd());
2898
- if (!root) return refuse(NO_PROJECT_REFUSAL);
2899
- const projectProblem = appProjectProblem(root);
2900
- if (projectProblem) return refuse({
2901
- code: "STIM_NO_PROJECT",
2902
- ...projectProblem
2903
- });
2904
- const settingsContext = {
2905
- projectPath: root,
2906
- gitCommonDir: d.gitCommonDir(root),
2907
- repoRoot: d.repoRoot(root)
2908
- };
2909
- const settings = d.resolveSettings(settingsContext);
2910
- const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
2911
- if (shapeError) return refuse({
2912
- code: "STIM_BAD_ARG",
2913
- message: shapeError,
2914
- remedy: SETTING_SHAPE_REMEDY
2915
- }, moreShapeErrors);
2916
- let optimizations;
2917
- try {
2918
- optimizations = resolveOptimizations(settings);
2919
- } catch (error) {
2920
- return refuse({
2921
- code: "STIM_BAD_ARG",
2922
- message: error.message,
2923
- remedy: SETTING_SHAPE_REMEDY
2924
- });
2925
- }
2926
- const cacheProviderError = cacheProviderSettingError(settings);
2927
- if (cacheProviderError) note(chalk.yellow(phaseLine("cache", `${cacheProviderError} Using the local cache.`)));
2928
- const slot = opts.slot ?? "default";
2929
- const target = {
2930
- platform: "ios",
2931
- slot,
2932
- root,
2933
- projectKey: statsProjectKey$1({
2934
- root,
2935
- commonDir: settingsContext.gitCommonDir,
2936
- repoRoot: settingsContext.repoRoot
2937
- })
2938
- };
2939
- const isExpo = d.detectIsExpo(root);
2940
- const settingsLayersForOrigin = d.settingsLayers(settingsContext);
2941
- const iosRemote = remoteIosSetting(settings);
2942
- const modelRefusal = deviceModelRefusal({
2943
- slot,
2944
- deviceTypeFlag: opts.deviceType,
2945
- runtimeFlag: opts.runtime,
2946
- deviceType: resolveDeviceType(opts.deviceType, settings),
2947
- runtime: resolveRuntime(opts.runtime, settings),
2948
- deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
2949
- runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
2950
- physical: false,
2951
- remoteBackend: iosRemote?.kind === "backend" ? iosRemote.backend : null,
2952
- hosted: iosRemote?.kind === "machine",
2953
- listRuntimes: d.listIosRuntimes
2954
- });
2955
- if (modelRefusal) return refuse(modelRefusal);
2956
- if (opts.easProfile !== void 0) {
2957
- const eas = await planEasDevelopmentBuild({
2958
- root,
2959
- platform: "ios",
2960
- profile: opts.easProfile,
2961
- note,
2962
- isExpo,
2963
- selectors: [opts.scheme, opts.configuration],
2964
- buildCache: opts.buildCache
2965
- });
2966
- if (!eas.ok) return refuse(eas);
2967
- return printPlan(planPayload(target, {
2968
- fingerprint: eas.fingerprint,
2969
- cacheKey: eas.cacheKey,
2970
- cacheHit: eas.buildId ? "remote" : false,
2971
- provider: "eas",
2972
- cacheSkipped: false,
2973
- prebuild: null,
2974
- refusal: eas.missing
2975
- }), json);
2976
- }
2977
- if (iosRemote?.kind === "backend") return refuse({
2978
- code: "STIM_BAD_ARG",
2979
- message: `ios.remote is ${iosRemote.backend}, whose simulator architecture --plan cannot read without a session.`,
2980
- remedy: "Run `stim ios` to build for the remote device, or unset ios.remote to plan the owned simulator."
2981
- });
2982
- const refusal = schemeRefusal(root, opts.scheme, isExpo);
2983
- if (refusal) return refuse({
2984
- code: refusal.code,
2985
- message: refusal.message ?? "",
2986
- remedy: refusal.remedy ?? ""
2987
- });
2988
- const configuration = resolveConfiguration(opts.configuration, settings);
2989
- const buildProfile = optimizationBuildProfile("ios", optimizations);
2990
- const cachePolicy = artifactCachePolicy(optimizations, opts.buildCache !== false, isReleaseConfiguration(configuration));
2991
- let fingerprint;
2992
- try {
2993
- fingerprint = await d.fingerprintProject(root, { platform: "ios" });
2994
- } catch (error) {
2995
- note(chalk.dim(`Fingerprinting failed: ${error?.message || error}`));
2996
- }
2997
- if (!fingerprint?.hash) return refuse({
2998
- code: "STIM_NO_FINGERPRINT",
2999
- message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
3000
- remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
3001
- });
3002
- const keyArch = (remoteArch) => simulatorBuildArch({
3003
- physical: false,
3004
- remoteArch,
3005
- hostArch: d.hostSimulatorArch(),
3006
- configuration
3007
- });
3008
- let arch = keyArch(null);
3009
- let placement;
3010
- if (iosRemote?.kind === "machine") {
3011
- const planned = await planHostedDevice({
3012
- root,
3013
- slot,
3014
- platform: "ios",
3015
- machine: iosRemote.machine,
3016
- selectors: hostedIosSelectors(resolveDeviceType(opts.deviceType, settings), resolveRuntime(opts.runtime, settings)),
3017
- sameKey: (choice) => keyArch("runtime" in choice ? choice.architecture : null) === arch
3018
- });
3019
- if (planned.kind === "unknown") return refuse({
3020
- code: "STIM_BAD_ARG",
3021
- message: `ios.remote is ${iosRemote.machine}, and its simulator architecture is unknown here: ${planned.reason}.`,
3022
- remedy: "Run `stim ios` to build for the remote device, or unset ios.remote to plan the owned simulator."
3023
- });
3024
- placement = planned.placement;
3025
- if (planned.kind === "hosted" && "runtime" in planned.choice) arch = keyArch(planned.choice.architecture);
3026
- }
3027
- const cacheKey = buildCacheKey("ios", fingerprint.hash, {
3028
- scheme: opts.scheme,
3029
- ...configuration ? { configuration } : {},
3030
- isSimulator: true,
3031
- ...arch ? { arch } : {},
3032
- ...buildProfile ? { buildProfile } : {}
3033
- });
3034
- const plan = await planCachedBuild({
3035
- ...target,
3036
- isExpo,
3037
- fingerprint: fingerprint.hash,
3038
- sources: fingerprint.sources ?? [],
3039
- cacheKey,
3040
- cachePolicy,
3041
- providerConfig: d.resolveCacheProviderConfig(settingsContext),
3042
- expoRemote: cachePolicy.remote && !buildProfile && !opts.scheme ? { runOptions: iosProviderRunOptions(configuration, arch) } : null
3043
- }, {
3044
- loadCacheProvider: d.loadCacheProvider,
3045
- loadProjectProvider: d.loadProjectProvider,
3046
- checkEasAuth: d.checkEasAuth,
3047
- resolveRemote: d.resolveRemote,
3048
- planPrebuild: d.planPrebuild,
3049
- note
3050
- });
3051
- printPlan(placement ? {
3052
- ...plan,
3053
- placement
3054
- } : plan, json);
3055
- }
3056
- //#endregion
3057
- //#region src/commands/ios.ts
3058
- function writeNote(line) {
3059
- console.error(line);
3060
- }
3061
- function writePhase(name, text) {
3062
- console.error(phaseLine(name, text));
3063
- }
3064
- function iosCommand(program) {
3065
- registerIos(program);
3066
- }
3067
- function registerIos(program, deps = {}) {
3068
- 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("--remote-build <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 remote.machines; named Macs never fall back locally. auto places on an approved Mac when this Mac is full or busy.", (value) => {
3069
- if (parseMachine(value)) return value.trim();
3070
- throw new InvalidArgumentError("expected eas, proxy, auto, or a remote Mac name");
3071
- }).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). Without --device, wait for an owned-device slot (default 600 seconds; --no-wait refuses immediately).").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. Without --device, refuse a full device cap or existing slot queue immediately.").action(async (opts) => {
3072
- if (opts.plan) {
3073
- const d = {
3074
- ...DEFAULT_DEPS,
3075
- ...deps
3076
- };
3077
- await planIos(opts, d, (root, scheme, isExpo) => explicitSchemeRefusal(root, scheme, isExpo, d));
3078
- return;
3079
- }
3080
- const completion = await runIosOperation((deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd()), {
3081
- ...opts,
3082
- waitConflict: waitFlagConflict(process.argv)
3083
- }, deps);
3084
- if (!completion) process.exit(runCancellation() ? 130 : 1);
3085
- else if (completion.uploadsAbandoned) exitAfterFlush(0);
3086
- });
3087
- }
3088
- function runIosOperation(root, opts = {}, deps = {}, onFailure = () => {}) {
3089
- const run = (progress) => runIos(root, opts, deps, progress, onFailure);
3090
- return root ? withNativeBuildRun(root, {
3091
- command: "ios",
3092
- platform: "ios",
3093
- slot: opts.slot ?? "default"
3094
- }, async (claim) => {
3095
- recordWorkspaceUse(root);
3096
- const progress = startBuildProgress({
3097
- root,
3098
- platform: "ios",
3099
- slot: opts.slot ?? "default",
3100
- claim,
3101
- note: (line) => writeNote(chalk.dim(line))
3102
- });
3103
- try {
3104
- return await run(progress);
3105
- } finally {
3106
- progress.clear();
3107
- }
3108
- }, { write: (line) => writeNote(chalk.dim(phaseLine("lock", line))) }) : run();
3109
- }
3110
- function unlessCancelled(result) {
3111
- if (!result.ok || !runCancellation()) return result;
3112
- return {
3113
- ok: false,
3114
- failure: {
3115
- code: "STIM_CANCELLED",
3116
- message: "before install"
3117
- },
3118
- compilationCache: result.artifact.cache.compilation
3119
- };
3120
- }
3121
- function explicitSchemeRefusal(root, scheme, isExpo, d) {
3122
- if (scheme === void 0) return null;
3123
- if (!scheme.trim()) return {
3124
- code: "STIM_BAD_ARG",
3125
- message: "--scheme must name a non-empty shared Xcode app scheme.",
3126
- remedy: "Pass the exact scheme name shown by xcodebuild -list."
3127
- };
3128
- if (isExpo) return null;
3129
- const project = d.discoverXcodeProject(root);
3130
- if (project.error) return project.error;
3131
- return d.resolveScheme(project, { scheme }).error ?? null;
3132
- }
3133
- function resolveIosBuildSetup(flag, settings) {
3134
- const placement = resolveBuildPlacement(flag);
3135
- if (placement.failure) return {
3136
- ok: false,
3137
- failure: {
3138
- ...placement.failure,
3139
- setup: true
3140
- }
3141
- };
3142
- try {
3143
- return {
3144
- ok: true,
3145
- buildMachine: placement.selected,
3146
- optimizations: resolveOptimizations(settings)
3147
- };
3148
- } catch (error) {
3149
- return {
3150
- ok: false,
3151
- failure: {
3152
- code: "STIM_BAD_ARG",
3153
- message: error.message,
3154
- remedy: SETTING_SHAPE_REMEDY
3155
- }
3156
- };
3157
- }
3158
- }
3159
- function iosSlotLogFile(root, slot) {
3160
- return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir$1(root), `build-${deviceSlotFileKey("ios", slot)}.ndjson`);
3161
- }
3162
- function iosSlotDeps(d, slot) {
3163
- if (slot !== "default") {
3164
- const base = d;
3165
- d = {
3166
- ...base,
3167
- ensureOwnedDevice: (args) => base.ensureOwnedDevice({
3168
- ...args,
3169
- slot
3170
- }),
3171
- checkDeviceCapacity: (args) => base.checkDeviceCapacity({
3172
- ...args,
3173
- slot
3174
- }),
3175
- selectFromPool: (args) => base.selectFromPool({
3176
- ...args,
3177
- slot
3178
- }),
3179
- acquireRunLease: (args) => base.acquireRunLease({
3180
- ...args,
3181
- slot
3182
- }),
3183
- runLease: (args) => base.runLease({
3184
- ...args,
3185
- slot
3186
- }),
3187
- replaceCollector: (args) => base.replaceCollector({
3188
- ...args,
3189
- slot
3190
- }),
3191
- stopPreviousCollector: (args) => base.stopPreviousCollector({
3192
- ...args,
3193
- slot
3194
- }),
3195
- clearIosAdoptionPending: (root) => base.clearIosAdoptionPending(root, slot),
3196
- writeWorkspaceLaunch: (root, platform, record) => base.writeWorkspaceLaunch(root, platform, record, slot),
3197
- createWriter: (file, options) => base.createWriter(file, {
3198
- ...options,
3199
- fields: { slot }
3200
- })
3201
- };
3202
- }
3203
- return d;
3204
- }
3205
- async function runIos(projectRoot, opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS, onFailure) {
3206
- const slot = validateDeviceSlot(opts.slot);
3207
- let d = iosSlotDeps({
3208
- ...DEFAULT_DEPS,
3209
- ...overrides
3210
- }, slot);
3211
- const json = Boolean(opts.json);
3212
- const metroCheck = opts.metroCheck !== false;
3213
- let useBuildCache = opts.buildCache !== false;
3214
- const phase = writePhase;
3215
- const note = writeNote;
3216
- const started = d.now();
3217
- const startedAt = new Date(started).toISOString();
3218
- const elapsed = () => d.now() - started;
3219
- const refuseProject = ({ message, remedy }) => {
3220
- note(chalk.red(phaseLine("error", message)));
3221
- note(chalk.dim(phaseLine("remedy", remedy)));
3222
- note(chalk.red(phaseLine("failed", "STIM_NO_PROJECT")));
3223
- if (json) console.log(JSON.stringify({
3224
- code: "STIM_NO_PROJECT",
3225
- message,
3226
- remedy
3227
- }));
3228
- onFailure({
3229
- code: "STIM_NO_PROJECT",
3230
- message,
3231
- remedy
3232
- });
3233
- return null;
3234
- };
3235
- if (!projectRoot) return refuseProject(NO_PROJECT_REFUSAL);
3236
- const root = projectRoot;
3237
- const projectProblem = appProjectProblem(root);
3238
- if (projectProblem) return refuseProject(projectProblem);
3239
- try {
3240
- await d.ensureWorkspaceStorage(root, { note });
3241
- } catch (error) {
3242
- const code = error?.code || "STIM_WORKSPACE_STATE";
3243
- const message = `Could not prepare this workspace's Stim state: ${error?.message || error}`;
3244
- note(chalk.red(`${code}: ${message}`));
3245
- note(chalk.dim("Check that STIM_HOME is writable and has free space. An EPERM on a directory you can write is a sandbox: allow writes to STIM_HOME, or run Stim with the sandbox disabled (`stim guide errors sandbox`)."));
3246
- if (json) console.log(JSON.stringify({
3247
- code,
3248
- message,
3249
- remedy: "Check that STIM_HOME is writable and has free space."
3250
- }));
3251
- onFailure({
3252
- code,
3253
- message,
3254
- remedy: "Check that STIM_HOME is writable and has free space."
3255
- });
3256
- return null;
3257
- }
3258
- const logsDir = workspaceLogsDir$1(root);
3259
- const logFile = iosSlotLogFile(root, slot);
3260
- let writer = null;
3261
- const logWriter = () => writer ||= tapBuildLog(d.createWriter(logFile, {
3262
- truncate: true,
3263
- fields: {
3264
- platform: "ios",
3265
- slot
3266
- }
3267
- }), progress);
3268
- setRemoteLogSink((record) => logWriter().write(record));
3269
- let leaseHandle = null;
3270
- let stopLeaseSignals = null;
3271
- const releaseLease = () => {
3272
- const held = leaseHandle;
3273
- const stopSignals = stopLeaseSignals;
3274
- leaseHandle = null;
3275
- stopLeaseSignals = null;
3276
- stopSignals?.();
3277
- try {
3278
- held?.release();
3279
- } catch (e) {
3280
- note(chalk.dim(`Could not release this run's device lease: ${e?.message || e}`));
3281
- }
3282
- };
3283
- const stats = createRunRecorder({
3284
- platform: "ios",
3285
- write: (statsRun, at) => d.recordStats(statsRun, at),
3286
- now: () => d.now(),
3287
- note: (line) => note(chalk.dim(line)),
3288
- phases: () => progress.durations(),
3289
- deviceSetup: () => progress.deviceSetupKnown()
3290
- });
3291
- const deviceWaitRun = createIosDeviceWaitRun(stats, progress, runCancellationSignal());
3292
- const recordRun = deviceWaitRun.record;
3293
- let compilationCache = COMPILATION_CACHE_NOT_RUN;
3294
- let reclaimed = [];
3295
- let buildMachine = "auto";
3296
- let devicePlacement;
3297
- let builtConfiguration = null;
3298
- const fail = ({ code, message, remedy = null, lines = [], logPath = null, build = null, lease, setup = false }) => {
3299
- ({code, message, remedy, lines} = cancelledFailure("ios", {
3300
- code,
3301
- message
3302
- }) ?? {
3303
- code,
3304
- message,
3305
- remedy,
3306
- lines
3307
- });
3308
- releaseLease();
3309
- if (message) note(chalk.red(phaseLine("error", message)));
3310
- for (const line of lines) note(chalk.dim(phaseLine("", line)));
3311
- if (remedy) note(chalk.dim(phaseLine("remedy", remedy)));
3312
- if (logPath) note(chalk.dim(phaseLine("log", logPath)));
3313
- if (build) writeLastBuild(root, lastBuildRecord({
3314
- devicePlacement,
3315
- buildMachine,
3316
- ...build,
3317
- configuration: builtConfiguration,
3318
- startedAt,
3319
- status: "failed",
3320
- errorCode: code,
3321
- durationMs: elapsed()
3322
- }));
3323
- note(chalk.red(phaseLine("failed", code)));
3324
- if (!setup) recordRun({
3325
- failed: true,
3326
- durationMs: elapsed()
3327
- });
3328
- if (json) console.log(JSON.stringify({
3329
- code,
3330
- message: message ?? null,
3331
- remedy: remedy ?? null,
3332
- ...devicePlacement ? { devicePlacement } : {},
3333
- ...compilationCache.status === "not-run" ? {} : { compilationCache },
3334
- ...lease === void 0 ? {} : { lease },
3335
- ...reclaimed.length ? { reclaimed } : {}
3336
- }));
3337
- writer?.close?.();
3338
- onFailure({
3339
- code,
3340
- message,
3341
- remedy,
3342
- lines,
3343
- logPath,
3344
- build,
3345
- lease,
3346
- setup
3347
- });
3348
- return null;
3349
- };
3350
- const settingsRepoRoot = d.repoRoot(root);
3351
- const settingsContext = {
3352
- projectPath: root,
3353
- gitCommonDir: d.gitCommonDir(root),
3354
- repoRoot: settingsRepoRoot
3355
- };
3356
- const projectKey = statsProjectKey$1({
3357
- root,
3358
- commonDir: settingsContext.gitCommonDir,
3359
- repoRoot: settingsRepoRoot
3360
- });
3361
- stats.setProject(projectKey);
3362
- progress.estimate(projectKey);
3363
- let estimatesRead = null;
3364
- const estimates = () => estimatesRead ??= d.readEstimates({
3365
- projectKey,
3366
- platform: "ios"
3367
- });
3368
- const settings = d.resolveSettings(settingsContext);
3369
- const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
3370
- if (shapeError) return fail({
3371
- code: "STIM_BAD_ARG",
3372
- message: shapeError,
3373
- lines: moreShapeErrors,
3374
- remedy: SETTING_SHAPE_REMEDY
3375
- });
3376
- const setup = resolveIosBuildSetup(opts.remoteBuild, settings);
3377
- if (!setup.ok) return fail(setup.failure);
3378
- buildMachine = setup.buildMachine;
3379
- const { optimizations } = setup;
3380
- const buildProfile = optimizationBuildProfile("ios", optimizations);
3381
- const cacheProviderConfig = d.resolveCacheProviderConfig(settingsContext);
3382
- for (const key of unknownSettingKeys(settings)) note(phaseLine("setting", chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`)));
3383
- const cacheProviderError = cacheProviderSettingError(settings);
3384
- if (cacheProviderError) note(chalk.yellow(phaseLine("cache", `${cacheProviderError} Using the local cache.`)));
3385
- const poolError = parkedMaxSetting("ios").error;
3386
- if (poolError) return fail({
3387
- code: "STIM_BAD_ARG",
3388
- message: poolError,
3389
- remedy: POOL_SETTING_REMEDY
3390
- });
3391
- for (const settingError of [
3392
- iosSigningIdentitySettingError(settings),
3393
- iosSigningIdentitySha1SettingError(settings),
3394
- iosLanHostSettingError(settings)
3395
- ]) if (settingError) return fail({
3396
- code: "STIM_BAD_ARG",
3397
- message: settingError,
3398
- remedy: SETTING_SHAPE_REMEDY
3399
- });
3400
- const configuration = opts.easProfile !== void 0 ? null : resolveConfiguration(opts.configuration, settings);
3401
- builtConfiguration = configuration ?? "Debug";
3402
- const buildScheme = opts.scheme;
3403
- const release = isReleaseConfiguration(configuration);
3404
- const cachePolicy = artifactCachePolicy(optimizations, useBuildCache, release);
3405
- useBuildCache = cachePolicy.read;
3406
- const deviceType = resolveDeviceType(opts.deviceType, settings);
3407
- const runtime = resolveRuntime(opts.runtime, settings);
3408
- const deviceFlag = opts.device;
3409
- const physical = isPhysicalDeviceRequest(deviceFlag);
3410
- if (physical && deviceFlag === "") return fail({
3411
- code: "STIM_BAD_ARG",
3412
- message: "--device was given an empty UDID.",
3413
- remedy: "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <udid>` to name one."
3414
- });
3415
- if (physical && opts.remote) return fail({
3416
- code: "STIM_BAD_ARG",
3417
- message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
3418
- remedy: "Pass only one of --device and --remote."
3419
- });
3420
- const wait = resolveIosWait(opts, physical);
3421
- if ("failure" in wait) return fail(wait.failure);
3422
- const { waitSeconds, noWait, deviceSlotWaitMs, checkCapacity } = wait;
3423
- const deviceSlotWait = {
3424
- waitMs: deviceSlotWaitMs,
3425
- displayName: basename(root),
3426
- ...deviceWaitRun.policy,
3427
- onWait: (ms) => {
3428
- deviceWaitRun.policy.onWait?.(ms);
3429
- if (devicePlacement?.decision === "local" && ms > 0) devicePlacement.decision = "waited-locally";
3430
- }
3431
- };
3432
- const isExpo = d.detectIsExpo(root);
3433
- const schemeRefusal = explicitSchemeRefusal(root, buildScheme, isExpo, d);
3434
- if (schemeRefusal) return fail(schemeRefusal);
3435
- const remoteSelection = await selectIosPlacement({
3436
- root,
3437
- slot,
3438
- opts,
3439
- settings,
3440
- physical,
3441
- release,
3442
- metroCheck,
3443
- d,
3444
- deviceType,
3445
- runtime,
3446
- validateSelectors: () => deviceModelRefusal({
3447
- slot,
3448
- deviceTypeFlag: opts.deviceType,
3449
- runtimeFlag: opts.runtime,
3450
- deviceType,
3451
- runtime,
3452
- physical,
3453
- remoteBackend: null,
3454
- deviceTypeOrigin: d.settingOriginScope(d.settingsLayers(settingsContext), "ios.deviceType"),
3455
- runtimeOrigin: d.settingOriginScope(d.settingsLayers(settingsContext), "ios.runtime"),
3456
- listRuntimes: d.listIosRuntimes
3457
- }),
3458
- noWait: deviceSlotWaitMs === 0,
3459
- note,
3460
- phase,
3461
- log: (entry) => logWriter().write(entry)
3462
- });
3463
- if ("failure" in remoteSelection) return fail(remoteSelection.failure);
3464
- devicePlacement = remoteSelection.devicePlacement;
3465
- writeDevicePlacement(root, slot, "ios", devicePlacement);
3466
- const { machine: hostedMachine, backend: remoteBackend } = remoteSelection;
3467
- const viewer = resolveSimulatorAppFlag(opts.simulatorApp, physical, hostedMachine ?? remoteBackend, Boolean(remoteSelection.auto));
3468
- if ("refusal" in viewer) return fail(viewer.refusal);
3469
- const { simulatorApp } = viewer;
3470
- const settingsLayersForOrigin = d.settingsLayers(settingsContext);
3471
- const modelRefusal = deviceModelRefusal({
3472
- slot,
3473
- deviceTypeFlag: opts.deviceType,
3474
- runtimeFlag: opts.runtime,
3475
- deviceType,
3476
- runtime,
3477
- deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
3478
- runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
3479
- physical,
3480
- remoteBackend,
3481
- hosted: Boolean(hostedMachine),
3482
- listRuntimes: d.listIosRuntimes
3483
- });
3484
- if (modelRefusal) return fail(modelRefusal);
3485
- const selectors = hostedIosSelectors(deviceType, runtime);
3486
- const connected = await connectIosTarget(remoteSelection, selectors, d);
3487
- if ("failure" in connected) return fail(connected.failure);
3488
- const hostedTarget = connected.target;
3489
- try {
3490
- const simSnapshot = createIosSimSnapshot();
3491
- const budget = await iosPlacementBudget(d, root, note, Boolean(hostedTarget), remoteSelection.budget, simSnapshot);
3492
- reclaimed = budget.reclaimed;
3493
- if (budget.refusal) return fail(budget.refusal);
3494
- const backendConnection = await connectIosBackend(root, remoteBackend, opts.deviceType, d);
3495
- if ("failure" in backendConnection) return fail(backendConnection.failure);
3496
- const remoteDevice = backendConnection.remote;
3497
- const remoteArch = hostedTarget ? hostedTarget.choice.architecture : backendConnection.arch;
3498
- if (remoteDevice) d = {
3499
- ...d,
3500
- checkDeviceCapacity: remoteDevice.checkDeviceCapacity,
3501
- ensureOwnedDevice: remoteDevice.ensureOwnedDevice,
3502
- ensureBooted: remoteDevice.ensureBooted,
3503
- installIosApp: remoteDevice.installIosApp,
3504
- launchIosApp: remoteDevice.launchIosApp
3505
- };
3506
- const easBuild = await d.resolveEasDevelopmentBuild({
3507
- root,
3508
- platform: "ios",
3509
- profile: opts.easProfile,
3510
- note,
3511
- isExpo,
3512
- physical,
3513
- selectors: [opts.scheme, opts.configuration],
3514
- buildCache: opts.buildCache
3515
- });
3516
- if (isEasBuildFailure(easBuild)) return fail(easBuild);
3517
- const registerProject = () => d.upsertProject(root, {
3518
- bundleId: d.detectBundleId(root) ?? void 0,
3519
- isExpo
3520
- });
3521
- if (remoteBackend !== "eas") registerProject();
3522
- const proj = d.getProject(root);
3523
- const limits = d.getConcurrencyLimits();
3524
- let physicalDevice = null;
3525
- let wireless = false;
3526
- if (physical && typeof deviceFlag !== "string") {
3527
- const pooled = await d.selectFromPool({
3528
- root,
3529
- platform: "ios",
3530
- idLabel: "udid",
3531
- list: () => iosPoolCandidates(d.listIosDevices()).map((entry) => ({
3532
- id: entry.udid,
3533
- name: entry.name,
3534
- fallback: isWirelessIosDevice(entry)
3535
- })),
3536
- noCandidates: () => {
3537
- const resolved = iosPoolNoCandidatesRefusal(d.listIosDevices());
3538
- return {
3539
- message: resolved.error,
3540
- remedy: resolved.remedy
3541
- };
3542
- },
3543
- waitSeconds,
3544
- noWait,
3545
- now: d.now,
3546
- warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
3547
- });
3548
- if (pooled.status === "refused") return fail({
3549
- code: pooled.refusal.code,
3550
- message: pooled.refusal.message,
3551
- remedy: pooled.refusal.remedy,
3552
- ...pooled.refusal.lease === null ? {} : { lease: pooled.refusal.lease }
3553
- });
3554
- physicalDevice = {
3555
- udid: pooled.candidate.id,
3556
- name: pooled.candidate.name ?? pooled.candidate.id
3557
- };
3558
- wireless = pooled.candidate.fallback === true;
3559
- } else if (physical) {
3560
- const resolved = resolveIosPhysicalDevice(typeof deviceFlag === "string" ? deviceFlag : null, d.listIosDevices());
3561
- if (!resolved.udid) return fail({
3562
- code: "STIM_NO_DEVICE",
3563
- message: resolved.error,
3564
- remedy: resolved.remedy
3565
- });
3566
- physicalDevice = {
3567
- udid: resolved.udid,
3568
- name: resolved.name ?? resolved.udid
3569
- };
3570
- wireless = resolved.wireless === true;
3571
- }
3572
- if (checkCapacity && !hostedTarget) {
3573
- const capacity = d.checkDeviceCapacity({
3574
- platform: "ios",
3575
- project: proj,
3576
- max: limits.maxDevices,
3577
- sims: () => simSnapshot.read().filter((sim) => sim.available)
3578
- });
3579
- if (capacity) {
3580
- if (capacity.code === "STIM_AT_CAPACITY") d.recordCapacityRefusal({
3581
- platform: "ios",
3582
- max: limits.maxDevices,
3583
- workspace: workspaceId(root)
3584
- }, d.now());
3585
- return fail(capacity);
3586
- }
3587
- }
3588
- let metroPort = proj?.metroPort ?? null;
3589
- let lanAddress = null;
3590
- let lanOriginUrl = null;
3591
- let devServer = null;
3592
- if (!await resolveMetroPort()) return null;
3593
- let device;
3594
- if (physicalDevice) device = {
3595
- deviceUdid: physicalDevice.udid,
3596
- deviceName: physicalDevice.name,
3597
- owned: false
3598
- };
3599
- else if (hostedTarget) device = {
3600
- owned: false,
3601
- deviceName: hostedTarget.choice.deviceType,
3602
- runtime: hostedTarget.choice.runtime
3603
- };
3604
- else {
3605
- const prepare = stepClock(d.now);
3606
- try {
3607
- device = await d.ensureOwnedDevice({
3608
- platform: "ios",
3609
- project: proj,
3610
- projectPath: root,
3611
- settingsRoot: root,
3612
- settings,
3613
- flags: {
3614
- deviceSlotWait,
3615
- deviceType,
3616
- runtime,
3617
- runtimeFlag: resolveRuntime(opts.runtime, null),
3618
- simulatorApp
3619
- },
3620
- note,
3621
- out: note,
3622
- simSnapshot
3623
- });
3624
- } catch (e) {
3625
- return fail(ownedSimFailure(e));
3626
- }
3627
- deviceWaitRun.booting(device.booting);
3628
- progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
3629
- const prepareMs = prepare();
3630
- if (device.created || prepareMs >= 2e3) phase("device", `${deviceLabel(device, device.deviceUdid)} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
3631
- }
3632
- let bootDuration = "";
3633
- let bootPromise;
3634
- let udid = "";
3635
- async function resolveMetroPort() {
3636
- if (release) {
3637
- metroPort = null;
3638
- phase("metro", `skipped (${configuration}: the JS bundle is embedded, no dev server is used)`);
3639
- } else if (metroCheck) {
3640
- const gate = await ensureDevServer({
3641
- root,
3642
- port: metroPort,
3643
- settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
3644
- remote: Boolean(remoteDevice),
3645
- note,
3646
- resolve: d.resolveProjectMetro,
3647
- start: d.startDevServer,
3648
- readState: d.readWorkspaceState
3649
- });
3650
- reclaimed = [...reclaimed, ...gate.reclaimed];
3651
- if (!gate.ok) {
3652
- fail({
3653
- code: gate.code,
3654
- message: gate.message,
3655
- remedy: gate.remedy,
3656
- lines: gate.lines
3657
- });
3658
- return false;
3659
- }
3660
- metroPort = gate.port;
3661
- devServer = gate.devServer;
3662
- } else {
3663
- const pin = metroPortSetting(root);
3664
- if (pin.error) {
3665
- fail({
3666
- code: "STIM_BAD_ARG",
3667
- message: pin.error,
3668
- remedy: SETTING_SHAPE_REMEDY
3669
- });
3670
- return false;
3671
- }
3672
- if (pin.port === null && !metroPort) note(chalk.yellow(`No Metro port is reserved for this workspace; wiring the app to ${DEFAULT_METRO_PORT}.`));
3673
- metroPort = pin.port ?? metroPort ?? 8081;
3674
- }
3675
- hostedIosMetroNote(Boolean(hostedTarget), settings, note, opts.simulatorApp);
3676
- if (physical && metroPort !== null && !await resolveLanOrigin()) return false;
3677
- if (remoteDevice && metroPort !== null) {
3678
- const reachable = await d.ensureMetroReachable({
3679
- ctx: remoteDevice.ctx,
3680
- metroPort,
3681
- isExpo,
3682
- tunnelMode: tunnelModeSetting(settings) ?? void 0,
3683
- publicUrl: publicUrlSetting(settings),
3684
- available: d.detectProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
3685
- });
3686
- if ("failed" in reachable) {
3687
- fail({
3688
- code: reachable.code ?? "STIM_NO_REMOTE_SESSION",
3689
- message: reachable.failed,
3690
- remedy: reachable.remedy
3691
- });
3692
- return false;
3693
- }
3694
- }
3695
- if (!release && metroCheck && optimizations.metroWarmup) d.warmMetro({
3696
- port: metroPort,
3697
- platform: "ios",
3698
- isExpo,
3699
- appId: proj?.bundleId,
3700
- bundleUrl: metroWarmupUrlSetting(settings, "ios")
3701
- });
3702
- return true;
3703
- }
3704
- async function resolveLanOrigin() {
3705
- const port = metroPort;
3706
- const chosen = chooseLanAddress({
3707
- pinned: iosLanHostSetting(settings),
3708
- candidates: d.hostLanCandidates()
3709
- });
3710
- if (!chosen) {
3711
- fail({
3712
- code: "STIM_NO_LAN_ADDRESS",
3713
- message: "A Debug run on a phone needs an address the phone can reach, and this Mac has no non-internal IPv4 interface.",
3714
- 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."
3715
- });
3716
- return false;
3717
- }
3718
- lanAddress = chosen.address;
3719
- lanOriginUrl = lanOriginUrlFor(chosen.address, port);
3720
- const source = chosen.pinned ? "ios.lanHost" : `${chosen.interfaceName ?? "interface"}${chosen.candidates > 1 ? ` of ${chosen.candidates} candidates` : ""}`;
3721
- phase("lan", `${lanOriginUrl} (${source})`);
3722
- 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.")));
3723
- if (!metroCheck) return true;
3724
- const reachable = await d.ensureLanReachable({
3725
- origin: lanOriginUrl,
3726
- metroPort: port,
3727
- root,
3728
- isExpo,
3729
- logsDir
3730
- });
3731
- if ("failed" in reachable) {
3732
- fail({
3733
- code: "STIM_LAN_METRO_UNREACHABLE",
3734
- message: reachable.failed,
3735
- remedy: reachable.remedy
3736
- });
3737
- return false;
3738
- }
3739
- phase("lan", `gated: ${lanOriginUrl} answered as this workspace's Metro`);
3740
- return true;
3741
- }
3742
- let artifact = null;
3743
- try {
3744
- const boot = () => physicalDevice ? Promise.resolve({
3745
- ok: true,
3746
- udid: physicalDevice.udid
3747
- }) : Promise.resolve(d.ensureBooted({
3748
- platform: "ios",
3749
- device,
3750
- projectPath: root,
3751
- deviceSlotWait,
3752
- simulatorApp,
3753
- out: note
3754
- })).catch((e) => ({
3755
- ok: false,
3756
- reason: String(e?.message || e)
3757
- }));
3758
- const startBoot = () => {
3759
- const bootTimer = stepTimer(d.now);
3760
- bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
3761
- root,
3762
- platform: "ios",
3763
- sessionName: ownedSessionName(remoteDevice.ctx.label),
3764
- startedAt: new Date(d.now()).toISOString(),
3765
- deviceType: remoteDevice.ctx.deviceType ?? null,
3766
- boot,
3767
- createdSessionId: remoteDevice.createdSessionId,
3768
- abandonCreatedSession: remoteDevice.abandonCreatedSession,
3769
- webPreviewUrl: remoteDevice.webPreviewUrl,
3770
- writeState: d.writeWorkspaceState,
3771
- register: registerProject,
3772
- notice: (line) => note(chalk.dim(phaseLine("lock", line)))
3773
- }) : boot()).then((result) => {
3774
- bootDuration = bootTimer();
3775
- return result;
3776
- });
3777
- deviceWaitRun.booting({ done: bootPromise });
3778
- return bootPromise.then((result) => result?.udid ?? "");
3779
- };
3780
- const localBoot = remoteDevice || hostedTarget ? null : startBoot();
3781
- udid = device.deviceUdid ?? await localBoot ?? "";
3782
- const acquiredArtifact = unlessCancelled(await acquireIosArtifact({
3783
- root,
3784
- logFile,
3785
- udid,
3786
- configuration,
3787
- buildScheme,
3788
- buildProfile,
3789
- buildMachine,
3790
- isExpo,
3791
- remoteDestination: Boolean(remoteDevice || hostedTarget),
3792
- ...hostedIosBuildTarget(hostedTarget),
3793
- simulatorArch: simulatorBuildArch({
3794
- physical,
3795
- remoteArch,
3796
- hostArch: d.hostSimulatorArch(),
3797
- configuration
3798
- }),
3799
- device: physical ? {
3800
- lanAddress,
3801
- metroPort,
3802
- signingName: iosSigningIdentitySetting(settings),
3803
- signingSha1: iosSigningIdentitySha1Setting(settings)
3804
- } : null,
3805
- optimizations: optimizations.ios,
3806
- cache: {
3807
- policy: cachePolicy,
3808
- providerConfig: cacheProviderConfig,
3809
- disabledByFlag: opts.buildCache === false
3810
- },
3811
- easBuild,
3812
- easProfile: opts.easProfile,
3813
- maxBuilds: limits.maxBuilds,
3814
- progress: {
3815
- phase,
3816
- note,
3817
- logWriter,
3818
- estimates,
3819
- stats,
3820
- step: progress.step,
3821
- miss: progress.miss,
3822
- hit: progress.hit,
3823
- place: progress.place,
3824
- waitingOn: progress.waitingOn,
3825
- waitingFor: progress.waitingFor
3826
- }
3827
- }, d));
3828
- if (!acquiredArtifact.ok) {
3829
- compilationCache = acquiredArtifact.compilationCache;
3830
- return fail(acquiredArtifact.failure);
3831
- }
3832
- artifact = acquiredArtifact.artifact;
3833
- compilationCache = artifact.cache.compilation;
3834
- if (hostedTarget) return await finishHostedIosRun({
3835
- target: hostedTarget,
3836
- root,
3837
- slot,
3838
- d,
3839
- artifact,
3840
- configuration,
3841
- buildScheme,
3842
- release,
3843
- isExpo,
3844
- metroCheck,
3845
- metroPort,
3846
- logsDir,
3847
- json,
3848
- elapsed,
3849
- startedAt,
3850
- closeWriter: () => writer?.close(),
3851
- devicePlacement,
3852
- recordRun,
3853
- reclaimed,
3854
- devServer,
3855
- fail,
3856
- note,
3857
- selectors
3858
- });
3859
- if (!localBoot) {
3860
- progress.step("device");
3861
- udid = await startBoot();
3862
- }
3863
- if (physicalDevice) {
3864
- progress.step("device");
3865
- const acquired = await d.acquireRunLease({
3866
- root,
3867
- platform: "ios",
3868
- id: physicalDevice.udid,
3869
- deviceName: physicalDevice.name,
3870
- idLabel: "udid",
3871
- waitSeconds,
3872
- noWait,
3873
- installBoundMs: iosDeviceBounds(wireless).installMs,
3874
- appId: artifact.bundleId ?? proj?.bundleId ?? null,
3875
- holderAppId: (holder) => d.getProject(holder)?.bundleId ?? null,
3876
- now: d.now,
3877
- warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
3878
- });
3879
- if (acquired.status === "refused") return fail({
3880
- code: acquired.refusal.code,
3881
- message: acquired.refusal.message,
3882
- remedy: acquired.refusal.remedy,
3883
- lease: acquired.refusal.lease
3884
- });
3885
- leaseHandle = d.runLease({
3886
- root,
3887
- platform: "ios",
3888
- kind: acquired.status === "leased" ? acquired.kind : null,
3889
- expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
3890
- });
3891
- if (acquired.status === "leased") {
3892
- stopLeaseSignals = d.releaseLeaseOnSignal(releaseLease);
3893
- phase("lease", `${acquired.kind} lease on ${physicalDevice.udid} until ${leaseExpiryText(acquired.expiresAt, d.now())}`);
3894
- }
3895
- }
3896
- try {
3897
- return await finishIosRun({
3898
- slot,
3899
- d,
3900
- root,
3901
- json,
3902
- release,
3903
- configuration,
3904
- buildScheme,
3905
- isExpo,
3906
- metroCheck,
3907
- metroPort,
3908
- logsDir,
3909
- logFile,
3910
- device,
3911
- udid,
3912
- physical,
3913
- wireless,
3914
- lanAddress,
3915
- lanOriginUrl,
3916
- remoteDevice,
3917
- bootPromise,
3918
- bootDuration: () => bootDuration,
3919
- artifact,
3920
- fail,
3921
- phase,
3922
- note,
3923
- logWriter,
3924
- elapsed,
3925
- startedAt,
3926
- closeWriter: () => writer?.close?.(),
3927
- lease: leaseHandle,
3928
- releaseLease,
3929
- devicePlacement,
3930
- recordRun,
3931
- reclaimed,
3932
- devServer,
3933
- enterPhase: progress.step
3934
- });
3935
- } finally {
3936
- releaseLease();
3937
- }
3938
- } catch (error) {
3939
- recordRun({
3940
- failed: true,
3941
- durationMs: elapsed()
3942
- });
3943
- throw error;
3944
- } finally {
3945
- artifact?.release();
3946
- }
3947
- } finally {
3948
- await deviceWaitRun.finish();
3949
- hostedTarget?.host.connection.close();
3950
- }
3951
- }
3952
- //#endregion
3953
- export { replaceCollector as a, lastBuildRecord as c, collectorEntry as i, writeLastBuild as l, registerIos as n, cacheDescription as o, runIosOperation as r, iosFacts as s, iosCommand as t };