stim 1.17.2 → 1.19.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 (162) hide show
  1. package/README.md +50 -1
  2. package/dist/{activity-IRvJY_ru.mjs → activity-BSw0x84R.mjs} +4 -4
  3. package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +8 -8
  4. package/dist/{android-B9yaNKwa.mjs → android-BXc6BmEi.mjs} +658 -404
  5. package/dist/{android-B_YNZy61.mjs → android-BdQC6zs1.mjs} +9 -9
  6. package/dist/android-BvV1gGdN.mjs +4 -0
  7. package/dist/{android-DE4v59Mb.mjs → android-DE5g_5gv.mjs} +2 -2
  8. package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-C87geAg-.mjs} +5 -5
  9. package/dist/android-cas-compiler.mjs +2 -2
  10. package/dist/api-DIf_s86s.mjs +150 -0
  11. package/dist/api-run.d.mts +1 -0
  12. package/dist/api-run.mjs +145 -0
  13. package/dist/api.d.mts +218 -0
  14. package/dist/api.mjs +2 -0
  15. package/dist/{app-install-VPkI5WEg.mjs → app-install-CFI4ptUL.mjs} +33 -13
  16. package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
  17. package/dist/{web-Dl0KSRHf.mjs → browser-web-CDhqQxhv.mjs} +138 -176
  18. package/dist/{budget-DfcgFdQB.mjs → budget-Dz-kyXLv.mjs} +1391 -58
  19. package/dist/build-facts-B87vo-Im.d.mts +142 -0
  20. package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-DiwKSyv9.mjs} +3 -3
  21. package/dist/cache-manifest.mjs +1 -1
  22. package/dist/{chrome-BrcVWQyD.mjs → chrome-BTUOo8p2.mjs} +1 -1
  23. package/dist/cli-C9Zvgbdc.mjs +68 -0
  24. package/dist/cli.mjs +1 -1
  25. package/dist/{client-SvBdgObD.mjs → client-CuUo8Nvg.mjs} +381 -49
  26. package/dist/collector-run.d.mts +6 -0
  27. package/dist/collector-run.mjs +9 -9
  28. package/dist/{command-output-CDAIFD-8.mjs → command-output-Bt8ygHE7.mjs} +2 -2
  29. package/dist/{config-D0DbpZtM.mjs → config-DC5jEtd1.mjs} +9 -5
  30. package/dist/{created-devices-CpvzBL25.mjs → created-devices-D0s26Hx5.mjs} +2 -2
  31. package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
  32. package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
  33. package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CDY_zAmD.mjs} +2 -2
  34. package/dist/{device-C8IOoJPv.mjs → device-BHw0WlHl.mjs} +8 -8
  35. package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-BoN1jQkO.mjs} +25 -14
  36. package/dist/device-host-worker.mjs +19 -19
  37. package/dist/{gradle-dJpre3Yn.mjs → device-ios-B2ruohE-.mjs} +661 -107
  38. package/dist/{device-lease-DpoS36_u.mjs → device-lease-Cu7PtQpZ.mjs} +9 -9
  39. package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
  40. package/dist/{device-pool-3o7s5lo4.mjs → device-pool-CP4rAsHx.mjs} +3 -3
  41. package/dist/{device-remote-hFSkpfQu.mjs → device-remote-B5GiUifk.mjs} +15 -16
  42. package/dist/{doctor-CFv1fCrF.mjs → doctor-PRJH94v6.mjs} +61 -34
  43. package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
  44. package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics--v5HGOU0.mjs} +11 -11
  45. package/dist/{exec-CaErVv7t.mjs → exec-i8GDPwq9.mjs} +93 -6
  46. package/dist/{gc-DEmkg3Xh.mjs → gc-CyHMcKcn.mjs} +25 -471
  47. package/dist/{guide-CSmhMTf8.mjs → guide-dRaQJJk9.mjs} +681 -507
  48. package/dist/{hosted-android-E1Heoqnv.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
  49. package/dist/{hosted-client-Bp4knSwu.mjs → hosted-client-C5G4h_OJ.mjs} +2 -2
  50. package/dist/{hosted-ios-BnbeYOUD.mjs → hosted-ios-Bik035eL.mjs} +1 -1
  51. package/dist/{hosted-logs-OcgIBvCT.mjs → hosted-logs-CI9Az9a4.mjs} +9 -9
  52. package/dist/{hosted-macos-DcxKjtfH.mjs → hosted-macos-DABm3ScO.mjs} +5 -5
  53. package/dist/{hosted-native-1Ebbig4w.mjs → hosted-native-DbV2aUVw.mjs} +13 -9
  54. package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-DYiMBn34.mjs} +7 -8
  55. package/dist/{ios-DSfnPfse.mjs → ios-BWbHFZVz.mjs} +19 -7
  56. package/dist/{ios-Cr1h5gnO.mjs → ios-DCrkgXEM.mjs} +1 -1
  57. package/dist/ios-DYUr9LGc.mjs +8 -0
  58. package/dist/ios-ITYno9rO.mjs +2241 -0
  59. package/dist/{ios-device-pAN4t54-.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
  60. package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-CrW8aMb5.mjs} +1 -1
  61. package/dist/{ios-state-8L3lgum6.mjs → ios-state-By0XuLTQ.mjs} +1 -1
  62. package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-DCveSE6d.mjs} +5 -5
  63. package/dist/{logs-BAaS7gS2.mjs → logs-Bao1HOMy.mjs} +13 -13
  64. package/dist/{macos-BIRQS90f.mjs → macos-ciyBdTY5.mjs} +194 -132
  65. package/dist/macos-nIOATJmR.mjs +2 -0
  66. package/dist/macos-run.mjs +1 -1
  67. package/dist/maintenance-run.mjs +349 -47
  68. package/dist/manifest-CX0d1IkZ.mjs +148 -0
  69. package/dist/{metro-g8NqiJp-.mjs → metro-DI2AjPF_.mjs} +196 -43
  70. package/dist/{metro-gateway-C-ijGI2F.mjs → metro-gateway-BxpJLIwh.mjs} +4 -4
  71. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
  72. package/dist/{build-plan-BDsSi09V.mjs → miss-reason-CjQCOKqq.mjs} +207 -249
  73. package/dist/{named-ports-C8m8EQs2.mjs → named-ports-B4iWH4E-.mjs} +70 -8
  74. package/dist/{native-runtime-BEK171xQ.mjs → native-runtime-BGxsbfny.mjs} +6 -6
  75. package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
  76. package/dist/offload-worker.d.mts +3 -45
  77. package/dist/offload-worker.mjs +11 -11
  78. package/dist/{ownership-DyRQru0q.mjs → ownership-EB0ySdGK.mjs} +3 -3
  79. package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
  80. package/dist/placement-log-Dxe34rJm.mjs +88 -0
  81. package/dist/plan-placement-BVY59qa8.mjs +290 -0
  82. package/dist/{ports-4nYqxPWC.mjs → ports-ByPc0_97.mjs} +3 -3
  83. package/dist/{prebuild-D7RUMkwN.mjs → prebuild-BKJ3wJI1.mjs} +5 -5
  84. package/dist/preview-BbSottmO.mjs +986 -0
  85. package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
  86. package/dist/project-B5eQ1USs.mjs +612 -0
  87. package/dist/{pull-request-pxnDCQzn.mjs → pull-request-IHNdMnG8.mjs} +1 -1
  88. package/dist/pull-requests.mjs +1 -1
  89. package/dist/react-native-ios-BMh2i25I.mjs +2 -0
  90. package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
  91. package/dist/{recordings-BNgsD1Tr.mjs → recordings-Bv8McZN4.mjs} +1 -1
  92. package/dist/{reload-DcRxj3qG.mjs → reload-rAPdqeRR.mjs} +14 -15
  93. package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-N3hAEaAL.mjs} +5 -5
  94. package/dist/{run-CkE9y0nc.mjs → run-lAkle1-c.mjs} +5 -5
  95. package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-CkIjnegq.mjs} +5 -6
  96. package/dist/server-expo-DGsdUGX2.mjs +2 -0
  97. package/dist/{server-expo-CHTyXD0G.mjs → server-expo-n3tUi3Em.mjs} +5 -6
  98. package/dist/{settings-SR9OmW9h.mjs → settings-2am7TSxp.mjs} +5 -5
  99. package/dist/{settings-Cfd-0r2d.mjs → settings-CxgngUZk.mjs} +116 -103
  100. package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
  101. package/dist/settings.schema.json +198 -9
  102. package/dist/{simslim-BWNrc3M3.mjs → simslim-D9LkJxIO.mjs} +7 -7
  103. package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
  104. package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
  105. package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
  106. package/dist/{stage-CzaiwFsI.mjs → stage-BgaZJVZY.mjs} +14 -3
  107. package/dist/{start-AlfCPRVU.mjs → start-D8NbxNBb.mjs} +1 -1
  108. package/dist/{start-DUz3mYdb.mjs → start-DtxthMfI.mjs} +34 -27
  109. package/dist/{state-PeQiJuIu.mjs → state-D2KtnALG.mjs} +4 -4
  110. package/dist/{state-DyaNTgfR.mjs → state-Tp4uImDS.mjs} +5 -3
  111. package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
  112. package/dist/{stats-Bq4fJOdB.mjs → stats-8EJ5ivFT.mjs} +6 -6
  113. package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
  114. package/dist/{status-BA2pehwf.mjs → status-BOyp0CUl.mjs} +31 -34
  115. package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
  116. package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
  117. package/dist/stop-D0UIpZuh.mjs +72 -0
  118. package/dist/{stop-zF1y04f2.mjs → stop-DRipAg-e.mjs} +2 -2
  119. package/dist/{stop-DQIasgKp.mjs → stop-zDlknFoz.mjs} +79 -33
  120. package/dist/supervisor-run.d.mts +24 -22
  121. package/dist/supervisor-run.mjs +21 -22
  122. package/dist/{support-CsJNVfOc.mjs → support-CNqYkV0y.mjs} +7 -7
  123. package/dist/{support-WvD__tSV.mjs → support-DsNaFg6D.mjs} +5 -5
  124. package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
  125. package/dist/{toolchain-DNyR3NPq.mjs → toolchain-C3xSoyUl.mjs} +475 -269
  126. package/dist/{trigger-Bemu8ytw.mjs → trigger-CAhmG-4s.mjs} +6 -6
  127. package/dist/trigger-CoalsMTV.mjs +2 -0
  128. package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-pFLB2e6P.mjs} +135 -38
  129. package/dist/{status-CzS4bPO9.mjs → watchman-BL1IdOtq.mjs} +75 -7
  130. package/dist/web-DFpFeZu5.mjs +2 -0
  131. package/dist/web-DbbrPUn0.mjs +127 -0
  132. package/dist/web-run.mjs +10 -10
  133. package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-CR9X86xJ.mjs} +3 -3
  134. package/dist/{ownership-BMLqyBgJ.mjs → workspaces-Djb2gfi9.mjs} +661 -23
  135. package/dist/{worktree-Be_7NLKy.mjs → worktree-BL4M1rhB.mjs} +252 -50
  136. package/dist/{worktree-j3IcFAI5.mjs → worktree-DV-A0LNL.mjs} +1 -1
  137. package/package.json +10 -5
  138. package/shim/bundle-response.cjs +2 -1
  139. package/shim/bundle-response.d.cts +1 -1
  140. package/shim/expo-metro-config.cjs +1 -1
  141. package/dist/build-progress-D0WkjCPd.mjs +0 -1036
  142. package/dist/build-slots-41v_d2x9.mjs +0 -142
  143. package/dist/cli-CYXb29oE.mjs +0 -51
  144. package/dist/deps-iKw0eD4P.mjs +0 -438
  145. package/dist/device-ios-C2Um-XUR.mjs +0 -412
  146. package/dist/errors-B6W7FkxQ.mjs +0 -15
  147. package/dist/guide-status-Du-ZQ3FD.mjs +0 -125
  148. package/dist/idle-BXG__1BA.mjs +0 -176
  149. package/dist/in-use-CvGOUn-x.mjs +0 -130
  150. package/dist/ios-BJ4XUSe1.mjs +0 -3867
  151. package/dist/native-run-D1Q56rAD.mjs +0 -158
  152. package/dist/preview-B-g9d7kV.mjs +0 -318
  153. package/dist/process-identity-BmRm1qV0.mjs +0 -12
  154. package/dist/project-D47Rh0ly.mjs +0 -276
  155. package/dist/server-expo-CeakhI8P.mjs +0 -2
  156. package/dist/state-BKa8wNN5.mjs +0 -148
  157. package/dist/stop-BIiM49EY.mjs +0 -48
  158. package/dist/stop-cause-rhmGfwvt.mjs +0 -97
  159. package/dist/trigger-CXts6qt1.mjs +0 -2
  160. package/dist/watchman-APYjXSQa.mjs +0 -48
  161. package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
  162. package/dist/workspaces-Bcib97-6.mjs +0 -306
@@ -0,0 +1,2083 @@
1
+ import { t as getExecutor } from "./exec-i8GDPwq9.mjs";
2
+ import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
3
+ import { _ as stepTimer, c as launchErrorReport, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-Bt8ygHE7.mjs";
4
+ import { E as upsertProject, L as deviceSlotFileKey, R as deviceSlotKey, p as getProject, u as getConcurrencyLimits, v as recordIosSchemeApprovals } from "./config-DC5jEtd1.mjs";
5
+ import { c as writeWorkspaceState, g as workspaceAgentDeviceDir, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-CR9X86xJ.mjs";
6
+ import { M as writeWorkspaceLaunch, b as MODE_EXPO, d as projectRegistry, p as detectIsExpo, r as findProjectRoot, y as MODE_BARE } from "./project-B5eQ1USs.mjs";
7
+ import { Ct as removeTemporaryEntry, F as resolveCacheProviderConfig, H as settingsLayers, I as resolveSettings, St as makeTemporaryDirectory, mt as repoRoot, nt as gitCommonDir, q as detectProviders, z as settingOriginScope } from "./settings-CxgngUZk.mjs";
8
+ import { d as signalProcessTree, s as pidExists, u as resolveProjectMetro } from "./metro-DI2AjPF_.mjs";
9
+ import { F as recordFinishedBuild, U as recordRunStats, V as recordCapacityRefusal, t as budgetGate, z as readRunEstimates } from "./budget-Dz-kyXLv.mjs";
10
+ import { M as acquireBuildSlot, P as releaseBuildSlot } from "./workspaces-Djb2gfi9.mjs";
11
+ import { 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, m as acquireBuildLock, x as changedDuringBuildLine } from "./device-lease-Cu7PtQpZ.mjs";
12
+ import { d as listAllIosSims, m as listIosRuntimes, r as clearIosAppData } from "./ios-BWbHFZVz.mjs";
13
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-C65jk_q2.mjs";
14
+ import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
15
+ import { r as checkDeviceCapacity } from "./device-capacity-BoN1jQkO.mjs";
16
+ import { A as bundlerPin, C as readBundleExecutable, T as resolveScheme, c as podsAreStale, f as runPodInstall, l as readPodState, p as COMPILATION_CACHE_NOT_RUN, r as iosToolchain, v as compilationCacheActivityLine, w as readBundleId } from "./toolchain-C3xSoyUl.mjs";
17
+ import { n as readHostedIos, o as writeHostedIos } from "./ios-state-By0XuLTQ.mjs";
18
+ import { c as swapJsBundle, d as ensureOwnedDevice, g as automaticDevicePlacement, h as checkEasFallback, m as warmMetro, n as fingerprintErrorMissReason, t as explainBuildMiss, u as ensureBooted } from "./miss-reason-CjQCOKqq.mjs";
19
+ import { S as restartedAppNote, _ as installIosApp, a as IOS_DEV_MENU_OFF_DEFAULTS_PLIST, d as devClientUrl, l as clearOtherUserApps, v as iosAppProcess, x as launchIosApp } from "./app-install-CFI4ptUL.mjs";
20
+ import { n as nativeRunCommand, r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-HV_KtPIw.mjs";
21
+ 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-DCveSE6d.mjs";
22
+ import { a as easAuthNote, c as loadProjectProvider, f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, n as UPLOAD_TIMEOUT_MS, p as resolveRemote, r as cacheLevel, s as isEasAuthFailureText } from "./remote-cache-N3hAEaAL.mjs";
23
+ import { n as devClientTakesDevMenuParams, t as devClientScheme } from "./dev-client-CDY_zAmD.mjs";
24
+ import { l as discoverXcodeProject, n as clearIosAdoptionPending, s as buildIos } from "./device-ios-B2ruohE-.mjs";
25
+ import { A as gateMetroOrigin, a as ensureMetroReachable, k as readRemoteSimulatorArch, l as remoteIosDeps, o as ensureRemoteBootOwned, r as bundleEntryPoint, u as resolveRemoteContext } from "./device-remote-B5GiUifk.mjs";
26
+ import { i as easDeviceBuildRemedy, s as resolveEasDevelopmentBuild } from "./simslim-D9LkJxIO.mjs";
27
+ import { c as startDevServer, h as detectBundleId } from "./start-DtxthMfI.mjs";
28
+ import { a as acquireRunLease, c as lostRefusal, d as runLease, s as lostLine, t as DEBUG_VERIFY_STEP_MS, u as releaseLeaseOnSignal } from "./device-lease-run-D38SnZtt.mjs";
29
+ import { n as verifyCollectorOwnership } from "./ownership-EB0ySdGK.mjs";
30
+ import { a as sleep, i as launchOutcomeRecord, n as ensureWorkspaceStorageSafely } from "./native-runtime-BGxsbfny.mjs";
31
+ import { t as selectFromPool } from "./device-pool-CP4rAsHx.mjs";
32
+ import { i as staleNativeDirRefusal, n as recordPrebuild, r as runPrebuild, t as planPrebuild } from "./prebuild-BKJ3wJI1.mjs";
33
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics--v5HGOU0.mjs";
34
+ import { n as prepareHostedIos, t as placeHostedIos } from "./hosted-ios-Bik035eL.mjs";
35
+ import { c as isWirelessIosDevice, i as iosDeviceProcess, l as listIosDevices, n as installIosDeviceApp, p as verifyIosDeviceReleaseLaunch, r as iosDeviceBounds, t as awaitIosDeviceLaunch, u as localNetworkPending } from "./ios-device-CJ_ZOGky.mjs";
36
+ import { c as iosProviderRunOptions, d as podAction, i as deviceLabel, l as isReleaseConfiguration, o as deviceShortName, t as appNameFromPath } from "./support-CNqYkV0y.mjs";
37
+ import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
38
+ import { basename, join } from "node:path";
39
+ import { buildDiagnostics } from "@stim-cli/core/state";
40
+ import { networkInterfaces, tmpdir } from "node:os";
41
+ import chalk from "chalk";
42
+ import { hostSimulatorArch } from "@stim-cli/core";
43
+ import { X509Certificate } from "node:crypto";
44
+ import { loadCacheProvider } from "@stim-cli/cache";
45
+ //#region src/engine/ios-profile.ts
46
+ const TAG = /<(\/?)\s*([A-Za-z0-9_]+)(?:\s[^>]*?)?(\/?)>/g;
47
+ const ENTITIES = {
48
+ amp: "&",
49
+ lt: "<",
50
+ gt: ">",
51
+ quot: "\"",
52
+ apos: "'"
53
+ };
54
+ var PlistError = class extends Error {};
55
+ function codePoint(value) {
56
+ if (!Number.isInteger(value) || value < 0 || value > 1114111 || value >= 55296 && value <= 57343) throw new PlistError(`character reference out of range: ${value}`);
57
+ return String.fromCodePoint(value);
58
+ }
59
+ function decodeEntities(raw) {
60
+ return raw.replace(/&(#x?[0-9A-Fa-f]+|[a-z]+);/g, (match, body) => {
61
+ if (body.startsWith("#x") || body.startsWith("#X")) return codePoint(Number.parseInt(body.slice(2), 16));
62
+ if (body.startsWith("#")) return codePoint(Number.parseInt(body.slice(1), 10));
63
+ return ENTITIES[body] ?? match;
64
+ });
65
+ }
66
+ function peekTag(cursor) {
67
+ TAG.lastIndex = cursor.pos;
68
+ const match = TAG.exec(cursor.xml);
69
+ if (!match) return null;
70
+ return {
71
+ name: (match[2] ?? "").toLowerCase(),
72
+ closing: match[1] === "/",
73
+ selfClosing: match[3] === "/",
74
+ end: TAG.lastIndex
75
+ };
76
+ }
77
+ function takeTag(cursor) {
78
+ const tag = peekTag(cursor);
79
+ if (tag) cursor.pos = tag.end;
80
+ return tag;
81
+ }
82
+ function takeText(cursor, name) {
83
+ const close = cursor.xml.indexOf(`</${name}>`, cursor.pos);
84
+ if (close < 0) {
85
+ cursor.pos = cursor.xml.length;
86
+ return "";
87
+ }
88
+ const raw = cursor.xml.slice(cursor.pos, close);
89
+ cursor.pos = close + name.length + 3;
90
+ return raw;
91
+ }
92
+ function parseValue(cursor) {
93
+ const peeked = peekTag(cursor);
94
+ if (!peeked || peeked.closing) return void 0;
95
+ const tag = takeTag(cursor);
96
+ if (tag.selfClosing) {
97
+ if (tag.name === "true") return true;
98
+ if (tag.name === "false") return false;
99
+ if (tag.name === "array") return [];
100
+ if (tag.name === "dict") return {};
101
+ return "";
102
+ }
103
+ switch (tag.name) {
104
+ case "plist": return parseValue(cursor);
105
+ case "dict": {
106
+ const out = {};
107
+ for (;;) {
108
+ const next = peekTag(cursor);
109
+ if (!next) break;
110
+ if (next.closing) {
111
+ cursor.pos = next.end;
112
+ break;
113
+ }
114
+ cursor.pos = next.end;
115
+ if (next.name !== "key") continue;
116
+ const key = decodeEntities(next.selfClosing ? "" : takeText(cursor, "key"));
117
+ const value = parseValue(cursor);
118
+ if (value !== void 0) out[key] = value;
119
+ }
120
+ return out;
121
+ }
122
+ case "array": {
123
+ const out = [];
124
+ for (;;) {
125
+ const next = peekTag(cursor);
126
+ if (!next) break;
127
+ if (next.closing) {
128
+ cursor.pos = next.end;
129
+ break;
130
+ }
131
+ const value = parseValue(cursor);
132
+ if (value === void 0) break;
133
+ out.push(value);
134
+ }
135
+ return out;
136
+ }
137
+ case "string":
138
+ case "key": return decodeEntities(takeText(cursor, tag.name));
139
+ case "integer": return Number.parseInt(takeText(cursor, "integer").trim(), 10);
140
+ case "real": return Number.parseFloat(takeText(cursor, "real").trim());
141
+ case "date": return new Date(takeText(cursor, "date").trim());
142
+ case "data": return Buffer.from(takeText(cursor, "data").replace(/\s+/g, ""), "base64");
143
+ case "true":
144
+ takeText(cursor, "true");
145
+ return true;
146
+ case "false":
147
+ takeText(cursor, "false");
148
+ return false;
149
+ default:
150
+ takeText(cursor, tag.name);
151
+ return "";
152
+ }
153
+ }
154
+ function parsePlist(xml) {
155
+ if (typeof xml !== "string" || xml.trim() === "") return null;
156
+ const stripped = xml.replace(/<!--[\s\S]*?-->/g, "");
157
+ const start = stripped.indexOf("<plist");
158
+ const cursor = {
159
+ xml: stripped,
160
+ pos: start < 0 ? 0 : start
161
+ };
162
+ try {
163
+ const value = parseValue(cursor);
164
+ return value === void 0 ? null : value;
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ function isDict(value) {
170
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !Buffer.isBuffer(value) && !(value instanceof Date);
171
+ }
172
+ function stringsOf(value) {
173
+ if (!Array.isArray(value)) return null;
174
+ return value.filter((v) => typeof v === "string");
175
+ }
176
+ function parseProvisioningProfilePlist(xml) {
177
+ const root = parsePlist(xml);
178
+ if (!isDict(root)) return null;
179
+ const entitlements = isDict(root.Entitlements ?? null) ? root.Entitlements : {};
180
+ const teamIdentifier = stringsOf(root.TeamIdentifier);
181
+ const expiration = root.ExpirationDate;
182
+ const certificates = Array.isArray(root.DeveloperCertificates) ? root.DeveloperCertificates.filter((v) => Buffer.isBuffer(v)) : [];
183
+ return {
184
+ name: typeof root.Name === "string" ? root.Name : null,
185
+ uuid: typeof root.UUID === "string" ? root.UUID : null,
186
+ teamIdentifier: teamIdentifier?.[0] ?? null,
187
+ expirationDate: expiration instanceof Date && !Number.isNaN(expiration.getTime()) ? expiration : null,
188
+ provisionedDevices: stringsOf(root.ProvisionedDevices),
189
+ provisionsAllDevices: root.ProvisionsAllDevices === true,
190
+ getTaskAllow: entitlements["get-task-allow"] === true,
191
+ certificates
192
+ };
193
+ }
194
+ function provisioningProfileKind(profile) {
195
+ if (profile.provisionedDevices) return profile.getTaskAllow ? "development" : "ad hoc";
196
+ return profile.provisionsAllDevices ? "enterprise" : "App Store";
197
+ }
198
+ function certificateCommonName(der) {
199
+ if (!Buffer.isBuffer(der) || der.length === 0) return null;
200
+ let subject;
201
+ try {
202
+ subject = new X509Certificate(der).subject;
203
+ } catch {
204
+ return null;
205
+ }
206
+ for (const part of String(subject).split(/\r?\n/)) {
207
+ const trimmed = part.trim();
208
+ if (trimmed.startsWith("CN=")) return trimmed.slice(3).trim() || null;
209
+ }
210
+ return /(?:^|,)\s*CN=([^,]+)/.exec(String(subject))?.[1]?.trim() || null;
211
+ }
212
+ const IDENTITY_LINE = /^\s*\d+\)\s+([0-9A-Fa-f]{40})\s+"(.+)"\s*$/;
213
+ function parseSigningIdentities(text) {
214
+ if (typeof text !== "string") return [];
215
+ const out = [];
216
+ for (const line of text.split(/\r?\n/)) {
217
+ const match = IDENTITY_LINE.exec(line);
218
+ if (!match) continue;
219
+ out.push({
220
+ sha1: match[1].toUpperCase(),
221
+ name: match[2]
222
+ });
223
+ }
224
+ return out;
225
+ }
226
+ const SIGNING_CODES = {
227
+ noProfile: "STIM_NO_PROFILE",
228
+ profileMismatch: "STIM_PROFILE_MISMATCH",
229
+ noIdentity: "STIM_NO_SIGNING_IDENTITY",
230
+ codesignFailed: "STIM_CODESIGN_FAILED"
231
+ };
232
+ 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.";
233
+ 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: ...`.";
234
+ function signingConfiguration(input) {
235
+ return input.configuration ?? "Debug";
236
+ }
237
+ function profileGate(input) {
238
+ const now = input.now ?? Date.now();
239
+ if (!input.profilePresent) return {
240
+ ok: false,
241
+ code: SIGNING_CODES.noProfile,
242
+ reason: "The app bundle has no embedded.mobileprovision, so it was built unsigned or for the simulator.",
243
+ remedy: `Set a team and profile for the target's ${signingConfiguration(input)} configuration in Xcode > Signing & Capabilities, then ${XCODE_STEP}`
244
+ };
245
+ if (!input.profile) return {
246
+ ok: false,
247
+ code: SIGNING_CODES.noProfile,
248
+ reason: "`openssl smime` could not decode the app bundle's embedded.mobileprovision.",
249
+ remedy: `The profile inside the app is corrupt. Delete the build cache entry with \`stim gc --delete\`, then ${XCODE_STEP}`
250
+ };
251
+ const profile = input.profile;
252
+ const named = profile.name ? `"${profile.name}"` : "the embedded profile";
253
+ if (!profile.expirationDate || profile.expirationDate.getTime() <= now) return {
254
+ ok: false,
255
+ code: SIGNING_CODES.profileMismatch,
256
+ 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.`,
257
+ remedy: `Renew the profile under Signing & Capabilities, then ${XCODE_STEP}`
258
+ };
259
+ if (!profile.provisionedDevices) {
260
+ const kind = provisioningProfileKind(profile);
261
+ return {
262
+ ok: false,
263
+ code: SIGNING_CODES.profileMismatch,
264
+ reason: `${named} is an ${kind} profile, which carries no ProvisionedDevices list, so Stim cannot prove ${input.udid ?? "the target device"} is admitted.`,
265
+ remedy: `Local device runs need a development profile. Select one under Signing & Capabilities, then ${XCODE_STEP}`
266
+ };
267
+ }
268
+ const udid = input.udid;
269
+ if (!udid || !profile.provisionedDevices.some((d) => d.toLowerCase() === udid.toLowerCase())) return {
270
+ ok: false,
271
+ code: SIGNING_CODES.profileMismatch,
272
+ reason: `The ${provisioningProfileKind(profile)} profile ${named} lists ${profile.provisionedDevices.length} device(s) and ${udid ?? "the target device"} is not one of them.`,
273
+ remedy: `Register ${udid ?? "the device"} at developer.apple.com, regenerate the profile, then ${XCODE_STEP}`
274
+ };
275
+ return {
276
+ ok: true,
277
+ profile
278
+ };
279
+ }
280
+ function signingGate(input) {
281
+ const gated = profileGate(input);
282
+ if (!gated.ok) return gated;
283
+ const profile = gated.profile;
284
+ const named = profile.name ? `"${profile.name}"` : "the embedded profile";
285
+ const identities = Array.isArray(input.identities) ? input.identities : [];
286
+ if (identities.length === 0) return {
287
+ ok: false,
288
+ code: SIGNING_CODES.noIdentity,
289
+ reason: "`security find-identity -v -p codesigning` lists no codesigning identity on this machine.",
290
+ remedy: KEYCHAIN_REMEDY
291
+ };
292
+ if (input.pinnedSha1) {
293
+ const wanted = input.pinnedSha1.trim().toUpperCase();
294
+ const match = identities.find((i) => i.sha1 === wanted);
295
+ if (match) return {
296
+ ok: true,
297
+ identity: match
298
+ };
299
+ return {
300
+ ok: false,
301
+ code: SIGNING_CODES.noIdentity,
302
+ reason: `ios.signingIdentitySha1 is ${wanted}, which is not in the keychain. Available: ${identities.map((i) => `${i.sha1} "${i.name}"`).join(", ")}.`,
303
+ remedy: KEYCHAIN_REMEDY
304
+ };
305
+ }
306
+ const wantedName = input.pinnedName?.trim() || certificateCommonName(profile.certificates[0] ?? null);
307
+ if (!wantedName) return {
308
+ ok: false,
309
+ code: SIGNING_CODES.noIdentity,
310
+ reason: `Stim could not read a signing-identity name out of ${named}: its DeveloperCertificates entry has no readable subject CN.`,
311
+ remedy: "Set ios.signingIdentity in .stim.json to the identity name to re-sign with, or ios.signingIdentitySha1 to its SHA-1 hash."
312
+ };
313
+ const matches = identities.filter((i) => i.name === wantedName);
314
+ if (matches.length === 1) return {
315
+ ok: true,
316
+ identity: matches[0]
317
+ };
318
+ if (matches.length === 0) return {
319
+ ok: false,
320
+ code: SIGNING_CODES.noIdentity,
321
+ reason: `The app was signed by "${wantedName}", which is not in this machine's keychain. Available: ${identities.map((i) => `"${i.name}"`).join(", ")}.`,
322
+ remedy: `${KEYCHAIN_REMEDY} Or set ios.signingIdentity in .stim.json to one of the identities listed above.`
323
+ };
324
+ return {
325
+ ok: false,
326
+ code: SIGNING_CODES.noIdentity,
327
+ reason: `${matches.length} keychain identities share the name "${wantedName}", so Stim cannot pick one: ${matches.map((i) => i.sha1).join(", ")}.`,
328
+ remedy: "Set ios.signingIdentitySha1 in .stim.json to the SHA-1 hash of the certificate to sign with."
329
+ };
330
+ }
331
+ //#endregion
332
+ //#region src/engine/ios-signing.ts
333
+ const EMBEDDED_PROFILE = "embedded.mobileprovision";
334
+ const SIGNING_TIMEOUT_MS = 12e4;
335
+ function lastLines(text, count = 5) {
336
+ return String(text ?? "").split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.trim() !== "").slice(-count);
337
+ }
338
+ function errorText(error) {
339
+ const withOutput = error;
340
+ const stderr = String(withOutput?.stderr ?? "").trim();
341
+ const stdout = String(withOutput?.stdout ?? "").trim();
342
+ return stderr || stdout || String(withOutput?.message ?? error);
343
+ }
344
+ function readEmbeddedProfile(appPath, { exec = null } = {}) {
345
+ const executor = exec || getExecutor();
346
+ const path = join(appPath, EMBEDDED_PROFILE);
347
+ if (!existsSync(path)) return {
348
+ present: false,
349
+ profile: null
350
+ };
351
+ try {
352
+ return {
353
+ present: true,
354
+ profile: parseProvisioningProfilePlist(executor.runFile("/usr/bin/openssl", [
355
+ "smime",
356
+ "-verify",
357
+ "-noverify",
358
+ "-inform",
359
+ "DER",
360
+ "-in",
361
+ path
362
+ ], { timeoutMs: SIGNING_TIMEOUT_MS }))
363
+ };
364
+ } catch {
365
+ return {
366
+ present: true,
367
+ profile: null
368
+ };
369
+ }
370
+ }
371
+ function findSigningIdentities({ exec = null } = {}) {
372
+ const executor = exec || getExecutor();
373
+ try {
374
+ return parseSigningIdentities(executor.runFile("security", [
375
+ "find-identity",
376
+ "-v",
377
+ "-p",
378
+ "codesigning"
379
+ ], { timeoutMs: SIGNING_TIMEOUT_MS }));
380
+ } catch {
381
+ return [];
382
+ }
383
+ }
384
+ function gateProfileForDevice(options) {
385
+ const read = readEmbeddedProfile(options.appPath, { exec: options.exec ?? null });
386
+ return profileGate({
387
+ profilePresent: read.present,
388
+ profile: read.profile,
389
+ identities: [],
390
+ udid: options.udid,
391
+ configuration: options.configuration ?? null,
392
+ ...options.now === void 0 ? {} : { now: options.now }
393
+ });
394
+ }
395
+ function gateAppForDevice(options) {
396
+ const read = readEmbeddedProfile(options.appPath, { exec: options.exec ?? null });
397
+ return signingGate({
398
+ profilePresent: read.present,
399
+ profile: read.profile,
400
+ identities: read.present ? findSigningIdentities({ exec: options.exec ?? null }) : [],
401
+ udid: options.udid,
402
+ configuration: options.configuration ?? null,
403
+ pinnedName: options.pinnedName ?? null,
404
+ pinnedSha1: options.pinnedSha1 ?? null,
405
+ ...options.now === void 0 ? {} : { now: options.now }
406
+ });
407
+ }
408
+ function extractEntitlements(appPath, executor) {
409
+ try {
410
+ return String(executor.runFile("codesign", [
411
+ "-d",
412
+ "--entitlements",
413
+ "-",
414
+ "--xml",
415
+ appPath
416
+ ], { timeoutMs: SIGNING_TIMEOUT_MS })).trim();
417
+ } catch {
418
+ return "";
419
+ }
420
+ }
421
+ function resealBundle({ appPath, identity, exec = null }) {
422
+ const executor = exec || getExecutor();
423
+ const signAs = identity.sha1 || identity.name;
424
+ let mode = "preserve-metadata";
425
+ let preserveError = null;
426
+ try {
427
+ executor.runFile("codesign", [
428
+ "--force",
429
+ "--sign",
430
+ signAs,
431
+ "--preserve-metadata=identifier,entitlements,flags,runtime",
432
+ appPath
433
+ ], { timeoutMs: SIGNING_TIMEOUT_MS });
434
+ } catch (error) {
435
+ preserveError = error;
436
+ }
437
+ let tempDir = null;
438
+ if (preserveError) {
439
+ const entitlements = extractEntitlements(appPath, executor);
440
+ const args = [
441
+ "--force",
442
+ "--sign",
443
+ signAs
444
+ ];
445
+ if (entitlements !== "") {
446
+ tempDir = mkdtempSync(join(tmpdir(), "stim-entitlements-"));
447
+ const plist = join(tempDir, "entitlements.plist");
448
+ writeFileSync(plist, `${entitlements}\n`, "utf-8");
449
+ args.push("--entitlements", plist);
450
+ mode = "entitlements";
451
+ } else mode = "no-entitlements";
452
+ args.push(appPath);
453
+ try {
454
+ executor.runFile("codesign", args, { timeoutMs: SIGNING_TIMEOUT_MS });
455
+ } catch (error) {
456
+ return {
457
+ ok: false,
458
+ code: SIGNING_CODES.codesignFailed,
459
+ reason: `codesign could not re-seal ${appPath} with "${identity.name}": ${errorText(error)}`,
460
+ remedy: `Unlock the login keychain (\`security unlock-keychain\`) and confirm exactly one identity matches "${identity.name}".`,
461
+ lastLines: [...lastLines(errorText(preserveError)), ...lastLines(errorText(error))]
462
+ };
463
+ } finally {
464
+ if (tempDir) rmSync(tempDir, {
465
+ recursive: true,
466
+ force: true
467
+ });
468
+ }
469
+ }
470
+ try {
471
+ executor.runFile("codesign", [
472
+ "--verify",
473
+ "--strict",
474
+ appPath
475
+ ], { timeoutMs: SIGNING_TIMEOUT_MS });
476
+ } catch (error) {
477
+ return {
478
+ ok: false,
479
+ code: SIGNING_CODES.codesignFailed,
480
+ reason: `codesign --verify --strict rejected ${appPath} after re-sealing it with "${identity.name}": ${errorText(error)}`,
481
+ remedy: "The bundle signed but did not seal. Delete the cached artifact with `stim gc --delete` and build fresh, then retry.",
482
+ lastLines: lastLines(errorText(error))
483
+ };
484
+ }
485
+ return {
486
+ ok: true,
487
+ identity,
488
+ mode
489
+ };
490
+ }
491
+ function sealAppForDevice(options) {
492
+ const gate = gateAppForDevice(options);
493
+ if (!gate.ok) return {
494
+ ...gate,
495
+ lastLines: []
496
+ };
497
+ return resealBundle({
498
+ appPath: options.appPath,
499
+ identity: gate.identity,
500
+ exec: options.exec ?? null
501
+ });
502
+ }
503
+ //#endregion
504
+ //#region src/engine/ios-lan.ts
505
+ const IP_TXT = "ip.txt";
506
+ function chooseLanAddress({ pinned = null, candidates }) {
507
+ const list = Array.isArray(candidates) ? candidates : [];
508
+ if (pinned) return {
509
+ address: pinned,
510
+ interfaceName: list.find((candidate) => candidate.address === pinned)?.interfaceName ?? null,
511
+ pinned: true,
512
+ candidates: list.length
513
+ };
514
+ const first = list[0];
515
+ if (!first) return null;
516
+ return {
517
+ address: first.address,
518
+ interfaceName: first.interfaceName,
519
+ pinned: false,
520
+ candidates: list.length
521
+ };
522
+ }
523
+ function lanOriginUrlFor(address, port) {
524
+ return `http://${address}:${port}`;
525
+ }
526
+ function ipTxtContents(address, port) {
527
+ return `${String(address).trim()}:${String(port).trim()}\n`;
528
+ }
529
+ function writeIpTxt(appPath, address, port, { write = writeFileSync } = {}) {
530
+ const path = join(appPath, IP_TXT);
531
+ write(path, ipTxtContents(address, port), "utf-8");
532
+ return path;
533
+ }
534
+ async function ensureLanReachable({ origin, metroPort, root, isExpo, logsDir, gateOrigin = gateMetroOrigin, readRecords = null }) {
535
+ const result = await gateOrigin({
536
+ origin,
537
+ metroPort,
538
+ platform: "ios",
539
+ entryPoint: bundleEntryPoint(root, isExpo),
540
+ readRecords: readRecords ?? (() => readMetroRecords(logsDir)),
541
+ isProof: isBundleProof
542
+ });
543
+ if (result.failed) return {
544
+ failed: result.reason ?? `${origin} is not this workspace's Metro.`,
545
+ 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.`
546
+ };
547
+ return { ok: true };
548
+ }
549
+ function copyAppAside(appPath, { exec = null, mkdtemp = () => makeTemporaryDirectory(appPath, "stim-ios-device-") } = {}) {
550
+ const e = exec || getExecutor();
551
+ const tmpDir = mkdtemp();
552
+ const copy = join(tmpDir, basename(appPath));
553
+ try {
554
+ e.runFile("cp", [
555
+ "-c",
556
+ "-R",
557
+ appPath,
558
+ copy
559
+ ]);
560
+ } catch {
561
+ try {
562
+ removeTemporaryEntry(copy);
563
+ e.runFile("cp", [
564
+ "-R",
565
+ appPath,
566
+ copy
567
+ ]);
568
+ } catch (error) {
569
+ removeTemporaryEntry(tmpDir);
570
+ throw error;
571
+ }
572
+ }
573
+ return {
574
+ tmpDir,
575
+ appPath: copy
576
+ };
577
+ }
578
+ //#endregion
579
+ //#region src/engine/lan-address.ts
580
+ const EN_INTERFACE = /^en(\d+)$/;
581
+ const EXCLUDED_INTERFACE = /^(?:utun|bridge|awdl)/;
582
+ const LINK_LOCAL = /^169\.254\./;
583
+ function isIpv4(entry) {
584
+ return entry.family === "IPv4" || entry.family === 4;
585
+ }
586
+ function lanCandidates(interfaces) {
587
+ const ranked = [];
588
+ const seen = /* @__PURE__ */ new Set();
589
+ for (const [interfaceName, entries] of Object.entries(interfaces ?? {})) {
590
+ if (EXCLUDED_INTERFACE.test(interfaceName)) continue;
591
+ const en = EN_INTERFACE.exec(interfaceName);
592
+ for (const entry of entries ?? []) {
593
+ if (!entry || typeof entry !== "object" || !isIpv4(entry) || entry.internal === true) continue;
594
+ const address = typeof entry.address === "string" ? entry.address.trim() : "";
595
+ if (address === "" || LINK_LOCAL.test(address)) continue;
596
+ if (seen.has(address)) continue;
597
+ seen.add(address);
598
+ ranked.push({
599
+ interfaceName,
600
+ address,
601
+ rank: en ? 0 : 1,
602
+ index: en ? Number(en[1]) : 0
603
+ });
604
+ }
605
+ }
606
+ ranked.sort((a, b) => a.rank - b.rank || a.index - b.index || a.interfaceName.localeCompare(b.interfaceName) || a.address.localeCompare(b.address));
607
+ return ranked.map(({ interfaceName, address }) => ({
608
+ interfaceName,
609
+ address
610
+ }));
611
+ }
612
+ function hostLanCandidates(interfaces = networkInterfaces) {
613
+ return lanCandidates(interfaces());
614
+ }
615
+ //#endregion
616
+ //#region src/commands/ios/collector.ts
617
+ function collectorLogFile(root, slot) {
618
+ return join(workspaceLogsDir$1(root), `collector-${deviceSlotFileKey("ios", slot)}.log`);
619
+ }
620
+ function collectorEntry() {
621
+ return spawnEntry("collector-run");
622
+ }
623
+ const COLLECTOR_EXIT_WAIT_MS = 2e3;
624
+ const COLLECTOR_POLL_MS = 25;
625
+ 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) => {} }) {
626
+ const previous = (readState(root)?.collectors)?.[deviceSlotKey("ios", slot)] || null;
627
+ const previousPid = Number(previous?.pid) || null;
628
+ let killed = null;
629
+ if (previousPid) {
630
+ const ownership = verify({
631
+ pid: previousPid,
632
+ platform: deviceSlotKey("ios", slot),
633
+ root,
634
+ isAlive: alive,
635
+ expected: previous
636
+ });
637
+ if (ownership.status === "ours") {
638
+ try {
639
+ kill(previousPid, "SIGTERM");
640
+ killed = previousPid;
641
+ } catch (err) {
642
+ if (err?.code !== "ESRCH") note(chalk.yellow(`Could not stop the previous ios log collector (pid ${previousPid}): ${err?.message || err}`));
643
+ }
644
+ const deadline = Date.now() + waitMs;
645
+ while (Date.now() < deadline && alive(previousPid)) await sleep(COLLECTOR_POLL_MS);
646
+ } 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`));
647
+ }
648
+ return { killed };
649
+ }
650
+ 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) => {} }) {
651
+ const { killed } = await stopPreviousCollector({
652
+ root,
653
+ slot,
654
+ kill,
655
+ alive,
656
+ readState,
657
+ verify,
658
+ waitMs,
659
+ note
660
+ });
661
+ const args = [
662
+ collectorEntry(),
663
+ "--platform",
664
+ "ios",
665
+ "--root",
666
+ root,
667
+ "--udid",
668
+ udid,
669
+ "--bundle",
670
+ bundleId
671
+ ];
672
+ if (slot !== "default") args.push("--slot", slot);
673
+ if (appName) args.push("--app-name", appName);
674
+ if (appExecutable) args.push("--app-executable", appExecutable);
675
+ if (physical) args.push("--physical");
676
+ if (payloadUrl) args.push("--payload-url", payloadUrl);
677
+ let stdio = "ignore";
678
+ try {
679
+ mkdirSync(workspaceLogsDir$1(root), { recursive: true });
680
+ const fd = openSync(collectorLogFile(root, slot), "a");
681
+ stdio = [
682
+ "ignore",
683
+ fd,
684
+ fd
685
+ ];
686
+ } catch {}
687
+ let child;
688
+ try {
689
+ child = spawn(process.execPath, args, {
690
+ cwd: root,
691
+ detached: true,
692
+ stdio,
693
+ env: process.env
694
+ });
695
+ child?.unref?.();
696
+ } catch (err) {
697
+ note(chalk.yellow(`Could not start the ios log collector: ${err?.message || err}`));
698
+ return {
699
+ killed,
700
+ pid: null
701
+ };
702
+ }
703
+ return {
704
+ killed,
705
+ pid: child?.pid ?? null
706
+ };
707
+ }
708
+ //#endregion
709
+ //#region src/commands/ios/dependencies.ts
710
+ const DEFAULT_DEPS = {
711
+ projectRegistry,
712
+ automaticDevicePlacement,
713
+ checkEasFallback,
714
+ prepareHostedIos,
715
+ placeHostedIos,
716
+ readHostedIos,
717
+ writeHostedIos,
718
+ resolveEasDevelopmentBuild,
719
+ findProjectRoot,
720
+ resolveSettings,
721
+ settingsLayers,
722
+ settingOriginScope,
723
+ gitCommonDir,
724
+ repoRoot,
725
+ detectBundleId,
726
+ detectIsExpo,
727
+ devClientScheme,
728
+ devClientTakesDevMenuParams,
729
+ getProject,
730
+ upsertProject,
731
+ recordIosSchemeApprovals,
732
+ checkDeviceCapacity,
733
+ budgetGate,
734
+ ensureOwnedDevice,
735
+ listIosRuntimes,
736
+ listAllIosSims,
737
+ ensureBooted,
738
+ resolveRemoteContext,
739
+ remoteIosDeps,
740
+ readRemoteSimulatorArch,
741
+ hostSimulatorArch,
742
+ ensureMetroReachable,
743
+ ensureRemoteBootOwned,
744
+ detectProviders,
745
+ resolveProjectMetro,
746
+ startDevServer,
747
+ warmMetro,
748
+ readWorkspaceState: readWorkspaceState$1,
749
+ pidExists,
750
+ getConcurrencyLimits,
751
+ fingerprintProject,
752
+ untrackedNativeFiles,
753
+ resolveBuild,
754
+ storeBuild,
755
+ resolveCacheProviderConfig,
756
+ loadCacheProvider,
757
+ acquireBuildLock,
758
+ releaseBuildLock,
759
+ waitForBuild,
760
+ acquireBuildSlot,
761
+ releaseBuildSlot,
762
+ loadProjectProvider,
763
+ checkEasAuth,
764
+ resolveEasCliBin,
765
+ resolveRemote,
766
+ uploadRemote,
767
+ planPrebuild,
768
+ runPrebuild,
769
+ readPodState,
770
+ podsAreStale,
771
+ runPodInstall,
772
+ buildIos,
773
+ discoverXcodeProject,
774
+ resolveScheme,
775
+ listIosDevices,
776
+ hostLanCandidates,
777
+ ensureLanReachable,
778
+ gateProfileForDevice,
779
+ sealAppForDevice,
780
+ installIosDeviceApp,
781
+ awaitIosDeviceLaunch,
782
+ acquireRunLease,
783
+ runLease,
784
+ selectFromPool,
785
+ releaseLeaseOnSignal,
786
+ iosDeviceProcess,
787
+ verifyIosDeviceReleaseLaunch,
788
+ readBundleId,
789
+ readBundleExecutable,
790
+ swapJsBundle,
791
+ installIosApp,
792
+ clearOtherUserApps,
793
+ clearIosAppData,
794
+ clearIosAdoptionPending,
795
+ launchIosApp,
796
+ verifyLaunch,
797
+ verifyReleaseLaunch,
798
+ ensureWorkspaceStorage: ensureWorkspaceStorageSafely,
799
+ replaceCollector,
800
+ stopPreviousCollector,
801
+ writeWorkspaceLaunch,
802
+ writeWorkspaceState,
803
+ createWriter: createNdjsonWriter,
804
+ recordStats: recordRunStats,
805
+ recordCapacityRefusal,
806
+ readEstimates: readRunEstimates,
807
+ now: () => Date.now()
808
+ };
809
+ //#endregion
810
+ //#region src/commands/ios/result.ts
811
+ 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 }) {
812
+ const record = {
813
+ platform: "ios",
814
+ fingerprint,
815
+ cacheKey,
816
+ cacheHit: cacheLevel(cacheHit),
817
+ cacheSkipped: Boolean(cacheSkipped),
818
+ durationMs,
819
+ appPath,
820
+ bundleId,
821
+ startedAt,
822
+ status,
823
+ configuration
824
+ };
825
+ if (errorCode) record.errorCode = errorCode;
826
+ if (devicePlacement) record.devicePlacement = devicePlacement;
827
+ record.buildMachine = buildMachine;
828
+ if (builtOn) record.builtOn = builtOn;
829
+ if (offloadedTo) record.offloadedTo = offloadedTo;
830
+ if (offloadFallback) record.offloadFallback = offloadFallback;
831
+ if (missReason && !cacheLevel(cacheHit)) record.missReason = missReason;
832
+ const recorded = status === "failed" ? buildDiagnostics(diagnostics) : [];
833
+ if (recorded.length) record.diagnostics = recorded;
834
+ return record;
835
+ }
836
+ 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 }) {
837
+ return {
838
+ platform: "ios",
839
+ ...slot && slot !== "default" ? { slot } : {},
840
+ udid,
841
+ deviceName: deviceName ?? null,
842
+ deviceType: deviceType ?? null,
843
+ runtime: runtime ?? null,
844
+ fingerprint,
845
+ configuration: configuration ?? null,
846
+ ...scheme ? { scheme } : {},
847
+ cacheKey,
848
+ cacheHit: cacheLevel(cacheHit),
849
+ cacheSkipped: Boolean(cacheSkipped),
850
+ buildMachine,
851
+ ...builtOn ? { builtOn } : {},
852
+ ...offloadedTo ? { offloadedTo } : {},
853
+ ...offloadFallback ? { offloadFallback } : {},
854
+ compilationCache,
855
+ waitedForBuild: waitedForBuild ? {
856
+ pid: waitedForBuild.pid ?? null,
857
+ ms: waitedForBuild.ms ?? 0
858
+ } : null,
859
+ appPath,
860
+ bundleId,
861
+ installSkipped: Boolean(installSkipped),
862
+ launched: launched === "unverified" || launched === "bundling" ? launched : Boolean(launched),
863
+ metroPort,
864
+ logs: { dir: logsDir },
865
+ ...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
866
+ durationMs,
867
+ ...webPreviewUrl ? { webPreviewUrl } : {},
868
+ ...lease === void 0 ? {} : { lease },
869
+ ...devServer ? { devServer } : {}
870
+ };
871
+ }
872
+ function writeLastBuild(root, record, { record: recordBuild = recordFinishedBuild } = {}) {
873
+ try {
874
+ recordBuild(root, record);
875
+ } catch {}
876
+ return record;
877
+ }
878
+ async function finishIosUpload(uploadPending, remote, phase, note) {
879
+ if (!uploadPending) return false;
880
+ const upload = await uploadPending;
881
+ if (upload?.uploaded) phase("cache", `uploaded (${remote?.name})`);
882
+ else if (upload?.timedOut) {
883
+ note(chalk.yellow(phaseLine("cache", `${remote?.name} upload still running after ${formatDuration(UPLOAD_TIMEOUT_MS)}; not waiting`)));
884
+ return true;
885
+ } else if (upload?.failed) {
886
+ const authNote = remote?.name === "eas" && isEasAuthFailureText(upload.failed) ? easAuthNote({
887
+ code: "logged-out",
888
+ reason: upload.failed,
889
+ phase: "upload"
890
+ }) : null;
891
+ note(chalk.yellow(phaseLine("cache", authNote || `${remote?.name} upload failed: ${upload.failed}`)));
892
+ }
893
+ return false;
894
+ }
895
+ function reportIosResult({ devicePlacement, host, slot, root, json, release, runtimeKind = release ? "embedded-js" : "metro", 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 }) {
896
+ const durationMs = elapsed();
897
+ recordRun({
898
+ failed: false,
899
+ cacheHit,
900
+ waited: waitedForBuild,
901
+ durationMs,
902
+ offloadedTo
903
+ });
904
+ writeLastBuild(root, lastBuildRecord({
905
+ devicePlacement,
906
+ fingerprint: storeHash,
907
+ cacheKey: storeKey,
908
+ cacheHit,
909
+ cacheSkipped: !useBuildCache,
910
+ missReason,
911
+ durationMs,
912
+ appPath,
913
+ bundleId,
914
+ startedAt,
915
+ status: "ok",
916
+ configuration: configuration ?? "Debug",
917
+ buildMachine,
918
+ builtOn,
919
+ offloadedTo,
920
+ offloadFallback
921
+ }));
922
+ closeWriter();
923
+ const facts = iosFacts({
924
+ root,
925
+ slot,
926
+ udid,
927
+ deviceName: device?.deviceName ?? null,
928
+ deviceType: device?.deviceType,
929
+ runtime: device?.runtime,
930
+ fingerprint: storeHash,
931
+ configuration,
932
+ scheme: buildScheme,
933
+ cacheKey: storeKey,
934
+ cacheHit,
935
+ compilationCache,
936
+ cacheSkipped: !useBuildCache,
937
+ waitedForBuild,
938
+ appPath,
939
+ bundleId,
940
+ installSkipped,
941
+ metroPort,
942
+ logsDir,
943
+ durationMs,
944
+ launched: launchState,
945
+ webPreviewUrl,
946
+ lease,
947
+ devServer,
948
+ buildMachine,
949
+ builtOn,
950
+ offloadedTo,
951
+ offloadFallback
952
+ });
953
+ if (devicePlacement) facts.devicePlacement = devicePlacement;
954
+ if (host) facts.host = host;
955
+ if (json) console.log(JSON.stringify({
956
+ ...facts,
957
+ ...links ? { links } : {},
958
+ ...reclaimed.length ? { reclaimed } : {}
959
+ }));
960
+ else {
961
+ const summary = `${launchWarning ? "WARNING" : "OK"}: ${bundleId} on ${host ? `${device.deviceName} (iOS ${device.runtime?.replace(/^iOS /, "")}) on ${host.machine}` : deviceLabel(device, udid)}, ` + (runtimeKind === "process" ? `${configuration ?? "Debug"} (native process, no Metro)` : runtimeKind === "embedded-js" ? `${configuration} (embedded JS, no Metro)` : `Metro port ${metroPort}`) + ` (${cacheDescription(cacheHit, providerName, offloadedTo)}, ${formatDuration(durationMs)})`;
962
+ 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);
963
+ const deviceName = device?.deviceName ?? device?.name ?? udid;
964
+ const cacheResult = useBuildCache ? cacheDescription(cacheHit, providerName, offloadedTo) : `bypassed; ${cacheDescription(false, null, offloadedTo)}`;
965
+ const metroResult = runtimeKind === "process" ? "native process (no Metro)" : runtimeKind === "embedded-js" ? `embedded (${configuration})` : !metroCheck ? `check skipped on port ${metroPort}` : launchState === "unverified" ? `state unverified on port ${metroPort}` : `running on port ${metroPort}${devServer ? ` (started: ${devServer.reason})` : ""}`;
966
+ console.log([
967
+ outcome,
968
+ phaseLine("device", host ? `${deviceName} (iOS ${device.runtime?.replace(/^iOS /, "")}) on ${host.machine}` : `${deviceName} (${udid})`),
969
+ phaseLine("app", bundleId),
970
+ phaseLine(runtimeKind === "process" ? "runtime" : "metro", metroResult),
971
+ phaseLine("cache", cacheResult),
972
+ ...compilationCache.status === "not-run" ? [phaseLine("compilation cache", compilationCacheActivityLine(compilationCache))] : [],
973
+ phaseLine("logs", logsDir),
974
+ phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${workspaceAgentDeviceDir(root)}`)
975
+ ].join("\n"));
976
+ if (facts.webPreviewUrl) console.error(chalk.dim(`Watch this device: ${facts.webPreviewUrl}`));
977
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
978
+ }
979
+ return facts;
980
+ }
981
+ function cacheDescription(cacheHit, providerName = null, offloadedTo = null) {
982
+ if (cacheHit === "remote") return `from ${providerName || "the remote cache"}`;
983
+ if (cacheHit === "local") return "from cache";
984
+ return offloadedTo ? `built on ${offloadedTo}` : "built";
985
+ }
986
+ //#endregion
987
+ //#region src/commands/ios/launch.ts
988
+ function iosProcessRuntime(prepare, kind = "process") {
989
+ return {
990
+ kind,
991
+ prepare,
992
+ devMenuParams: () => false,
993
+ scheme: () => void 0,
994
+ verificationWaitMs: RELEASE_VERIFY_WAIT_MS,
995
+ launch: async (_prepared, { launch }) => launch({
996
+ metroPort: null,
997
+ devMenuParams: false,
998
+ payloadUrl: null
999
+ }),
1000
+ verify: async (_prepared, context) => verifyIosProcessRun({
1001
+ ...context,
1002
+ embeddedJavaScript: kind === "embedded-js"
1003
+ })
1004
+ };
1005
+ }
1006
+ function iosMetroRuntime(prepare) {
1007
+ return {
1008
+ kind: "metro",
1009
+ prepare,
1010
+ devMenuParams: (root, d) => d.devClientTakesDevMenuParams(root),
1011
+ scheme: (root, appPath, d) => d.devClientScheme(root, appPath),
1012
+ verificationWaitMs: DEBUG_VERIFY_STEP_MS,
1013
+ launch: async ({ metroPort }, { launch, scheme, devMenuParams, lanAddress }) => launch({
1014
+ metroPort,
1015
+ devClientScheme: scheme,
1016
+ devMenuParams,
1017
+ payloadUrl: scheme && metroPort !== null && lanAddress ? devClientUrl(scheme, metroPort, lanAddress) : null
1018
+ }),
1019
+ verify: async ({ metroPort }, context) => verifyIosMetroRun({
1020
+ ...context,
1021
+ metroPort
1022
+ })
1023
+ };
1024
+ }
1025
+ function missingCrashReportHint({ physical, remote, crashed }) {
1026
+ 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.";
1027
+ }
1028
+ async function verifyIosProcessRun({ root, slot, appPath, d, launched, configuration, phase, note, logsDir, launchedAt, bundleId, udid, physical, appName, remoteDevice, embeddedJavaScript }) {
1029
+ const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1030
+ root,
1031
+ slot,
1032
+ platform: "ios",
1033
+ deviceId: udid,
1034
+ appId: bundleId,
1035
+ since: launchedAt,
1036
+ appPath,
1037
+ physical
1038
+ }, logsDir);
1039
+ const processCheck = physical ? await d.verifyIosDeviceReleaseLaunch({
1040
+ udid,
1041
+ appName: appName ?? bundleId
1042
+ }) : await d.verifyReleaseLaunch({ pid: launched?.pid ?? null });
1043
+ const crashes = readNativeCrashes();
1044
+ if (processCheck?.verified && !crashes.length) {
1045
+ phase("verify", `process alive ${formatDuration(processCheck.waitedMs ?? 0)} after launch (${configuration}: no bundle fetch to observe)`);
1046
+ return { state: true };
1047
+ }
1048
+ for (const line of launchErrorPreview(crashes, root)) note(chalk.red(phaseLine("launch", line)));
1049
+ if (!crashes.length) note(phaseLine("logs", missingCrashReportHint({
1050
+ physical,
1051
+ remote: Boolean(remoteDevice),
1052
+ crashed: processCheck?.reason === "exited"
1053
+ })));
1054
+ 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"));
1055
+ note(chalk.yellow(phaseLine("", `${embeddedJavaScript ? "Release" : "Process"} readiness was not established. Run \`stim logs --errors\` for captured crash reports, or \`stim logs --source device\` for the full device output.`)));
1056
+ return { state: crashes.length || processCheck?.reason === "exited" ? LAUNCH_FATAL : LAUNCH_UNVERIFIED };
1057
+ }
1058
+ async function verifyIosMetroRun({ root, slot, appPath, d, launched, phase, note, metroCheck, logsDir, launchedAt, metroPort, isExpo, bundleId, udid, scheme, physical, appName, lanAddress, lanOrigin, remoteDevice, metroOrigin }) {
1059
+ const runCommand = nativeRunCommand("ios", slot, {
1060
+ physical,
1061
+ deviceId: udid
1062
+ });
1063
+ const readNativeCrashes = () => remoteDevice ? [] : captureNativeCrashes({
1064
+ root,
1065
+ slot,
1066
+ platform: "ios",
1067
+ deviceId: udid,
1068
+ appId: bundleId,
1069
+ since: launchedAt,
1070
+ appPath,
1071
+ physical
1072
+ }, logsDir);
1073
+ const deviceProcess = () => {
1074
+ const pid = d.iosDeviceProcess({
1075
+ udid,
1076
+ appName: appName ?? bundleId
1077
+ });
1078
+ return pid === void 0 ? null : pid !== null;
1079
+ };
1080
+ const siblings = metroCheck ? siblingPlatformSlots(root, "ios", slot) : [];
1081
+ const verification = metroCheck ? await d.verifyLaunch({
1082
+ requireBundleResponse: true,
1083
+ slot: launchSlotScope(root, slot),
1084
+ appPid: physical || remoteDevice ? null : launched?.pid,
1085
+ platformShared: siblings.length > 0,
1086
+ onReadinessPending: () => phase("readiness", "waiting for app readiness (up to 30s after bundle load)"),
1087
+ logsDir,
1088
+ since: launchedAt,
1089
+ metroPort,
1090
+ platform: "ios",
1091
+ mode: isExpo ? MODE_EXPO : MODE_BARE,
1092
+ readNativeCrashes,
1093
+ processAlive: remoteDevice ? null : physical ? deviceProcess : () => {
1094
+ if (launched?.pid) return d.pidExists(launched.pid);
1095
+ const pid = iosAppProcess(udid, bundleId);
1096
+ return pid === void 0 ? null : pid !== null;
1097
+ }
1098
+ }) : {
1099
+ verified: false,
1100
+ skipped: true
1101
+ };
1102
+ const nativeCrashes = verification.errors?.some((record) => record.event === "native_crash") ? [] : readNativeCrashes();
1103
+ if (nativeCrashes.length) verification.fatal = true;
1104
+ verification.errors = await errorDiagnostics([...verification.errors ?? [], ...nativeCrashes], {
1105
+ root,
1106
+ logsDir,
1107
+ port: metroPort,
1108
+ allowRequest: true
1109
+ });
1110
+ if (verification.readiness) phase("readiness", appReadinessMessage(verification.readiness, verification.waitedMs ?? 0));
1111
+ if (verification?.fatal) {
1112
+ const nativeFatal = verification.errors?.some((record) => record.event === "native_crash");
1113
+ const deliveryFailed = verification.record?.event === "bundle_response_failed";
1114
+ 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";
1115
+ phase("verify", chalk.red(`FATAL after ${formatDuration(verification.waitedMs ?? 0)}: ${reason}`));
1116
+ for (const line of launchErrorPreview(verification.errors ?? [], root)) note(chalk.red(phaseLine("", line)));
1117
+ if (verification.processAlive === false && !verification.errors?.some((record) => record.event === "native_crash")) note(phaseLine("logs", missingCrashReportHint({
1118
+ physical,
1119
+ remote: Boolean(remoteDevice),
1120
+ crashed: true
1121
+ })));
1122
+ 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.`)));
1123
+ else if (verification.processAlive === true && metroPort !== null) {
1124
+ 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.`;
1125
+ 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.`)));
1126
+ }
1127
+ return { state: LAUNCH_FATAL };
1128
+ }
1129
+ if (verification?.verified) {
1130
+ 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)`);
1131
+ const hasAppErrors = reportLaunchErrors(verification.errors ?? [], note, root);
1132
+ if (hasAppErrors && verification.processAlive === true && metroPort !== null) {
1133
+ const reloadRemedy = physical || remoteDevice ? `run \`agent-device metro reload --metro-port ${metroPort}\`.` : "run `stim reload ios`.";
1134
+ 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.`)));
1135
+ }
1136
+ return {
1137
+ state: true,
1138
+ 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
1139
+ };
1140
+ }
1141
+ if (verification?.skipped) {
1142
+ phase("verify", "skipped (--no-metro-check): the launch is reported as unverified");
1143
+ return { state: LAUNCH_UNVERIFIED };
1144
+ }
1145
+ if (verification?.requested) {
1146
+ 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)`);
1147
+ note(chalk.dim(phaseLine("", "Nothing to do: `stim logs --source metro` shows the build finishing, usually within a minute.")));
1148
+ return { state: LAUNCH_BUNDLING };
1149
+ }
1150
+ return reportUnverified({
1151
+ verification,
1152
+ siblings,
1153
+ slot,
1154
+ phase,
1155
+ note,
1156
+ metroPort,
1157
+ bundleId,
1158
+ udid,
1159
+ scheme,
1160
+ lanAddress,
1161
+ isExpo,
1162
+ remoteDevice,
1163
+ physical,
1164
+ lanOrigin,
1165
+ metroOrigin,
1166
+ logsDir,
1167
+ launchedAt,
1168
+ launched
1169
+ });
1170
+ }
1171
+ function reportUnverified({ verification, siblings, slot, phase, note, metroPort, bundleId, udid, scheme, lanAddress, isExpo, remoteDevice, physical, lanOrigin, metroOrigin, logsDir, launchedAt, launched }) {
1172
+ if (verification.unattributed) {
1173
+ const [headline, ...lines] = unattributedLaunchLines({
1174
+ platform: "ios",
1175
+ metroPort,
1176
+ slot,
1177
+ siblings
1178
+ });
1179
+ phase("verify", chalk.yellow(headline));
1180
+ for (const line of lines) note(chalk.yellow(phaseLine("", line)));
1181
+ return {
1182
+ state: LAUNCH_UNVERIFIED,
1183
+ unattributed: true
1184
+ };
1185
+ }
1186
+ phase("verify", chalk.yellow("UNVERIFIED: no bundle request reached this workspace's Metro"));
1187
+ for (const line of unverifiedLaunchLines({
1188
+ platform: "ios",
1189
+ metroPort: metroPort ?? 8081,
1190
+ waitedMs: verification.waitedMs,
1191
+ bundleId,
1192
+ udid,
1193
+ devClientUrl: scheme ? launched?.url ?? devClientUrl(scheme, metroPort ?? 8081, lanAddress ?? void 0) : null,
1194
+ mode: isExpo ? MODE_EXPO : MODE_BARE,
1195
+ remote: remoteDevice,
1196
+ physical,
1197
+ devClient: Boolean(scheme),
1198
+ lanOrigin,
1199
+ metroOrigin,
1200
+ localNetworkPending: physical && localNetworkPending(readCollectorRecords(logsDir), {
1201
+ since: launchedAt,
1202
+ pid: launched?.pid ?? null,
1203
+ lanOrigin
1204
+ })
1205
+ })) note(chalk.yellow(phaseLine("", line)));
1206
+ return { state: LAUNCH_UNVERIFIED };
1207
+ }
1208
+ function reportLaunchErrors(errors, note, root) {
1209
+ const report = launchErrorReport(errors, root);
1210
+ if (report.summary) note(chalk.dim(phaseLine("launch", report.summary)));
1211
+ for (const line of report.lines) note(chalk.yellow(phaseLine("launch", line)));
1212
+ return report.lines.length > 0;
1213
+ }
1214
+ function installsOverWifi(d, udid, selectedWireless, note) {
1215
+ const current = d.listIosDevices().find((entry) => entry.udid === udid);
1216
+ const wireless = current ? isWirelessIosDevice(current) : selectedWireless;
1217
+ 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`));
1218
+ return wireless;
1219
+ }
1220
+ function cleanAdoptedIosApps({ d, udid, bundleId, devClient, phase, note }) {
1221
+ const swept = d.clearOtherUserApps({
1222
+ udid,
1223
+ keep: bundleId
1224
+ });
1225
+ if (swept.removed.length) phase("install", `removed ${swept.removed.join(", ")}, left by the previous workspace`);
1226
+ if (swept.listed && swept.failed.length === 0) {
1227
+ if (swept.kept && bundleId) {
1228
+ try {
1229
+ d.clearIosAppData(udid, bundleId, { defaultsPlist: devClient ? IOS_DEV_MENU_OFF_DEFAULTS_PLIST : null });
1230
+ } catch (error) {
1231
+ return `Could not clear the data ${bundleId} kept from the previous workspace: ${String(error?.message || error)}`;
1232
+ }
1233
+ phase("install", `cleared ${bundleId} data left by the previous workspace`);
1234
+ }
1235
+ return null;
1236
+ }
1237
+ if (!swept.listed) return "Could not list apps left by the previous workspace.";
1238
+ for (const failure of swept.failed) note(chalk.yellow(phaseLine("install", `could not remove ${failure}, left by the previous workspace`)));
1239
+ return `Could not remove ${swept.failed.join(", ")}, left by the previous workspace.`;
1240
+ }
1241
+ function installMayBeProven(adopting, parkedCacheKey, storeKey) {
1242
+ return !adopting || !parkedCacheKey || parkedCacheKey === storeKey;
1243
+ }
1244
+ function resolveRunBundleId(d, projectBundleId, appPath, bundleId) {
1245
+ if (!appPath || bundleId) return bundleId;
1246
+ return d.readBundleId(appPath) || projectBundleId();
1247
+ }
1248
+ function readRunExecutable(d, appPath, note) {
1249
+ const executable = appPath ? d.readBundleExecutable(appPath) : null;
1250
+ if (appPath && !executable) note(chalk.dim(`Could not read CFBundleExecutable from ${appPath}; the device log predicate falls back to the .app basename.`));
1251
+ return executable;
1252
+ }
1253
+ function recordIosReloadTarget({ d, root, physical, remoteDevice, bundleId, udid, metroPort, release, runtimeKind, launchedAt, note }) {
1254
+ if (physical || remoteDevice) return;
1255
+ try {
1256
+ d.writeWorkspaceLaunch(root, "ios", {
1257
+ appId: bundleId,
1258
+ deviceId: udid,
1259
+ metroPort,
1260
+ release,
1261
+ ...runtimeKind === "process" ? { runtime: "process" } : {},
1262
+ launchedAt: new Date(launchedAt).toISOString()
1263
+ });
1264
+ } catch (error) {
1265
+ note(chalk.yellow(phaseLine("state", `could not record iOS launch: ${error?.message || error}`)));
1266
+ }
1267
+ }
1268
+ function installFailureRemedy(remoteDevice) {
1269
+ return remoteDevice?.failureRemedy() ?? "Check that the simulator is booted and that the app was built for the simulator SDK.";
1270
+ }
1271
+ function launchFailureRemedy(remoteDevice, udid, bundleId, logFile) {
1272
+ if (remoteDevice) return remoteDevice.failureRemedy();
1273
+ 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}.`;
1274
+ }
1275
+ async function finishIosRun({ projectBundleId, runtime, runtimePreparation, 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 }) {
1276
+ const { path: appPath, bundleId: initialBundleId, failureFields: buildFailure, cache } = artifact;
1277
+ const { hit: cacheHit, providerName, buildMachine, builtOn, offloadedTo, offloadFallback, readEnabled: useBuildCache, missReason, waitedForBuild, compilation: compilationCache } = cache;
1278
+ const storeHash = cache.identity?.fingerprint ?? null;
1279
+ const storeKey = cache.identity?.key ?? null;
1280
+ const runCommand = nativeRunCommand("ios", slot, {
1281
+ physical,
1282
+ deviceId: udid
1283
+ });
1284
+ let bundleId = initialBundleId;
1285
+ let leaseWarned = false;
1286
+ const raiseLeaseFor = (boundMs, beforeInstall) => {
1287
+ const step = lease?.raise(boundMs);
1288
+ if (!step || step.ok) return null;
1289
+ if (beforeInstall) {
1290
+ const refusal = lostRefusal(step.holder, step.expiresAt, d.now());
1291
+ return {
1292
+ code: refusal.code,
1293
+ message: refusal.message,
1294
+ remedy: refusal.remedy,
1295
+ lease: refusal.lease
1296
+ };
1297
+ }
1298
+ if (!leaseWarned) {
1299
+ leaseWarned = true;
1300
+ note(chalk.yellow(phaseLine("lease", lostLine(step.holder, step.expiresAt, d.now()))));
1301
+ }
1302
+ return null;
1303
+ };
1304
+ bundleId = resolveRunBundleId(d, projectBundleId, appPath, bundleId);
1305
+ if (appPath && !bundleId) return fail({
1306
+ code: "STIM_INSTALL_FAILED",
1307
+ message: `Could not read a bundle identifier from the cached app at ${appPath}.`,
1308
+ remedy: "Remove the cache entry (`stim gc`) and run again to rebuild it.",
1309
+ build: {
1310
+ ...buildFailure,
1311
+ appPath
1312
+ }
1313
+ });
1314
+ if (bundleId) d.upsertProject(root, { bundleId });
1315
+ enterPhase("device");
1316
+ const booted = await bootPromise;
1317
+ if (!booted?.ok) return fail({
1318
+ code: booted?.code || "STIM_NO_DEVICE",
1319
+ message: booted?.reason || "The owned simulator could not be booted.",
1320
+ remedy: booted?.remedy || `Run \`${runCommand}\` again to re-establish an owned simulator for this workspace.`
1321
+ });
1322
+ const deviceOutcome = physical ? "connected" : `${device?.adopted ? "adopted" : "booted"} ${bootDuration()}`;
1323
+ phase("device", `${deviceLabel(device, udid)} ${deviceOutcome}`);
1324
+ const scheme = runtime.scheme(root, appPath, d);
1325
+ const devMenuParams = runtime.devMenuParams(root, d);
1326
+ const appName = appNameFromPath(appPath);
1327
+ const appExecutable = readRunExecutable(d, appPath, note);
1328
+ const dropSwapDir = artifact.release;
1329
+ let installSkipped = false;
1330
+ let launched;
1331
+ let launchedAt = d.now();
1332
+ if (physical) {
1333
+ const overWifi = installsOverWifi(d, udid, wireless, note);
1334
+ const bounds = iosDeviceBounds(overWifi);
1335
+ const lostBeforeInstall = raiseLeaseFor(bounds.installMs, true);
1336
+ if (lostBeforeInstall) return fail(lostBeforeInstall);
1337
+ await d.stopPreviousCollector({
1338
+ root,
1339
+ note
1340
+ });
1341
+ enterPhase("install");
1342
+ const installTimer = stepTimer(d.now);
1343
+ const installed = d.installIosDeviceApp({
1344
+ udid,
1345
+ appPath,
1346
+ bundleId,
1347
+ wireless: overWifi
1348
+ }, { beforeStep: () => raiseLeaseFor(bounds.installMs, false) });
1349
+ if (installed?.failed) {
1350
+ dropSwapDir();
1351
+ return fail({
1352
+ code: installed.code || "STIM_INSTALL_FAILED",
1353
+ message: installed.reason ?? `devicectl could not install ${appPath} on ${udid}.`,
1354
+ remedy: installed.remedy ?? null,
1355
+ build: {
1356
+ ...buildFailure,
1357
+ appPath,
1358
+ bundleId
1359
+ }
1360
+ });
1361
+ }
1362
+ phase("install", `${basename(appPath)} -> ${deviceLabel(device, udid)} ${installTimer()}`);
1363
+ if (installed?.note) {
1364
+ note(chalk.yellow(phaseLine("install", installed.note)));
1365
+ logWriter().write({
1366
+ src: "build",
1367
+ level: "warn",
1368
+ event: "install_uninstalled_first",
1369
+ msg: installed.note
1370
+ });
1371
+ }
1372
+ dropSwapDir();
1373
+ raiseLeaseFor(COLLECTOR_EXIT_WAIT_MS + bounds.launchMs, false);
1374
+ enterPhase("launch");
1375
+ const launchTimer = stepTimer(d.now);
1376
+ launchedAt = d.now();
1377
+ logWriter().write({
1378
+ src: "build",
1379
+ level: "info",
1380
+ marker: true,
1381
+ event: "launch_attempt",
1382
+ ts: launchedAt,
1383
+ platform: "ios",
1384
+ appId: bundleId,
1385
+ deviceId: udid,
1386
+ appPath,
1387
+ physical: true,
1388
+ msg: `launching ${bundleId} on ${udid}`
1389
+ });
1390
+ launched = await runtime.launch(runtimePreparation, {
1391
+ scheme,
1392
+ devMenuParams,
1393
+ lanAddress,
1394
+ launch: async ({ payloadUrl }) => {
1395
+ const collector = await d.replaceCollector({
1396
+ root,
1397
+ slot,
1398
+ udid,
1399
+ bundleId,
1400
+ appName,
1401
+ physical: true,
1402
+ payloadUrl,
1403
+ note
1404
+ });
1405
+ if (!collector?.pid) return { error: {
1406
+ code: "STIM_LAUNCH_FAILED",
1407
+ message: `The device log collector, which is what launches ${bundleId} on a phone, could not be started.`,
1408
+ remedy: `Check ${logFile} and the workspace collector log, then run the command again.`,
1409
+ build: {
1410
+ ...buildFailure,
1411
+ appPath,
1412
+ bundleId
1413
+ }
1414
+ } };
1415
+ const started = await d.awaitIosDeviceLaunch({
1416
+ udid,
1417
+ bundleId,
1418
+ appName: appName ?? bundleId,
1419
+ collectorPid: collector.pid,
1420
+ wireless: overWifi,
1421
+ readRecords: () => readCollectorRecords(logsDir).filter((entry) => Number(entry.ts) >= launchedAt && (entry.slot ?? "default") === (slot ?? "default"))
1422
+ });
1423
+ if (started.failed || !started.pid) return { error: {
1424
+ code: started.code ?? "STIM_LAUNCH_FAILED",
1425
+ message: started.reason ?? `${bundleId} did not start on ${udid}.`,
1426
+ remedy: started.remedy ?? null,
1427
+ lines: started.lines ?? [],
1428
+ logPath: logsDir,
1429
+ build: {
1430
+ ...buildFailure,
1431
+ appPath,
1432
+ bundleId
1433
+ }
1434
+ } };
1435
+ return {
1436
+ ok: true,
1437
+ mode: payloadUrl ? "payload-url" : "launch",
1438
+ pid: started.pid,
1439
+ ...payloadUrl ? { url: payloadUrl } : {},
1440
+ ...lanOriginUrl ? { jsLocation: lanOriginUrl } : {}
1441
+ };
1442
+ }
1443
+ });
1444
+ if ("error" in launched) return fail(launched.error);
1445
+ if (launched.failed) return fail({
1446
+ code: launched.code ?? "STIM_LAUNCH_FAILED",
1447
+ message: launched.reason ?? `${bundleId} did not start on ${udid}.`,
1448
+ remedy: `Check ${logFile} and the workspace collector log, then run the command again.`,
1449
+ logPath: logsDir,
1450
+ build: {
1451
+ ...buildFailure,
1452
+ appPath,
1453
+ bundleId
1454
+ }
1455
+ });
1456
+ phase("launch", `${bundleId} pid ${launched.pid} ${launchTimer()}`);
1457
+ } else {
1458
+ const adopting = Boolean(device?.adoptionPending);
1459
+ if (adopting) {
1460
+ const cleanupFailure = cleanAdoptedIosApps({
1461
+ d,
1462
+ udid,
1463
+ bundleId,
1464
+ devClient: Boolean(scheme),
1465
+ phase,
1466
+ note
1467
+ });
1468
+ if (cleanupFailure) return fail({
1469
+ code: "STIM_INSTALL_FAILED",
1470
+ message: `${cleanupFailure} Stim kept the adoption cleanup pending and did not install or launch the app.`,
1471
+ remedy: `Run \`${runCommand}\` again after simulator tooling is responsive.`,
1472
+ build: {
1473
+ ...buildFailure,
1474
+ appPath,
1475
+ bundleId
1476
+ }
1477
+ });
1478
+ }
1479
+ enterPhase("install");
1480
+ const installTimer = stepTimer(d.now);
1481
+ const installed = d.installIosApp({
1482
+ udid,
1483
+ appPath,
1484
+ bundleId,
1485
+ devClientScheme: scheme,
1486
+ schemeApprovals: device.schemeApprovals,
1487
+ proveInstalled: installMayBeProven(adopting, device?.parkedCacheKey, storeKey)
1488
+ }, { now: d.now });
1489
+ if (installed?.failed) return fail({
1490
+ code: installed.code || "STIM_INSTALL_FAILED",
1491
+ message: installed.reason,
1492
+ remedy: installFailureRemedy(remoteDevice),
1493
+ build: {
1494
+ ...buildFailure,
1495
+ appPath,
1496
+ bundleId
1497
+ }
1498
+ });
1499
+ if (adopting) d.clearIosAdoptionPending(root, slot);
1500
+ installSkipped = Boolean(installed?.skipped);
1501
+ const artifactDuration = installed?.artifactDurationMs === void 0 ? installTimer() : `(${formatDuration(installed.artifactDurationMs)})`;
1502
+ phase("install", installSkipped ? `unchanged (${deviceShortName(device, udid)} already has this build) ${artifactDuration}` : `${basename(appPath)} -> ${deviceLabel(device, udid)} ${artifactDuration}`);
1503
+ if (!remoteDevice && installed?.devClientPreparationDurationMs !== void 0) phase("install", `dev client prepared (${formatDuration(installed.devClientPreparationDurationMs)})`);
1504
+ if (installed.schemeApprovals) d.recordIosSchemeApprovals(root, udid, installed.schemeApprovals, slot);
1505
+ dropSwapDir();
1506
+ if (!remoteDevice) await d.replaceCollector({
1507
+ root,
1508
+ slot,
1509
+ udid,
1510
+ bundleId,
1511
+ appName,
1512
+ appExecutable,
1513
+ note
1514
+ });
1515
+ enterPhase("launch");
1516
+ const launchTimer = stepTimer(d.now);
1517
+ launchedAt = d.now();
1518
+ logWriter().write({
1519
+ src: "build",
1520
+ level: "info",
1521
+ marker: true,
1522
+ event: "launch_attempt",
1523
+ ts: launchedAt,
1524
+ platform: "ios",
1525
+ appId: bundleId,
1526
+ deviceId: udid,
1527
+ appPath,
1528
+ remote: Boolean(remoteDevice),
1529
+ msg: `launching ${bundleId} on ${udid}`
1530
+ });
1531
+ launched = await runtime.launch(runtimePreparation, {
1532
+ scheme,
1533
+ devMenuParams,
1534
+ lanAddress,
1535
+ launch: async ({ metroPort: port, devClientScheme, devMenuParams: menuParams }) => d.launchIosApp({
1536
+ udid,
1537
+ bundleId,
1538
+ metroPort: port,
1539
+ devClientScheme,
1540
+ devMenuParams: menuParams,
1541
+ consolePaths: simulatorConsolePaths({
1542
+ deviceId: udid,
1543
+ appId: bundleId,
1544
+ since: launchedAt
1545
+ }, true, Boolean(remoteDevice))
1546
+ })
1547
+ });
1548
+ if ("error" in launched) return fail(launched.error);
1549
+ if (launched.failed) {
1550
+ printNativeCrashReport({
1551
+ root,
1552
+ slot,
1553
+ platform: "ios",
1554
+ deviceId: udid,
1555
+ appId: bundleId,
1556
+ since: launchedAt,
1557
+ appPath
1558
+ }, logsDir, (line) => note(chalk.red(phaseLine("launch", line))), Boolean(remoteDevice));
1559
+ return fail({
1560
+ code: launched.code || "STIM_LAUNCH_FAILED",
1561
+ message: launched.reason,
1562
+ remedy: launchFailureRemedy(remoteDevice, udid, bundleId, logFile),
1563
+ build: {
1564
+ ...buildFailure,
1565
+ appPath,
1566
+ bundleId
1567
+ }
1568
+ });
1569
+ }
1570
+ phase("launch", `${bundleId}${restartedAppNote(launched.restartedPid)} ${launchTimer()}`);
1571
+ }
1572
+ recordIosReloadTarget({
1573
+ d,
1574
+ root,
1575
+ slot,
1576
+ physical,
1577
+ remoteDevice: Boolean(remoteDevice),
1578
+ bundleId,
1579
+ udid,
1580
+ metroPort,
1581
+ release,
1582
+ runtimeKind: runtime.kind,
1583
+ launchedAt,
1584
+ note
1585
+ });
1586
+ logWriter().write({
1587
+ src: "build",
1588
+ level: "info",
1589
+ event: "launch",
1590
+ msg: runtime.kind === "process" ? `launched ${bundleId} on ${udid} (${configuration ?? "Debug"}, native process, no Metro)` : runtime.kind === "embedded-js" ? `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, "; ")
1591
+ });
1592
+ if (remoteDevice) logWriter().write({
1593
+ src: "build",
1594
+ level: "info",
1595
+ event: "collector_skipped",
1596
+ msg: `remote session ${udid}: device logs come from agent-device/EAS, so no simctl log stream is attached`
1597
+ });
1598
+ if (physical) raiseLeaseFor(runtime.verificationWaitMs, false);
1599
+ const { state: launchState, warning: launchWarning, unattributed } = await runtime.verify(runtimePreparation, {
1600
+ root,
1601
+ slot,
1602
+ appPath,
1603
+ d,
1604
+ launched,
1605
+ configuration,
1606
+ phase,
1607
+ note,
1608
+ metroCheck,
1609
+ logsDir,
1610
+ launchedAt,
1611
+ metroPort,
1612
+ isExpo,
1613
+ bundleId,
1614
+ udid,
1615
+ scheme,
1616
+ physical,
1617
+ appName,
1618
+ lanAddress,
1619
+ lanOrigin: lanOriginUrl,
1620
+ remoteDevice: Boolean(remoteDevice),
1621
+ metroOrigin: typeof launched?.jsLocation === "string" ? launched.jsLocation : null
1622
+ });
1623
+ if (launchState === "fatal") return fail({
1624
+ code: "STIM_LAUNCH_FAILED",
1625
+ message: "The app failed its launch readiness check.",
1626
+ remedy: `Read the launch error above or run \`stim logs --errors\`. The full timeline is in ${logsDir}.`,
1627
+ logPath: logsDir,
1628
+ build: {
1629
+ ...buildFailure,
1630
+ appPath,
1631
+ bundleId
1632
+ }
1633
+ });
1634
+ logWriter().write(runtime.kind === "process" ? {
1635
+ src: "build",
1636
+ level: launchState === "unverified" ? "warn" : "info",
1637
+ event: launchState === "unverified" ? "launch_unverified" : "launch_verified",
1638
+ msg: launchState === "unverified" ? `${bundleId} could not be verified as running` : `${bundleId} is running its native process`
1639
+ } : launchOutcomeRecord({
1640
+ launchState,
1641
+ release: runtime.kind === "embedded-js",
1642
+ bundleId,
1643
+ configuration,
1644
+ metroPort,
1645
+ unattributed
1646
+ }));
1647
+ const leaseFacts = lease?.facts() ?? null;
1648
+ releaseLease();
1649
+ const uploadsAbandoned = await artifact.completeUploads();
1650
+ return {
1651
+ facts: reportIosResult({
1652
+ devicePlacement,
1653
+ root,
1654
+ slot,
1655
+ json,
1656
+ release,
1657
+ runtimeKind: runtime.kind,
1658
+ configuration,
1659
+ buildScheme,
1660
+ metroCheck,
1661
+ metroPort,
1662
+ logsDir,
1663
+ device,
1664
+ udid,
1665
+ appPath,
1666
+ bundleId,
1667
+ installSkipped,
1668
+ elapsed,
1669
+ startedAt,
1670
+ storeHash,
1671
+ storeKey,
1672
+ cacheHit,
1673
+ missReason,
1674
+ compilationCache,
1675
+ useBuildCache,
1676
+ waitedForBuild,
1677
+ launchState,
1678
+ launchWarning,
1679
+ providerName,
1680
+ buildMachine,
1681
+ builtOn,
1682
+ offloadedTo,
1683
+ offloadFallback,
1684
+ closeWriter,
1685
+ webPreviewUrl: remoteDevice?.webPreviewUrl() ?? null,
1686
+ lease: physical ? leaseFacts : void 0,
1687
+ recordRun,
1688
+ reclaimed,
1689
+ devServer,
1690
+ links: workspaceLinks(root, {
1691
+ platform: "ios",
1692
+ slot
1693
+ })
1694
+ }),
1695
+ uploadsAbandoned
1696
+ };
1697
+ }
1698
+ //#endregion
1699
+ //#region src/integrations/ios-project.ts
1700
+ var IosRecipeRefusal = class extends Error {
1701
+ failure;
1702
+ constructor(failure) {
1703
+ super(failure.message ?? failure.code);
1704
+ this.failure = failure;
1705
+ }
1706
+ };
1707
+ //#endregion
1708
+ //#region src/integrations/react-native-ios.ts
1709
+ const PLATFORM = "ios";
1710
+ function reactNativeIosSchemeProblem(root, scheme, isExpo, d) {
1711
+ if (scheme === void 0) return null;
1712
+ if (!scheme.trim()) return {
1713
+ code: "STIM_BAD_ARG",
1714
+ message: "--scheme must name a non-empty shared Xcode app scheme.",
1715
+ remedy: "Pass the exact scheme name shown by xcodebuild -list."
1716
+ };
1717
+ if (isExpo) return null;
1718
+ const project = d.discoverXcodeProject(root);
1719
+ if (project.error) return project.error;
1720
+ return d.resolveScheme(project, { scheme }).error ?? null;
1721
+ }
1722
+ function reactNativeIosProject(root, dependencies = {}) {
1723
+ const d = {
1724
+ ...DEFAULT_DEPS,
1725
+ ...dependencies
1726
+ };
1727
+ const isExpo = d.detectIsExpo(root);
1728
+ return {
1729
+ isExpo,
1730
+ bundleId: () => d.detectBundleId(root),
1731
+ schemeProblem: (scheme) => reactNativeIosSchemeProblem(root, scheme, isExpo, d),
1732
+ targets: [
1733
+ "simulator",
1734
+ "physical",
1735
+ "remote",
1736
+ "hosted"
1737
+ ],
1738
+ eas: true,
1739
+ runtime: ({ configuration, prepareMetro, prepareEmbedded }) => isReleaseConfiguration(configuration) ? iosProcessRuntime(prepareEmbedded, "embedded-js") : iosMetroRuntime(prepareMetro),
1740
+ artifact: (context) => reactNativeIosArtifact(context, isExpo, d)
1741
+ };
1742
+ }
1743
+ function fail(failure) {
1744
+ throw new IosRecipeRefusal(failure);
1745
+ }
1746
+ function reactNativeIosArtifact({ root, logFile, configuration, buildScheme, buildProfile, target, device, easProfile, optimizations, cache: cachePolicy, phase, note, logWriter, estimates, step, setPodsMs }, isExpo, d) {
1747
+ const physical = device !== null;
1748
+ const { udid } = target;
1749
+ const release = isReleaseConfiguration(configuration);
1750
+ const keyOptions = {
1751
+ scheme: buildScheme,
1752
+ ...configuration ? { configuration } : {},
1753
+ isSimulator: !physical,
1754
+ ...target.keyArch ? { arch: target.keyArch } : {},
1755
+ ...buildProfile ? { buildProfile } : {}
1756
+ };
1757
+ let fingerprint = "";
1758
+ let fingerprintSources = [];
1759
+ let storeHash = null;
1760
+ let storeSources = [];
1761
+ let mutatingSteps = [];
1762
+ const identity = () => storeHash ? {
1763
+ hash: storeHash,
1764
+ key: buildCacheKey(PLATFORM, storeHash, keyOptions)
1765
+ } : null;
1766
+ const materialize = async (artifactPath, { fresh: isFresh, ownTemporary }) => {
1767
+ const lanAddress = device?.lanAddress ?? null;
1768
+ const metroPort = device?.metroPort ?? null;
1769
+ const prepareDeviceApp = async (path, { fresh }) => {
1770
+ const refuse = (code, reason, remedy) => {
1771
+ if (fresh) fail({
1772
+ code,
1773
+ message: reason,
1774
+ remedy,
1775
+ build: { appPath: path }
1776
+ });
1777
+ note(chalk.yellow(phaseLine("cache", `${reason} -- building fresh instead`)));
1778
+ note(chalk.dim(phaseLine("", remedy)));
1779
+ return null;
1780
+ };
1781
+ const gateProfile = () => {
1782
+ const gate = d.gateProfileForDevice({
1783
+ appPath: path,
1784
+ udid,
1785
+ configuration
1786
+ });
1787
+ return gate.ok ? path : refuse(gate.code, gate.reason, gate.remedy);
1788
+ };
1789
+ if (release) {
1790
+ if (!fresh) {
1791
+ 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`)));
1792
+ return null;
1793
+ }
1794
+ return gateProfile();
1795
+ }
1796
+ if (d.devClientScheme(root, path)) return gateProfile();
1797
+ let copy;
1798
+ try {
1799
+ copy = copyAppAside(path);
1800
+ } catch (e) {
1801
+ 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.");
1802
+ }
1803
+ ownTemporary(copy.tmpDir);
1804
+ writeIpTxt(copy.appPath, lanAddress, metroPort);
1805
+ const sealed = d.sealAppForDevice({
1806
+ appPath: copy.appPath,
1807
+ udid,
1808
+ configuration,
1809
+ pinnedName: device?.signingName ?? null,
1810
+ pinnedSha1: device?.signingSha1 ?? null
1811
+ });
1812
+ if (!sealed.ok) {
1813
+ try {
1814
+ rmSync(copy.tmpDir, {
1815
+ recursive: true,
1816
+ force: true
1817
+ });
1818
+ } catch {}
1819
+ for (const line of sealed.lastLines ?? []) note(chalk.dim(phaseLine("", line)));
1820
+ return refuse(sealed.code, sealed.reason, sealed.remedy);
1821
+ }
1822
+ phase("ip.txt", `${lanAddress}:${metroPort} written into the install copy and re-sealed with "${sealed.identity.name}"${sealed.mode === "preserve-metadata" ? "" : ` (${sealed.mode})`}`);
1823
+ return copy.appPath;
1824
+ };
1825
+ const installableCachedApp = async (cachedPath) => {
1826
+ if (physical) return prepareDeviceApp(cachedPath, { fresh: false });
1827
+ if (!release) return cachedPath;
1828
+ phase("swap", `regenerating this workspace's JS for the cached ${configuration} app`);
1829
+ const swap = await d.swapJsBundle({
1830
+ root,
1831
+ isExpo,
1832
+ cachedAppPath: cachedPath,
1833
+ logWriter: logWriter()
1834
+ });
1835
+ if (swap?.ok && swap.appPath) {
1836
+ if (swap.note) note(chalk.yellow(phaseLine("swap", swap.note)));
1837
+ if (swap.tmpDir) ownTemporary(swap.tmpDir);
1838
+ phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} + assets replaced, re-signed (${formatDuration(swap.durationMs ?? 0)})`);
1839
+ return swap.appPath;
1840
+ }
1841
+ 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)`)));
1842
+ for (const line of swap?.lastLines ?? []) note(chalk.dim(phaseLine("", line)));
1843
+ return null;
1844
+ };
1845
+ if (isFresh) return physical ? prepareDeviceApp(artifactPath, { fresh: true }) : artifactPath;
1846
+ return installableCachedApp(artifactPath);
1847
+ };
1848
+ return {
1849
+ async identity() {
1850
+ let computedFingerprint;
1851
+ let fingerprintError = "no hash";
1852
+ try {
1853
+ const computed = await d.fingerprintProject(root, { platform: PLATFORM });
1854
+ computedFingerprint = computed?.hash ?? null;
1855
+ fingerprintSources = computed?.sources ?? [];
1856
+ } catch (error) {
1857
+ computedFingerprint = null;
1858
+ fingerprintError = String(error?.message || error);
1859
+ note(chalk.dim(`Fingerprinting failed: ${fingerprintError}`));
1860
+ }
1861
+ if (!computedFingerprint) fail({
1862
+ code: "STIM_NO_FINGERPRINT",
1863
+ message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
1864
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry.",
1865
+ build: {
1866
+ cacheSkipped: !cachePolicy.read,
1867
+ missReason: fingerprintErrorMissReason(fingerprintError)
1868
+ }
1869
+ });
1870
+ fingerprint = storeHash = computedFingerprint;
1871
+ storeSources = fingerprintSources;
1872
+ return identity();
1873
+ },
1874
+ cache: () => filesystemBuildCapability({
1875
+ resolve: d.resolveBuild,
1876
+ store: d.storeBuild,
1877
+ sources: storeSources
1878
+ }),
1879
+ validateExternal(path) {
1880
+ if (physical) {
1881
+ const gate = d.gateProfileForDevice({
1882
+ appPath: path,
1883
+ udid,
1884
+ configuration
1885
+ });
1886
+ if (!gate.ok) fail({
1887
+ code: gate.code,
1888
+ message: gate.reason,
1889
+ remedy: easDeviceBuildRemedy(easProfile)
1890
+ });
1891
+ if (!d.devClientScheme(root, path)) fail({
1892
+ code: "STIM_BAD_ARG",
1893
+ message: "The EAS device app has no development-client URL scheme.",
1894
+ remedy: "Install expo-dev-client with npx expo install expo-dev-client, then rebuild the EAS profile and retry."
1895
+ });
1896
+ }
1897
+ },
1898
+ materialize,
1899
+ async prepare(beforePrepare) {
1900
+ mutatingSteps = [];
1901
+ const prebuild = d.planPrebuild(root, PLATFORM, {
1902
+ isExpo,
1903
+ fingerprint,
1904
+ sources: fingerprintSources
1905
+ });
1906
+ if (prebuild === "refuse") fail({ ...staleNativeDirRefusal(PLATFORM) });
1907
+ const pods = d.readPodState(root);
1908
+ if ((prebuild === "generate" || prebuild === "regenerate" || podAction(pods, d.podsAreStale(pods.lockText, pods.manifestText)).install) && cachePolicy.read) beforePrepare();
1909
+ if (prebuild === "generate" || prebuild === "regenerate") {
1910
+ step("prebuild");
1911
+ recordPrebuild(root, PLATFORM, null);
1912
+ const result = await d.runPrebuild(root, PLATFORM, logWriter(), { clean: prebuild === "regenerate" });
1913
+ if (result?.failed) {
1914
+ phase("prebuild", "FAILED");
1915
+ fail({
1916
+ code: result.code || "STIM_PREBUILD_FAILED",
1917
+ message: result.reason || "expo prebuild failed.",
1918
+ remedy: result.remedy || `See ${logFile} for the transcript.`,
1919
+ lines: (result.lastLines || []).slice(-5)
1920
+ });
1921
+ }
1922
+ phase("prebuild", `${prebuild === "generate" ? "ios/ absent -> generated" : "ios/ not generated from this fingerprint -> regenerated with --clean"} (${formatDuration(result?.durationMs ?? 0)})`);
1923
+ mutatingSteps.push("prebuild");
1924
+ }
1925
+ if (isExpo && buildScheme !== void 0) {
1926
+ const project = d.discoverXcodeProject(root);
1927
+ if (project.error) fail({ ...project.error });
1928
+ const schemeError = d.resolveScheme(project, { scheme: buildScheme }).error;
1929
+ if (schemeError) fail({ ...schemeError });
1930
+ }
1931
+ const podState = d.readPodState(root);
1932
+ const verdict = d.podsAreStale(podState.lockText, podState.manifestText);
1933
+ const action = podAction(podState, verdict);
1934
+ if (action.install) {
1935
+ step("pods");
1936
+ const result = await d.runPodInstall(root, logWriter(), { estimateMs: estimates().podsMs });
1937
+ const podCommand = result?.command || "pod install";
1938
+ for (const line of result?.notes || []) note(chalk.dim(phaseLine("pods", line)));
1939
+ if (result?.failed) {
1940
+ phase("pods", "FAILED");
1941
+ fail({
1942
+ code: result.code || "STIM_DEPS_FAILED",
1943
+ message: result.reason || "`pod install` failed.",
1944
+ remedy: result.remedy || `See ${logFile} for the transcript.`,
1945
+ lines: result.diagnosticLines?.length ? result.diagnosticLines : (result.lastLines || []).slice(-5)
1946
+ });
1947
+ }
1948
+ setPodsMs(result?.durationMs ?? 0);
1949
+ phase("pods", `${action.reason} -> installed with \`${podCommand}\` (${formatDuration(result?.durationMs ?? 0)})`);
1950
+ mutatingSteps.push(podCommand);
1951
+ }
1952
+ },
1953
+ async reconcile() {
1954
+ const rekeyedBy = [];
1955
+ if (mutatingSteps.length) {
1956
+ const after = await refingerprintAfterMutation({
1957
+ projectRoot: root,
1958
+ platform: PLATFORM,
1959
+ previousHash: fingerprint,
1960
+ fingerprint: d.fingerprintProject
1961
+ });
1962
+ const prebuildRan = mutatingSteps.includes("prebuild");
1963
+ const editedConfig = after ? configInputsChanged(fingerprintSources, after.sources, { prebuildRan }) : [];
1964
+ if (after && !editedConfig.length && prebuildRan) recordPrebuild(root, PLATFORM, after.hash);
1965
+ if (!after || editedConfig.length) {
1966
+ storeHash = null;
1967
+ note(chalk.yellow(phaseLine("fingerprint", after ? changedDuringBuildLine(editedConfig) : `unavailable after ${mutatingSteps.join(", ")}; the build will be installed but not cached`)));
1968
+ } else if (after.moved) {
1969
+ rekeyedBy.push(...mutatingSteps.map((mutation) => mutation === "prebuild" ? mutation : "pod install"));
1970
+ storeHash = after.hash;
1971
+ storeSources = after.sources;
1972
+ note(chalk.dim(phaseLine("fingerprint", `${shortHash(fingerprint)} -> ${shortHash(storeHash)} (after ${mutatingSteps.join(", ")})`)));
1973
+ }
1974
+ }
1975
+ return {
1976
+ identity: identity(),
1977
+ rekeyedBy,
1978
+ mutationLabel: mutatingSteps.join("/")
1979
+ };
1980
+ },
1981
+ async validate() {
1982
+ if (!storeHash) return null;
1983
+ const after = await refingerprintAfterMutation({
1984
+ projectRoot: root,
1985
+ platform: PLATFORM,
1986
+ previousHash: storeHash,
1987
+ fingerprint: d.fingerprintProject
1988
+ });
1989
+ const changed = after ? inputsChangedDuringBuild({
1990
+ platform: PLATFORM,
1991
+ lookup: fingerprintSources,
1992
+ prebuildRan: mutatingSteps.includes("prebuild"),
1993
+ compiled: storeSources,
1994
+ current: after.sources
1995
+ }) : [];
1996
+ if (!after || changed.length) {
1997
+ storeHash = null;
1998
+ note(chalk.yellow(phaseLine("fingerprint", after ? changedDuringBuildLine(changed) : "unavailable after xcodebuild; the build will be installed but not cached")));
1999
+ } else if (after.moved) {
2000
+ const before = storeHash;
2001
+ storeHash = after.hash;
2002
+ storeSources = after.sources;
2003
+ note(chalk.dim(phaseLine("fingerprint", `${shortHash(before)} -> ${shortHash(storeHash)} (after xcodebuild)`)));
2004
+ }
2005
+ return identity();
2006
+ },
2007
+ compile: () => d.buildIos({
2008
+ root,
2009
+ scheme: buildScheme,
2010
+ udid,
2011
+ destination: target.destination,
2012
+ arch: target.arch,
2013
+ ...physical ? { sdk: target.sdk } : {},
2014
+ logWriter: logWriter(),
2015
+ ...configuration ? { configuration } : {},
2016
+ estimateMs: estimates().coldBuildMs,
2017
+ optimizations
2018
+ }),
2019
+ explain(rekeyedBy) {
2020
+ const current = {
2021
+ hash: storeHash ?? fingerprint,
2022
+ sources: storeSources
2023
+ };
2024
+ const explained = explainBuildMiss({
2025
+ root,
2026
+ platform: PLATFORM,
2027
+ current,
2028
+ rekeyedBy,
2029
+ baselineDeps: { readState: d.readWorkspaceState }
2030
+ });
2031
+ return {
2032
+ reason: explained.reason,
2033
+ diff: explained.previousHash && explained.changedNames.length ? fingerprintDiffRecord({
2034
+ changed: explained.changedNames,
2035
+ previousHash: explained.previousHash,
2036
+ hash: current.hash
2037
+ }) : null
2038
+ };
2039
+ },
2040
+ untrackedLine: () => untrackedMissLine(d.untrackedNativeFiles({ projectRoot: root })),
2041
+ legacyCache: physical || !cachePolicy.remote || buildProfile || buildScheme ? null : {
2042
+ load: () => d.loadProjectProvider(root, { isExpo }),
2043
+ runOptions: iosProviderRunOptions(configuration, target.keyArch)
2044
+ },
2045
+ offload: {
2046
+ context: () => {
2047
+ const runtime = physical || release ? null : target.offloadRuntime();
2048
+ return {
2049
+ runtime,
2050
+ unsupported: target.offloadRefusal ?? (release ? `${configuration} builds build here` : !cachePolicy.write ? "the build cache is off" : !runtime ? `the runtime of simulator ${udid} is unknown` : null)
2051
+ };
2052
+ },
2053
+ target: (runtime) => ({
2054
+ platform: "ios",
2055
+ local: {
2056
+ ...iosToolchain(root),
2057
+ ...target.hostedArchitecture ? { arch: target.hostedArchitecture === "x86_64" ? "x64" : "arm64" } : {}
2058
+ },
2059
+ runtime,
2060
+ cocoapodsPinned: bundlerPin(root) !== null
2061
+ }),
2062
+ request: (runtime) => ({
2063
+ platform: "ios",
2064
+ runtime,
2065
+ configuration,
2066
+ scheme: buildScheme ?? null,
2067
+ isExpo,
2068
+ optimizations
2069
+ }),
2070
+ unchanged: async () => {
2071
+ const after = await refingerprintAfterMutation({
2072
+ projectRoot: root,
2073
+ platform: PLATFORM,
2074
+ previousHash: storeHash,
2075
+ fingerprint: d.fingerprintProject
2076
+ });
2077
+ return Boolean(after && !after.moved);
2078
+ }
2079
+ }
2080
+ };
2081
+ }
2082
+ //#endregion
2083
+ export { cacheDescription as a, lastBuildRecord as c, DEFAULT_DEPS as d, collectorEntry as f, lanOriginUrlFor as h, finishIosRun as i, reportIosResult as l, chooseLanAddress as m, reactNativeIosSchemeProblem as n, finishIosUpload as o, replaceCollector as p, IosRecipeRefusal as r, iosFacts as s, reactNativeIosProject as t, writeLastBuild as u };