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.
- package/README.md +1 -1
- package/dist/{activity-BxnkDZDi.mjs → activity-BSw0x84R.mjs} +3 -3
- package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-Bjxu_VmX.mjs} +5 -5
- package/dist/{android-BvwuMUJ9.mjs → android-BXc6BmEi.mjs} +507 -347
- package/dist/{android-DC3ZLuw0.mjs → android-BdQC6zs1.mjs} +6 -6
- package/dist/{android-BKA16DTf.mjs → android-BvV1gGdN.mjs} +3 -3
- package/dist/{android-CBwCNicO.mjs → android-DE5g_5gv.mjs} +2 -2
- package/dist/{android-cas-BBFyr9CA.mjs → android-cas-C87geAg-.mjs} +3 -3
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{api-D0VtFhqk.mjs → api-DIf_s86s.mjs} +5 -7
- package/dist/api-run.mjs +8 -8
- package/dist/api.d.mts +8 -2
- package/dist/api.mjs +1 -1
- package/dist/{app-install-D-PUENug.mjs → app-install-CFI4ptUL.mjs} +33 -13
- package/dist/{web-DTU0uiWj.mjs → browser-web-CDhqQxhv.mjs} +134 -172
- package/dist/{budget-DIhL2BKx.mjs → budget-Dz-kyXLv.mjs} +15 -202
- package/dist/{build-facts-DcLv2HSd.d.mts → build-facts-B87vo-Im.d.mts} +32 -1
- package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-DiwKSyv9.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-DJnmnS5q.mjs → chrome-BTUOo8p2.mjs} +1 -1
- package/dist/{cli-DNJMwADx.mjs → cli-C9Zvgbdc.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-DzhapoMB.mjs → client-CuUo8Nvg.mjs} +248 -23
- package/dist/collector-run.d.mts +6 -0
- package/dist/collector-run.mjs +6 -6
- package/dist/{command-output-CBtzyIgF.mjs → command-output-Bt8ygHE7.mjs} +1 -1
- package/dist/{config-CgYMahz7.mjs → config-DC5jEtd1.mjs} +6 -2
- package/dist/{created-devices-DKbeWsiK.mjs → created-devices-D0s26Hx5.mjs} +2 -2
- package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-DKaS1OM9.mjs} +2 -2
- package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-pB_IPfV4.mjs} +1 -1
- package/dist/{dev-client-CLnIH02x.mjs → dev-client-CDY_zAmD.mjs} +2 -2
- package/dist/{device-D7oCHGWi.mjs → device-BHw0WlHl.mjs} +8 -8
- package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-BoN1jQkO.mjs} +23 -12
- package/dist/device-host-worker.mjs +14 -14
- package/dist/{gradle-C6GKnunQ.mjs → device-ios-B2ruohE-.mjs} +659 -105
- package/dist/{device-lease-Co-EwifR.mjs → device-lease-Cu7PtQpZ.mjs} +5 -5
- package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-D38SnZtt.mjs} +4 -4
- package/dist/{device-pool-F5WvRPtD.mjs → device-pool-CP4rAsHx.mjs} +3 -3
- package/dist/{device-remote-tHbd1fKy.mjs → device-remote-B5GiUifk.mjs} +15 -16
- package/dist/{doctor-B9ckYUoU.mjs → doctor-PRJH94v6.mjs} +53 -29
- package/dist/doctor-watchman-wZhyTdp4.mjs +259 -0
- package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics--v5HGOU0.mjs} +10 -10
- package/dist/{exec-DWhCZDGm.mjs → exec-i8GDPwq9.mjs} +32 -8
- package/dist/{gc-Dh6mXOKC.mjs → gc-CyHMcKcn.mjs} +19 -19
- package/dist/{guide-tm6igAaa.mjs → guide-dRaQJJk9.mjs} +409 -472
- package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-Bs7fytj7.mjs} +3 -3
- package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-C5G4h_OJ.mjs} +1 -1
- package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-Bik035eL.mjs} +1 -1
- package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-CI9Az9a4.mjs} +7 -7
- package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-DABm3ScO.mjs} +4 -4
- package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-DbV2aUVw.mjs} +12 -8
- package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DYiMBn34.mjs} +7 -7
- package/dist/{ios-B3SbOWdw.mjs → ios-BWbHFZVz.mjs} +5 -5
- package/dist/{ios-Bvbf0SGY.mjs → ios-DCrkgXEM.mjs} +1 -1
- package/dist/{ios-DNpdygiv.mjs → ios-DYUr9LGc.mjs} +7 -6
- package/dist/ios-ITYno9rO.mjs +2241 -0
- package/dist/{ios-device-vOaByMhw.mjs → ios-device-CJ_ZOGky.mjs} +3 -3
- package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-CrW8aMb5.mjs} +1 -1
- package/dist/{ios-state-B2pES6Vs.mjs → ios-state-By0XuLTQ.mjs} +1 -1
- package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-DCveSE6d.mjs} +4 -4
- package/dist/{logs-1Ylc1Ri6.mjs → logs-Bao1HOMy.mjs} +10 -10
- package/dist/{macos-DF6C7wUU.mjs → macos-ciyBdTY5.mjs} +108 -130
- package/dist/macos-nIOATJmR.mjs +2 -0
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +7 -7
- package/dist/manifest-CX0d1IkZ.mjs +148 -0
- package/dist/{metro-Dh8T4Ytd.mjs → metro-DI2AjPF_.mjs} +194 -41
- package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BxpJLIwh.mjs} +2 -2
- package/dist/{plan-placement-CtxkCO54.mjs → miss-reason-CjQCOKqq.mjs} +180 -296
- package/dist/{named-ports-BFIz1Shz.mjs → named-ports-B4iWH4E-.mjs} +12 -5
- package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BGxsbfny.mjs} +6 -6
- package/dist/offload-worker.d.mts +1 -29
- package/dist/offload-worker.mjs +9 -8
- package/dist/{ownership-D6Se7gg_.mjs → ownership-EB0ySdGK.mjs} +2 -2
- package/dist/{placement-log-BcPhRwR4.mjs → placement-log-Dxe34rJm.mjs} +7 -3
- package/dist/plan-placement-BVY59qa8.mjs +290 -0
- package/dist/{ports-BLTtfBq-.mjs → ports-ByPc0_97.mjs} +3 -3
- package/dist/{prebuild-juCyreaR.mjs → prebuild-BKJ3wJI1.mjs} +4 -4
- package/dist/{preview-D5V07Nrl.mjs → preview-BbSottmO.mjs} +16 -17
- package/dist/project-B5eQ1USs.mjs +612 -0
- package/dist/{pull-request-u4vp7zmK.mjs → pull-request-IHNdMnG8.mjs} +1 -1
- package/dist/pull-requests.mjs +1 -1
- package/dist/react-native-ios-BMh2i25I.mjs +2 -0
- package/dist/react-native-ios-CSpIxexZ.mjs +2083 -0
- package/dist/{recordings-BAWltsVt.mjs → recordings-Bv8McZN4.mjs} +1 -1
- package/dist/{reload-DvyWzVjJ.mjs → reload-rAPdqeRR.mjs} +13 -14
- package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-N3hAEaAL.mjs} +4 -4
- package/dist/{run-vhtDgpHO.mjs → run-lAkle1-c.mjs} +2 -2
- package/dist/{server-bare-B9E7S1aR.mjs → server-bare-CkIjnegq.mjs} +3 -3
- package/dist/server-expo-DGsdUGX2.mjs +2 -0
- package/dist/{server-expo-DoBHhuzK.mjs → server-expo-n3tUi3Em.mjs} +3 -3
- package/dist/{settings-Byp2AQ_E.mjs → settings-2am7TSxp.mjs} +5 -6
- package/dist/{settings-CQyMElEP.mjs → settings-CxgngUZk.mjs} +767 -15
- package/dist/settings.schema.json +72 -6
- package/dist/{simslim-EHBQ2R4G.mjs → simslim-D9LkJxIO.mjs} +5 -5
- package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-HV_KtPIw.mjs} +6 -6
- package/dist/{stage-g_WqWeaE.mjs → stage-BgaZJVZY.mjs} +1 -1
- package/dist/{start-NTc1Ifqs.mjs → start-D8NbxNBb.mjs} +1 -1
- package/dist/{start-D-IeOyoK.mjs → start-DtxthMfI.mjs} +29 -22
- package/dist/{state-BG-aAk4k.mjs → state-D2KtnALG.mjs} +2 -2
- package/dist/{state-xnqpx3AD.mjs → state-Tp4uImDS.mjs} +4 -2
- package/dist/{stats-DY6ekz8F.mjs → stats-8EJ5ivFT.mjs} +6 -6
- package/dist/{status-DCjZeE-K.mjs → status-BOyp0CUl.mjs} +25 -27
- package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-C65jk_q2.mjs} +1 -1
- package/dist/{stim-installations-aR_WsNOA.mjs → stim-installations-DbmUjsLv.mjs} +1 -1
- package/dist/stop-D0UIpZuh.mjs +72 -0
- package/dist/{stop-DIJtD2GL.mjs → stop-DRipAg-e.mjs} +2 -2
- package/dist/{stop-BTjs3xbc.mjs → stop-zDlknFoz.mjs} +78 -31
- package/dist/supervisor-run.mjs +14 -14
- package/dist/{support-BaayeYCf.mjs → support-CNqYkV0y.mjs} +7 -7
- package/dist/{support-Bwvbrq5-.mjs → support-DsNaFg6D.mjs} +5 -5
- package/dist/swiftpm-macos-aJzPAj1t.mjs +119 -0
- package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-C3xSoyUl.mjs} +27 -270
- package/dist/{trigger-CJMhwXe8.mjs → trigger-CAhmG-4s.mjs} +3 -3
- package/dist/trigger-CoalsMTV.mjs +2 -0
- package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-pFLB2e6P.mjs} +133 -36
- package/dist/{status-Dcr0Qexd.mjs → watchman-BL1IdOtq.mjs} +75 -7
- package/dist/web-DFpFeZu5.mjs +2 -0
- package/dist/web-DbbrPUn0.mjs +127 -0
- package/dist/web-run.mjs +7 -7
- package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CR9X86xJ.mjs} +1 -1
- package/dist/{workspaces-xXItD8Dj.mjs → workspaces-Djb2gfi9.mjs} +102 -18
- package/dist/{worktree-BSYlofkq.mjs → worktree-BL4M1rhB.mjs} +45 -42
- package/dist/{worktree-BJfnU6r5.mjs → worktree-DV-A0LNL.mjs} +1 -1
- package/package.json +5 -5
- package/shim/bundle-response.cjs +2 -1
- package/shim/bundle-response.d.cts +1 -1
- package/shim/expo-metro-config.cjs +1 -1
- package/dist/device-ios-6MewKKnc.mjs +0 -412
- package/dist/guide-status-Bhf9SJXc.mjs +0 -125
- package/dist/ios-CPdkoKaa.mjs +0 -3953
- package/dist/macos-Ck7OBMNY.mjs +0 -2
- package/dist/native-run-B06FkmZx.mjs +0 -158
- package/dist/project-C3ChRLZy.mjs +0 -212
- package/dist/projects-DD7dBTCj.mjs +0 -92
- package/dist/server-expo-Db9HBUoa.mjs +0 -2
- package/dist/stop-BGhKMOuH.mjs +0 -48
- package/dist/trigger-DWAq9hkw.mjs +0 -2
- package/dist/web-B6_bFDn7.mjs +0 -2
- 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 };
|