stim 1.20.2 → 1.22.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 +141 -8
- package/dist/acquire-artifact-Co2k4f7O.mjs +171 -0
- package/dist/act-DsuqEkiM.mjs +2801 -0
- package/dist/activity-CMLmzp7o.mjs +1397 -0
- package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-DUFaTMdd.mjs} +23 -67
- package/dist/{android-DCKZrNvV.mjs → android-ByRMUoiJ.mjs} +157 -93
- package/dist/{android-Mz_Inn3w.mjs → android-C7sVmyI9.mjs} +299 -116
- package/dist/{android-Dlzxshgp.mjs → android-DGkNqa_c.mjs} +5 -5
- package/dist/{android-HqP1_vB_.mjs → android-DR5lZ2Az.mjs} +2 -2
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-ngferVq2.mjs} +7 -6
- package/dist/android-project-BCAAmMwz.mjs +12 -0
- package/dist/android-project-DVWyCbWU.d.mts +391 -0
- package/dist/api.d.mts +810 -64
- package/dist/api.mjs +782 -1
- package/dist/{artifact-HSwkIWEQ.mjs → artifact-8EY8SAPw.mjs} +240 -213
- package/dist/{artifact-D3czlUvT.mjs → artifact-DJPy518Y.mjs} +203 -164
- package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
- package/dist/{browser-web-CA-F1GQo.mjs → browser-web-Bcv99PBM.mjs} +194 -64
- package/dist/budget-Cvwn54qV.mjs +9285 -0
- package/dist/{build-DYDbfkk5.mjs → build-CabC3G23.mjs} +153 -98
- package/dist/build-cache-Dv7RN200.mjs +602 -0
- package/dist/build-plan-DUC8Ka9y.mjs +248 -0
- package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DSeQjt-Q.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-zdKiYQ3E.mjs → ccache-Btwc36-I.mjs} +10 -9
- package/dist/{cdp-Dq_ypAlJ.mjs → cdp-3fwICjzi.mjs} +18 -5
- package/dist/{chrome-BnzZMqsR.mjs → chrome-Bbw689-6.mjs} +7 -4
- package/dist/{cli-CLP3diBm.mjs → cli-CHZa9Lvk.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/client-OyRoEuSr.mjs +1577 -0
- package/dist/collector-run.d.mts +4 -40
- package/dist/collector-run.mjs +9 -8
- package/dist/{command-output-BTy8hZ0F.mjs → command-output-gwpm28fu.mjs} +1 -1
- package/dist/config-DlrsduwG.mjs +955 -0
- package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-x9rN6bhs.mjs} +14 -4
- package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-1I9KZT1Y.mjs} +20 -5
- package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
- package/dist/{dev-client-DAWVIGBu.mjs → dev-client-BSDR5h3F.mjs} +2 -2
- package/dist/{device-BECOiufl.mjs → device-B-L3Fbxy.mjs} +91 -34
- package/dist/device-DklWZLqb.mjs +1068 -0
- package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-DK5_EwRS.mjs} +42 -13
- package/dist/device-host-worker.mjs +76 -79
- package/dist/{device-ios-COSi6oS1.mjs → device-ios-DoB6KFCO.mjs} +77 -51
- package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-xJPQ_QQG.mjs} +14 -26
- package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-BMkdsefq.mjs} +5 -5
- package/dist/{doctor-kfMBnUw6.mjs → doctor-Bo7BXLux.mjs} +271 -320
- package/dist/{doctor-CB8oZKCk.mjs → doctor-D5_EzwfA.mjs} +186 -91
- package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
- package/dist/doctor-fingerprint.mjs +19 -0
- package/dist/doctor-thjWAV0z.mjs +2 -0
- package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-N2wDotwQ.mjs} +40 -21
- package/dist/{eas-build-JyfqyHfC.mjs → eas-build-CwknaQYj.mjs} +53 -20
- package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-CSgAulr5.mjs} +41 -214
- package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-DlY_serQ.mjs} +230 -11
- package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-DBWO2rxE.mjs} +172 -82
- package/dist/exec-DEoAhcze.mjs +858 -0
- package/dist/exec-DnYc4HGH.d.mts +78 -0
- package/dist/{gc-qWOMdnER.mjs → gc-CXxN-4SE.mjs} +1091 -1029
- package/dist/gc-DnNDQyaP.mjs +3 -0
- package/dist/{guide-G9cAbVAc.mjs → guide-Br0LEfKy.mjs} +1165 -297
- package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-B-9f-jkd.mjs} +10 -8
- package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DZAPrcro.mjs} +34 -19
- package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
- package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-FIRx9X2f.mjs} +77 -43
- package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-_awYSpfv.mjs} +35 -22
- package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-BdzE668-.mjs} +49 -27
- package/dist/idle-C4cZ757K.d.mts +24 -0
- package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DDg3SdvY.mjs} +11 -10
- package/dist/ios-CDvlmUs7.d.mts +24 -0
- package/dist/{ios-BwVCF7oA.mjs → ios-Ds1clbIt.mjs} +8 -8
- package/dist/{ios-device-D7pi2Sca.mjs → ios-device--h_-8ENa.mjs} +51 -26
- package/dist/{ios-device-B5veSDDQ.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
- package/dist/{ios-RkW4pnQf.mjs → ios-hcBpnEGk.mjs} +200 -93
- package/dist/{ios-Ce7bc7br.mjs → ios-pm4nL6By.mjs} +160 -108
- package/dist/{ios-project-K9hY_Jab.mjs → ios-project-CZhKODF3.mjs} +13 -13
- package/dist/ios-signing-BtAffm0A.mjs +489 -0
- package/dist/{ios-state-CBLe1xYw.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
- package/dist/{ios-nOLJGVdy.mjs → ios-xtlRHaeB.mjs} +1 -1
- package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Bwhh4S7V.mjs} +30 -25
- package/dist/{launch-C3qJ6Agb.mjs → launch-BjxemNpK.mjs} +85 -543
- package/dist/{launch-F4sOqAhB.mjs → launch-CCzXlc8r.mjs} +91 -90
- package/dist/logs-BfwwadsZ.mjs +479 -0
- package/dist/logs-jTS_lHSo.mjs +248 -0
- package/dist/{macos-D7uDvO-z.mjs → macos-DVlSSyUg.mjs} +1 -1
- package/dist/macos-NfV2D7dO.mjs +694 -0
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +123 -248
- package/dist/{metro-DnCEjCxs.mjs → metro-DiXtA3Ty.mjs} +107 -30
- package/dist/metro-gateway-CKZqKqx7.mjs +209 -0
- package/dist/{named-ports-4WopRPg-.mjs → named-ports-DkEzOfzO.mjs} +42 -90
- package/dist/native-android-CqgnOgsT.mjs +645 -0
- package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
- package/dist/native-gradle-inputs-Dnh84Zbd.mjs +211 -0
- package/dist/native-inputs-TRTTTRxt.mjs +91 -0
- package/dist/native-run-NwUuxbkg.mjs +276 -0
- package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-B7KIafpM.mjs} +11 -9
- package/dist/native-source-Y0yLhFyQ.mjs +133 -0
- package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-D_VOfFL7.mjs} +26 -18
- package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-BYDOHWLN.mjs} +104 -60
- package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
- package/dist/offload-worker.d.mts +59 -41
- package/dist/offload-worker.mjs +478 -115
- package/dist/operation-exec-BW0em61G.mjs +171 -0
- package/dist/{ownership-DCtw_2IA.mjs → ownership-C5fo5Edb.mjs} +3 -3
- package/dist/{page-DRHy329N.mjs → page-DBL2PiUU.mjs} +12 -5
- package/dist/paths-BsJMoPP2.mjs +12 -0
- package/dist/paths-DQtTgsBf.mjs +42 -0
- package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BnGgJRRZ.mjs} +5 -5
- package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-DIsMtN-d.mjs} +15 -11
- package/dist/{ports-DdSUicVo.mjs → ports-Cy1RkmuQ.mjs} +3 -3
- package/dist/prebuild-D4JoEAEs.mjs +484 -0
- package/dist/{project-_phNIJfs.mjs → project-wM_XFTvE.mjs} +65 -37
- package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-W1IuLXDt.mjs} +102 -20
- package/dist/pull-request-eODyIqZF.d.mts +124 -0
- package/dist/pull-requests.d.mts +2 -120
- package/dist/pull-requests.mjs +2 -2
- package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-6OZ7MSM7.mjs} +121 -72
- package/dist/{gradle-DSrXb6lG.mjs → react-native-build-Dvd8hC21.mjs} +292 -15
- package/dist/react-native-doctor-DsoSF1Y9.mjs +234 -0
- package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-4u1Rn-GR.mjs} +71 -39
- package/dist/{recordings-D0d5aQt0.mjs → recordings-CQmfzAh7.mjs} +12 -9
- package/dist/registration-BcRgF6Ox.mjs +51 -0
- package/dist/{reload-C8JhxjSh.mjs → reload-BzkYabB-.mjs} +94 -47
- package/dist/reload-D7AVlb8N.mjs +2 -0
- package/dist/{remote-BiR6aYjy.mjs → remote-DjGDmlTZ.mjs} +26 -18
- package/dist/result-D2Ykz_iD.mjs +435 -0
- package/dist/{run-BtPgpKks.mjs → run-DidHghD1.mjs} +5 -5
- package/dist/selection-DSMbWEX2.mjs +620 -0
- package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CafzuyFB.mjs} +3 -4
- package/dist/{server-command-CTtCkmcQ.mjs → server-command-C6QjeupS.mjs} +14 -7
- package/dist/{server-expo-DsguMGUF.mjs → server-expo-CrjB29pq.mjs} +54 -11
- package/dist/server-expo-D58c9L0M.mjs +2 -0
- package/dist/{settings-B717odTD.mjs → settings-C7O8A6ZI.mjs} +309 -119
- package/dist/settings-DpR-1Wnn.mjs +114 -0
- package/dist/settings-write-CzD60rOD.mjs +360 -0
- package/dist/settings.schema.json +176 -16
- package/dist/{simslim-CCr3rE-A.mjs → simslim-BNckWpXi.mjs} +14 -12
- package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-CbfmNgM6.mjs} +15 -20
- package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
- package/dist/stage-Bps5oius.mjs +200 -0
- package/dist/{start-TXeMmmO2.mjs → start-Br-oxqMD.mjs} +1 -1
- package/dist/start-EojN4WFz.mjs +1364 -0
- package/dist/{state-D1Icyjlu.mjs → state--smjFSTL.mjs} +55 -13
- package/dist/{state-CG_OFoaA.mjs → state-08I3hw3_.mjs} +3 -2
- package/dist/{stats-BoY5sEPU.mjs → stats-D3GerlFW.mjs} +7 -13
- package/dist/stats-report-BzaPrRm6.mjs +16 -0
- package/dist/{status-BTA7kKiQ.mjs → status-BRROykC8.mjs} +7 -5
- package/dist/status-EvrK72L2.mjs +2 -0
- package/dist/{status-WgFbkRzj.mjs → status-WBDajew2.mjs} +554 -452
- package/dist/{status-watch-C23ILtf9.mjs → status-watch-Bg_u-pwR.mjs} +222 -25
- package/dist/status-worker.d.mts +1 -0
- package/dist/status-worker.mjs +19 -0
- package/dist/stim-desktop-D5OGpYZM.mjs +112 -0
- package/dist/{stop-DR6VoDIk.mjs → stop-DTZ9uTw3.mjs} +2 -2
- package/dist/{stop-DPEhunlw.mjs → stop-Dky10AEJ.mjs} +33 -23
- package/dist/{stop-BNmBMi1F.mjs → stop-Mv6W9FAk.mjs} +137 -98
- package/dist/supervisor-run.d.mts +8 -17
- package/dist/supervisor-run.mjs +56 -28
- package/dist/{support-CwjbFs9H.mjs → support-CNioewsd.mjs} +14 -14
- package/dist/{support-DMTqj3Hm.mjs → support-yUiOjnVY.mjs} +20 -13
- package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
- package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
- package/dist/toolchain-BY8X8HXx.mjs +2 -0
- package/dist/toolchain-DpJMHq0n.mjs +876 -0
- package/dist/{trigger-CigAYjeL.mjs → trigger-Cbyc41d_.mjs} +21 -6
- package/dist/trigger-yb_nFzPF.mjs +2 -0
- package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-CA3TgT3-.mjs} +263 -156
- package/dist/web-CZ07gd77.mjs +2 -0
- package/dist/{web-BDBMxDpx.mjs → web-DKx6yoFA.mjs} +20 -14
- package/dist/web-run.mjs +13 -12
- package/dist/windows-report.d.mts +1 -0
- package/dist/windows-report.mjs +18 -0
- package/dist/windows-report.ps1 +43 -0
- package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-U08dEzhG.mjs} +62 -58
- package/dist/{workspaces-C36s2jb9.mjs → workspaces-CpANmBlq.mjs} +1723 -1286
- package/dist/{worktree-CgvJYJ-Q.mjs → worktree-CHecZ4Oq.mjs} +1097 -547
- package/dist/worktree-ZqZVjD9K.mjs +2 -0
- package/dist/xcode-BXn9-xYx.mjs +896 -0
- package/package.json +7 -5
- package/shim/android-audio/Main.java +24 -0
- package/shim/android-audio/README.md +17 -0
- package/shim/android-audio.jar +0 -0
- package/shim/android-optimizations.gradle +7 -5
- package/dist/activity-BSV745Kz.mjs +0 -629
- package/dist/api-qvOzWyfO.mjs +0 -154
- package/dist/api-run.mjs +0 -294
- package/dist/app-install-BIKtMfPq.mjs +0 -896
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
- package/dist/budget-f7516Zk0.mjs +0 -4106
- package/dist/build-facts-DcLv2HSd.d.mts +0 -111
- package/dist/cancellation-C_22VQTH.mjs +0 -24
- package/dist/client-qQAaXSUs.mjs +0 -1771
- package/dist/config-BYDxlMY3.mjs +0 -562
- package/dist/device-DuiPJQSn.mjs +0 -290
- package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
- package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
- package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
- package/dist/exec-DqKo9GKJ.mjs +0 -268
- package/dist/git-DDfFzkMm.mjs +0 -45
- package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
- package/dist/logs-BGrEUaEs.mjs +0 -383
- package/dist/macos-Ch5VJmcS.mjs +0 -584
- package/dist/metro-gateway-CX9Xlkq2.mjs +0 -103
- package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
- package/dist/native-android-D5qc3-_v.mjs +0 -226
- package/dist/native-gradle-inputs-BsLRrl7u.mjs +0 -349
- package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
- package/dist/plan-C4TjdPsR.mjs +0 -260
- package/dist/prebuild-DYbucPXy.mjs +0 -212
- package/dist/preview-78a9kR_-.mjs +0 -1011
- package/dist/react-native-build-CtCccJtb.mjs +0 -239
- package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
- package/dist/remote-cache-OyOyVIS3.mjs +0 -526
- package/dist/result-CaJg5Iat.mjs +0 -186
- package/dist/server-expo-Bigriskl.mjs +0 -2
- package/dist/settings-BORNaaCO.mjs +0 -402
- package/dist/settings-vk3mJGSm.d.mts +0 -13
- package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
- package/dist/stage-KNTQopqv.mjs +0 -157
- package/dist/start-BWqzODpb.mjs +0 -1116
- package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
- package/dist/swiftpm-macos-DRNVJWlb.mjs +0 -119
- package/dist/tailnet-Dh-qiYzG.mjs +0 -265
- package/dist/toolchain-BFkAcstd.mjs +0 -2
- package/dist/toolchain-DxSsNssu.mjs +0 -1657
- package/dist/trigger-9_USgmxz.mjs +0 -2
- package/dist/web-Dq-i3wzJ.mjs +0 -2
- package/dist/worktree-DpLdCHga.mjs +0 -2
- package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
- package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
|
@@ -1,1272 +0,0 @@
|
|
|
1
|
-
import { d as getConfigDir } from "./config-BYDxlMY3.mjs";
|
|
2
|
-
import { t as getExecutor } from "./exec-DqKo9GKJ.mjs";
|
|
3
|
-
import { st as planMetroReach } from "./settings-B717odTD.mjs";
|
|
4
|
-
import { E as workspaceStateFile, S as workspaceId, w as workspaceLogsDir$1, x as workspaceDir } from "./workspace-state-DJt_MMvf.mjs";
|
|
5
|
-
import { a as formatElapsed } from "./command-output-BTy8hZ0F.mjs";
|
|
6
|
-
import { N as readRemoteSession, O as clearRemoteSession, j as readMetroTunnel, o as ownedDeviceLabel } from "./project-_phNIJfs.mjs";
|
|
7
|
-
import { s as pidExists } from "./metro-DnCEjCxs.mjs";
|
|
8
|
-
import { N as probePublicHttp } from "./workspaces-C36s2jb9.mjs";
|
|
9
|
-
import { a as withWorkspaceProcessLock } from "./named-ports-4WopRPg-.mjs";
|
|
10
|
-
import { v as nativeRunWaitNotice } from "./tailnet-Dh-qiYzG.mjs";
|
|
11
|
-
import { D as sanitizeDeviceLabel } from "./ios-RkW4pnQf.mjs";
|
|
12
|
-
import { c as readMetroRecords, o as isBundleProof } from "./launch-verify-B6Q8hZP-.mjs";
|
|
13
|
-
import { d as devClientDeepLink, i as INSTALL_ERROR, o as LAUNCH_ERROR } from "./app-install-BIKtMfPq.mjs";
|
|
14
|
-
import { a as removeEasSessionClaim, i as recordEasSessionClaim, n as easMachineStateRoot$1, o as compareStimVersions, r as readEasSessionLedger$1, t as assertEasMachineRootWritable } from "./eas-session-ledger-Cm0Gwq2F.mjs";
|
|
15
|
-
import { existsSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
16
|
-
import { dirname, join, resolve } from "node:path";
|
|
17
|
-
import { readJsonObject } from "@stim-cli/core/state";
|
|
18
|
-
//#region src/engine/metro-gate.ts
|
|
19
|
-
const REMOTE_METRO_WRONG = "STIM_REMOTE_METRO_WRONG";
|
|
20
|
-
const GATE_TIMEOUT_MS = 25e3;
|
|
21
|
-
const POLL_MS = 250;
|
|
22
|
-
function probeBundleUrl(origin, platform, entryPoint = "index") {
|
|
23
|
-
const entry = entryPoint.replace(/^\/+/, "").replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
24
|
-
return `${origin.replace(/\/+$/, "")}/${entry}.bundle?platform=${platform}&dev=true`;
|
|
25
|
-
}
|
|
26
|
-
async function gateMetroOrigin({ origin, metroPort, platform, entryPoint = "index", readRecords, isProof, probe = probePublicHttp, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), timeoutMs = GATE_TIMEOUT_MS }) {
|
|
27
|
-
const since = now();
|
|
28
|
-
const controller = new AbortController();
|
|
29
|
-
const bundleUrl = probeBundleUrl(origin, platform, entryPoint);
|
|
30
|
-
let status = null;
|
|
31
|
-
let pending = false;
|
|
32
|
-
let request = Promise.resolve();
|
|
33
|
-
const startProbe = () => {
|
|
34
|
-
pending = true;
|
|
35
|
-
request = (async () => {
|
|
36
|
-
try {
|
|
37
|
-
status = await probe(bundleUrl, controller.signal);
|
|
38
|
-
} catch {
|
|
39
|
-
status = null;
|
|
40
|
-
} finally {
|
|
41
|
-
pending = false;
|
|
42
|
-
}
|
|
43
|
-
})();
|
|
44
|
-
};
|
|
45
|
-
startProbe();
|
|
46
|
-
try {
|
|
47
|
-
const deadline = now() + timeoutMs;
|
|
48
|
-
while (now() < deadline) {
|
|
49
|
-
for (const record of readRecords()) if (isProof(record, since)) return { ok: true };
|
|
50
|
-
if (!pending && status === null) startProbe();
|
|
51
|
-
await sleep(POLL_MS);
|
|
52
|
-
}
|
|
53
|
-
} finally {
|
|
54
|
-
controller.abort();
|
|
55
|
-
}
|
|
56
|
-
await request;
|
|
57
|
-
return {
|
|
58
|
-
failed: true,
|
|
59
|
-
code: REMOTE_METRO_WRONG,
|
|
60
|
-
reason: describeMiss(origin, metroPort, status),
|
|
61
|
-
remedy: `Check that ${origin} forwards to port ${metroPort} on THIS machine. A tunnel built for a port this workspace no longer holds will answer normally and serve a different project. \`stim start\` prints the port it reserved.`
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function describeMiss(origin, metroPort, status) {
|
|
65
|
-
if (status === null) return `${origin} did not answer, so it cannot be this workspace's Metro (port ${metroPort}).`;
|
|
66
|
-
if (status >= 500) return `${origin} answered ${status}, so it reached a tunnel but not a dev server (port ${metroPort} may no longer be forwarded).`;
|
|
67
|
-
return `${origin} answered ${status}, but the request never reached THIS workspace's Metro on port ${metroPort} -- it is serving a different dev server.`;
|
|
68
|
-
}
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/engine/agent-device.ts
|
|
71
|
-
const DAEMON_TOKEN_ENV = "AGENT_DEVICE_DAEMON_AUTH_TOKEN";
|
|
72
|
-
const PROFILE_FILE = "agent-device.remote.json";
|
|
73
|
-
function sessionNameFor(label) {
|
|
74
|
-
return `stim-${sanitizeDeviceLabel(label)}`;
|
|
75
|
-
}
|
|
76
|
-
function remoteProfilePath(root) {
|
|
77
|
-
return join(workspaceDir(root), PROFILE_FILE);
|
|
78
|
-
}
|
|
79
|
-
function remoteProfile({ daemon, platform, label }) {
|
|
80
|
-
const scope = sessionNameFor(label);
|
|
81
|
-
return {
|
|
82
|
-
daemonBaseUrl: daemon.baseUrl,
|
|
83
|
-
daemonTransport: "http",
|
|
84
|
-
platform,
|
|
85
|
-
session: scope,
|
|
86
|
-
tenant: scope,
|
|
87
|
-
runId: scope,
|
|
88
|
-
sessionIsolation: "tenant",
|
|
89
|
-
...platform === "android" ? {
|
|
90
|
-
leaseProvider: "proxy",
|
|
91
|
-
clientId: scope
|
|
92
|
-
} : {}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function daemonEnv(daemon) {
|
|
96
|
-
return { [DAEMON_TOKEN_ENV]: daemon.token };
|
|
97
|
-
}
|
|
98
|
-
function withProfile(profilePath, args) {
|
|
99
|
-
return [
|
|
100
|
-
...args,
|
|
101
|
-
"--remote-config",
|
|
102
|
-
profilePath
|
|
103
|
-
];
|
|
104
|
-
}
|
|
105
|
-
function connectArgs(profilePath) {
|
|
106
|
-
return withProfile(profilePath, ["connect", "--force"]);
|
|
107
|
-
}
|
|
108
|
-
function installArgs(profilePath, artifactPath) {
|
|
109
|
-
return withProfile(profilePath, ["install", artifactPath]);
|
|
110
|
-
}
|
|
111
|
-
function openArgs(profilePath, bundleId, url, metro = null) {
|
|
112
|
-
const positional = url ? [bundleId, url] : [bundleId];
|
|
113
|
-
const hint = metro ? [
|
|
114
|
-
"--metro-host",
|
|
115
|
-
metro.host,
|
|
116
|
-
"--metro-port",
|
|
117
|
-
metro.port
|
|
118
|
-
] : [];
|
|
119
|
-
return withProfile(profilePath, [
|
|
120
|
-
"open",
|
|
121
|
-
...positional,
|
|
122
|
-
"--relaunch",
|
|
123
|
-
...hint
|
|
124
|
-
]);
|
|
125
|
-
}
|
|
126
|
-
function metroHintFrom(origin) {
|
|
127
|
-
let url;
|
|
128
|
-
try {
|
|
129
|
-
url = new URL(origin);
|
|
130
|
-
} catch {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const port = url.port || (url.protocol === "https:" ? "443" : url.protocol === "http:" ? "80" : "");
|
|
134
|
-
return port ? {
|
|
135
|
-
host: url.hostname,
|
|
136
|
-
port
|
|
137
|
-
} : null;
|
|
138
|
-
}
|
|
139
|
-
function acceptAlertArgs(profilePath) {
|
|
140
|
-
return withProfile(profilePath, ["alert", "accept"]);
|
|
141
|
-
}
|
|
142
|
-
function disconnectArgs(profilePath) {
|
|
143
|
-
return withProfile(profilePath, ["disconnect"]);
|
|
144
|
-
}
|
|
145
|
-
function closeArgs(profilePath) {
|
|
146
|
-
return withProfile(profilePath, ["close"]);
|
|
147
|
-
}
|
|
148
|
-
const HEALTH_TIMEOUT_MS = 3e3;
|
|
149
|
-
function archField(value) {
|
|
150
|
-
if (!value || typeof value !== "object") return null;
|
|
151
|
-
const arch = value.hostArch;
|
|
152
|
-
return arch === "arm64" || arch === "x86_64" ? arch : null;
|
|
153
|
-
}
|
|
154
|
-
/** agent-device proxies report the daemon machine under `upstream`; a daemon reports itself at the top level. */
|
|
155
|
-
function parseHealthHostArch(body) {
|
|
156
|
-
if (!body || typeof body !== "object") return null;
|
|
157
|
-
return archField(body.upstream) ?? archField(body);
|
|
158
|
-
}
|
|
159
|
-
async function readRemoteSimulatorArch(daemon, { fetch: fetchImpl = fetch, timeoutMs = HEALTH_TIMEOUT_MS } = {}) {
|
|
160
|
-
if (!daemon) return "arm64";
|
|
161
|
-
try {
|
|
162
|
-
const response = await fetchImpl(`${daemon.baseUrl.replace(/\/+$/, "")}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
163
|
-
if (!response.ok) return "arm64";
|
|
164
|
-
return parseHealthHostArch(await response.json()) ?? "arm64";
|
|
165
|
-
} catch {
|
|
166
|
-
return "arm64";
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
//#endregion
|
|
170
|
-
//#region src/engine/eas-simulator.ts
|
|
171
|
-
const OWNED_PREFIX = "stim-";
|
|
172
|
-
const LIVE_STATUSES$1 = ["new", "in-progress"];
|
|
173
|
-
const LIVE_SESSION_STATUSES = /* @__PURE__ */ new Set(["NEW", "IN_PROGRESS"]);
|
|
174
|
-
const TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set(["STOPPED", "ERRORED"]);
|
|
175
|
-
const SESSION_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
176
|
-
const MIN_EAS_CLI_SIMULATOR_VERSION = "21.6.0";
|
|
177
|
-
const MIN_EAS_CLI_DEVICE_VERSION = "22.2.0";
|
|
178
|
-
function easCliUpgradeRemedy(minimum) {
|
|
179
|
-
return `Upgrade eas-cli to ${minimum} or later (\`npm install --global eas-cli@latest\`, or the project's eas-cli dependency)`;
|
|
180
|
-
}
|
|
181
|
-
function easCliSupport(versionOutput, minimum) {
|
|
182
|
-
const version = /\beas-cli\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(versionOutput ?? "")?.[1] ?? null;
|
|
183
|
-
const order = version ? compareStimVersions(version, minimum) : null;
|
|
184
|
-
return {
|
|
185
|
-
supported: order !== null && order >= 0,
|
|
186
|
-
version
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function ownedSessionName(label) {
|
|
190
|
-
const clean = sanitizeDeviceLabel(label);
|
|
191
|
-
return `${OWNED_PREFIX}${clean.startsWith(OWNED_PREFIX) ? clean.slice(5) : clean}`;
|
|
192
|
-
}
|
|
193
|
-
function isOwnedSessionName(name) {
|
|
194
|
-
return typeof name === "string" && name.startsWith(OWNED_PREFIX);
|
|
195
|
-
}
|
|
196
|
-
function createSessionArgs({ label, platform, maxDurationMinutes = null, device = null }) {
|
|
197
|
-
const args = [
|
|
198
|
-
"sim",
|
|
199
|
-
"--platform",
|
|
200
|
-
platform,
|
|
201
|
-
"--json",
|
|
202
|
-
"--non-interactive",
|
|
203
|
-
"--out-config-type",
|
|
204
|
-
"env",
|
|
205
|
-
"--name",
|
|
206
|
-
ownedSessionName(label)
|
|
207
|
-
];
|
|
208
|
-
if (maxDurationMinutes) args.push("--max-duration-minutes", String(maxDurationMinutes));
|
|
209
|
-
if (device) args.push("--device", device);
|
|
210
|
-
return args;
|
|
211
|
-
}
|
|
212
|
-
function getSessionArgs(sessionId) {
|
|
213
|
-
return [
|
|
214
|
-
"simulator:get",
|
|
215
|
-
"--id",
|
|
216
|
-
sessionId,
|
|
217
|
-
"--json",
|
|
218
|
-
"--non-interactive"
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
function stopSessionArgs(sessionId) {
|
|
222
|
-
return [
|
|
223
|
-
"simulator:stop",
|
|
224
|
-
"--id",
|
|
225
|
-
sessionId,
|
|
226
|
-
"--json",
|
|
227
|
-
"--non-interactive"
|
|
228
|
-
];
|
|
229
|
-
}
|
|
230
|
-
function listOwnedSessionsArgs(after = null) {
|
|
231
|
-
const args = [
|
|
232
|
-
"simulator:list",
|
|
233
|
-
"--name",
|
|
234
|
-
OWNED_PREFIX,
|
|
235
|
-
...LIVE_STATUSES$1.flatMap((s) => ["--status", s]),
|
|
236
|
-
"--limit",
|
|
237
|
-
"100",
|
|
238
|
-
"--json",
|
|
239
|
-
"--non-interactive"
|
|
240
|
-
];
|
|
241
|
-
if (after) args.push("--after", after);
|
|
242
|
-
return args;
|
|
243
|
-
}
|
|
244
|
-
function remoteDaemonFrom(config) {
|
|
245
|
-
if (!isRecord(config)) return null;
|
|
246
|
-
const typename = str(config["__typename"]);
|
|
247
|
-
if (typename && typename !== "AgentDeviceRunSessionRemoteConfig") return null;
|
|
248
|
-
const baseUrl = str(config.agentDeviceRemoteSessionUrl);
|
|
249
|
-
const token = str(config.agentDeviceRemoteSessionToken);
|
|
250
|
-
if (!baseUrl || !token) return null;
|
|
251
|
-
const daemon = {
|
|
252
|
-
baseUrl,
|
|
253
|
-
token
|
|
254
|
-
};
|
|
255
|
-
const preview = str(config.webPreviewUrl);
|
|
256
|
-
if (preview) daemon.webPreviewUrl = preview;
|
|
257
|
-
return daemon;
|
|
258
|
-
}
|
|
259
|
-
function parseJson(stdout) {
|
|
260
|
-
try {
|
|
261
|
-
return JSON.parse(stdout);
|
|
262
|
-
} catch {
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function isRecord(value) {
|
|
267
|
-
return typeof value === "object" && value !== null;
|
|
268
|
-
}
|
|
269
|
-
function str(value) {
|
|
270
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
271
|
-
}
|
|
272
|
-
function inspectSessionForTeardown(stdout, sessionId) {
|
|
273
|
-
const data = parseJson(stdout);
|
|
274
|
-
if (!isRecord(data)) return {
|
|
275
|
-
action: "refused",
|
|
276
|
-
reason: `Session ${sessionId} lookup did not return valid JSON.`
|
|
277
|
-
};
|
|
278
|
-
const id = str(data.id);
|
|
279
|
-
if (!id) return {
|
|
280
|
-
action: "refused",
|
|
281
|
-
reason: `Session ${sessionId} lookup returned no session id.`
|
|
282
|
-
};
|
|
283
|
-
if (id !== sessionId) return {
|
|
284
|
-
action: "refused",
|
|
285
|
-
reason: `Session ${sessionId} lookup returned different session ${id}.`
|
|
286
|
-
};
|
|
287
|
-
const status = str(data.status);
|
|
288
|
-
if (!status) return {
|
|
289
|
-
action: "refused",
|
|
290
|
-
reason: `Session ${sessionId} lookup returned no status.`
|
|
291
|
-
};
|
|
292
|
-
const name = str(data.name);
|
|
293
|
-
if (!isOwnedSessionName(name)) return {
|
|
294
|
-
action: "refused",
|
|
295
|
-
reason: `Session ${sessionId} is not owned by Stim (name: ${name ?? "missing"}).`
|
|
296
|
-
};
|
|
297
|
-
if (TERMINAL_SESSION_STATUSES.has(status)) return {
|
|
298
|
-
action: "already-stopped",
|
|
299
|
-
name,
|
|
300
|
-
status
|
|
301
|
-
};
|
|
302
|
-
if (!LIVE_SESSION_STATUSES.has(status)) return {
|
|
303
|
-
action: "refused",
|
|
304
|
-
reason: `Session ${sessionId} has unknown status ${status}.`
|
|
305
|
-
};
|
|
306
|
-
return {
|
|
307
|
-
action: "stop",
|
|
308
|
-
name,
|
|
309
|
-
status
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
function isDefinitiveMissingSessionError(error, sessionId) {
|
|
313
|
-
const candidate = error;
|
|
314
|
-
const stderr = typeof candidate?.stderr === "string" ? candidate.stderr : "";
|
|
315
|
-
const message = typeof candidate?.message === "string" ? candidate.message : "";
|
|
316
|
-
const escapedSessionId = sessionId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
317
|
-
const missingSession = new RegExp(`(?:device[\\s-]*run[\\s-]*session|simulator[\\s-]*session)[\\s:#]*\\b${escapedSessionId}\\b[\\s,:]*(?:(?:was\\s+)?not\\s+found|does\\s+not\\s+exist)\\b`, "i");
|
|
318
|
-
return `${stderr}\n${message}`.split(/\r?\n/).map((line) => line.replace(/\s+/g, " ").trim()).some((line) => missingSession.test(line));
|
|
319
|
-
}
|
|
320
|
-
function verifyStoppedSession(stdout, sessionId) {
|
|
321
|
-
const stopped = parseStoppedSession(stdout);
|
|
322
|
-
if (!stopped) return {
|
|
323
|
-
ok: false,
|
|
324
|
-
reason: `Stop for session ${sessionId} did not return valid JSON with an id.`
|
|
325
|
-
};
|
|
326
|
-
if (stopped.id !== sessionId) return {
|
|
327
|
-
ok: false,
|
|
328
|
-
reason: `Stop for session ${sessionId} returned different session ${stopped.id}.`
|
|
329
|
-
};
|
|
330
|
-
if (!stopped.status || !TERMINAL_SESSION_STATUSES.has(stopped.status)) return {
|
|
331
|
-
ok: false,
|
|
332
|
-
reason: `Stop for session ${sessionId} returned unknown status ${stopped.status ?? "missing"}.`
|
|
333
|
-
};
|
|
334
|
-
return { ok: true };
|
|
335
|
-
}
|
|
336
|
-
function parseCreatedSession(stdout) {
|
|
337
|
-
const data = parseJson(stdout);
|
|
338
|
-
if (!isRecord(data)) return null;
|
|
339
|
-
const id = str(data.id);
|
|
340
|
-
if (!id) return null;
|
|
341
|
-
return {
|
|
342
|
-
id,
|
|
343
|
-
name: str(data.name),
|
|
344
|
-
url: str(data.deviceRunSessionUrl),
|
|
345
|
-
daemon: remoteDaemonFrom(data.remoteConfig)
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
function parseSessionListPage(stdout) {
|
|
349
|
-
const data = parseJson(stdout);
|
|
350
|
-
if (!isRecord(data) || !Array.isArray(data.sessions)) return {
|
|
351
|
-
ok: false,
|
|
352
|
-
reason: "EAS session list did not return valid JSON with a sessions array."
|
|
353
|
-
};
|
|
354
|
-
if (!isRecord(data.pageInfo) || typeof data.pageInfo.hasNextPage !== "boolean") return {
|
|
355
|
-
ok: false,
|
|
356
|
-
reason: "EAS session list page did not return valid pagination information."
|
|
357
|
-
};
|
|
358
|
-
const endCursor = data.pageInfo.endCursor;
|
|
359
|
-
if (endCursor !== null && typeof endCursor !== "string") return {
|
|
360
|
-
ok: false,
|
|
361
|
-
reason: "EAS session list returned a malformed pagination cursor."
|
|
362
|
-
};
|
|
363
|
-
return {
|
|
364
|
-
ok: true,
|
|
365
|
-
page: {
|
|
366
|
-
sessions: data.sessions,
|
|
367
|
-
hasNextPage: data.pageInfo.hasNextPage,
|
|
368
|
-
endCursor
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
function findOrphanedOwnedSessions({ sessions, recordedSessionIds, projectScope }) {
|
|
373
|
-
const scope = typeof projectScope === "string" ? projectScope.trim() : "";
|
|
374
|
-
if (!scope) return {
|
|
375
|
-
orphaned: [],
|
|
376
|
-
notices: ["EAS session list has no current-project scope."]
|
|
377
|
-
};
|
|
378
|
-
const recorded = new Set(recordedSessionIds.filter((id) => typeof id === "string" && id.length > 0));
|
|
379
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
380
|
-
const conflicts = /* @__PURE__ */ new Set();
|
|
381
|
-
const blocked = /* @__PURE__ */ new Set();
|
|
382
|
-
const notices = [];
|
|
383
|
-
for (const raw of sessions) {
|
|
384
|
-
if (!isRecord(raw)) {
|
|
385
|
-
notices.push("EAS session list contains a malformed entry.");
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
const id = str(raw.id);
|
|
389
|
-
const name = str(raw.name);
|
|
390
|
-
if (!id) {
|
|
391
|
-
if (isOwnedSessionName(name)) notices.push("EAS session list contains an owned entry with no session id.");
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
if (!name) {
|
|
395
|
-
blocked.add(id);
|
|
396
|
-
candidates.delete(id);
|
|
397
|
-
notices.push(`EAS session ${id} has no name and cannot be classified as Stim-owned.`);
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
if (!isOwnedSessionName(name)) continue;
|
|
401
|
-
const status = str(raw.status)?.toUpperCase().replace(/-/g, "_") ?? null;
|
|
402
|
-
if (!status) {
|
|
403
|
-
blocked.add(id);
|
|
404
|
-
candidates.delete(id);
|
|
405
|
-
notices.push(`Owned EAS session ${id} has no status.`);
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
if (TERMINAL_SESSION_STATUSES.has(status)) continue;
|
|
409
|
-
if (!LIVE_SESSION_STATUSES.has(status)) {
|
|
410
|
-
blocked.add(id);
|
|
411
|
-
candidates.delete(id);
|
|
412
|
-
notices.push(`Owned EAS session ${id} has unknown status ${status}.`);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
const platformValue = str(raw.platform);
|
|
416
|
-
const normalizedPlatform = platformValue?.toLowerCase() ?? null;
|
|
417
|
-
if (!normalizedPlatform || !SESSION_PLATFORMS.has(normalizedPlatform)) {
|
|
418
|
-
blocked.add(id);
|
|
419
|
-
candidates.delete(id);
|
|
420
|
-
notices.push(platformValue ? `Owned EAS session ${id} has unknown platform ${platformValue}.` : `Owned EAS session ${id} has no platform.`);
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
if (recorded.has(id) || blocked.has(id)) continue;
|
|
424
|
-
const candidate = {
|
|
425
|
-
id,
|
|
426
|
-
name,
|
|
427
|
-
status,
|
|
428
|
-
platform: normalizedPlatform,
|
|
429
|
-
projectScope: scope
|
|
430
|
-
};
|
|
431
|
-
const previous = candidates.get(id);
|
|
432
|
-
if (!previous) {
|
|
433
|
-
candidates.set(id, candidate);
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
if (previous.name !== candidate.name || previous.status !== candidate.status || previous.platform !== candidate.platform) {
|
|
437
|
-
conflicts.add(id);
|
|
438
|
-
candidates.delete(id);
|
|
439
|
-
notices.push(`EAS session list contains conflicting entries for session ${id}.`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
for (const id of [...conflicts, ...blocked]) candidates.delete(id);
|
|
443
|
-
return {
|
|
444
|
-
orphaned: [...candidates.values()],
|
|
445
|
-
notices
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
function parseStoppedSession(stdout) {
|
|
449
|
-
const data = parseJson(stdout);
|
|
450
|
-
if (!isRecord(data)) return null;
|
|
451
|
-
const id = str(data.id);
|
|
452
|
-
if (!id) return null;
|
|
453
|
-
return {
|
|
454
|
-
id,
|
|
455
|
-
status: str(data.status)
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
//#endregion
|
|
459
|
-
//#region src/engine/eas-project-lock.ts
|
|
460
|
-
const EAS_PROJECT_LOCK_WAIT_MS = 24e4;
|
|
461
|
-
function describeEasProjectLockHolder(holder, now) {
|
|
462
|
-
const { purpose, workspace } = holder.details;
|
|
463
|
-
const facts = [`pid ${holder.owner.pid}`];
|
|
464
|
-
if (typeof workspace === "string") facts.push(`in ${workspace}`);
|
|
465
|
-
const started = Date.parse(holder.startedAt);
|
|
466
|
-
if (Number.isFinite(started)) facts.push(`running for ${formatElapsed(now - started)}`);
|
|
467
|
-
return `${typeof purpose === "string" ? purpose : "another Stim EAS operation"} (${facts.join(", ")})`;
|
|
468
|
-
}
|
|
469
|
-
function withEasProjectLock(_root, fn, options = {}) {
|
|
470
|
-
const { machineRoot = easMachineStateRoot$1(), ...lockOptions } = options;
|
|
471
|
-
assertEasMachineRootWritable(machineRoot);
|
|
472
|
-
return withWorkspaceProcessLock(machineRoot, "eas-project", fn, {
|
|
473
|
-
waitMs: EAS_PROJECT_LOCK_WAIT_MS,
|
|
474
|
-
...lockOptions,
|
|
475
|
-
external: true
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
//#endregion
|
|
479
|
-
//#region src/engine/device-remote.ts
|
|
480
|
-
const REMOTE_SESSION_ERROR = "STIM_NO_REMOTE_SESSION";
|
|
481
|
-
const REMOTE_METRO_ERROR = "STIM_REMOTE_METRO_UNREACHABLE";
|
|
482
|
-
function resolveMetroOrigin({ metroPort, publicUrl = null, mode = "auto", isExpo = false, available = [] }) {
|
|
483
|
-
const plan = planMetroReach({
|
|
484
|
-
mode,
|
|
485
|
-
metroPort,
|
|
486
|
-
publicUrl,
|
|
487
|
-
isExpo,
|
|
488
|
-
available
|
|
489
|
-
});
|
|
490
|
-
if ("origin" in plan) return plan;
|
|
491
|
-
if ("failed" in plan) return plan;
|
|
492
|
-
return {
|
|
493
|
-
failed: "Metro has no address the device can reach yet.",
|
|
494
|
-
remedy: "This is a Stim bug: the tunnel step did not run before the launch."
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
function describe(err) {
|
|
498
|
-
const e = err;
|
|
499
|
-
const stderr = typeof e?.stderr === "string" ? e.stderr.trim() : "";
|
|
500
|
-
if (err?.code === "ETIMEDOUT") return String(e?.message ?? err);
|
|
501
|
-
return stderr || String(e?.message ?? err);
|
|
502
|
-
}
|
|
503
|
-
const PROXY_CREDENTIAL_ENV = ["AGENT_DEVICE_DAEMON_BASE_URL", "AGENT_DEVICE_DAEMON_AUTH_TOKEN"];
|
|
504
|
-
function easExecOptions(root) {
|
|
505
|
-
return {
|
|
506
|
-
cwd: root,
|
|
507
|
-
omitEnv: PROXY_CREDENTIAL_ENV
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
const EAS_OPERATION_TIMEOUT_MS = 3e4;
|
|
511
|
-
const EAS_SESSION_CREATE_TIMEOUT_MS = 6e5;
|
|
512
|
-
const AGENT_DEVICE_TIMEOUT_MS = 12e4;
|
|
513
|
-
const AGENT_DEVICE_INSTALL_TIMEOUT_MS = 6e5;
|
|
514
|
-
function easBoundedExecOptions(root) {
|
|
515
|
-
return {
|
|
516
|
-
...easExecOptions(root),
|
|
517
|
-
timeoutMs: EAS_OPERATION_TIMEOUT_MS
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
function writeProfile(ctx, daemon) {
|
|
521
|
-
const path = remoteProfilePath(ctx.root);
|
|
522
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
523
|
-
const profile = remoteProfile({
|
|
524
|
-
daemon,
|
|
525
|
-
platform: ctx.platform ?? "ios",
|
|
526
|
-
label: ctx.label
|
|
527
|
-
});
|
|
528
|
-
writeFileSync(path, `${JSON.stringify(profile, null, 2)}\n`);
|
|
529
|
-
return path;
|
|
530
|
-
}
|
|
531
|
-
function remoteDeviceDeps(ctx) {
|
|
532
|
-
let session = null;
|
|
533
|
-
let createdSession = null;
|
|
534
|
-
const easEnv = easExecOptions(ctx.root);
|
|
535
|
-
return {
|
|
536
|
-
checkCapacity: () => null,
|
|
537
|
-
ensureDevice: async () => ({
|
|
538
|
-
deviceName: ctx.backend === "proxy" ? "remote device (your daemon)" : "EAS Simulator",
|
|
539
|
-
owned: true,
|
|
540
|
-
remote: true
|
|
541
|
-
}),
|
|
542
|
-
ensureBooted: async ({ out = () => {} } = {}) => {
|
|
543
|
-
session = null;
|
|
544
|
-
createdSession = null;
|
|
545
|
-
const note = (message) => {
|
|
546
|
-
try {
|
|
547
|
-
out(message);
|
|
548
|
-
} catch {}
|
|
549
|
-
};
|
|
550
|
-
let daemon = ctx.existingDaemon ?? null;
|
|
551
|
-
let id = null;
|
|
552
|
-
let createdHere = null;
|
|
553
|
-
if (!daemon) {
|
|
554
|
-
const recorded = readRemoteSession(ctx.root);
|
|
555
|
-
if (recorded && recorded.platform !== (ctx.platform ?? "ios")) return {
|
|
556
|
-
failed: true,
|
|
557
|
-
code: "STIM_REMOTE_PLATFORM_MISMATCH",
|
|
558
|
-
reason: `Session ${recorded.sessionId} belongs to ${recorded.platform ?? "an unknown platform"}, not ${ctx.platform ?? "ios"}.`,
|
|
559
|
-
remedy: "Run `stim stop` for this workspace before selecting a different remote platform."
|
|
560
|
-
};
|
|
561
|
-
if (recorded) {
|
|
562
|
-
const existing = readLiveDaemon(ctx, recorded.sessionId);
|
|
563
|
-
if (existing && ctx.deviceType && recorded.deviceType !== ctx.deviceType) return {
|
|
564
|
-
failed: true,
|
|
565
|
-
code: "STIM_REMOTE_DEVICE_MISMATCH",
|
|
566
|
-
reason: `EAS Simulator session ${recorded.sessionId} runs ${recorded.deviceType ? `"${recorded.deviceType}"` : "the model EAS chose"}, not "${ctx.deviceType}". Stim will not reuse a session on a different model.`,
|
|
567
|
-
remedy: `Run \`stim stop\` to end that session, then run \`stim ${ctx.platform ?? "ios"} --remote eas --device-type "${ctx.deviceType}"\` again to start one on the requested model.`
|
|
568
|
-
};
|
|
569
|
-
if (existing) {
|
|
570
|
-
note(`Reusing EAS Simulator session ${recorded.sessionId}.`);
|
|
571
|
-
daemon = existing;
|
|
572
|
-
id = recorded.sessionId;
|
|
573
|
-
} else {
|
|
574
|
-
note(`Recorded session ${recorded.sessionId} is not usable; verifying it before replacement.`);
|
|
575
|
-
const cleanup = teardownRemote(ctx, { sessionId: recorded.sessionId });
|
|
576
|
-
if (cleanup.status === "failed" || cleanup.reason) return {
|
|
577
|
-
failed: true,
|
|
578
|
-
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
579
|
-
reason: cleanup.reason ?? `Could not verify recorded EAS session ${recorded.sessionId}.`,
|
|
580
|
-
remedy: "Inspect the recorded session, then run `stim stop` again."
|
|
581
|
-
};
|
|
582
|
-
clearRemoteSession(ctx.root, recorded.sessionId);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (!daemon) {
|
|
587
|
-
note("Creating an EAS Simulator session (this takes a moment).");
|
|
588
|
-
let stdout;
|
|
589
|
-
try {
|
|
590
|
-
stdout = getExecutor().runFile(ctx.easBin, createSessionArgs({
|
|
591
|
-
label: ctx.label,
|
|
592
|
-
platform: ctx.platform ?? "ios",
|
|
593
|
-
maxDurationMinutes: ctx.maxDurationMinutes ?? null,
|
|
594
|
-
device: ctx.deviceType ?? null
|
|
595
|
-
}), {
|
|
596
|
-
...easEnv,
|
|
597
|
-
timeoutMs: EAS_SESSION_CREATE_TIMEOUT_MS,
|
|
598
|
-
killSignal: "SIGKILL"
|
|
599
|
-
});
|
|
600
|
-
} catch (err) {
|
|
601
|
-
if (err?.code === "ETIMEDOUT") {
|
|
602
|
-
const partial = parseCreatedSession(String(err.stdout ?? ""));
|
|
603
|
-
if (partial) return abandonSession(ctx, partial.id, `eas sim did not finish within ${EAS_SESSION_CREATE_TIMEOUT_MS / 6e4} minutes after creating session ${partial.id}.`);
|
|
604
|
-
const name = ownedSessionName(ctx.label);
|
|
605
|
-
return {
|
|
606
|
-
failed: true,
|
|
607
|
-
reason: `eas sim did not finish within ${EAS_SESSION_CREATE_TIMEOUT_MS / 6e4} minutes and the eas process was killed. EAS may still have started session ${name}; it has no Stim ownership claim, so \`stim gc\` will not stop it.`,
|
|
608
|
-
remedy: `Run \`eas simulator:list --name ${name}\` and stop any running session with \`eas simulator:stop --id <id>\`, then retry.`
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
return {
|
|
612
|
-
failed: true,
|
|
613
|
-
reason: `eas sim failed: ${describe(err)}`
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
const created = parseCreatedSession(stdout);
|
|
617
|
-
if (!created) return {
|
|
618
|
-
failed: true,
|
|
619
|
-
reason: "eas sim returned no session; run it by hand to see what it reported."
|
|
620
|
-
};
|
|
621
|
-
id = created.id;
|
|
622
|
-
createdHere = created.id;
|
|
623
|
-
daemon = created.daemon ?? await waitForDaemon(ctx, created.id, note, { sleep: ctx.sleep ?? defaultSleep });
|
|
624
|
-
if (!daemon) return abandonSession(ctx, created.id, `Session ${created.id} never published an agent-device endpoint. It may be an appium or argent session rather than an agent-device one.`);
|
|
625
|
-
}
|
|
626
|
-
let profilePath;
|
|
627
|
-
try {
|
|
628
|
-
profilePath = writeProfile(ctx, daemon);
|
|
629
|
-
} catch (err) {
|
|
630
|
-
const reason = `Could not write the remote connection profile: ${describe(err)}`;
|
|
631
|
-
if (createdHere) return abandonSession(ctx, createdHere, reason);
|
|
632
|
-
return {
|
|
633
|
-
failed: true,
|
|
634
|
-
reason
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
try {
|
|
638
|
-
getExecutor().runFile(ctx.agentDeviceBin, closeArgs(profilePath), {
|
|
639
|
-
cwd: ctx.root,
|
|
640
|
-
env: daemonEnv(daemon),
|
|
641
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
642
|
-
killSignal: "SIGKILL"
|
|
643
|
-
});
|
|
644
|
-
} catch {}
|
|
645
|
-
try {
|
|
646
|
-
getExecutor().runFile(ctx.agentDeviceBin, connectArgs(profilePath), {
|
|
647
|
-
cwd: ctx.root,
|
|
648
|
-
env: daemonEnv(daemon),
|
|
649
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
650
|
-
killSignal: "SIGKILL"
|
|
651
|
-
});
|
|
652
|
-
} catch (err) {
|
|
653
|
-
if (createdHere) return abandonSession(ctx, createdHere, `agent-device connect failed: ${describe(err)}`);
|
|
654
|
-
return {
|
|
655
|
-
failed: true,
|
|
656
|
-
reason: `agent-device connect failed: ${describe(err)}`
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
session = {
|
|
660
|
-
id,
|
|
661
|
-
daemon,
|
|
662
|
-
profilePath
|
|
663
|
-
};
|
|
664
|
-
createdSession = createdHere;
|
|
665
|
-
if (daemon.webPreviewUrl) note(`Watch this device: ${daemon.webPreviewUrl}`);
|
|
666
|
-
return {
|
|
667
|
-
ok: true,
|
|
668
|
-
udid: id ?? daemonHostLabel(daemon.baseUrl)
|
|
669
|
-
};
|
|
670
|
-
},
|
|
671
|
-
installArtifact: (artifactPath) => {
|
|
672
|
-
if (!session) return notConnected(INSTALL_ERROR);
|
|
673
|
-
try {
|
|
674
|
-
getExecutor().runFile(ctx.agentDeviceBin, installArgs(session.profilePath, artifactPath), {
|
|
675
|
-
cwd: ctx.root,
|
|
676
|
-
env: daemonEnv(session.daemon),
|
|
677
|
-
timeoutMs: AGENT_DEVICE_INSTALL_TIMEOUT_MS,
|
|
678
|
-
killSignal: "SIGKILL"
|
|
679
|
-
});
|
|
680
|
-
return {
|
|
681
|
-
ok: true,
|
|
682
|
-
appPath: artifactPath
|
|
683
|
-
};
|
|
684
|
-
} catch (err) {
|
|
685
|
-
return {
|
|
686
|
-
failed: true,
|
|
687
|
-
code: INSTALL_ERROR,
|
|
688
|
-
reason: `agent-device install failed for ${artifactPath}: ${describe(err)}`
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
},
|
|
692
|
-
launchApp: ({ appId, metroPort, devClientScheme = null }) => {
|
|
693
|
-
if (!session) return notConnected(LAUNCH_ERROR);
|
|
694
|
-
if (metroPort === null) try {
|
|
695
|
-
getExecutor().runFile(ctx.agentDeviceBin, openArgs(session.profilePath, appId, null, null), {
|
|
696
|
-
cwd: ctx.root,
|
|
697
|
-
env: daemonEnv(session.daemon),
|
|
698
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
699
|
-
killSignal: "SIGKILL"
|
|
700
|
-
});
|
|
701
|
-
return {
|
|
702
|
-
ok: true,
|
|
703
|
-
mode: "launch"
|
|
704
|
-
};
|
|
705
|
-
} catch (err) {
|
|
706
|
-
return {
|
|
707
|
-
failed: true,
|
|
708
|
-
code: LAUNCH_ERROR,
|
|
709
|
-
reason: `agent-device open ${appId} failed: ${describe(err)}`
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
const origin = resolveMetroOrigin({
|
|
713
|
-
metroPort,
|
|
714
|
-
publicUrl: ctx.publicMetroUrl ?? null,
|
|
715
|
-
mode: ctx.tunnelMode ?? "auto",
|
|
716
|
-
isExpo: ctx.isExpo ?? false
|
|
717
|
-
});
|
|
718
|
-
if ("failed" in origin) return {
|
|
719
|
-
failed: true,
|
|
720
|
-
code: REMOTE_METRO_ERROR,
|
|
721
|
-
reason: `${origin.failed} ${origin.remedy}`
|
|
722
|
-
};
|
|
723
|
-
const url = devClientScheme ? devClientDeepLink(devClientScheme, origin.origin) : null;
|
|
724
|
-
try {
|
|
725
|
-
getExecutor().runFile(ctx.agentDeviceBin, openArgs(session.profilePath, appId, url, metroHintFrom(origin.origin)), {
|
|
726
|
-
cwd: ctx.root,
|
|
727
|
-
env: daemonEnv(session.daemon),
|
|
728
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
729
|
-
killSignal: "SIGKILL"
|
|
730
|
-
});
|
|
731
|
-
if (url) try {
|
|
732
|
-
getExecutor().runFile(ctx.agentDeviceBin, acceptAlertArgs(session.profilePath), {
|
|
733
|
-
cwd: ctx.root,
|
|
734
|
-
env: daemonEnv(session.daemon),
|
|
735
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
736
|
-
killSignal: "SIGKILL"
|
|
737
|
-
});
|
|
738
|
-
} catch {}
|
|
739
|
-
return {
|
|
740
|
-
ok: true,
|
|
741
|
-
mode: url ? "openurl" : "launch",
|
|
742
|
-
url: url ?? void 0,
|
|
743
|
-
jsLocation: origin.origin
|
|
744
|
-
};
|
|
745
|
-
} catch (err) {
|
|
746
|
-
return {
|
|
747
|
-
failed: true,
|
|
748
|
-
code: LAUNCH_ERROR,
|
|
749
|
-
reason: `agent-device open ${appId} failed: ${describe(err)}`
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
},
|
|
753
|
-
createdSessionId: () => createdSession,
|
|
754
|
-
abandonCreatedSession: () => {
|
|
755
|
-
if (!createdSession) return {
|
|
756
|
-
ok: true,
|
|
757
|
-
sessionId: null
|
|
758
|
-
};
|
|
759
|
-
const stopped = stopCreatedSession(ctx, createdSession);
|
|
760
|
-
if (stopped.ok) {
|
|
761
|
-
session = null;
|
|
762
|
-
createdSession = null;
|
|
763
|
-
}
|
|
764
|
-
return stopped;
|
|
765
|
-
},
|
|
766
|
-
webPreviewUrl: () => session?.daemon.webPreviewUrl ?? null,
|
|
767
|
-
failureRemedy: () => ctx.backend === "eas" && session?.id ? `EAS Simulator session ${session.id} is still running and billed until it reaches its time limit or \`stim stop\` ends it. Fix the error above and run this command again to reuse the session, or run \`stim stop\` to end it.` : "Check that the remote daemon is reachable and still has the device, then run this command again."
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
async function resolveRemoteContext({ root, backend, platform = "ios", easBin, env = process.env, lookupAgentDevice = defaultLookupAgentDevice, readEasCliVersion = readInstalledEasCliVersion, maxDurationMinutes = null, deviceType = null }) {
|
|
771
|
-
const agentDeviceBin = lookupAgentDevice();
|
|
772
|
-
if (!agentDeviceBin) return {
|
|
773
|
-
failed: "agent-device is not on PATH, and it is what drives a remote device.",
|
|
774
|
-
remedy: `Install it (\`npm i -g agent-device\`), then run \`stim ${platform} --remote ${backend}\` again.`
|
|
775
|
-
};
|
|
776
|
-
const baseUrl = env.AGENT_DEVICE_DAEMON_BASE_URL?.trim();
|
|
777
|
-
const token = env.AGENT_DEVICE_DAEMON_AUTH_TOKEN?.trim();
|
|
778
|
-
let existingDaemon = null;
|
|
779
|
-
if (backend === "proxy") {
|
|
780
|
-
if (!baseUrl && !token) return {
|
|
781
|
-
failed: "The proxy backend requires AGENT_DEVICE_DAEMON_BASE_URL and AGENT_DEVICE_DAEMON_AUTH_TOKEN.",
|
|
782
|
-
remedy: "Export AGENT_DEVICE_DAEMON_BASE_URL and AGENT_DEVICE_DAEMON_AUTH_TOKEN, then run the device command with `--remote proxy` again.",
|
|
783
|
-
code: "STIM_REMOTE_PROXY_CONFIG"
|
|
784
|
-
};
|
|
785
|
-
if (!baseUrl) return {
|
|
786
|
-
failed: "The proxy backend requires AGENT_DEVICE_DAEMON_BASE_URL.",
|
|
787
|
-
remedy: "Export AGENT_DEVICE_DAEMON_BASE_URL, then run the device command with `--remote proxy` again.",
|
|
788
|
-
code: "STIM_REMOTE_PROXY_CONFIG"
|
|
789
|
-
};
|
|
790
|
-
if (!token) return {
|
|
791
|
-
failed: "The proxy backend requires AGENT_DEVICE_DAEMON_AUTH_TOKEN.",
|
|
792
|
-
remedy: "Export AGENT_DEVICE_DAEMON_AUTH_TOKEN, then run the device command with `--remote proxy` again.",
|
|
793
|
-
code: "STIM_REMOTE_PROXY_CONFIG"
|
|
794
|
-
};
|
|
795
|
-
existingDaemon = {
|
|
796
|
-
baseUrl,
|
|
797
|
-
token
|
|
798
|
-
};
|
|
799
|
-
} else if (!easBin) return {
|
|
800
|
-
failed: "The eas backend requires eas-cli.",
|
|
801
|
-
remedy: "Install eas-cli, then run the device command with `--remote eas` again.",
|
|
802
|
-
code: "STIM_REMOTE_EAS_UNAVAILABLE"
|
|
803
|
-
};
|
|
804
|
-
else {
|
|
805
|
-
const versionOutput = readEasCliVersion(easBin, root);
|
|
806
|
-
const { supported, version } = easCliSupport(versionOutput, MIN_EAS_CLI_SIMULATOR_VERSION);
|
|
807
|
-
if (!supported) return {
|
|
808
|
-
failed: version ? `eas-cli ${version} (${easBin}) has no EAS Simulator commands; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.` : `Could not read an eas-cli version from \`${easBin} --version\`; the eas backend needs eas-cli ${MIN_EAS_CLI_SIMULATOR_VERSION} or later.`,
|
|
809
|
-
remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_SIMULATOR_VERSION)}, then run the device command with \`--remote eas\` again.`,
|
|
810
|
-
code: "STIM_REMOTE_EAS_UNAVAILABLE"
|
|
811
|
-
};
|
|
812
|
-
if (deviceType && !easCliSupport(versionOutput, "22.2.0").supported) return {
|
|
813
|
-
failed: `eas-cli ${version} (${easBin}) cannot choose the EAS Simulator model; --device-type on the eas backend needs eas-cli ${MIN_EAS_CLI_DEVICE_VERSION} or later.`,
|
|
814
|
-
remedy: `${easCliUpgradeRemedy(MIN_EAS_CLI_DEVICE_VERSION)}, or drop --device-type to let EAS pick the model.`,
|
|
815
|
-
code: "STIM_BAD_ARG"
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
return { ctx: {
|
|
819
|
-
root,
|
|
820
|
-
label: `${ownedDeviceLabel(root)}-${workspaceId(root)}`,
|
|
821
|
-
backend,
|
|
822
|
-
platform,
|
|
823
|
-
easBin: easBin ?? "",
|
|
824
|
-
agentDeviceBin,
|
|
825
|
-
maxDurationMinutes,
|
|
826
|
-
deviceType: backend === "eas" ? deviceType : null,
|
|
827
|
-
publicMetroUrl: null,
|
|
828
|
-
existingDaemon
|
|
829
|
-
} };
|
|
830
|
-
}
|
|
831
|
-
function readInstalledEasCliVersion(easBin, root) {
|
|
832
|
-
try {
|
|
833
|
-
return getExecutor().runFile(easBin, ["--version"], easBoundedExecOptions(root));
|
|
834
|
-
} catch {
|
|
835
|
-
return null;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
function binOnPath(bin) {
|
|
839
|
-
try {
|
|
840
|
-
return Boolean(getExecutor().findExecutable(bin));
|
|
841
|
-
} catch {
|
|
842
|
-
return false;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
function defaultLookupAgentDevice() {
|
|
846
|
-
const found = getExecutor().findExecutable("agent-device");
|
|
847
|
-
return (String(found ?? "").split("\n")[0] ?? "").trim() || null;
|
|
848
|
-
}
|
|
849
|
-
const DAEMON_WAIT_MS = 18e4;
|
|
850
|
-
const DAEMON_POLL_MS = 5e3;
|
|
851
|
-
async function waitForDaemon(ctx, sessionId, out, { sleep = defaultSleep } = {}) {
|
|
852
|
-
const attempts = Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS);
|
|
853
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
854
|
-
const daemon = readDaemon(ctx, sessionId);
|
|
855
|
-
if (daemon) return daemon;
|
|
856
|
-
if (attempt === 0) out(`Waiting for session ${sessionId} to become reachable.`);
|
|
857
|
-
await sleep(DAEMON_POLL_MS);
|
|
858
|
-
}
|
|
859
|
-
return null;
|
|
860
|
-
}
|
|
861
|
-
function defaultSleep(ms) {
|
|
862
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
863
|
-
}
|
|
864
|
-
function stopCreatedSession(ctx, sessionId) {
|
|
865
|
-
let stopOutput;
|
|
866
|
-
try {
|
|
867
|
-
stopOutput = getExecutor().runFile(ctx.easBin, stopSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
|
|
868
|
-
} catch (err) {
|
|
869
|
-
return {
|
|
870
|
-
failed: true,
|
|
871
|
-
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
872
|
-
reason: `Stim could not stop session ${sessionId} (${describe(err)}). The session bills until its cap.`,
|
|
873
|
-
remedy: `Run \`eas simulator:stop --id ${sessionId}\`.`,
|
|
874
|
-
sessionId
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
const verified = verifyStoppedSession(stopOutput, sessionId);
|
|
878
|
-
if (!verified.ok) return {
|
|
879
|
-
failed: true,
|
|
880
|
-
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
881
|
-
reason: `Stim could not verify that session ${sessionId} stopped (${verified.reason}). The session bills until its cap.`,
|
|
882
|
-
remedy: `Run \`eas simulator:stop --id ${sessionId}\`.`,
|
|
883
|
-
sessionId
|
|
884
|
-
};
|
|
885
|
-
return {
|
|
886
|
-
ok: true,
|
|
887
|
-
sessionId
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
function abandonSession(ctx, sessionId, reason) {
|
|
891
|
-
const stopped = stopCreatedSession(ctx, sessionId);
|
|
892
|
-
if (stopped.ok) return {
|
|
893
|
-
failed: true,
|
|
894
|
-
reason: `${reason} The session was stopped.`
|
|
895
|
-
};
|
|
896
|
-
return {
|
|
897
|
-
failed: true,
|
|
898
|
-
code: stopped.code,
|
|
899
|
-
reason: `${reason} ${stopped.reason} ${stopped.remedy}`,
|
|
900
|
-
remedy: stopped.remedy
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
function withRemoteSessionLock(root, fn, options = {}) {
|
|
904
|
-
let canonicalRoot;
|
|
905
|
-
try {
|
|
906
|
-
canonicalRoot = realpathSync(root);
|
|
907
|
-
} catch {
|
|
908
|
-
canonicalRoot = resolve(root);
|
|
909
|
-
}
|
|
910
|
-
return withWorkspaceProcessLock(workspaceDir(canonicalRoot), "remote-session", fn, {
|
|
911
|
-
external: true,
|
|
912
|
-
waitMs: 24e4,
|
|
913
|
-
...options
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
const EAS_START_HOLD_MS = 108e4 + Math.ceil(DAEMON_WAIT_MS / DAEMON_POLL_MS) * 35e3;
|
|
917
|
-
async function ensureRemoteBootOwned({ root, platform, sessionName, startedAt, deviceType = null, boot, createdSessionId, abandonCreatedSession, webPreviewUrl = () => null, writeState, register = () => {}, withProjectLock = withEasProjectLock, withLock = withRemoteSessionLock, removeEasSessionClaim: removeClaim = removeEasSessionClaim, ledgerRoot = easMachineStateRoot$1(), notice = () => {}, now = Date.now }) {
|
|
918
|
-
const waitNotice = nativeRunWaitNotice({
|
|
919
|
-
write: notice,
|
|
920
|
-
now,
|
|
921
|
-
describe: describeEasProjectLockHolder
|
|
922
|
-
});
|
|
923
|
-
let stuck = null;
|
|
924
|
-
let seen = null;
|
|
925
|
-
let projectLocked = false;
|
|
926
|
-
try {
|
|
927
|
-
return await withProjectLock(root, () => {
|
|
928
|
-
projectLocked = true;
|
|
929
|
-
return withLock(root, async () => {
|
|
930
|
-
register();
|
|
931
|
-
const booted = await boot();
|
|
932
|
-
if (booted.failed) return booted;
|
|
933
|
-
const sessionId = createdSessionId();
|
|
934
|
-
if (!sessionId) return booted;
|
|
935
|
-
try {
|
|
936
|
-
recordEasSessionClaim({
|
|
937
|
-
sessionId,
|
|
938
|
-
name: sessionName,
|
|
939
|
-
platform,
|
|
940
|
-
workspaceRoot: root,
|
|
941
|
-
workspaceHome: getConfigDir(),
|
|
942
|
-
stateFile: workspaceStateFile(root)
|
|
943
|
-
}, ledgerRoot);
|
|
944
|
-
const preview = webPreviewUrl();
|
|
945
|
-
writeState(root, { remoteDevice: {
|
|
946
|
-
platform,
|
|
947
|
-
sessionId,
|
|
948
|
-
startedAt,
|
|
949
|
-
...preview ? { webPreviewUrl: preview } : {},
|
|
950
|
-
...deviceType ? { deviceType } : {}
|
|
951
|
-
} });
|
|
952
|
-
return booted;
|
|
953
|
-
} catch (err) {
|
|
954
|
-
const cleanup = abandonCreatedSession();
|
|
955
|
-
if (cleanup.ok) {
|
|
956
|
-
let removalFailure = "the claim store did not remove it";
|
|
957
|
-
try {
|
|
958
|
-
if (removeClaim(sessionId, ledgerRoot)) return {
|
|
959
|
-
failed: true,
|
|
960
|
-
code: "STIM_REMOTE_SESSION_STATE",
|
|
961
|
-
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped.`,
|
|
962
|
-
remedy: "Fix workspace state storage, then retry the remote command."
|
|
963
|
-
};
|
|
964
|
-
} catch (error) {
|
|
965
|
-
removalFailure = describe(error);
|
|
966
|
-
}
|
|
967
|
-
return {
|
|
968
|
-
failed: true,
|
|
969
|
-
code: "STIM_REMOTE_SESSION_CLEANUP",
|
|
970
|
-
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. The session was stopped, but its ownership claim could not be removed: ${removalFailure}.`,
|
|
971
|
-
remedy: `The stopped session claim remains under ${ledgerRoot}. Repair that ownership ledger before retrying the remote command.`
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
return {
|
|
975
|
-
failed: true,
|
|
976
|
-
code: cleanup.code ?? "STIM_REMOTE_SESSION_CLEANUP",
|
|
977
|
-
reason: `Could not record EAS session ${sessionId}: ${describe(err)}. ${cleanup.reason ?? ""}`.trim(),
|
|
978
|
-
remedy: cleanup.remedy ?? `Run \`eas simulator:stop --id ${sessionId}\`.`
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
});
|
|
982
|
-
}, {
|
|
983
|
-
ownerPurpose: "EAS remote start",
|
|
984
|
-
machineRoot: ledgerRoot,
|
|
985
|
-
waitMs: Number.POSITIVE_INFINITY,
|
|
986
|
-
details: {
|
|
987
|
-
workspace: root,
|
|
988
|
-
platform
|
|
989
|
-
},
|
|
990
|
-
now,
|
|
991
|
-
onHeld: (holder) => {
|
|
992
|
-
const at = now();
|
|
993
|
-
if (seen?.claimId !== holder.claimId) seen = {
|
|
994
|
-
claimId: holder.claimId,
|
|
995
|
-
at
|
|
996
|
-
};
|
|
997
|
-
const claimedAt = Date.parse(holder.startedAt);
|
|
998
|
-
const heldMs = at - (Number.isFinite(claimedAt) ? Math.min(claimedAt, seen.at) : seen.at);
|
|
999
|
-
if (heldMs > EAS_START_HOLD_MS) {
|
|
1000
|
-
stuck = {
|
|
1001
|
-
holder,
|
|
1002
|
-
heldMs
|
|
1003
|
-
};
|
|
1004
|
-
const error = /* @__PURE__ */ new Error(`The eas-project lock was held past ${formatElapsed(EAS_START_HOLD_MS)}.`);
|
|
1005
|
-
error.code = "STIM_LOCK_TIMEOUT";
|
|
1006
|
-
throw error;
|
|
1007
|
-
}
|
|
1008
|
-
waitNotice(holder, `waiting for ${describeEasProjectLockHolder(holder, at)} to release the EAS project lock; one EAS session starts at a time on this machine`);
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
} catch (err) {
|
|
1012
|
-
const code = err.code ?? "STIM_NO_REMOTE_SESSION";
|
|
1013
|
-
if (stuck && !projectLocked) {
|
|
1014
|
-
const { holder, heldMs } = stuck;
|
|
1015
|
-
return {
|
|
1016
|
-
failed: true,
|
|
1017
|
-
code,
|
|
1018
|
-
reason: `${describeEasProjectLockHolder(holder, now())} has held the EAS project lock for ${formatElapsed(heldMs)}; an EAS session start holds it for at most ${formatElapsed(EAS_START_HOLD_MS)}.`,
|
|
1019
|
-
remedy: `If pid ${holder.owner.pid} is stuck, stop it, then run the remote command again.`
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
return {
|
|
1023
|
-
failed: true,
|
|
1024
|
-
code,
|
|
1025
|
-
reason: describe(err),
|
|
1026
|
-
remedy: "Retry the remote command after the other workspace operation finishes."
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
function daemonHostLabel(baseUrl) {
|
|
1031
|
-
try {
|
|
1032
|
-
return new URL(baseUrl).host;
|
|
1033
|
-
} catch {
|
|
1034
|
-
return baseUrl;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
function notConnected(code) {
|
|
1038
|
-
return {
|
|
1039
|
-
failed: true,
|
|
1040
|
-
code,
|
|
1041
|
-
reason: "No remote session is connected; the device step did not complete."
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
const LIVE_STATUSES = /* @__PURE__ */ new Set(["NEW", "IN_PROGRESS"]);
|
|
1045
|
-
function readLiveDaemon(ctx, sessionId) {
|
|
1046
|
-
let stdout;
|
|
1047
|
-
try {
|
|
1048
|
-
stdout = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
|
|
1049
|
-
} catch {
|
|
1050
|
-
return null;
|
|
1051
|
-
}
|
|
1052
|
-
try {
|
|
1053
|
-
const inspection = inspectSessionForTeardown(stdout, sessionId);
|
|
1054
|
-
if (inspection.action !== "stop" || !LIVE_STATUSES.has(inspection.status)) return null;
|
|
1055
|
-
return remoteDaemonFrom(JSON.parse(stdout).remoteConfig);
|
|
1056
|
-
} catch {
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
function readDaemon(ctx, sessionId) {
|
|
1061
|
-
let stdout;
|
|
1062
|
-
try {
|
|
1063
|
-
stdout = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
|
|
1064
|
-
} catch {
|
|
1065
|
-
return null;
|
|
1066
|
-
}
|
|
1067
|
-
try {
|
|
1068
|
-
return remoteDaemonFrom(JSON.parse(stdout).remoteConfig);
|
|
1069
|
-
} catch {
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
function remoteIosDeps(ctx) {
|
|
1074
|
-
const shared = {
|
|
1075
|
-
...ctx,
|
|
1076
|
-
platform: "ios"
|
|
1077
|
-
};
|
|
1078
|
-
const core = remoteDeviceDeps(shared);
|
|
1079
|
-
return {
|
|
1080
|
-
ctx: shared,
|
|
1081
|
-
checkDeviceCapacity: core.checkCapacity,
|
|
1082
|
-
ensureOwnedDevice: core.ensureDevice,
|
|
1083
|
-
ensureBooted: core.ensureBooted,
|
|
1084
|
-
installIosApp: ({ appPath }) => core.installArtifact(appPath),
|
|
1085
|
-
launchIosApp: ({ bundleId, metroPort, devClientScheme }) => core.launchApp({
|
|
1086
|
-
appId: bundleId,
|
|
1087
|
-
metroPort,
|
|
1088
|
-
devClientScheme
|
|
1089
|
-
}),
|
|
1090
|
-
createdSessionId: core.createdSessionId,
|
|
1091
|
-
abandonCreatedSession: core.abandonCreatedSession,
|
|
1092
|
-
webPreviewUrl: core.webPreviewUrl,
|
|
1093
|
-
failureRemedy: core.failureRemedy
|
|
1094
|
-
};
|
|
1095
|
-
}
|
|
1096
|
-
function remoteAndroidDeps(ctx) {
|
|
1097
|
-
const shared = {
|
|
1098
|
-
...ctx,
|
|
1099
|
-
platform: "android"
|
|
1100
|
-
};
|
|
1101
|
-
const core = remoteDeviceDeps(shared);
|
|
1102
|
-
return {
|
|
1103
|
-
ctx: shared,
|
|
1104
|
-
checkCapacity: core.checkCapacity,
|
|
1105
|
-
ensureDevice: core.ensureDevice,
|
|
1106
|
-
ensureDeviceBooted: async (opts) => {
|
|
1107
|
-
const booted = await core.ensureBooted(opts);
|
|
1108
|
-
return booted.ok ? {
|
|
1109
|
-
ok: true,
|
|
1110
|
-
serial: booted.udid
|
|
1111
|
-
} : booted;
|
|
1112
|
-
},
|
|
1113
|
-
install: ({ apkPath }) => core.installArtifact(apkPath),
|
|
1114
|
-
launch: ({ packageName, metroPort, devClientScheme }) => core.launchApp({
|
|
1115
|
-
appId: packageName,
|
|
1116
|
-
metroPort,
|
|
1117
|
-
devClientScheme
|
|
1118
|
-
}),
|
|
1119
|
-
createdSessionId: core.createdSessionId,
|
|
1120
|
-
abandonCreatedSession: core.abandonCreatedSession,
|
|
1121
|
-
webPreviewUrl: core.webPreviewUrl,
|
|
1122
|
-
failureRemedy: core.failureRemedy
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
|
-
function endRecordedSession({ root, sessionId, easBin, lookupAgentDevice, ledgerRoot }) {
|
|
1126
|
-
const agentDeviceBin = (lookupAgentDevice ?? defaultLookupAgentDevice)();
|
|
1127
|
-
if (!easBin) return {
|
|
1128
|
-
status: "failed",
|
|
1129
|
-
reason: `No eas-cli to stop session ${sessionId} with; it keeps billing until its max duration. Run \`eas simulator:stop --id ${sessionId}\`.`
|
|
1130
|
-
};
|
|
1131
|
-
return teardownRemote({
|
|
1132
|
-
root,
|
|
1133
|
-
label: "",
|
|
1134
|
-
backend: "eas",
|
|
1135
|
-
easBin,
|
|
1136
|
-
agentDeviceBin: agentDeviceBin ?? "",
|
|
1137
|
-
existingDaemon: null,
|
|
1138
|
-
easLedgerRoot: ledgerRoot
|
|
1139
|
-
}, { sessionId });
|
|
1140
|
-
}
|
|
1141
|
-
function teardownRemote(ctx, { sessionId }) {
|
|
1142
|
-
const profilePath = remoteProfilePath(ctx.root);
|
|
1143
|
-
try {
|
|
1144
|
-
getExecutor().runFile(ctx.agentDeviceBin, disconnectArgs(profilePath), {
|
|
1145
|
-
cwd: ctx.root,
|
|
1146
|
-
timeoutMs: AGENT_DEVICE_TIMEOUT_MS,
|
|
1147
|
-
killSignal: "SIGKILL"
|
|
1148
|
-
});
|
|
1149
|
-
} catch {}
|
|
1150
|
-
if (!sessionId) return { status: "torn-down" };
|
|
1151
|
-
const ledger = readEasSessionLedger$1(ctx.easLedgerRoot);
|
|
1152
|
-
const claimNeedsRemoval = !ledger.safe || ledger.claims.has(sessionId);
|
|
1153
|
-
const removeClaim = () => {
|
|
1154
|
-
try {
|
|
1155
|
-
if ((ctx.removeEasSessionClaim ?? removeEasSessionClaim)(sessionId, ctx.easLedgerRoot) || !claimNeedsRemoval) return void 0;
|
|
1156
|
-
return `EAS session ${sessionId} is stopped, but its ownership claim could not be removed. The claim and workspace record were kept for reconciliation. Re-run stop.`;
|
|
1157
|
-
} catch (error) {
|
|
1158
|
-
return `EAS session ${sessionId} is stopped, but its ownership claim could not be removed: ${describe(error)}. The claim and workspace record were kept for reconciliation. Re-run stop.`;
|
|
1159
|
-
}
|
|
1160
|
-
};
|
|
1161
|
-
let sessionOutput;
|
|
1162
|
-
try {
|
|
1163
|
-
sessionOutput = getExecutor().runFile(ctx.easBin, getSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
|
|
1164
|
-
} catch (err) {
|
|
1165
|
-
if (isDefinitiveMissingSessionError(err, sessionId)) return {
|
|
1166
|
-
status: "torn-down",
|
|
1167
|
-
reason: removeClaim()
|
|
1168
|
-
};
|
|
1169
|
-
return {
|
|
1170
|
-
status: "failed",
|
|
1171
|
-
reason: `Could not verify EAS session ${sessionId}: ${describe(err)}. The session record was kept for retry.`
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
const inspection = inspectSessionForTeardown(sessionOutput, sessionId);
|
|
1175
|
-
if (inspection.action === "refused") return {
|
|
1176
|
-
status: "failed",
|
|
1177
|
-
reason: `${inspection.reason} The session record was kept for retry.`
|
|
1178
|
-
};
|
|
1179
|
-
if (inspection.action === "already-stopped") return {
|
|
1180
|
-
status: "torn-down",
|
|
1181
|
-
reason: removeClaim()
|
|
1182
|
-
};
|
|
1183
|
-
let stopOutput;
|
|
1184
|
-
try {
|
|
1185
|
-
stopOutput = getExecutor().runFile(ctx.easBin, stopSessionArgs(sessionId), easBoundedExecOptions(ctx.root));
|
|
1186
|
-
} catch (err) {
|
|
1187
|
-
return {
|
|
1188
|
-
status: "failed",
|
|
1189
|
-
reason: `eas simulator:stop ${sessionId} failed: ${describe(err)}. The session record was kept for retry.`
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
const verified = verifyStoppedSession(stopOutput, sessionId);
|
|
1193
|
-
if (!verified.ok) return {
|
|
1194
|
-
status: "failed",
|
|
1195
|
-
reason: `${verified.reason} The session record was kept for retry.`
|
|
1196
|
-
};
|
|
1197
|
-
return {
|
|
1198
|
-
status: "torn-down",
|
|
1199
|
-
reason: removeClaim()
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
async function ensureMetroReachable({ ctx, metroPort, isExpo, tunnelMode = "auto", publicUrl = null, available = [], env = process.env, readTunnelRecord = readMetroTunnel, isTunnelAlive = pidExists, gateOrigin = gateMetroOrigin }) {
|
|
1203
|
-
const { root } = ctx;
|
|
1204
|
-
const platform = ctx.platform ?? "ios";
|
|
1205
|
-
const publicMetroUrl = env["STIM_METRO_PUBLIC_URL"]?.trim() || publicUrl || null;
|
|
1206
|
-
const recorded = readTunnelRecord(root);
|
|
1207
|
-
const effectiveAvailable = recorded?.kind === "managed" && isTunnelAlive(recorded.pid) ? [recorded.provider, ...available.filter((provider) => provider !== recorded.provider)] : available;
|
|
1208
|
-
const plan = planMetroReach({
|
|
1209
|
-
mode: tunnelMode,
|
|
1210
|
-
metroPort,
|
|
1211
|
-
publicUrl: publicMetroUrl,
|
|
1212
|
-
isExpo,
|
|
1213
|
-
available: effectiveAvailable
|
|
1214
|
-
});
|
|
1215
|
-
if ("failed" in plan) return plan;
|
|
1216
|
-
let resolvedUrl = null;
|
|
1217
|
-
let gate = false;
|
|
1218
|
-
if ("origin" in plan) {
|
|
1219
|
-
resolvedUrl = plan.origin;
|
|
1220
|
-
gate = plan.gate;
|
|
1221
|
-
} else if ("expoTunnel" in plan) {
|
|
1222
|
-
if (!recorded || recorded.kind !== "expo") return {
|
|
1223
|
-
failed: "This workspace's Metro tunnels itself (metro.tunnel is expo, or auto on an Expo project), but no Expo tunnel URL is recorded.",
|
|
1224
|
-
remedy: "Run `stim start --remote` so Expo can establish its own tunnel before a remote device connects."
|
|
1225
|
-
};
|
|
1226
|
-
resolvedUrl = recorded.url;
|
|
1227
|
-
gate = true;
|
|
1228
|
-
} else {
|
|
1229
|
-
const port = Number(metroPort);
|
|
1230
|
-
if (plan.start === "tailscale" && ctx.backend === "eas") return {
|
|
1231
|
-
failed: "metro.tunnel is \"tailscale\", but an EAS Simulator is not on your tailnet and cannot reach it.",
|
|
1232
|
-
remedy: "Use the proxy backend with a device on the same tailnet, or set metro.tunnel to \"auto\" or \"ngrok\".",
|
|
1233
|
-
code: REMOTE_METRO_ERROR
|
|
1234
|
-
};
|
|
1235
|
-
const providerMatches = recorded?.kind === "managed" && recorded.provider === plan.start;
|
|
1236
|
-
if (recorded && recorded.kind === "managed" && recorded.port === port && providerMatches && isTunnelAlive(recorded.pid)) resolvedUrl = recorded.url;
|
|
1237
|
-
else return {
|
|
1238
|
-
failed: `No live managed Metro tunnel is recorded for port ${port}.`,
|
|
1239
|
-
remedy: "Run `stim start --remote`, then retry the device command."
|
|
1240
|
-
};
|
|
1241
|
-
gate = recorded.provider !== "tailscale";
|
|
1242
|
-
}
|
|
1243
|
-
if (gate && resolvedUrl) {
|
|
1244
|
-
const logsDir = workspaceLogsDir$1(root);
|
|
1245
|
-
const result = await gateOrigin({
|
|
1246
|
-
origin: resolvedUrl,
|
|
1247
|
-
metroPort,
|
|
1248
|
-
platform,
|
|
1249
|
-
entryPoint: bundleEntryPoint(root, isExpo),
|
|
1250
|
-
readRecords: () => readMetroRecords(logsDir),
|
|
1251
|
-
isProof: isBundleProof
|
|
1252
|
-
});
|
|
1253
|
-
if (result.failed) return {
|
|
1254
|
-
failed: result.reason ?? "Metro gate failed.",
|
|
1255
|
-
remedy: result.remedy ?? "",
|
|
1256
|
-
code: REMOTE_METRO_WRONG
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
ctx.publicMetroUrl = resolvedUrl;
|
|
1260
|
-
return { ok: true };
|
|
1261
|
-
}
|
|
1262
|
-
function bundleEntryPoint(root, isExpo) {
|
|
1263
|
-
if (!isExpo) return "index";
|
|
1264
|
-
const declared = readJsonObject(resolve(root, "package.json"))?.main;
|
|
1265
|
-
if (typeof declared !== "string" || !declared.trim()) return "index";
|
|
1266
|
-
const main = declared.trim();
|
|
1267
|
-
const local = main.startsWith(".") || /\.[cm]?[jt]sx?$/.test(main) || existsSync(resolve(root, main));
|
|
1268
|
-
const normalized = main.replace(/^\.\//, "").replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
1269
|
-
return local ? normalized : `node_modules/${normalized}`;
|
|
1270
|
-
}
|
|
1271
|
-
//#endregion
|
|
1272
|
-
export { parseSessionListPage as C, gateMetroOrigin as D, readRemoteSimulatorArch as E, ownedSessionName as S, verifyStoppedSession as T, findOrphanedOwnedSessions as _, ensureMetroReachable as a, isDefinitiveMissingSessionError as b, remoteAndroidDeps as c, withRemoteSessionLock as d, withEasProjectLock as f, easCliUpgradeRemedy as g, easCliSupport as h, endRecordedSession as i, remoteIosDeps as l, MIN_EAS_CLI_SIMULATOR_VERSION as m, binOnPath as n, ensureRemoteBootOwned as o, MIN_EAS_CLI_DEVICE_VERSION as p, bundleEntryPoint as r, readInstalledEasCliVersion as s, REMOTE_SESSION_ERROR as t, resolveRemoteContext as u, getSessionArgs as v, stopSessionArgs as w, listOwnedSessionsArgs as x, inspectSessionForTeardown as y };
|