stim 1.18.0 → 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 (140) hide show
  1. package/README.md +1 -1
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BSw0x84R.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +5 -5
  4. package/dist/{android-BvwuMUJ9.mjs → android-BXc6BmEi.mjs} +507 -347
  5. package/dist/{android-DC3ZLuw0.mjs → android-BdQC6zs1.mjs} +6 -6
  6. package/dist/{android-BKA16DTf.mjs → android-BvV1gGdN.mjs} +3 -3
  7. package/dist/{android-CBwCNicO.mjs → android-DE5g_5gv.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-C87geAg-.mjs} +3 -3
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/{api-D0VtFhqk.mjs → api-DIf_s86s.mjs} +5 -7
  11. package/dist/api-run.mjs +8 -8
  12. package/dist/api.d.mts +8 -2
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-CFI4ptUL.mjs} +33 -13
  15. package/dist/{web-DTU0uiWj.mjs → browser-web-CDhqQxhv.mjs} +134 -172
  16. package/dist/{budget-DIhL2BKx.mjs → budget-Dz-kyXLv.mjs} +15 -202
  17. package/dist/{build-facts-DcLv2HSd.d.mts → build-facts-B87vo-Im.d.mts} +32 -1
  18. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-DiwKSyv9.mjs} +1 -1
  19. package/dist/cache-manifest.mjs +1 -1
  20. package/dist/{chrome-DJnmnS5q.mjs → chrome-BTUOo8p2.mjs} +1 -1
  21. package/dist/{cli-DNJMwADx.mjs → cli-C9Zvgbdc.mjs} +20 -20
  22. package/dist/cli.mjs +1 -1
  23. package/dist/{client-DzhapoMB.mjs → client-CuUo8Nvg.mjs} +248 -23
  24. package/dist/collector-run.d.mts +6 -0
  25. package/dist/collector-run.mjs +6 -6
  26. package/dist/{command-output-CBtzyIgF.mjs → command-output-Bt8ygHE7.mjs} +1 -1
  27. package/dist/{config-CgYMahz7.mjs → config-DC5jEtd1.mjs} +6 -2
  28. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-D0s26Hx5.mjs} +2 -2
  29. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
  30. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
  31. package/dist/{dev-client-CLnIH02x.mjs → dev-client-CDY_zAmD.mjs} +2 -2
  32. package/dist/{device-D7oCHGWi.mjs → device-BHw0WlHl.mjs} +8 -8
  33. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-BoN1jQkO.mjs} +23 -12
  34. package/dist/device-host-worker.mjs +14 -14
  35. package/dist/{gradle-C6GKnunQ.mjs → device-ios-B2ruohE-.mjs} +659 -105
  36. package/dist/{device-lease-Co-EwifR.mjs → device-lease-Cu7PtQpZ.mjs} +5 -5
  37. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
  38. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-CP4rAsHx.mjs} +3 -3
  39. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-B5GiUifk.mjs} +15 -16
  40. package/dist/{doctor-B9ckYUoU.mjs → doctor-PRJH94v6.mjs} +53 -29
  41. package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
  42. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics--v5HGOU0.mjs} +10 -10
  43. package/dist/{exec-DWhCZDGm.mjs → exec-i8GDPwq9.mjs} +32 -8
  44. package/dist/{gc-Dh6mXOKC.mjs → gc-CyHMcKcn.mjs} +19 -19
  45. package/dist/{guide-tm6igAaa.mjs → guide-dRaQJJk9.mjs} +409 -472
  46. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
  47. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-C5G4h_OJ.mjs} +1 -1
  48. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-Bik035eL.mjs} +1 -1
  49. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-CI9Az9a4.mjs} +7 -7
  50. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-DABm3ScO.mjs} +4 -4
  51. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-DbV2aUVw.mjs} +12 -8
  52. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DYiMBn34.mjs} +7 -7
  53. package/dist/{ios-B3SbOWdw.mjs → ios-BWbHFZVz.mjs} +5 -5
  54. package/dist/{ios-Bvbf0SGY.mjs → ios-DCrkgXEM.mjs} +1 -1
  55. package/dist/{ios-DNpdygiv.mjs → ios-DYUr9LGc.mjs} +7 -6
  56. package/dist/ios-ITYno9rO.mjs +2241 -0
  57. package/dist/{ios-device-vOaByMhw.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
  58. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-CrW8aMb5.mjs} +1 -1
  59. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-By0XuLTQ.mjs} +1 -1
  60. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-DCveSE6d.mjs} +4 -4
  61. package/dist/{logs-1Ylc1Ri6.mjs → logs-Bao1HOMy.mjs} +10 -10
  62. package/dist/{macos-DF6C7wUU.mjs → macos-ciyBdTY5.mjs} +108 -130
  63. package/dist/macos-nIOATJmR.mjs +2 -0
  64. package/dist/macos-run.mjs +1 -1
  65. package/dist/maintenance-run.mjs +7 -7
  66. package/dist/manifest-CX0d1IkZ.mjs +148 -0
  67. package/dist/{metro-Dh8T4Ytd.mjs → metro-DI2AjPF_.mjs} +194 -41
  68. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BxpJLIwh.mjs} +2 -2
  69. package/dist/{plan-placement-CtxkCO54.mjs → miss-reason-CjQCOKqq.mjs} +180 -296
  70. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-B4iWH4E-.mjs} +12 -5
  71. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BGxsbfny.mjs} +6 -6
  72. package/dist/offload-worker.d.mts +1 -29
  73. package/dist/offload-worker.mjs +9 -8
  74. package/dist/{ownership-D6Se7gg_.mjs → ownership-EB0ySdGK.mjs} +2 -2
  75. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-Dxe34rJm.mjs} +7 -3
  76. package/dist/plan-placement-BVY59qa8.mjs +290 -0
  77. package/dist/{ports-BLTtfBq-.mjs → ports-ByPc0_97.mjs} +3 -3
  78. package/dist/{prebuild-juCyreaR.mjs → prebuild-BKJ3wJI1.mjs} +4 -4
  79. package/dist/{preview-D5V07Nrl.mjs → preview-BbSottmO.mjs} +16 -17
  80. package/dist/project-B5eQ1USs.mjs +612 -0
  81. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-IHNdMnG8.mjs} +1 -1
  82. package/dist/pull-requests.mjs +1 -1
  83. package/dist/react-native-ios-BMh2i25I.mjs +2 -0
  84. package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
  85. package/dist/{recordings-BAWltsVt.mjs → recordings-Bv8McZN4.mjs} +1 -1
  86. package/dist/{reload-DvyWzVjJ.mjs → reload-rAPdqeRR.mjs} +13 -14
  87. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-N3hAEaAL.mjs} +4 -4
  88. package/dist/{run-vhtDgpHO.mjs → run-lAkle1-c.mjs} +2 -2
  89. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-CkIjnegq.mjs} +3 -3
  90. package/dist/server-expo-DGsdUGX2.mjs +2 -0
  91. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-n3tUi3Em.mjs} +3 -3
  92. package/dist/{settings-Byp2AQ_E.mjs → settings-2am7TSxp.mjs} +5 -6
  93. package/dist/{settings-CQyMElEP.mjs → settings-CxgngUZk.mjs} +767 -15
  94. package/dist/settings.schema.json +72 -6
  95. package/dist/{simslim-EHBQ2R4G.mjs → simslim-D9LkJxIO.mjs} +5 -5
  96. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
  97. package/dist/{stage-g_WqWeaE.mjs → stage-BgaZJVZY.mjs} +1 -1
  98. package/dist/{start-NTc1Ifqs.mjs → start-D8NbxNBb.mjs} +1 -1
  99. package/dist/{start-D-IeOyoK.mjs → start-DtxthMfI.mjs} +29 -22
  100. package/dist/{state-BG-aAk4k.mjs → state-D2KtnALG.mjs} +2 -2
  101. package/dist/{state-xnqpx3AD.mjs → state-Tp4uImDS.mjs} +4 -2
  102. package/dist/{stats-DY6ekz8F.mjs → stats-8EJ5ivFT.mjs} +6 -6
  103. package/dist/{status-DCjZeE-K.mjs → status-BOyp0CUl.mjs} +25 -27
  104. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
  105. package/dist/{stim-installations-aR_WsNOA.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
  106. package/dist/stop-D0UIpZuh.mjs +72 -0
  107. package/dist/{stop-DIJtD2GL.mjs → stop-DRipAg-e.mjs} +2 -2
  108. package/dist/{stop-BTjs3xbc.mjs → stop-zDlknFoz.mjs} +78 -31
  109. package/dist/supervisor-run.mjs +14 -14
  110. package/dist/{support-BaayeYCf.mjs → support-CNqYkV0y.mjs} +7 -7
  111. package/dist/{support-Bwvbrq5-.mjs → support-DsNaFg6D.mjs} +5 -5
  112. package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
  113. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-C3xSoyUl.mjs} +27 -270
  114. package/dist/{trigger-CJMhwXe8.mjs → trigger-CAhmG-4s.mjs} +3 -3
  115. package/dist/trigger-CoalsMTV.mjs +2 -0
  116. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-pFLB2e6P.mjs} +133 -36
  117. package/dist/{status-Dcr0Qexd.mjs → watchman-BL1IdOtq.mjs} +75 -7
  118. package/dist/web-DFpFeZu5.mjs +2 -0
  119. package/dist/web-DbbrPUn0.mjs +127 -0
  120. package/dist/web-run.mjs +7 -7
  121. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CR9X86xJ.mjs} +1 -1
  122. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-Djb2gfi9.mjs} +102 -18
  123. package/dist/{worktree-BSYlofkq.mjs → worktree-BL4M1rhB.mjs} +45 -42
  124. package/dist/{worktree-BJfnU6r5.mjs → worktree-DV-A0LNL.mjs} +1 -1
  125. package/package.json +5 -5
  126. package/shim/bundle-response.cjs +2 -1
  127. package/shim/bundle-response.d.cts +1 -1
  128. package/shim/expo-metro-config.cjs +1 -1
  129. package/dist/device-ios-6MewKKnc.mjs +0 -412
  130. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  131. package/dist/ios-CPdkoKaa.mjs +0 -3953
  132. package/dist/macos-Ck7OBMNY.mjs +0 -2
  133. package/dist/native-run-B06FkmZx.mjs +0 -158
  134. package/dist/project-C3ChRLZy.mjs +0 -212
  135. package/dist/projects-DD7dBTCj.mjs +0 -92
  136. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  137. package/dist/stop-BGhKMOuH.mjs +0 -48
  138. package/dist/trigger-DWAq9hkw.mjs +0 -2
  139. package/dist/web-B6_bFDn7.mjs +0 -2
  140. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -0,0 +1,2241 @@
1
+ import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
2
+ import { _ as stepTimer, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash } from "./command-output-Bt8ygHE7.mjs";
3
+ import { L as deviceSlotFileKey, V as parseDeviceSlotOption, W as validateDeviceSlot, z as deviceSlotPlatforms } from "./config-DC5jEtd1.mjs";
4
+ import { i as recordWorkspaceUse, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-CR9X86xJ.mjs";
5
+ import { b as MODE_EXPO, s as projectProblem, t as NO_PROJECT_REFUSAL, y as MODE_BARE } from "./project-B5eQ1USs.mjs";
6
+ import { A as publicUrlSetting, B as settingShapeErrors, C as metroWarmupUrlSetting, Dt as optimizationBuildProfile, N as remoteEasFallbackSetting, Ot as resolveOptimizations, P as remoteIosSetting, T as parseIosRemote, Tt as artifactCachePolicy, U as tunnelModeSetting, W as unknownSettingKeys, _ as iosSigningIdentitySha1SettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, i as SETTING_SHAPE_REMEDY, m as iosSigningIdentitySetting, p as iosLanHostSettingError, u as cacheProviderSettingError, x as metroPortSetting } from "./settings-CxgngUZk.mjs";
7
+ import { I as startBuildProgress, L as tapBuildLog, O as NO_BUILD_PROGRESS, R as createRunRecorder, W as statsProjectKey$1 } from "./budget-Dz-kyXLv.mjs";
8
+ import { A as providerDownloadPath, b as buildCacheKey, j as providerUploadOutcome, k as prepareProviderDownloadDir, v as waitForSharedBuild } from "./device-lease-Cu7PtQpZ.mjs";
9
+ import { ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY } from "./android-BdQC6zs1.mjs";
10
+ import { i as createIosSimSnapshot } from "./ios-BWbHFZVz.mjs";
11
+ import { A as resolveBuildPlacement, C as setRemoteLogSink, D as parseBuildMachineOption, E as namedBuildMachine, O as requireLocalBuild, S as withNativeBuildRun, a as offloadBuild, b as runCancellation, c as placementLoad, i as closeOffload, l as remotePhaseText, n as buildPlacementCandidates, p as cancelledFailure, r as chooseBuildMachine, s as offloadPlacement, u as simulatorRuntime, w as OffloadRefusal, x as runCancellationSignal } from "./client-CuUo8Nvg.mjs";
12
+ import { p as COMPILATION_CACHE_NOT_RUN, v as compilationCacheActivityLine } from "./toolchain-C3xSoyUl.mjs";
13
+ import { r as hostedMetroSettings } from "./metro-gateway-BxpJLIwh.mjs";
14
+ import { i as writeDevicePlacement } from "./ios-state-By0XuLTQ.mjs";
15
+ import { _ as devicePlacementLine, i as skippedMissReason, l as didSetUpDevice } from "./miss-reason-CjQCOKqq.mjs";
16
+ import { n as devicePlacementRecord, t as buildPlacementRecord } from "./placement-log-Dxe34rJm.mjs";
17
+ import { r as DEFAULT_METRO_PORT } from "./app-install-CFI4ptUL.mjs";
18
+ import { r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-HV_KtPIw.mjs";
19
+ import { a as easAuthNote, o as exitAfterFlush, s as isEasAuthFailureText, t as RESOLVE_TIMEOUT_MS } from "./remote-cache-N3hAEaAL.mjs";
20
+ import "./dev-client-CDY_zAmD.mjs";
21
+ import { T as ownedSessionName, n as binOnPath } from "./device-remote-B5GiUifk.mjs";
22
+ import { a as isEasBuildFailure, o as planEasDevelopmentBuild } from "./simslim-D9LkJxIO.mjs";
23
+ import { f as waitFlagConflict, o as leaseExpiryText } from "./device-lease-run-D38SnZtt.mjs";
24
+ import { r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-BGxsbfny.mjs";
25
+ import { a as printPlan, i as planPayload, n as planCachedBuild, o as refusePlan, r as planFlagRefusal, s as runArtifactLifecycle, t as planHostedDevice } from "./plan-placement-BVY59qa8.mjs";
26
+ import { a as iosPoolCandidates, c as isWirelessIosDevice, f as resolveIosPhysicalDevice, o as iosPoolNoCandidatesRefusal, r as iosDeviceBounds } from "./ios-device-CJ_ZOGky.mjs";
27
+ import { c as lastBuildRecord, d as DEFAULT_DEPS, h as lanOriginUrlFor, i as finishIosRun, l as reportIosResult, m as chooseLanAddress, n as reactNativeIosSchemeProblem, o as finishIosUpload, r as IosRecipeRefusal, u as writeLastBuild } from "./react-native-ios-CSpIxexZ.mjs";
28
+ import { _ as resolveSimulatorAppFlag, a as deviceModelRefusal, c as iosProviderRunOptions, f as printDiagnostics, g as resolveRuntime, h as resolveIosWait, i as deviceLabel, l as isReleaseConfiguration, m as resolveDeviceType, n as buildLogFile, p as resolveConfiguration, r as createIosDeviceWaitRun, u as ownedSimFailure, v as simulatorBuildArch, y as xcodeFailureReport } from "./support-CNqYkV0y.mjs";
29
+ import { rmSync } from "node:fs";
30
+ import { basename, join } from "node:path";
31
+ import { hostedIosStatus, machineCapacity, parseMachine } from "@stim-cli/core/state";
32
+ import chalk from "chalk";
33
+ import { InvalidArgumentError } from "commander";
34
+ import { workspaceId } from "@stim-cli/core";
35
+ import { createWarnOnce, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
36
+ //#region src/commands/ios/artifact.ts
37
+ const PROVIDER_SKIPPED_ON_DEVICE = "a device build is local-tier only: its cache key names the iphoneos slice, and a remote or provider entry is keyed for the simulator";
38
+ function fail(failure) {
39
+ throw new IosRecipeRefusal(failure);
40
+ }
41
+ async function acquireIosArtifact({ root, logFile, recipe, physical, configuration, buildMachine = "auto", cache, easBuild, maxBuilds, progress }, d) {
42
+ const { phase, note, logWriter, stats, step, miss, hit: lateHit, place, waitingOn, waitingFor } = progress;
43
+ const cachePolicy = cache.policy;
44
+ const signal = runCancellationSignal() ?? new AbortController().signal;
45
+ const cacheTarget = (key) => ({
46
+ projectRoot: root,
47
+ platform: "ios",
48
+ key,
49
+ signal
50
+ });
51
+ const useBuildCache = cachePolicy.read;
52
+ const cacheProviderConfig = cache.providerConfig;
53
+ let compilationCache = COMPILATION_CACHE_NOT_RUN;
54
+ const temporaryDirs = /* @__PURE__ */ new Set();
55
+ const releaseArtifact = () => {
56
+ for (const directory of temporaryDirs) {
57
+ try {
58
+ rmSync(directory, {
59
+ recursive: true,
60
+ force: true
61
+ });
62
+ } catch {}
63
+ temporaryDirs.delete(directory);
64
+ }
65
+ };
66
+ let buildLock = null;
67
+ const releaseLock = () => {
68
+ if (!buildLock) return;
69
+ const held = buildLock;
70
+ buildLock = null;
71
+ try {
72
+ d.releaseBuildLock(held);
73
+ } catch (e) {
74
+ note(chalk.dim(`Could not release the build lock at ${held.path}: ${e?.message || e}`));
75
+ }
76
+ };
77
+ let buildSlot = null;
78
+ const releaseSlot = () => {
79
+ if (!buildSlot) return;
80
+ const held = buildSlot;
81
+ buildSlot = null;
82
+ try {
83
+ d.releaseBuildSlot(held);
84
+ } catch (e) {
85
+ note(chalk.dim(`Could not release the build slot: ${e?.message || e}`));
86
+ }
87
+ };
88
+ let fingerprint = "";
89
+ let cacheKey = "";
90
+ let storeHash = null;
91
+ let storeKey = null;
92
+ let appPath = null;
93
+ let bundleId = null;
94
+ let cacheHit = false;
95
+ let offloadedTo = null;
96
+ let handoff = null;
97
+ let offloadFallback = null;
98
+ let fallbackMachine = null;
99
+ let hereReason = "no remote Mac is paired";
100
+ let slotWaitMs;
101
+ let builtOn;
102
+ const fallBack = (reason, line = reason, info = {}) => {
103
+ logWriter().write(buildPlacementRecord({
104
+ platform: "ios",
105
+ buildMachine,
106
+ candidates: info.candidates,
107
+ event: "placement_fallback",
108
+ fallback: {
109
+ code: info.code ?? "fallback",
110
+ reason,
111
+ machine: info.machine
112
+ }
113
+ }));
114
+ if (namedBuildMachine(buildMachine)) throw new OffloadRefusal(buildMachine, reason);
115
+ requireLocalBuild(buildMachine);
116
+ offloadFallback = reason;
117
+ buildFailure = {
118
+ ...buildFailure,
119
+ offloadFallback: reason
120
+ };
121
+ phase("build", `${line} -> building here`);
122
+ };
123
+ const openOffload = { choice: null };
124
+ let remote = null;
125
+ let abandonedRemote = false;
126
+ let uploadPending = null;
127
+ let providerUpload = null;
128
+ let providerName = null;
129
+ let providerLoad = null;
130
+ const cacheWarn = createWarnOnce((line) => note(chalk.yellow(phaseLine("cache", line))));
131
+ const loadProvider = cachePolicy.remote && cacheProviderConfig && !physical ? () => providerLoad ??= d.loadCacheProvider({
132
+ projectRoot: root,
133
+ config: cacheProviderConfig
134
+ }) : null;
135
+ if (cacheProviderConfig && physical) note(chalk.dim(phaseLine("cache", PROVIDER_SKIPPED_ON_DEVICE)));
136
+ let waitedForBuild = null;
137
+ let releasedWait = null;
138
+ let swapFellBack = false;
139
+ let buildFailure = {};
140
+ let missReason = null;
141
+ async function resolveInitialFingerprint() {
142
+ if (easBuild?.ok) {
143
+ fingerprint = storeHash = easBuild.fingerprint;
144
+ cacheKey = storeKey = easBuild.cacheKey;
145
+ appPath = easBuild.path;
146
+ cacheHit = easBuild.cacheHit;
147
+ providerName = "eas";
148
+ stats.setCacheKey(cacheKey);
149
+ recipe.validateExternal(appPath);
150
+ return;
151
+ }
152
+ if (useBuildCache) step("cache-lookup");
153
+ const fingerprintTimer = stepTimer(d.now);
154
+ const identity = await recipe.identity();
155
+ fingerprint = identity.hash;
156
+ cacheKey = identity.key;
157
+ stats.setCacheKey(cacheKey);
158
+ storeHash = fingerprint;
159
+ storeKey = cacheKey;
160
+ const found = await resolveTieredBuild({
161
+ local: recipe.cache(),
162
+ loadProvider,
163
+ target: {
164
+ projectRoot: root,
165
+ platform: "ios",
166
+ key: cacheKey
167
+ },
168
+ destinationDir: providerDownloadPath(workspaceDir(root)),
169
+ ensureDestination: prepareProviderDownloadDir,
170
+ skipRead: !useBuildCache,
171
+ warn: cacheWarn
172
+ });
173
+ const cached = found?.tier === "local" ? found.path : null;
174
+ cacheHit = cached ? "local" : false;
175
+ phase("fingerprint", `${shortHash(fingerprint)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : cache.disabledByFlag ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}`);
176
+ if (found?.tier === "provider") {
177
+ cacheHit = "remote";
178
+ providerName = found.providerName ?? null;
179
+ phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
180
+ }
181
+ appPath = found?.path ?? null;
182
+ }
183
+ async function resolveRemoteArtifact() {
184
+ if (!recipe.legacyCache) return null;
185
+ if (!appPath) {
186
+ const loaded = await recipe.legacyCache.load();
187
+ if (loaded?.unavailable) note(chalk.yellow(phaseLine("cache", `provider not usable: ${loaded.unavailable}`)));
188
+ else if (loaded?.provider) remote = loaded;
189
+ if (remote?.name === "eas") {
190
+ const auth = d.checkEasAuth({
191
+ projectRoot: root,
192
+ owner: loaded?.owner || null
193
+ });
194
+ const authNote = easAuthNote(auth);
195
+ if (authNote) note(chalk.yellow(phaseLine("cache", authNote)));
196
+ if (auth?.code === "logged-out") remote = null;
197
+ }
198
+ }
199
+ if (remote && useBuildCache) {
200
+ const remoteTimer = stepTimer(d.now);
201
+ const hit = await d.resolveRemote({
202
+ logWriter: logWriter(),
203
+ provider: remote.provider,
204
+ platform: "ios",
205
+ projectRoot: root,
206
+ fingerprintHash: fingerprint,
207
+ runOptions: recipe.legacyCache?.runOptions ?? null
208
+ });
209
+ if (hit?.appPath) {
210
+ let stored = null;
211
+ try {
212
+ stored = await recipe.cache().store({
213
+ ...cacheTarget(cacheKey),
214
+ sourcePath: hit.appPath,
215
+ overwrite: false
216
+ }) ?? null;
217
+ } catch (e) {
218
+ note(chalk.yellow(phaseLine("cache", `remote hit could not be stored locally: ${e?.message || e}`)));
219
+ }
220
+ appPath = stored || hit.appPath;
221
+ cacheHit = "remote";
222
+ phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
223
+ } else if (hit?.timedOut) {
224
+ abandonedRemote = true;
225
+ note(chalk.yellow(phaseLine("cache", `${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`)));
226
+ } else if (hit?.failed) {
227
+ const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
228
+ code: "logged-out",
229
+ reason: hit.failed
230
+ }) : null;
231
+ note(chalk.yellow(phaseLine("cache", authNote || `${remote.name} could not be used: ${hit.failed}; building instead`)));
232
+ } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
233
+ }
234
+ return remote;
235
+ }
236
+ async function awaitSharedBuild() {
237
+ if (!useBuildCache || appPath) return;
238
+ const shared = await waitForSharedBuild({
239
+ platform: "ios",
240
+ key: cacheKey,
241
+ fingerprint,
242
+ root,
243
+ logFile,
244
+ command: "stim ios",
245
+ acquire: d.acquireBuildLock,
246
+ wait: d.waitForBuild,
247
+ now: d.now,
248
+ phase: (text) => {
249
+ step("wait");
250
+ phase("build", text);
251
+ },
252
+ waitingOn,
253
+ warn: (text) => note(chalk.yellow(phaseLine("build", text))),
254
+ out: note
255
+ });
256
+ if (shared.refusal) fail({
257
+ ...shared.refusal,
258
+ build: {
259
+ fingerprint,
260
+ cacheKey,
261
+ cacheHit,
262
+ cacheSkipped: !useBuildCache
263
+ }
264
+ });
265
+ buildLock = shared.lock;
266
+ releasedWait = shared.released;
267
+ if (shared.hit) {
268
+ appPath = shared.hit.path;
269
+ cacheHit = "local";
270
+ waitedForBuild = shared.hit.waited;
271
+ }
272
+ }
273
+ const installableCachedApp = async (path) => {
274
+ const prepared = await recipe.materialize(path, {
275
+ fresh: false,
276
+ ownTemporary: (directory) => temporaryDirs.add(directory)
277
+ });
278
+ if (!prepared) swapFellBack = true;
279
+ return prepared;
280
+ };
281
+ async function prepareCachedArtifact(cachedPath) {
282
+ const prepared = await installableCachedApp(cachedPath);
283
+ if (!prepared) {
284
+ cacheHit = false;
285
+ waitedForBuild = null;
286
+ }
287
+ return prepared;
288
+ }
289
+ function reasonForMiss(rekeyedBy) {
290
+ if (swapFellBack) return {
291
+ reason: skippedMissReason("the cached app could not be reused, so this run built it fresh"),
292
+ diff: null
293
+ };
294
+ if (!useBuildCache) return {
295
+ reason: skippedMissReason(cache.disabledByFlag ? "cache reuse turned off by --no-build-cache" : "cache reuse off in config"),
296
+ diff: null
297
+ };
298
+ return recipe.explain(rekeyedBy);
299
+ }
300
+ function explainMiss(rekeyedBy) {
301
+ const explained = reasonForMiss(rekeyedBy);
302
+ missReason = explained.reason;
303
+ if (explained.diff) logWriter().write(explained.diff);
304
+ buildFailure = {
305
+ ...buildFailure,
306
+ missReason
307
+ };
308
+ miss(missReason);
309
+ phase("cache", `miss: ${missReason.summary}`);
310
+ if (missReason.kind === "no-baseline") {
311
+ const untracked = recipe.untrackedLine();
312
+ if (untracked) note(chalk.dim(phaseLine("fingerprint", untracked)));
313
+ }
314
+ }
315
+ async function settleStoreKeyAfterCompile() {
316
+ if (!storeHash || !storeKey) return;
317
+ const identity = await recipe.validate();
318
+ storeHash = identity?.hash ?? null;
319
+ storeKey = identity?.key ?? null;
320
+ buildFailure = {
321
+ ...buildFailure,
322
+ fingerprint: storeHash,
323
+ cacheKey: storeKey
324
+ };
325
+ }
326
+ async function takeBuildSlot() {
327
+ requireLocalBuild(buildMachine);
328
+ if (!maxBuilds) return;
329
+ try {
330
+ buildSlot = await d.acquireBuildSlot({
331
+ automatic: buildMachine === "auto",
332
+ max: maxBuilds,
333
+ root,
334
+ logFile,
335
+ out: note,
336
+ waitingFor: (info) => waitingFor?.(info, "build-slot")
337
+ });
338
+ slotWaitMs = buildSlot.slotWaitMs;
339
+ } catch (e) {
340
+ requireLocalBuild(buildMachine);
341
+ const refusal = (0, ownership_claim_exports.claimFailure)(e, "stim ios");
342
+ if (refusal) fail({
343
+ code: refusal.code,
344
+ message: refusal.message,
345
+ remedy: refusal.remedy,
346
+ build: buildFailure
347
+ });
348
+ note(chalk.yellow(phaseLine("build", `could not take a build slot: ${e?.message || e}; building anyway`)));
349
+ }
350
+ }
351
+ /** Whether this build should leave this Mac, before any machine is asked; null builds here. */
352
+ function placeBuild() {
353
+ const { mode, machines, localEnabled } = buildPlacementCandidates(buildMachine);
354
+ if (localEnabled && machines.length === 0 && !namedBuildMachine(buildMachine) && buildMachine !== "local") return null;
355
+ const { runtime, unsupported } = recipe.offload?.context() ?? {
356
+ runtime: null,
357
+ unsupported: "this project integration does not support offloaded builds"
358
+ };
359
+ const here = machineCapacity();
360
+ const placement = offloadPlacement({
361
+ mode,
362
+ localEnabled,
363
+ machines: machines.length,
364
+ here,
365
+ unsupported,
366
+ selected: buildMachine
367
+ });
368
+ if (!placement.offload) {
369
+ hereReason = placement.reason;
370
+ logWriter().write(buildPlacementRecord({
371
+ platform: "ios",
372
+ buildMachine,
373
+ stays: {
374
+ code: placement.code,
375
+ reason: placement.reason
376
+ }
377
+ }));
378
+ if (mode !== "off") phase("build", `placement: here (${placement.reason})`);
379
+ return null;
380
+ }
381
+ return {
382
+ mode,
383
+ here,
384
+ code: placement.code,
385
+ reason: placement.reason,
386
+ runtime,
387
+ machines
388
+ };
389
+ }
390
+ /** Asks the paired machines once the post-mutation key is known; null builds here. */
391
+ async function chooseMachine(candidate) {
392
+ let asked = [];
393
+ const choice = await chooseBuildMachine({
394
+ projectRoot: root,
395
+ target: recipe.offload.target(candidate.runtime),
396
+ mode: candidate.mode,
397
+ here: candidate.here,
398
+ note: (line) => note(chalk.dim(phaseLine("build", `offload: ${line}`))),
399
+ machines: candidate.machines,
400
+ selected: buildMachine,
401
+ onCandidates: (each) => asked = each
402
+ });
403
+ if (typeof choice === "string") {
404
+ const only = candidate.machines.length === 1 ? candidate.machines[0].machine : null;
405
+ if (only && choice.startsWith(`${only}: `)) fallbackMachine = only;
406
+ fallBack(choice, choice, {
407
+ code: "no-remote-mac-took-it",
408
+ candidates: asked
409
+ });
410
+ return null;
411
+ }
412
+ openOffload.choice = choice;
413
+ logWriter().write(buildPlacementRecord({
414
+ platform: "ios",
415
+ buildMachine,
416
+ candidates: asked,
417
+ chose: {
418
+ machine: choice.machine,
419
+ reason: `${candidate.reason}${placementLoad(choice)}`
420
+ }
421
+ }));
422
+ phase("build", `placement: ${choice.machine} (${candidate.reason}${placementLoad(choice)})`);
423
+ return choice;
424
+ }
425
+ /** Builds on the chosen machine and stores the app under the post-mutation key; false builds here instead unless a machine was named. */
426
+ async function compileElsewhere({ choice, candidate }) {
427
+ const runtime = candidate.runtime;
428
+ if (!storeKey || !storeHash) {
429
+ if (namedBuildMachine(buildMachine)) fallBack("the build fingerprint or cache key is unavailable");
430
+ return false;
431
+ }
432
+ const stagingDir = join(workspaceDir(root), "offload", "ios");
433
+ const outcome = await offloadBuild({
434
+ choice,
435
+ expectedFingerprint: storeHash,
436
+ request: recipe.offload.request(runtime),
437
+ stagingDir,
438
+ onPhase: (name, msg) => phase(name, remotePhaseText(name, msg, choice.machine)),
439
+ onEnter: (name) => {
440
+ if (name === "compile" || name === "build" || name === "prebuild" || name === "pods") {
441
+ builtOn = choice.machine;
442
+ buildFailure = {
443
+ ...buildFailure,
444
+ builtOn
445
+ };
446
+ }
447
+ place({
448
+ host: choice.machine,
449
+ phase: name
450
+ });
451
+ step(name === "prebuild" || name === "pods" ? name : "compile");
452
+ },
453
+ onRecord: (record) => logWriter().write({
454
+ ...record,
455
+ offloadedTo: choice.machine
456
+ }),
457
+ note: (line) => phase("build", line)
458
+ });
459
+ place(null);
460
+ let stored = null;
461
+ let reason = outcome.ok ? null : outcome.reason;
462
+ if (outcome.ok) {
463
+ if (!await recipe.offload.unchanged()) reason = "the checkout here changed while it built";
464
+ else try {
465
+ stored = await recipe.cache().store({
466
+ ...cacheTarget(storeKey),
467
+ sourcePath: outcome.artifactPath,
468
+ overwrite: !useBuildCache
469
+ }) ?? null;
470
+ } catch (e) {
471
+ reason = `could not store the app: ${e?.message || e}`;
472
+ }
473
+ }
474
+ try {
475
+ rmSync(stagingDir, {
476
+ recursive: true,
477
+ force: true
478
+ });
479
+ } catch {}
480
+ step("compile");
481
+ const prepared = stored ? await installableCachedApp(stored) : null;
482
+ if (!outcome.ok || !prepared) {
483
+ const why = reason ?? "the stored app is not installable";
484
+ fallbackMachine = choice.machine;
485
+ fallBack(`${choice.machine}: ${why}`, `offload failed: ${why}`, {
486
+ code: "offload-failed",
487
+ machine: choice.machine
488
+ });
489
+ logWriter().write({
490
+ src: "build",
491
+ level: "warn",
492
+ event: "offload_failed",
493
+ msg: reason,
494
+ machine: choice.machine
495
+ });
496
+ return false;
497
+ }
498
+ const { timings } = outcome;
499
+ appPath = prepared;
500
+ builtOn = outcome.machine;
501
+ offloadedTo = outcome.machine;
502
+ handoff = outcome.handoff ?? null;
503
+ stats.setPlacement({
504
+ decision: "offloaded",
505
+ machine: outcome.machine,
506
+ reason: `${candidate.reason}${placementLoad(choice)}`,
507
+ buildMs: timings.totalMs
508
+ });
509
+ phase("build", `built on ${outcome.machine} in ${formatDuration(timings.totalMs)}: offer ${formatDuration(timings.offerMs)}, sync ${formatDuration(timings.syncMs)}, build ${formatDuration(timings.workerMs)}, fetch ${formatDuration(timings.fetchMs)}`);
510
+ compilationCache = outcome.compilationCache;
511
+ phase("cache", `compilation cache on ${outcome.machine} ${compilationCacheActivityLine(compilationCache)}`);
512
+ logWriter().write({
513
+ src: "build",
514
+ level: "info",
515
+ event: "offload_done",
516
+ msg: `built on ${outcome.machine}`,
517
+ timings
518
+ });
519
+ return true;
520
+ }
521
+ async function prepareSource() {
522
+ await recipe.prepare(() => miss(reasonForMiss([]).reason, true));
523
+ return recipe.reconcile();
524
+ }
525
+ async function revalidateSource({ identity, mutationLabel }) {
526
+ if (!identity) {
527
+ storeHash = null;
528
+ storeKey = null;
529
+ buildFailure = {
530
+ ...buildFailure,
531
+ fingerprint: null,
532
+ cacheKey: null
533
+ };
534
+ } else if (identity.key !== storeKey) {
535
+ storeHash = identity.hash;
536
+ storeKey = identity.key;
537
+ buildFailure = {
538
+ ...buildFailure,
539
+ fingerprint: storeHash,
540
+ cacheKey: storeKey
541
+ };
542
+ const late = useBuildCache ? await recipe.cache().resolve({
543
+ ...cacheTarget(storeKey),
544
+ destinationDir: providerDownloadPath(workspaceDir(root))
545
+ }) : null;
546
+ if (late) {
547
+ const prepared = await installableCachedApp(late);
548
+ if (prepared) {
549
+ appPath = prepared;
550
+ cacheHit = "local";
551
+ lateHit();
552
+ phase("cache", `hit ${shortHash(storeHash)} (post-${mutationLabel} key)`);
553
+ if (releasedWait) {
554
+ waitedForBuild = releasedWait.facts;
555
+ phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
556
+ }
557
+ }
558
+ }
559
+ }
560
+ return appPath ? { ready: appPath } : null;
561
+ }
562
+ async function acquireRemoteArtifact(offload, { rekeyedBy }) {
563
+ explainMiss(rekeyedBy);
564
+ step("compile");
565
+ if (!storeKey) fallBack("no cache key to store the app under", "offload failed: no cache key to store the app under");
566
+ const choice = storeKey ? await chooseMachine(offload) : null;
567
+ if (choice) await compileElsewhere({
568
+ choice,
569
+ candidate: offload
570
+ });
571
+ return appPath ? { ready: appPath } : null;
572
+ }
573
+ async function compileHere(offload, { rekeyedBy }) {
574
+ if (!offload) explainMiss(rekeyedBy);
575
+ step("compile");
576
+ if (offload) stats.setPlacement({
577
+ decision: "fell-back",
578
+ slotWaitMs,
579
+ deviceSlotWaitMs: stats.deviceSlotWaitMs(),
580
+ reason: offloadFallback ?? "offload failed",
581
+ ...fallbackMachine ? { machine: fallbackMachine } : {}
582
+ });
583
+ else stats.setPlacement({
584
+ decision: "here",
585
+ reason: hereReason,
586
+ slotWaitMs,
587
+ deviceSlotWaitMs: stats.deviceSlotWaitMs()
588
+ });
589
+ requireLocalBuild(buildMachine);
590
+ builtOn = "here";
591
+ buildFailure = {
592
+ ...buildFailure,
593
+ builtOn
594
+ };
595
+ phase("build", `compiling ${configuration || "Debug"} with xcodebuild`);
596
+ const result = await recipe.compile();
597
+ compilationCache = result.compilationCache;
598
+ phase("cache", `compilation cache ${compilationCacheActivityLine(compilationCache)}`);
599
+ if (!result.ok) {
600
+ phase("build", `FAILED after ${formatDuration(result.durationMs)}`);
601
+ printDiagnostics(note, result);
602
+ const report = xcodeFailureReport(result, logFile);
603
+ fail({
604
+ code: result.code,
605
+ message: report.message,
606
+ remedy: report.remedy,
607
+ logPath: logFile,
608
+ build: {
609
+ ...buildFailure,
610
+ diagnostics: result.diagnostics
611
+ }
612
+ });
613
+ }
614
+ stats.setBuildMs(result.durationMs);
615
+ phase("build", `ok (${formatDuration(result.durationMs)})`);
616
+ appPath = result.appPath;
617
+ bundleId = result.bundleId;
618
+ return appPath;
619
+ }
620
+ async function storeArtifact(artifactPath) {
621
+ if (storeKey && cachePolicy.write) try {
622
+ const stored = await storeTieredBuild({
623
+ local: recipe.cache(),
624
+ loadProvider,
625
+ target: {
626
+ projectRoot: root,
627
+ platform: "ios",
628
+ key: storeKey
629
+ },
630
+ sourcePath: artifactPath,
631
+ overwrite: !useBuildCache || swapFellBack,
632
+ warn: cacheWarn
633
+ });
634
+ providerUpload = stored.providerUpload;
635
+ providerName = stored.providerName ?? providerName;
636
+ } catch (e) {
637
+ note(chalk.yellow(`Could not store the build in the shared cache: ${e?.message || e}`));
638
+ }
639
+ }
640
+ async function finishArtifact(artifactPath) {
641
+ let installPath = artifactPath;
642
+ const prepared = await recipe.materialize(installPath, {
643
+ fresh: true,
644
+ ownTemporary: (directory) => temporaryDirs.add(directory)
645
+ });
646
+ if (!prepared) fail({
647
+ code: "STIM_INSTALL_FAILED",
648
+ message: "The project integration could not prepare the built app for installation.",
649
+ remedy: "Check the artifact preparation details above, then retry.",
650
+ build: {
651
+ ...buildFailure,
652
+ appPath: installPath
653
+ }
654
+ });
655
+ installPath = prepared;
656
+ if (remote && !physical && storeHash) uploadPending = d.uploadRemote({
657
+ logWriter: logWriter(),
658
+ provider: remote.provider,
659
+ platform: "ios",
660
+ projectRoot: root,
661
+ fingerprintHash: storeHash,
662
+ buildPath: installPath,
663
+ runOptions: recipe.legacyCache?.runOptions ?? null
664
+ });
665
+ return installPath;
666
+ }
667
+ let transferred = false;
668
+ try {
669
+ appPath = await runArtifactLifecycle({
670
+ resolve: async () => {
671
+ await resolveInitialFingerprint();
672
+ if (easBuild) return {
673
+ kind: "ready",
674
+ artifact: appPath
675
+ };
676
+ remote = await resolveRemoteArtifact();
677
+ if (appPath) return {
678
+ kind: "cached",
679
+ artifact: appPath
680
+ };
681
+ miss(reasonForMiss([]).reason);
682
+ return { kind: "miss" };
683
+ },
684
+ claim: useBuildCache ? async () => {
685
+ await awaitSharedBuild();
686
+ return appPath;
687
+ } : void 0,
688
+ reuse: async (cached) => {
689
+ appPath = await prepareCachedArtifact(cached);
690
+ if (appPath) lateHit();
691
+ else if (swapFellBack) miss(reasonForMiss([]).reason);
692
+ return appPath;
693
+ },
694
+ build: {
695
+ placement: {
696
+ select: () => {
697
+ buildFailure = {
698
+ fingerprint,
699
+ cacheKey,
700
+ cacheHit,
701
+ cacheSkipped: !useBuildCache,
702
+ buildMachine
703
+ };
704
+ return placeBuild();
705
+ },
706
+ acquire: acquireRemoteArtifact
707
+ },
708
+ admit: takeBuildSlot,
709
+ prepare: prepareSource,
710
+ revalidate: revalidateSource,
711
+ compile: compileHere,
712
+ validate: async () => {
713
+ await settleStoreKeyAfterCompile();
714
+ return storeKey ? "cacheable" : "uncacheable";
715
+ },
716
+ store: storeArtifact,
717
+ finish: finishArtifact
718
+ },
719
+ release: () => {
720
+ if (openOffload.choice) closeOffload(openOffload.choice);
721
+ releaseLock();
722
+ releaseSlot();
723
+ }
724
+ });
725
+ const artifact = {
726
+ handoff,
727
+ path: appPath,
728
+ bundleId,
729
+ cache: {
730
+ identity: storeHash && storeKey ? {
731
+ fingerprint: storeHash,
732
+ key: storeKey
733
+ } : null,
734
+ hit: cacheHit,
735
+ providerName: remote?.name ?? providerName,
736
+ buildMachine,
737
+ ...builtOn ? { builtOn } : {},
738
+ offloadedTo,
739
+ offloadFallback: offloadedTo ? null : offloadFallback,
740
+ readEnabled: useBuildCache,
741
+ missReason: cacheHit ? null : missReason,
742
+ waitedForBuild,
743
+ compilation: compilationCache
744
+ },
745
+ failureFields: {
746
+ ...buildFailure,
747
+ buildMachine,
748
+ ...builtOn ? { builtOn } : {},
749
+ fingerprint: storeHash,
750
+ cacheKey: storeKey,
751
+ cacheHit,
752
+ cacheSkipped: !useBuildCache
753
+ },
754
+ completeUploads: async () => {
755
+ const uploadWasAbandoned = await finishIosUpload(uploadPending, remote, phase, note);
756
+ const outcome = providerUploadOutcome(providerUpload ? await providerUpload : null, providerName);
757
+ if (outcome) {
758
+ if (outcome.warn) note(chalk.yellow(phaseLine("cache", outcome.line)));
759
+ else phase("cache", outcome.line);
760
+ }
761
+ return abandonedRemote || uploadWasAbandoned;
762
+ },
763
+ release: releaseArtifact
764
+ };
765
+ transferred = true;
766
+ return {
767
+ ok: true,
768
+ artifact
769
+ };
770
+ } catch (error) {
771
+ if (error instanceof OffloadRefusal) {
772
+ const refusal = error;
773
+ return {
774
+ ok: false,
775
+ failure: {
776
+ code: refusal.code,
777
+ message: refusal.message,
778
+ remedy: refusal.remedy,
779
+ build: {
780
+ ...buildFailure,
781
+ buildMachine,
782
+ ...builtOn ? { builtOn } : {}
783
+ }
784
+ },
785
+ compilationCache
786
+ };
787
+ }
788
+ if (error instanceof IosRecipeRefusal) return {
789
+ ok: false,
790
+ failure: {
791
+ ...error.failure,
792
+ build: {
793
+ ...buildFailure,
794
+ ...error.failure.build
795
+ }
796
+ },
797
+ compilationCache
798
+ };
799
+ throw error;
800
+ } finally {
801
+ if (!transferred) releaseArtifact();
802
+ }
803
+ }
804
+ //#endregion
805
+ //#region src/commands/ios/remote.ts
806
+ function resolveIosRemote({ opts, settings, physical, recorded }) {
807
+ const target = opts.remote !== void 0 ? parseIosRemote(opts.remote) : remoteIosSetting(settings);
808
+ if (physical && target?.kind === "machine") return { failure: {
809
+ code: "STIM_BAD_ARG",
810
+ message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
811
+ remedy: "Pass only one of --device and --remote; unset ios.remote for a local phone."
812
+ } };
813
+ const machine = target?.kind === "machine" ? target.machine : null;
814
+ if (recorded && recorded.machine !== machine && machine !== "auto") return { failure: {
815
+ code: "STIM_BAD_ARG",
816
+ message: `This workspace's iOS simulator runs on ${recorded.machine}; run stim stop first.`
817
+ } };
818
+ return {
819
+ machine,
820
+ backend: !physical && target?.kind === "backend" ? target.backend : null,
821
+ recorded
822
+ };
823
+ }
824
+ function selectIosTarget({ root, slot, opts, settings, physical, release, metroCheck, d }) {
825
+ let recorded;
826
+ try {
827
+ recorded = d.readHostedIos(root, slot)[slot];
828
+ } catch (error) {
829
+ return { failure: {
830
+ code: "STIM_HOSTING_REFUSED",
831
+ message: error.message,
832
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
833
+ } };
834
+ }
835
+ const selection = resolveIosRemote({
836
+ opts,
837
+ settings,
838
+ physical,
839
+ recorded
840
+ });
841
+ if ("failure" in selection) return selection;
842
+ const localIos = selection.machine ? deviceSlotPlatforms(d.getProject(root), slot)?.ios : void 0;
843
+ if (selection.machine !== "auto" && selection.machine && localIos?.owned && localIos.deviceUdid) {
844
+ let localRunning = true;
845
+ try {
846
+ localRunning = d.listAllIosSims({ timeoutMs: 5e3 }).some((sim) => sim.udid === localIos.deviceUdid && sim.state !== "Shutdown");
847
+ } catch {}
848
+ if (localRunning) return { failure: {
849
+ code: "STIM_BAD_ARG",
850
+ message: `This workspace's iOS simulator for slot ${slot} runs on this Mac; run stim stop first.`
851
+ } };
852
+ }
853
+ if (selection.machine !== "auto" && selection.machine && !release && !metroCheck) return { failure: {
854
+ code: "STIM_BAD_ARG",
855
+ message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
856
+ remedy: "Run stim stop; stim start, then retry without --no-metro-check."
857
+ } };
858
+ return selection;
859
+ }
860
+ async function selectIosPlacement(args) {
861
+ const selection = selectIosTarget(args);
862
+ if ("failure" in selection || selection.machine !== "auto") return selection;
863
+ const { root, slot, d, opts, deviceType, runtime, noWait, phase, log, release, metroCheck } = args;
864
+ const viewer = resolveSimulatorAppFlag(opts.simulatorApp, args.physical, null);
865
+ if ("refusal" in viewer) return { failure: viewer.refusal };
866
+ const refusal = args.validateSelectors();
867
+ if (refusal) return { failure: refusal };
868
+ const easFallback = remoteEasFallbackSetting(args.settings);
869
+ try {
870
+ const placed = await d.automaticDevicePlacement({
871
+ root,
872
+ slot,
873
+ platform: "ios",
874
+ selectors: hostedIosSelectors(deviceType, runtime),
875
+ buildMachine: opts.remoteBuild,
876
+ noWait,
877
+ ...easFallback ? { eas: () => d.checkEasFallback({
878
+ root,
879
+ platform: "ios",
880
+ slot,
881
+ release,
882
+ isExpo: d.detectIsExpo(root),
883
+ tunnelMode: tunnelModeSetting(args.settings),
884
+ publicUrl: publicUrlSetting(args.settings),
885
+ deviceTypeFlag: opts.deviceType,
886
+ localOnlyFlags: typeof opts.runtime === "string" ? ["--runtime"] : []
887
+ }) } : {}
888
+ });
889
+ phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
890
+ log?.(devicePlacementRecord({
891
+ platform: "ios",
892
+ fromFlag: opts.remote !== void 0,
893
+ easFallback,
894
+ placed
895
+ }));
896
+ if (placed.placement.decision === "eas") return {
897
+ ...selection,
898
+ machine: null,
899
+ backend: "eas",
900
+ recorded: void 0,
901
+ auto: { target: null },
902
+ devicePlacement: placed.placement
903
+ };
904
+ if (placed.target && !release && !metroCheck) return { failure: {
905
+ code: "STIM_BAD_ARG",
906
+ message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
907
+ remedy: "Run stim stop; stim start, then retry without --no-metro-check."
908
+ } };
909
+ return {
910
+ ...selection,
911
+ machine: placed.target?.host.machine ?? null,
912
+ recorded: void 0,
913
+ auto: { target: placed.target },
914
+ budget: placed.target ? {
915
+ reclaimed: [],
916
+ refusal: null
917
+ } : void 0,
918
+ devicePlacement: placed.placement
919
+ };
920
+ } catch (error) {
921
+ return { failure: {
922
+ code: error.code ?? "STIM_HOSTING_REFUSED",
923
+ message: error.message,
924
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
925
+ } };
926
+ }
927
+ }
928
+ function hostedIosSelectors(deviceType, runtime) {
929
+ return {
930
+ ...deviceType ? { deviceType } : {},
931
+ ...runtime ? { runtime } : {}
932
+ };
933
+ }
934
+ async function connectIosTarget(selection, selectors, d) {
935
+ if (selection.auto) return { target: selection.auto.target };
936
+ if (!selection.machine) return { target: null };
937
+ try {
938
+ return { target: await d.prepareHostedIos(selection.machine, selectors, selection.recorded) };
939
+ } catch (error) {
940
+ return { failure: {
941
+ code: error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : "STIM_HOSTING_REFUSED",
942
+ message: error instanceof Error ? error.message : String(error),
943
+ ...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
944
+ } };
945
+ }
946
+ }
947
+ async function iosPlacementBudget(d, root, note, hosted, automatic, simSnapshot) {
948
+ if (automatic) return {
949
+ ...automatic,
950
+ refusal: hosted ? null : automatic.refusal
951
+ };
952
+ return hosted ? {
953
+ reclaimed: [],
954
+ refusal: null
955
+ } : d.budgetGate({
956
+ root,
957
+ note,
958
+ simSnapshot
959
+ });
960
+ }
961
+ function hostedIosMetroNote(hosted, settings, note, simulatorApp) {
962
+ if (hosted && (settings.iosSimulatorApp !== void 0 || simulatorApp !== void 0)) note(chalk.dim(phaseLine("device", "iosSimulatorApp is ignored on a hosting Mac; the simulator boots headless.")));
963
+ if (hosted && (publicUrlSetting(settings) || tunnelModeSetting(settings))) note(chalk.dim(phaseLine("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.")));
964
+ }
965
+ async function connectIosBackend(root, backend, deviceType, d) {
966
+ if (!backend) return {
967
+ remote: null,
968
+ arch: null
969
+ };
970
+ const resolved = await d.resolveRemoteContext({
971
+ root,
972
+ backend,
973
+ easBin: d.resolveEasCliBin(root)?.file ?? null,
974
+ deviceType: deviceType?.trim() || null
975
+ });
976
+ if ("failed" in resolved) return { failure: {
977
+ code: resolved.code ?? "STIM_NO_REMOTE_SESSION",
978
+ message: resolved.failed,
979
+ remedy: resolved.remedy
980
+ } };
981
+ return {
982
+ remote: d.remoteIosDeps(resolved.ctx),
983
+ arch: await d.readRemoteSimulatorArch(resolved.ctx.existingDaemon)
984
+ };
985
+ }
986
+ //#endregion
987
+ //#region src/commands/ios/hosted.ts
988
+ async function finishHostedIosRun({ target, d, artifact, root, slot, release, isExpo, metroCheck, metroPort, logsDir, fail, note, enterPhase, selectors, ...report }) {
989
+ const bundleId = artifact.bundleId ?? (d.readBundleId(artifact.path) || d.detectBundleId(root));
990
+ if (!bundleId) return fail({
991
+ code: "STIM_INSTALL_FAILED",
992
+ message: "The built app has no bundle identifier."
993
+ });
994
+ const launchedAt = d.now();
995
+ let run;
996
+ try {
997
+ run = await d.placeHostedIos(target, {
998
+ root,
999
+ slot,
1000
+ bundle: artifact.path,
1001
+ handoff: artifact.handoff,
1002
+ bundleId,
1003
+ selectors,
1004
+ release,
1005
+ ...!release && isExpo ? { devClientScheme: d.devClientScheme(root, artifact.path) ?? void 0 } : {},
1006
+ reserved: (placement) => d.writeHostedIos(root, slot, placement),
1007
+ note,
1008
+ enterPhase
1009
+ });
1010
+ } catch (error) {
1011
+ return fail({
1012
+ code: error.code ?? "STIM_HOSTING_REFUSED",
1013
+ message: error.message,
1014
+ remedy: error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? error.remedy : `Retry stim ios --remote ${target.host.machine}, or run stim stop to reconcile any recorded placement.`
1015
+ });
1016
+ }
1017
+ d.writeHostedIos(root, slot, run.placement);
1018
+ d.writeWorkspaceLaunch(root, "ios", {
1019
+ appId: bundleId,
1020
+ deviceId: run.placement.session,
1021
+ metroPort,
1022
+ release,
1023
+ launchedAt: new Date(launchedAt).toISOString()
1024
+ }, slot);
1025
+ let launched = run.launched;
1026
+ if (!release && metroCheck) {
1027
+ const evidence = await d.verifyLaunch({
1028
+ requireBundleResponse: true,
1029
+ platform: "ios",
1030
+ logsDir,
1031
+ since: launchedAt,
1032
+ metroPort,
1033
+ mode: isExpo ? MODE_EXPO : MODE_BARE,
1034
+ slot: launchSlotScope(root, slot),
1035
+ platformShared: siblingPlatformSlots(root, "ios", slot).length > 0
1036
+ });
1037
+ if (evidence.fatal) return fail({
1038
+ code: "STIM_LAUNCH_FAILED",
1039
+ message: "Metro could not deliver the hosted app bundle.",
1040
+ remedy: "Run stim logs --errors and fix the bundle error."
1041
+ });
1042
+ launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
1043
+ }
1044
+ if (launched === "unverified") note(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim ios --remote ${target.host.machine}.`);
1045
+ const cache = artifact.cache;
1046
+ const device = run.placement.device;
1047
+ const uploadsAbandoned = await artifact.completeUploads();
1048
+ return {
1049
+ facts: reportIosResult({
1050
+ ...report,
1051
+ root,
1052
+ slot,
1053
+ release,
1054
+ metroCheck,
1055
+ metroPort,
1056
+ logsDir,
1057
+ device: {
1058
+ deviceName: device.deviceType,
1059
+ deviceType: device.deviceType,
1060
+ runtime: device.runtime
1061
+ },
1062
+ udid: "",
1063
+ host: hostedIosStatus(run.placement),
1064
+ appPath: artifact.path,
1065
+ bundleId,
1066
+ installSkipped: false,
1067
+ storeHash: cache.identity?.fingerprint ?? null,
1068
+ storeKey: cache.identity?.key ?? null,
1069
+ cacheHit: cache.hit,
1070
+ missReason: cache.missReason,
1071
+ compilationCache: cache.compilation,
1072
+ useBuildCache: cache.readEnabled,
1073
+ waitedForBuild: cache.waitedForBuild,
1074
+ launchState: launched,
1075
+ providerName: cache.providerName,
1076
+ buildMachine: cache.buildMachine,
1077
+ builtOn: cache.builtOn,
1078
+ offloadedTo: cache.offloadedTo,
1079
+ offloadFallback: cache.offloadFallback,
1080
+ webPreviewUrl: null
1081
+ }),
1082
+ uploadsAbandoned
1083
+ };
1084
+ }
1085
+ //#endregion
1086
+ //#region src/commands/ios/next-build.ts
1087
+ function note(line) {
1088
+ console.error(line);
1089
+ }
1090
+ function runOnlyFlag(opts) {
1091
+ if (isPhysicalDeviceRequest(opts.device)) return "--device";
1092
+ if (opts.remote) return "--remote";
1093
+ if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
1094
+ if (opts.simulatorApp !== void 0) return "--simulator-app";
1095
+ if (opts.metroCheck === false) return "--no-metro-check";
1096
+ return null;
1097
+ }
1098
+ async function planIos(opts, d, schemeRefusal) {
1099
+ const json = Boolean(opts.json);
1100
+ const refuse = (refusal, lines = []) => refusePlan(refusal, json, lines);
1101
+ const flag = runOnlyFlag(opts);
1102
+ if (flag) return refuse(planFlagRefusal(flag));
1103
+ const root = d.findProjectRoot(process.cwd());
1104
+ if (!root) return refuse(NO_PROJECT_REFUSAL);
1105
+ const problem = projectProblem(root, "ios");
1106
+ if (problem) return refuse({
1107
+ code: "STIM_NO_PROJECT",
1108
+ ...problem
1109
+ });
1110
+ const settingsContext = {
1111
+ projectPath: root,
1112
+ gitCommonDir: d.gitCommonDir(root),
1113
+ repoRoot: d.repoRoot(root)
1114
+ };
1115
+ const settings = d.resolveSettings(settingsContext);
1116
+ const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
1117
+ if (shapeError) return refuse({
1118
+ code: "STIM_BAD_ARG",
1119
+ message: shapeError,
1120
+ remedy: SETTING_SHAPE_REMEDY
1121
+ }, moreShapeErrors);
1122
+ let optimizations;
1123
+ try {
1124
+ optimizations = resolveOptimizations(settings);
1125
+ } catch (error) {
1126
+ return refuse({
1127
+ code: "STIM_BAD_ARG",
1128
+ message: error.message,
1129
+ remedy: SETTING_SHAPE_REMEDY
1130
+ });
1131
+ }
1132
+ const cacheProviderError = cacheProviderSettingError(settings);
1133
+ if (cacheProviderError) note(chalk.yellow(phaseLine("cache", `${cacheProviderError} Using the local cache.`)));
1134
+ const slot = opts.slot ?? "default";
1135
+ const target = {
1136
+ platform: "ios",
1137
+ slot,
1138
+ root,
1139
+ projectKey: statsProjectKey$1({
1140
+ root,
1141
+ commonDir: settingsContext.gitCommonDir,
1142
+ repoRoot: settingsContext.repoRoot
1143
+ })
1144
+ };
1145
+ const isExpo = d.detectIsExpo(root);
1146
+ const settingsLayersForOrigin = d.settingsLayers(settingsContext);
1147
+ const iosRemote = remoteIosSetting(settings);
1148
+ const modelRefusal = deviceModelRefusal({
1149
+ slot,
1150
+ deviceTypeFlag: opts.deviceType,
1151
+ runtimeFlag: opts.runtime,
1152
+ deviceType: resolveDeviceType(opts.deviceType, settings),
1153
+ runtime: resolveRuntime(opts.runtime, settings),
1154
+ deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
1155
+ runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
1156
+ physical: false,
1157
+ remoteBackend: iosRemote?.kind === "backend" ? iosRemote.backend : null,
1158
+ hosted: iosRemote?.kind === "machine",
1159
+ listRuntimes: d.listIosRuntimes
1160
+ });
1161
+ if (modelRefusal) return refuse(modelRefusal);
1162
+ if (opts.easProfile !== void 0) {
1163
+ const eas = await planEasDevelopmentBuild({
1164
+ root,
1165
+ platform: "ios",
1166
+ profile: opts.easProfile,
1167
+ note,
1168
+ isExpo,
1169
+ selectors: [opts.scheme, opts.configuration],
1170
+ buildCache: opts.buildCache
1171
+ });
1172
+ if (!eas.ok) return refuse(eas);
1173
+ return printPlan(planPayload(target, {
1174
+ fingerprint: eas.fingerprint,
1175
+ cacheKey: eas.cacheKey,
1176
+ cacheHit: eas.buildId ? "remote" : false,
1177
+ provider: "eas",
1178
+ cacheSkipped: false,
1179
+ prebuild: null,
1180
+ refusal: eas.missing
1181
+ }), json);
1182
+ }
1183
+ if (iosRemote?.kind === "backend") return refuse({
1184
+ code: "STIM_BAD_ARG",
1185
+ message: `ios.remote is ${iosRemote.backend}, whose simulator architecture --plan cannot read without a session.`,
1186
+ remedy: "Run `stim ios` to build for the remote device, or unset ios.remote to plan the owned simulator."
1187
+ });
1188
+ const refusal = schemeRefusal(root, opts.scheme, isExpo);
1189
+ if (refusal) return refuse({
1190
+ code: refusal.code,
1191
+ message: refusal.message ?? "",
1192
+ remedy: refusal.remedy ?? ""
1193
+ });
1194
+ const configuration = resolveConfiguration(opts.configuration, settings);
1195
+ const buildProfile = optimizationBuildProfile("ios", optimizations);
1196
+ const cachePolicy = artifactCachePolicy(optimizations, opts.buildCache !== false, isReleaseConfiguration(configuration));
1197
+ let fingerprint;
1198
+ try {
1199
+ fingerprint = await d.fingerprintProject(root, { platform: "ios" });
1200
+ } catch (error) {
1201
+ note(chalk.dim(`Fingerprinting failed: ${error?.message || error}`));
1202
+ }
1203
+ if (!fingerprint?.hash) return refuse({
1204
+ code: "STIM_NO_FINGERPRINT",
1205
+ message: `Could not fingerprint ${root}: @expo/fingerprint produced no hash for it.`,
1206
+ remedy: "Check the project native inputs and the @expo/fingerprint error above, then retry."
1207
+ });
1208
+ const keyArch = (remoteArch) => simulatorBuildArch({
1209
+ physical: false,
1210
+ remoteArch,
1211
+ hostArch: d.hostSimulatorArch(),
1212
+ configuration
1213
+ });
1214
+ let arch = keyArch(null);
1215
+ let placement;
1216
+ if (iosRemote?.kind === "machine") {
1217
+ const planned = await planHostedDevice({
1218
+ root,
1219
+ slot,
1220
+ platform: "ios",
1221
+ machine: iosRemote.machine,
1222
+ selectors: hostedIosSelectors(resolveDeviceType(opts.deviceType, settings), resolveRuntime(opts.runtime, settings)),
1223
+ sameKey: (choice) => keyArch("runtime" in choice ? choice.architecture : null) === arch,
1224
+ ...remoteEasFallbackSetting(settings) ? { eas: () => d.checkEasFallback({
1225
+ root,
1226
+ platform: "ios",
1227
+ slot,
1228
+ release: isReleaseConfiguration(configuration),
1229
+ isExpo,
1230
+ tunnelMode: tunnelModeSetting(settings),
1231
+ publicUrl: publicUrlSetting(settings),
1232
+ deviceTypeFlag: opts.deviceType,
1233
+ localOnlyFlags: typeof opts.runtime === "string" ? ["--runtime"] : []
1234
+ }) } : {}
1235
+ });
1236
+ if (planned.kind === "unknown") return refuse({
1237
+ code: "STIM_BAD_ARG",
1238
+ message: `ios.remote is ${iosRemote.machine}, and its simulator architecture is unknown here: ${planned.reason}.`,
1239
+ remedy: "Run `stim ios` to build for the remote device, or unset ios.remote to plan the owned simulator."
1240
+ });
1241
+ placement = planned.placement;
1242
+ if (planned.kind === "hosted" && "runtime" in planned.choice) arch = keyArch(planned.choice.architecture);
1243
+ }
1244
+ const cacheKey = buildCacheKey("ios", fingerprint.hash, {
1245
+ scheme: opts.scheme,
1246
+ ...configuration ? { configuration } : {},
1247
+ isSimulator: true,
1248
+ ...arch ? { arch } : {},
1249
+ ...buildProfile ? { buildProfile } : {}
1250
+ });
1251
+ const plan = await planCachedBuild({
1252
+ ...target,
1253
+ isExpo,
1254
+ fingerprint: fingerprint.hash,
1255
+ sources: fingerprint.sources ?? [],
1256
+ cacheKey,
1257
+ cachePolicy,
1258
+ providerConfig: d.resolveCacheProviderConfig(settingsContext),
1259
+ expoRemote: cachePolicy.remote && !buildProfile && !opts.scheme ? { runOptions: iosProviderRunOptions(configuration, arch) } : null
1260
+ }, {
1261
+ loadCacheProvider: d.loadCacheProvider,
1262
+ loadProjectProvider: d.loadProjectProvider,
1263
+ checkEasAuth: d.checkEasAuth,
1264
+ resolveRemote: d.resolveRemote,
1265
+ planPrebuild: d.planPrebuild,
1266
+ note
1267
+ });
1268
+ printPlan(placement ? {
1269
+ ...plan,
1270
+ placement
1271
+ } : plan, json);
1272
+ }
1273
+ //#endregion
1274
+ //#region src/commands/ios.ts
1275
+ function writeNote(line) {
1276
+ console.error(line);
1277
+ }
1278
+ function writePhase(name, text) {
1279
+ console.error(phaseLine(name, text));
1280
+ }
1281
+ function iosCommand(program) {
1282
+ registerIos(program);
1283
+ }
1284
+ function registerIos(program, deps = {}) {
1285
+ program.command("ios").description("Build (or restore from the fingerprint cache), install and launch this workspace's app on its owned simulator, wired to the reserved Metro port. A Debug run starts the dev server when it is not running.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--remote-build <value>", "Build on auto, local, or one named machine; a name refuses without fallback", parseBuildMachineOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--plan", "Predict the next build without building, booting or installing: resolve the fingerprint, check the local then remote cache, and print the expected cache result and duration. Writes no Stim state.").option("--scheme <name>", "Shared Xcode app scheme to build; overrides automatic scheme selection, not the app URL scheme").option("--no-metro-check", "Skip the \"is this workspace's dev server running?\" check: do not start it, and build anyway").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--configuration <name>", "Xcode configuration to build (e.g. Release). A non-Debug configuration embeds the JS bundle and skips Metro entirely. Overrides the ios.configuration setting. Default: Debug").option("--device-type <name>", "Simulator model to create this workspace's owned sim as, exactly as `xcrun simctl list devicetypes` names it (e.g. \"iPad Pro 13-inch (M5)\"). Overrides the ios.deviceType setting for this invocation. A model no installed runtime can create refuses with STIM_BAD_ARG and prints the models they do offer.").option("--runtime <version>", "Simulator runtime to create this workspace's owned sim on, as a version (\"18.5\") or a runtime's full name (\"iOS 18.5\"); nothing else matches. Overrides the ios.runtime setting for this invocation. An unknown version refuses with STIM_BAD_ARG and prints the installed runtimes.").option("--simulator-app <app>", "Open the owned simulator in xcode, siniulator, or stim-desktop for this run, overriding the machine iosSimulatorApp setting. Also opens an already running simulator; local simulators only.").option("--device [udid]", "Build the iphoneos slice for a connected iPhone, install it, and launch it, instead of using this workspace's owned simulator. The phone can be cabled or paired over Wi-Fi. With no UDID, the first cabled device this workspace can lease is used, waiting for one that is busy; a Wi-Fi device is used only when no cabled one is connected and ready. In Debug the app is wired to this workspace's Metro over the LAN. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or an approved Mac in remote.machines; named Macs never fall back locally. auto places on an approved Mac when this Mac is full or busy. local runs here, whatever ios.remote says.", (value) => {
1286
+ if (parseMachine(value)) return value.trim();
1287
+ throw new InvalidArgumentError("expected eas, proxy, auto, local, or a remote Mac name");
1288
+ }).option("--wait <seconds>", "How long to wait for another workspace to release the phone it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Without --device, wait for an owned-device slot (default 600 seconds; --no-wait refuses immediately).").option("--no-wait", "Install on a phone another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Without --device, refuse a full device cap or existing slot queue immediately.").action(async (opts) => {
1289
+ if (opts.plan) {
1290
+ const d = {
1291
+ ...DEFAULT_DEPS,
1292
+ ...deps
1293
+ };
1294
+ await planIos(opts, d, (root, scheme, isExpo) => reactNativeIosSchemeProblem(root, scheme, isExpo, d));
1295
+ return;
1296
+ }
1297
+ const completion = await runIosOperation((deps.findProjectRoot ?? DEFAULT_DEPS.findProjectRoot)(process.cwd()), {
1298
+ ...opts,
1299
+ waitConflict: waitFlagConflict(process.argv)
1300
+ }, deps);
1301
+ if (!completion) process.exit(runCancellation() ? 130 : 1);
1302
+ else if (completion.uploadsAbandoned) exitAfterFlush(0);
1303
+ });
1304
+ }
1305
+ function runIosOperation(root, opts = {}, deps = {}, onFailure = () => {}) {
1306
+ const run = (progress) => runIos(root, opts, deps, progress, onFailure);
1307
+ return root ? withNativeBuildRun(root, {
1308
+ command: "ios",
1309
+ platform: "ios",
1310
+ slot: opts.slot ?? "default"
1311
+ }, async (claim) => {
1312
+ recordWorkspaceUse(root);
1313
+ const progress = startBuildProgress({
1314
+ root,
1315
+ platform: "ios",
1316
+ slot: opts.slot ?? "default",
1317
+ claim,
1318
+ note: (line) => writeNote(chalk.dim(line))
1319
+ });
1320
+ try {
1321
+ return await run(progress);
1322
+ } finally {
1323
+ progress.clear();
1324
+ }
1325
+ }, { write: (line) => writeNote(chalk.dim(phaseLine("lock", line))) }) : run();
1326
+ }
1327
+ function unlessCancelled(result) {
1328
+ if (!result.ok || !runCancellation()) return result;
1329
+ return {
1330
+ ok: false,
1331
+ failure: {
1332
+ code: "STIM_CANCELLED",
1333
+ message: "before install"
1334
+ },
1335
+ compilationCache: result.artifact.cache.compilation
1336
+ };
1337
+ }
1338
+ function resolveIosBuildSetup(flag, settings) {
1339
+ const placement = resolveBuildPlacement(flag);
1340
+ if (placement.failure) return {
1341
+ ok: false,
1342
+ failure: {
1343
+ ...placement.failure,
1344
+ setup: true
1345
+ }
1346
+ };
1347
+ try {
1348
+ return {
1349
+ ok: true,
1350
+ buildMachine: placement.selected,
1351
+ optimizations: resolveOptimizations(settings)
1352
+ };
1353
+ } catch (error) {
1354
+ return {
1355
+ ok: false,
1356
+ failure: {
1357
+ code: "STIM_BAD_ARG",
1358
+ message: error.message,
1359
+ remedy: SETTING_SHAPE_REMEDY
1360
+ }
1361
+ };
1362
+ }
1363
+ }
1364
+ function iosProjectTargetRefusal(project, { physical, easProfile, placement }) {
1365
+ const target = physical ? "physical" : placement ? placement.machine ? "hosted" : placement.backend ? "remote" : "simulator" : null;
1366
+ if (target && !project.targets.includes(target)) return {
1367
+ code: "STIM_BAD_ARG",
1368
+ message: `This project integration does not support the ${target} iOS target.`,
1369
+ remedy: "Use a local iOS simulator for this integration."
1370
+ };
1371
+ if (easProfile !== void 0 && !project.eas) return {
1372
+ code: "STIM_BAD_ARG",
1373
+ message: "This project integration does not support EAS builds.",
1374
+ remedy: "Use its local iOS build recipe."
1375
+ };
1376
+ return null;
1377
+ }
1378
+ function iosArtifactTargetInputs({ udid, physical, remote, hosted, simulatorArch, lanAddress, metroPort, settings }) {
1379
+ return {
1380
+ target: {
1381
+ udid,
1382
+ destination: remote || hosted ? "generic/platform=iOS Simulator" : null,
1383
+ sdk: physical ? "iphoneos" : "iphonesimulator",
1384
+ arch: remote || hosted ? simulatorArch : null,
1385
+ keyArch: simulatorArch,
1386
+ offloadRuntime: () => hosted?.choice.runtimeId ?? (remote ? null : simulatorRuntime(udid)),
1387
+ hostedArchitecture: hosted?.choice.architecture,
1388
+ offloadRefusal: physical ? "device builds build here" : remote ? "--remote builds build here" : null
1389
+ },
1390
+ device: physical ? {
1391
+ lanAddress,
1392
+ metroPort,
1393
+ signingName: iosSigningIdentitySetting(settings),
1394
+ signingSha1: iosSigningIdentitySha1Setting(settings)
1395
+ } : null
1396
+ };
1397
+ }
1398
+ function iosSlotLogFile(root, slot) {
1399
+ return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir$1(root), `build-${deviceSlotFileKey("ios", slot)}.ndjson`);
1400
+ }
1401
+ function iosSlotDeps(d, slot) {
1402
+ if (slot !== "default") {
1403
+ const base = d;
1404
+ d = {
1405
+ ...base,
1406
+ ensureOwnedDevice: (args) => base.ensureOwnedDevice({
1407
+ ...args,
1408
+ slot
1409
+ }),
1410
+ checkDeviceCapacity: (args) => base.checkDeviceCapacity({
1411
+ ...args,
1412
+ slot
1413
+ }),
1414
+ selectFromPool: (args) => base.selectFromPool({
1415
+ ...args,
1416
+ slot
1417
+ }),
1418
+ acquireRunLease: (args) => base.acquireRunLease({
1419
+ ...args,
1420
+ slot
1421
+ }),
1422
+ runLease: (args) => base.runLease({
1423
+ ...args,
1424
+ slot
1425
+ }),
1426
+ replaceCollector: (args) => base.replaceCollector({
1427
+ ...args,
1428
+ slot
1429
+ }),
1430
+ stopPreviousCollector: (args) => base.stopPreviousCollector({
1431
+ ...args,
1432
+ slot
1433
+ }),
1434
+ clearIosAdoptionPending: (root) => base.clearIosAdoptionPending(root, slot),
1435
+ writeWorkspaceLaunch: (root, platform, record) => base.writeWorkspaceLaunch(root, platform, record, slot),
1436
+ createWriter: (file, options) => base.createWriter(file, {
1437
+ ...options,
1438
+ fields: { slot }
1439
+ })
1440
+ };
1441
+ }
1442
+ return d;
1443
+ }
1444
+ async function runIos(projectRoot, opts = {}, overrides = {}, progress = NO_BUILD_PROGRESS, onFailure) {
1445
+ const slot = validateDeviceSlot(opts.slot);
1446
+ let d = iosSlotDeps({
1447
+ ...DEFAULT_DEPS,
1448
+ ...overrides
1449
+ }, slot);
1450
+ const json = Boolean(opts.json);
1451
+ const metroCheck = opts.metroCheck !== false;
1452
+ let useBuildCache = opts.buildCache !== false;
1453
+ const phase = writePhase;
1454
+ const note = writeNote;
1455
+ const started = d.now();
1456
+ const startedAt = new Date(started).toISOString();
1457
+ const elapsed = () => d.now() - started;
1458
+ const refuseProject = ({ message, remedy }) => {
1459
+ note(chalk.red(phaseLine("error", message)));
1460
+ note(chalk.dim(phaseLine("remedy", remedy)));
1461
+ note(chalk.red(phaseLine("failed", "STIM_NO_PROJECT")));
1462
+ if (json) console.log(JSON.stringify({
1463
+ code: "STIM_NO_PROJECT",
1464
+ message,
1465
+ remedy
1466
+ }));
1467
+ onFailure({
1468
+ code: "STIM_NO_PROJECT",
1469
+ message,
1470
+ remedy
1471
+ });
1472
+ return null;
1473
+ };
1474
+ if (!projectRoot) return refuseProject(NO_PROJECT_REFUSAL);
1475
+ const root = projectRoot;
1476
+ const selectedProject = d.projectRegistry.selectIos(root);
1477
+ if ("problem" in selectedProject) return refuseProject(selectedProject.problem);
1478
+ const integration = await selectedProject.load();
1479
+ try {
1480
+ await d.ensureWorkspaceStorage(root, { note });
1481
+ } catch (error) {
1482
+ const code = error?.code || "STIM_WORKSPACE_STATE";
1483
+ const message = `Could not prepare this workspace's Stim state: ${error?.message || error}`;
1484
+ note(chalk.red(`${code}: ${message}`));
1485
+ note(chalk.dim("Check that STIM_HOME is writable and has free space. An EPERM on a directory you can write is a sandbox: allow writes to STIM_HOME, or run Stim with the sandbox disabled (`stim guide errors sandbox`)."));
1486
+ if (json) console.log(JSON.stringify({
1487
+ code,
1488
+ message,
1489
+ remedy: "Check that STIM_HOME is writable and has free space."
1490
+ }));
1491
+ onFailure({
1492
+ code,
1493
+ message,
1494
+ remedy: "Check that STIM_HOME is writable and has free space."
1495
+ });
1496
+ return null;
1497
+ }
1498
+ const logsDir = workspaceLogsDir$1(root);
1499
+ const logFile = iosSlotLogFile(root, slot);
1500
+ let writer = null;
1501
+ const logWriter = () => writer ||= tapBuildLog(d.createWriter(logFile, {
1502
+ truncate: true,
1503
+ fields: {
1504
+ platform: "ios",
1505
+ slot
1506
+ }
1507
+ }), progress);
1508
+ setRemoteLogSink((record) => logWriter().write(record));
1509
+ let leaseHandle = null;
1510
+ let stopLeaseSignals = null;
1511
+ const releaseLease = () => {
1512
+ const held = leaseHandle;
1513
+ const stopSignals = stopLeaseSignals;
1514
+ leaseHandle = null;
1515
+ stopLeaseSignals = null;
1516
+ stopSignals?.();
1517
+ try {
1518
+ held?.release();
1519
+ } catch (e) {
1520
+ note(chalk.dim(`Could not release this run's device lease: ${e?.message || e}`));
1521
+ }
1522
+ };
1523
+ const stats = createRunRecorder({
1524
+ platform: "ios",
1525
+ write: (statsRun, at) => d.recordStats(statsRun, at),
1526
+ now: () => d.now(),
1527
+ note: (line) => note(chalk.dim(line)),
1528
+ phases: () => progress.durations(),
1529
+ deviceSetup: () => progress.deviceSetupKnown()
1530
+ });
1531
+ const deviceWaitRun = createIosDeviceWaitRun(stats, progress, runCancellationSignal());
1532
+ const recordRun = deviceWaitRun.record;
1533
+ let compilationCache = COMPILATION_CACHE_NOT_RUN;
1534
+ let reclaimed = [];
1535
+ let buildMachine = "auto";
1536
+ let devicePlacement;
1537
+ let builtConfiguration = null;
1538
+ const fail = ({ code, message, remedy = null, lines = [], logPath = null, build = null, lease, setup = false }) => {
1539
+ ({code, message, remedy, lines} = cancelledFailure("ios", {
1540
+ code,
1541
+ message
1542
+ }) ?? {
1543
+ code,
1544
+ message,
1545
+ remedy,
1546
+ lines
1547
+ });
1548
+ releaseLease();
1549
+ if (message) note(chalk.red(phaseLine("error", message)));
1550
+ for (const line of lines) note(chalk.dim(phaseLine("", line)));
1551
+ if (remedy) note(chalk.dim(phaseLine("remedy", remedy)));
1552
+ if (logPath) note(chalk.dim(phaseLine("log", logPath)));
1553
+ if (build) writeLastBuild(root, lastBuildRecord({
1554
+ devicePlacement,
1555
+ buildMachine,
1556
+ ...build,
1557
+ configuration: builtConfiguration,
1558
+ startedAt,
1559
+ status: "failed",
1560
+ errorCode: code,
1561
+ durationMs: elapsed()
1562
+ }));
1563
+ note(chalk.red(phaseLine("failed", code)));
1564
+ if (!setup) recordRun({
1565
+ failed: true,
1566
+ durationMs: elapsed()
1567
+ });
1568
+ if (json) console.log(JSON.stringify({
1569
+ code,
1570
+ message: message ?? null,
1571
+ remedy: remedy ?? null,
1572
+ ...devicePlacement ? { devicePlacement } : {},
1573
+ ...compilationCache.status === "not-run" ? {} : { compilationCache },
1574
+ ...lease === void 0 ? {} : { lease },
1575
+ ...reclaimed.length ? { reclaimed } : {}
1576
+ }));
1577
+ writer?.close?.();
1578
+ onFailure({
1579
+ code,
1580
+ message,
1581
+ remedy,
1582
+ lines,
1583
+ logPath,
1584
+ build,
1585
+ lease,
1586
+ setup
1587
+ });
1588
+ return null;
1589
+ };
1590
+ const settingsRepoRoot = d.repoRoot(root);
1591
+ const settingsContext = {
1592
+ projectPath: root,
1593
+ gitCommonDir: d.gitCommonDir(root),
1594
+ repoRoot: settingsRepoRoot
1595
+ };
1596
+ const projectKey = statsProjectKey$1({
1597
+ root,
1598
+ commonDir: settingsContext.gitCommonDir,
1599
+ repoRoot: settingsRepoRoot
1600
+ });
1601
+ stats.setProject(projectKey);
1602
+ progress.estimate(projectKey);
1603
+ let estimatesRead = null;
1604
+ const estimates = () => estimatesRead ??= d.readEstimates({
1605
+ projectKey,
1606
+ platform: "ios"
1607
+ });
1608
+ const settings = d.resolveSettings(settingsContext);
1609
+ const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
1610
+ if (shapeError) return fail({
1611
+ code: "STIM_BAD_ARG",
1612
+ message: shapeError,
1613
+ lines: moreShapeErrors,
1614
+ remedy: SETTING_SHAPE_REMEDY
1615
+ });
1616
+ const setup = resolveIosBuildSetup(opts.remoteBuild, settings);
1617
+ if (!setup.ok) return fail(setup.failure);
1618
+ buildMachine = setup.buildMachine;
1619
+ const { optimizations } = setup;
1620
+ const buildProfile = optimizationBuildProfile("ios", optimizations);
1621
+ const cacheProviderConfig = d.resolveCacheProviderConfig(settingsContext);
1622
+ for (const key of unknownSettingKeys(settings)) note(phaseLine("setting", chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`)));
1623
+ const cacheProviderError = cacheProviderSettingError(settings);
1624
+ if (cacheProviderError) note(chalk.yellow(phaseLine("cache", `${cacheProviderError} Using the local cache.`)));
1625
+ const poolError = parkedMaxSetting("ios").error;
1626
+ if (poolError) return fail({
1627
+ code: "STIM_BAD_ARG",
1628
+ message: poolError,
1629
+ remedy: POOL_SETTING_REMEDY
1630
+ });
1631
+ for (const settingError of [
1632
+ iosSigningIdentitySettingError(settings),
1633
+ iosSigningIdentitySha1SettingError(settings),
1634
+ iosLanHostSettingError(settings)
1635
+ ]) if (settingError) return fail({
1636
+ code: "STIM_BAD_ARG",
1637
+ message: settingError,
1638
+ remedy: SETTING_SHAPE_REMEDY
1639
+ });
1640
+ const configuration = opts.easProfile !== void 0 ? null : resolveConfiguration(opts.configuration, settings);
1641
+ builtConfiguration = configuration ?? "Debug";
1642
+ const buildScheme = opts.scheme;
1643
+ const release = isReleaseConfiguration(configuration);
1644
+ const cachePolicy = artifactCachePolicy(optimizations, useBuildCache, release);
1645
+ useBuildCache = cachePolicy.read;
1646
+ const deviceType = resolveDeviceType(opts.deviceType, settings);
1647
+ const runtime = resolveRuntime(opts.runtime, settings);
1648
+ const deviceFlag = opts.device;
1649
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1650
+ if (physical && deviceFlag === "") return fail({
1651
+ code: "STIM_BAD_ARG",
1652
+ message: "--device was given an empty UDID.",
1653
+ remedy: "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <udid>` to name one."
1654
+ });
1655
+ if (physical && opts.remote) return fail({
1656
+ code: "STIM_BAD_ARG",
1657
+ message: "--device builds for a phone connected to this machine, and --remote installs on a remote one.",
1658
+ remedy: "Pass only one of --device and --remote."
1659
+ });
1660
+ const wait = resolveIosWait(opts, physical);
1661
+ if ("failure" in wait) return fail(wait.failure);
1662
+ const { waitSeconds, noWait, deviceSlotWaitMs, checkCapacity } = wait;
1663
+ const deviceSlotWait = {
1664
+ automatic: false,
1665
+ waitMs: deviceSlotWaitMs,
1666
+ displayName: basename(root),
1667
+ ...deviceWaitRun.policy,
1668
+ onWait: (ms) => {
1669
+ deviceWaitRun.policy.onWait?.(ms);
1670
+ if (devicePlacement?.decision === "local" && ms > 0) devicePlacement.decision = "waited-locally";
1671
+ }
1672
+ };
1673
+ const isExpo = integration.isExpo;
1674
+ const schemeRefusal = integration.schemeProblem(buildScheme);
1675
+ if (schemeRefusal) return fail(schemeRefusal);
1676
+ const capabilityRefusal = iosProjectTargetRefusal(integration, {
1677
+ physical,
1678
+ easProfile: opts.easProfile
1679
+ });
1680
+ if (capabilityRefusal) return fail(capabilityRefusal);
1681
+ const remoteSelection = await selectIosPlacement({
1682
+ root,
1683
+ slot,
1684
+ opts,
1685
+ settings,
1686
+ physical,
1687
+ release,
1688
+ metroCheck,
1689
+ d,
1690
+ deviceType,
1691
+ runtime,
1692
+ validateSelectors: () => deviceModelRefusal({
1693
+ slot,
1694
+ deviceTypeFlag: opts.deviceType,
1695
+ runtimeFlag: opts.runtime,
1696
+ deviceType,
1697
+ runtime,
1698
+ physical,
1699
+ remoteBackend: null,
1700
+ deviceTypeOrigin: d.settingOriginScope(d.settingsLayers(settingsContext), "ios.deviceType"),
1701
+ runtimeOrigin: d.settingOriginScope(d.settingsLayers(settingsContext), "ios.runtime"),
1702
+ listRuntimes: d.listIosRuntimes
1703
+ }),
1704
+ noWait: deviceSlotWaitMs === 0,
1705
+ note,
1706
+ phase,
1707
+ log: (entry) => logWriter().write(entry)
1708
+ });
1709
+ if ("failure" in remoteSelection) return fail(remoteSelection.failure);
1710
+ deviceSlotWait.automatic = Boolean(remoteSelection.auto);
1711
+ const targetRefusal = iosProjectTargetRefusal(integration, {
1712
+ physical,
1713
+ placement: remoteSelection
1714
+ });
1715
+ if (targetRefusal) return fail(targetRefusal);
1716
+ devicePlacement = remoteSelection.devicePlacement;
1717
+ writeDevicePlacement(root, slot, "ios", devicePlacement);
1718
+ const { machine: hostedMachine, backend: remoteBackend } = remoteSelection;
1719
+ const viewer = resolveSimulatorAppFlag(opts.simulatorApp, physical, hostedMachine ?? remoteBackend, Boolean(remoteSelection.auto));
1720
+ if ("refusal" in viewer) return fail(viewer.refusal);
1721
+ const { simulatorApp } = viewer;
1722
+ const settingsLayersForOrigin = d.settingsLayers(settingsContext);
1723
+ const modelRefusal = deviceModelRefusal({
1724
+ slot,
1725
+ deviceTypeFlag: opts.deviceType,
1726
+ runtimeFlag: opts.runtime,
1727
+ deviceType,
1728
+ runtime,
1729
+ deviceTypeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.deviceType"),
1730
+ runtimeOrigin: d.settingOriginScope(settingsLayersForOrigin, "ios.runtime"),
1731
+ physical,
1732
+ remoteBackend,
1733
+ hosted: Boolean(hostedMachine),
1734
+ listRuntimes: d.listIosRuntimes
1735
+ });
1736
+ if (modelRefusal) return fail(modelRefusal);
1737
+ const selectors = hostedIosSelectors(deviceType, runtime);
1738
+ const connected = await connectIosTarget(remoteSelection, selectors, d);
1739
+ if ("failure" in connected) return fail(connected.failure);
1740
+ const hostedTarget = connected.target;
1741
+ try {
1742
+ const simSnapshot = createIosSimSnapshot();
1743
+ const budget = await iosPlacementBudget(d, root, note, Boolean(hostedTarget), remoteSelection.budget, simSnapshot);
1744
+ reclaimed = budget.reclaimed;
1745
+ if (budget.refusal) return fail(budget.refusal);
1746
+ const backendConnection = await connectIosBackend(root, remoteBackend, opts.deviceType, d);
1747
+ if ("failure" in backendConnection) return fail(backendConnection.failure);
1748
+ const remoteDevice = backendConnection.remote;
1749
+ const remoteArch = hostedTarget ? hostedTarget.choice.architecture : backendConnection.arch;
1750
+ if (remoteDevice) d = {
1751
+ ...d,
1752
+ checkDeviceCapacity: remoteDevice.checkDeviceCapacity,
1753
+ ensureOwnedDevice: remoteDevice.ensureOwnedDevice,
1754
+ ensureBooted: remoteDevice.ensureBooted,
1755
+ installIosApp: remoteDevice.installIosApp,
1756
+ launchIosApp: remoteDevice.launchIosApp
1757
+ };
1758
+ const easBuild = await d.resolveEasDevelopmentBuild({
1759
+ root,
1760
+ platform: "ios",
1761
+ profile: opts.easProfile,
1762
+ note,
1763
+ isExpo,
1764
+ physical,
1765
+ selectors: [opts.scheme, opts.configuration],
1766
+ buildCache: opts.buildCache
1767
+ });
1768
+ if (isEasBuildFailure(easBuild)) return fail(easBuild);
1769
+ const registerProject = () => d.upsertProject(root, {
1770
+ bundleId: integration.bundleId() ?? void 0,
1771
+ isExpo
1772
+ });
1773
+ if (remoteBackend !== "eas") registerProject();
1774
+ const proj = d.getProject(root);
1775
+ const limits = d.getConcurrencyLimits();
1776
+ let physicalDevice = null;
1777
+ let wireless = false;
1778
+ if (physical && typeof deviceFlag !== "string") {
1779
+ const pooled = await d.selectFromPool({
1780
+ root,
1781
+ platform: "ios",
1782
+ idLabel: "udid",
1783
+ list: () => iosPoolCandidates(d.listIosDevices()).map((entry) => ({
1784
+ id: entry.udid,
1785
+ name: entry.name,
1786
+ fallback: isWirelessIosDevice(entry)
1787
+ })),
1788
+ noCandidates: () => {
1789
+ const resolved = iosPoolNoCandidatesRefusal(d.listIosDevices());
1790
+ return {
1791
+ message: resolved.error,
1792
+ remedy: resolved.remedy
1793
+ };
1794
+ },
1795
+ waitSeconds,
1796
+ noWait,
1797
+ now: d.now,
1798
+ warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
1799
+ });
1800
+ if (pooled.status === "refused") return fail({
1801
+ code: pooled.refusal.code,
1802
+ message: pooled.refusal.message,
1803
+ remedy: pooled.refusal.remedy,
1804
+ ...pooled.refusal.lease === null ? {} : { lease: pooled.refusal.lease }
1805
+ });
1806
+ physicalDevice = {
1807
+ udid: pooled.candidate.id,
1808
+ name: pooled.candidate.name ?? pooled.candidate.id
1809
+ };
1810
+ wireless = pooled.candidate.fallback === true;
1811
+ } else if (physical) {
1812
+ const resolved = resolveIosPhysicalDevice(typeof deviceFlag === "string" ? deviceFlag : null, d.listIosDevices());
1813
+ if (!resolved.udid) return fail({
1814
+ code: "STIM_NO_DEVICE",
1815
+ message: resolved.error,
1816
+ remedy: resolved.remedy
1817
+ });
1818
+ physicalDevice = {
1819
+ udid: resolved.udid,
1820
+ name: resolved.name ?? resolved.udid
1821
+ };
1822
+ wireless = resolved.wireless === true;
1823
+ }
1824
+ if (checkCapacity && !hostedTarget) {
1825
+ const capacity = d.checkDeviceCapacity({
1826
+ platform: "ios",
1827
+ project: proj,
1828
+ max: limits.maxDevices,
1829
+ sims: () => simSnapshot.read().filter((sim) => sim.available)
1830
+ });
1831
+ if (capacity) {
1832
+ if (capacity.code === "STIM_AT_CAPACITY") d.recordCapacityRefusal({
1833
+ platform: "ios",
1834
+ max: limits.maxDevices,
1835
+ workspace: workspaceId(root)
1836
+ }, d.now());
1837
+ return fail(capacity);
1838
+ }
1839
+ }
1840
+ let metroPort = proj?.metroPort ?? null;
1841
+ let lanAddress = null;
1842
+ let lanOriginUrl = null;
1843
+ let devServer = null;
1844
+ const appRuntime = composeRuntime();
1845
+ const preparation = await appRuntime.prepare();
1846
+ if (!preparation.ok) return fail(preparation.error);
1847
+ const runtimePreparation = preparation.prepared;
1848
+ metroPort = runtimePreparation.metroPort;
1849
+ let device;
1850
+ if (physicalDevice) device = {
1851
+ deviceUdid: physicalDevice.udid,
1852
+ deviceName: physicalDevice.name,
1853
+ owned: false
1854
+ };
1855
+ else if (hostedTarget) device = {
1856
+ owned: false,
1857
+ deviceName: hostedTarget.choice.deviceType,
1858
+ runtime: hostedTarget.choice.runtime
1859
+ };
1860
+ else {
1861
+ const prepare = stepClock(d.now);
1862
+ try {
1863
+ device = await d.ensureOwnedDevice({
1864
+ platform: "ios",
1865
+ project: proj,
1866
+ projectPath: root,
1867
+ settingsRoot: root,
1868
+ settings,
1869
+ flags: {
1870
+ deviceSlotWait,
1871
+ deviceType,
1872
+ runtime,
1873
+ runtimeFlag: resolveRuntime(opts.runtime, null),
1874
+ simulatorApp
1875
+ },
1876
+ note,
1877
+ out: note,
1878
+ simSnapshot
1879
+ });
1880
+ } catch (e) {
1881
+ return fail(ownedSimFailure(e));
1882
+ }
1883
+ deviceWaitRun.booting(device.booting);
1884
+ progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
1885
+ const prepareMs = prepare();
1886
+ if (device.created || prepareMs >= 2e3) phase("device", `${deviceLabel(device, device.deviceUdid)} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
1887
+ }
1888
+ let bootDuration = "";
1889
+ let bootPromise;
1890
+ let udid = "";
1891
+ function composeRuntime() {
1892
+ return d.runtimePlan ?? integration.runtime({
1893
+ configuration,
1894
+ prepareMetro,
1895
+ prepareEmbedded: async () => {
1896
+ phase("metro", `skipped (${configuration}: the JS bundle is embedded, no dev server is used)`);
1897
+ hostedIosMetroNote(Boolean(hostedTarget), settings, note, opts.simulatorApp);
1898
+ return {
1899
+ ok: true,
1900
+ prepared: { metroPort: null }
1901
+ };
1902
+ }
1903
+ });
1904
+ }
1905
+ async function prepareMetro() {
1906
+ if (metroCheck) {
1907
+ const gate = await ensureDevServer({
1908
+ root,
1909
+ port: metroPort,
1910
+ settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
1911
+ remote: Boolean(remoteDevice),
1912
+ note,
1913
+ resolve: d.resolveProjectMetro,
1914
+ start: d.startDevServer,
1915
+ readState: d.readWorkspaceState
1916
+ });
1917
+ reclaimed = [...reclaimed, ...gate.reclaimed];
1918
+ if (!gate.ok) return {
1919
+ ok: false,
1920
+ error: {
1921
+ code: gate.code,
1922
+ message: gate.message,
1923
+ remedy: gate.remedy,
1924
+ lines: gate.lines
1925
+ }
1926
+ };
1927
+ metroPort = gate.port;
1928
+ devServer = gate.devServer;
1929
+ } else {
1930
+ const pin = metroPortSetting(root);
1931
+ if (pin.error) return {
1932
+ ok: false,
1933
+ error: {
1934
+ code: "STIM_BAD_ARG",
1935
+ message: pin.error,
1936
+ remedy: SETTING_SHAPE_REMEDY
1937
+ }
1938
+ };
1939
+ if (pin.port === null && !metroPort) note(chalk.yellow(`No Metro port is reserved for this workspace; wiring the app to ${DEFAULT_METRO_PORT}.`));
1940
+ metroPort = pin.port ?? metroPort ?? 8081;
1941
+ }
1942
+ hostedIosMetroNote(Boolean(hostedTarget), settings, note, opts.simulatorApp);
1943
+ if (physical && metroPort !== null) {
1944
+ const error = await resolveLanOrigin();
1945
+ if (error) return {
1946
+ ok: false,
1947
+ error
1948
+ };
1949
+ }
1950
+ if (remoteDevice && metroPort !== null) {
1951
+ const reachable = await d.ensureMetroReachable({
1952
+ ctx: remoteDevice.ctx,
1953
+ metroPort,
1954
+ isExpo,
1955
+ tunnelMode: tunnelModeSetting(settings) ?? void 0,
1956
+ publicUrl: publicUrlSetting(settings),
1957
+ available: d.detectProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
1958
+ });
1959
+ if ("failed" in reachable) return {
1960
+ ok: false,
1961
+ error: {
1962
+ code: reachable.code ?? "STIM_NO_REMOTE_SESSION",
1963
+ message: reachable.failed,
1964
+ remedy: reachable.remedy
1965
+ }
1966
+ };
1967
+ }
1968
+ if (metroCheck && optimizations.metroWarmup) d.warmMetro({
1969
+ port: metroPort,
1970
+ platform: "ios",
1971
+ isExpo,
1972
+ appId: proj?.bundleId,
1973
+ bundleUrl: metroWarmupUrlSetting(settings, "ios")
1974
+ });
1975
+ return {
1976
+ ok: true,
1977
+ prepared: { metroPort }
1978
+ };
1979
+ }
1980
+ async function resolveLanOrigin() {
1981
+ const port = metroPort;
1982
+ const pinned = iosLanHostSetting(settings);
1983
+ const candidates = d.hostLanCandidates();
1984
+ const chosen = chooseLanAddress({
1985
+ pinned,
1986
+ candidates
1987
+ });
1988
+ if (!chosen) return {
1989
+ code: "STIM_NO_LAN_ADDRESS",
1990
+ message: "A Debug run on a phone needs an address the phone can reach, and this Mac has no non-internal IPv4 interface.",
1991
+ remedy: "The phone reaches Metro over the network you share, because USB carries no reverse forward. Join a Wi-Fi or Ethernet network, or connect this Mac by cable, then run the command again."
1992
+ };
1993
+ lanAddress = chosen.address;
1994
+ lanOriginUrl = lanOriginUrlFor(chosen.address, port);
1995
+ const source = chosen.pinned ? "ios.lanHost" : `${chosen.interfaceName ?? "interface"}${chosen.candidates > 1 ? ` of ${chosen.candidates} candidates` : ""}`;
1996
+ phase("lan", `${lanOriginUrl} (${source})`);
1997
+ if (publicUrlSetting(settings) || tunnelModeSetting(settings)) note(chalk.dim(phaseLine("lan", "metro.publicUrl and metro.tunnel are ignored on --device: neither channel to a phone carries a URL, only a host and a port. They still apply to --remote.")));
1998
+ if (!metroCheck) return null;
1999
+ const reachable = await d.ensureLanReachable({
2000
+ origin: lanOriginUrl,
2001
+ metroPort: port,
2002
+ root,
2003
+ isExpo,
2004
+ logsDir
2005
+ });
2006
+ if ("failed" in reachable) return {
2007
+ code: "STIM_LAN_METRO_UNREACHABLE",
2008
+ message: reachable.failed,
2009
+ remedy: reachable.remedy
2010
+ };
2011
+ phase("lan", `gated: ${lanOriginUrl} answered as this workspace's Metro`);
2012
+ return null;
2013
+ }
2014
+ let artifact = null;
2015
+ try {
2016
+ const boot = () => physicalDevice ? Promise.resolve({
2017
+ ok: true,
2018
+ udid: physicalDevice.udid
2019
+ }) : Promise.resolve(d.ensureBooted({
2020
+ platform: "ios",
2021
+ device,
2022
+ projectPath: root,
2023
+ deviceSlotWait,
2024
+ simulatorApp,
2025
+ out: note
2026
+ })).catch((e) => ({
2027
+ ok: false,
2028
+ reason: String(e?.message || e)
2029
+ }));
2030
+ const startBoot = () => {
2031
+ const bootTimer = stepTimer(d.now);
2032
+ bootPromise = (remoteDevice?.ctx.backend === "eas" ? d.ensureRemoteBootOwned({
2033
+ root,
2034
+ platform: "ios",
2035
+ sessionName: ownedSessionName(remoteDevice.ctx.label),
2036
+ startedAt: new Date(d.now()).toISOString(),
2037
+ deviceType: remoteDevice.ctx.deviceType ?? null,
2038
+ boot,
2039
+ createdSessionId: remoteDevice.createdSessionId,
2040
+ abandonCreatedSession: remoteDevice.abandonCreatedSession,
2041
+ webPreviewUrl: remoteDevice.webPreviewUrl,
2042
+ writeState: d.writeWorkspaceState,
2043
+ register: registerProject,
2044
+ notice: (line) => note(chalk.dim(phaseLine("lock", line)))
2045
+ }) : boot()).then((result) => {
2046
+ bootDuration = bootTimer();
2047
+ return result;
2048
+ });
2049
+ deviceWaitRun.booting({ done: bootPromise });
2050
+ return bootPromise.then((result) => result?.udid ?? "");
2051
+ };
2052
+ const localBoot = remoteDevice || hostedTarget ? null : startBoot();
2053
+ udid = device.deviceUdid ?? await localBoot ?? "";
2054
+ const simulatorArch = simulatorBuildArch({
2055
+ physical,
2056
+ remoteArch,
2057
+ hostArch: d.hostSimulatorArch(),
2058
+ configuration
2059
+ });
2060
+ const acquiredArtifact = unlessCancelled(await acquireIosArtifact({
2061
+ root,
2062
+ logFile,
2063
+ configuration,
2064
+ buildMachine,
2065
+ physical,
2066
+ recipe: integration.artifact({
2067
+ root,
2068
+ logFile,
2069
+ configuration,
2070
+ buildScheme,
2071
+ buildProfile,
2072
+ ...iosArtifactTargetInputs({
2073
+ udid,
2074
+ physical,
2075
+ remote: Boolean(remoteDevice),
2076
+ hosted: hostedTarget,
2077
+ simulatorArch,
2078
+ lanAddress,
2079
+ metroPort,
2080
+ settings
2081
+ }),
2082
+ optimizations: optimizations.ios,
2083
+ easProfile: opts.easProfile,
2084
+ cache: cachePolicy,
2085
+ phase,
2086
+ note,
2087
+ logWriter,
2088
+ estimates,
2089
+ step: progress.step,
2090
+ setPodsMs: (ms) => stats.setPodsMs(ms)
2091
+ }),
2092
+ cache: {
2093
+ policy: cachePolicy,
2094
+ providerConfig: cacheProviderConfig,
2095
+ disabledByFlag: opts.buildCache === false
2096
+ },
2097
+ easBuild,
2098
+ maxBuilds: limits.maxBuilds,
2099
+ progress: {
2100
+ phase,
2101
+ note,
2102
+ logWriter,
2103
+ stats,
2104
+ step: progress.step,
2105
+ miss: progress.miss,
2106
+ hit: progress.hit,
2107
+ place: progress.place,
2108
+ waitingOn: progress.waitingOn,
2109
+ waitingFor: progress.waitingFor
2110
+ }
2111
+ }, d));
2112
+ if (!acquiredArtifact.ok) {
2113
+ compilationCache = acquiredArtifact.compilationCache;
2114
+ return fail(acquiredArtifact.failure);
2115
+ }
2116
+ artifact = acquiredArtifact.artifact;
2117
+ compilationCache = artifact.cache.compilation;
2118
+ if (hostedTarget) return await finishHostedIosRun({
2119
+ target: hostedTarget,
2120
+ root,
2121
+ slot,
2122
+ d,
2123
+ artifact,
2124
+ configuration,
2125
+ buildScheme,
2126
+ release,
2127
+ isExpo,
2128
+ metroCheck,
2129
+ metroPort,
2130
+ logsDir,
2131
+ json,
2132
+ elapsed,
2133
+ startedAt,
2134
+ closeWriter: () => writer?.close(),
2135
+ devicePlacement,
2136
+ recordRun,
2137
+ reclaimed,
2138
+ devServer,
2139
+ fail,
2140
+ note,
2141
+ enterPhase: progress.step,
2142
+ selectors
2143
+ });
2144
+ if (!localBoot) {
2145
+ progress.step("device");
2146
+ udid = await startBoot();
2147
+ }
2148
+ if (physicalDevice) {
2149
+ progress.step("device");
2150
+ const acquired = await d.acquireRunLease({
2151
+ root,
2152
+ platform: "ios",
2153
+ id: physicalDevice.udid,
2154
+ deviceName: physicalDevice.name,
2155
+ idLabel: "udid",
2156
+ waitSeconds,
2157
+ noWait,
2158
+ installBoundMs: iosDeviceBounds(wireless).installMs,
2159
+ appId: artifact.bundleId ?? proj?.bundleId ?? null,
2160
+ holderAppId: (holder) => d.getProject(holder)?.bundleId ?? null,
2161
+ now: d.now,
2162
+ warn: (line) => note(chalk.yellow(phaseLine("lease", line)))
2163
+ });
2164
+ if (acquired.status === "refused") return fail({
2165
+ code: acquired.refusal.code,
2166
+ message: acquired.refusal.message,
2167
+ remedy: acquired.refusal.remedy,
2168
+ lease: acquired.refusal.lease
2169
+ });
2170
+ leaseHandle = d.runLease({
2171
+ root,
2172
+ platform: "ios",
2173
+ kind: acquired.status === "leased" ? acquired.kind : null,
2174
+ expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
2175
+ });
2176
+ if (acquired.status === "leased") {
2177
+ stopLeaseSignals = d.releaseLeaseOnSignal(releaseLease);
2178
+ phase("lease", `${acquired.kind} lease on ${physicalDevice.udid} until ${leaseExpiryText(acquired.expiresAt, d.now())}`);
2179
+ }
2180
+ }
2181
+ try {
2182
+ return await finishIosRun({
2183
+ projectBundleId: integration.bundleId,
2184
+ runtime: appRuntime,
2185
+ runtimePreparation,
2186
+ slot,
2187
+ d,
2188
+ root,
2189
+ json,
2190
+ release,
2191
+ configuration,
2192
+ buildScheme,
2193
+ isExpo,
2194
+ metroCheck,
2195
+ metroPort,
2196
+ logsDir,
2197
+ logFile,
2198
+ device,
2199
+ udid,
2200
+ physical,
2201
+ wireless,
2202
+ lanAddress,
2203
+ lanOriginUrl,
2204
+ remoteDevice,
2205
+ bootPromise,
2206
+ bootDuration: () => bootDuration,
2207
+ artifact,
2208
+ fail,
2209
+ phase,
2210
+ note,
2211
+ logWriter,
2212
+ elapsed,
2213
+ startedAt,
2214
+ closeWriter: () => writer?.close?.(),
2215
+ lease: leaseHandle,
2216
+ releaseLease,
2217
+ devicePlacement,
2218
+ recordRun,
2219
+ reclaimed,
2220
+ devServer,
2221
+ enterPhase: progress.step
2222
+ });
2223
+ } finally {
2224
+ releaseLease();
2225
+ }
2226
+ } catch (error) {
2227
+ recordRun({
2228
+ failed: true,
2229
+ durationMs: elapsed()
2230
+ });
2231
+ throw error;
2232
+ } finally {
2233
+ artifact?.release();
2234
+ }
2235
+ } finally {
2236
+ await deviceWaitRun.finish();
2237
+ hostedTarget?.host.connection.close();
2238
+ }
2239
+ }
2240
+ //#endregion
2241
+ export { registerIos as n, runIosOperation as r, iosCommand as t };