stim 1.19.0 → 1.20.1
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 +20 -3
- package/dist/{activity-BSw0x84R.mjs → activity-BaMpUQ4-.mjs} +3 -3
- package/dist/{agent-device-usage-output-Bjxu_VmX.mjs → agent-device-usage-output-VPaOgGaF.mjs} +7 -7
- package/dist/android-CLbBJ2ve.mjs +1258 -0
- package/dist/{android-DE5g_5gv.mjs → android-CZh6ElG5.mjs} +2 -2
- package/dist/{android-BdQC6zs1.mjs → android-DE8-JfGW.mjs} +105 -47
- package/dist/{android-cas-C87geAg-.mjs → android-cas-BFpS84v2.mjs} +9 -5
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/android-eXfIjKJr.mjs +6 -0
- package/dist/api-run.mjs +159 -8
- package/dist/{api-DIf_s86s.mjs → api-zuX3ynYN.mjs} +5 -1
- package/dist/api.d.mts +98 -4
- package/dist/api.mjs +1 -1
- package/dist/{app-install-CFI4ptUL.mjs → app-install-Cg9YJy3x.mjs} +9 -8
- package/dist/artifact-Ctb2jpw6.mjs +807 -0
- package/dist/artifact-DXk_Fle_.mjs +772 -0
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
- package/dist/{browser-web-CDhqQxhv.mjs → browser-web-xQWC2RWl.mjs} +19 -19
- package/dist/{budget-Dz-kyXLv.mjs → budget-RFbwFpG5.mjs} +3101 -3062
- package/dist/build-COBA1tBc.mjs +462 -0
- package/dist/{build-facts-B87vo-Im.d.mts → build-facts-DcLv2HSd.d.mts} +1 -32
- package/dist/build-plan-CRjAA6Qi.mjs +553 -0
- package/dist/{cache-manifest-DiwKSyv9.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{manifest-CX0d1IkZ.mjs → ccache-CtbqWxIC.mjs} +13 -19
- package/dist/{chrome-BTUOo8p2.mjs → chrome-Dp2lMLBe.mjs} +1 -1
- package/dist/{cli-C9Zvgbdc.mjs → cli-sGqlPu1n.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-CuUo8Nvg.mjs → client-zqjFd79D.mjs} +89 -307
- package/dist/collector-run.d.mts +4 -0
- package/dist/collector-run.mjs +7 -7
- package/dist/{command-output-Bt8ygHE7.mjs → command-output-CPDq_90S.mjs} +2 -2
- package/dist/{config-DC5jEtd1.mjs → config-D0LhTY3S.mjs} +8 -12
- package/dist/{created-devices-D0s26Hx5.mjs → created-devices-CXBMtoj5.mjs} +7 -6
- package/dist/{dependency-state-DKaS1OM9.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
- package/dist/{detached-entry-pB_IPfV4.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
- package/dist/{dev-client-CDY_zAmD.mjs → dev-client-BqYjMIaC.mjs} +2 -2
- package/dist/{device-BHw0WlHl.mjs → device-BgNj-qVj.mjs} +8 -8
- package/dist/device-D4DqjSZZ.mjs +727 -0
- package/dist/{device-capacity-BoN1jQkO.mjs → device-capacity-DrwHwR97.mjs} +13 -14
- package/dist/device-host-worker.mjs +38 -18
- package/dist/device-ios-DE8KJd3b.mjs +413 -0
- package/dist/{device-lease-Cu7PtQpZ.mjs → device-lease-C309csaB.mjs} +138 -24
- package/dist/{device-lease-run-D38SnZtt.mjs → device-lease-run-Baelt3nc.mjs} +4 -4
- package/dist/{device-pool-CP4rAsHx.mjs → device-pool-DtK9S6AD.mjs} +3 -3
- package/dist/{device-remote-B5GiUifk.mjs → device-remote-DjsNw8DD.mjs} +17 -68
- package/dist/doctor-DiP9HsIP.mjs +515 -0
- package/dist/{doctor-PRJH94v6.mjs → doctor-Dunu3Nv1.mjs} +333 -744
- package/dist/{doctor-watchman-wZhyTdp4.mjs → doctor-watchman-XJzT8tK-.mjs} +15 -13
- package/dist/{simslim-D9LkJxIO.mjs → eas-build-0gadvrca.mjs} +6 -152
- package/dist/{stim-installations-DbmUjsLv.mjs → eas-session-ledger-DJDeL4Uq.mjs} +57 -4
- package/dist/{error-diagnostics--v5HGOU0.mjs → error-diagnostics-BhQtoLrZ.mjs} +17 -12
- package/dist/errors-DzkMYh8Q.mjs +205 -0
- package/dist/{exec-i8GDPwq9.mjs → exec-B81Qodjn.mjs} +40 -10
- package/dist/{gc-CyHMcKcn.mjs → gc-D5cz5G0k.mjs} +22 -21
- package/dist/{device-ios-B2ruohE-.mjs → gradle-CZZo1TSA.mjs} +21 -650
- package/dist/{guide-dRaQJJk9.mjs → guide-DakjcoUt.mjs} +503 -251
- package/dist/{hosted-android-Bs7fytj7.mjs → hosted-android-C-D8Dj0_.mjs} +5 -5
- package/dist/{hosted-client-C5G4h_OJ.mjs → hosted-client--hNFgixu.mjs} +9 -3
- package/dist/{hosted-ios-Bik035eL.mjs → hosted-ios-Chx9XQyI.mjs} +3 -3
- package/dist/{hosted-logs-CI9Az9a4.mjs → hosted-logs-Bkz0Hxzh.mjs} +8 -8
- package/dist/{hosted-macos-DABm3ScO.mjs → hosted-macos-ckTjN-0V.mjs} +7 -6
- package/dist/{hosted-native-DbV2aUVw.mjs → hosted-native-3KXZ5-qn.mjs} +17 -14
- package/dist/{idle-shutdown-DYiMBn34.mjs → idle-shutdown-DhAUyf5s.mjs} +7 -7
- package/dist/{ios-DYUr9LGc.mjs → ios-BDBN-xpf.mjs} +8 -7
- package/dist/ios-CQ8bHQXB.mjs +1112 -0
- package/dist/{ios-DCrkgXEM.mjs → ios-VGvNdsv2.mjs} +1 -1
- package/dist/{ios-device-CrW8aMb5.mjs → ios-device-B1B14xND.mjs} +1 -1
- package/dist/{ios-device-CJ_ZOGky.mjs → ios-device-BWHKGaJq.mjs} +3 -3
- package/dist/{ios-BWbHFZVz.mjs → ios-o896T9PE.mjs} +195 -35
- package/dist/ios-project-BeOXKPt2.mjs +197 -0
- package/dist/{ios-state-By0XuLTQ.mjs → ios-state-BsXMwo5y.mjs} +1 -1
- package/dist/js-swap-Z_mSuRrx.mjs +432 -0
- package/dist/{react-native-ios-CSpIxexZ.mjs → launch-BootbMk4.mjs} +59 -612
- package/dist/launch-V1zqiOYn.mjs +745 -0
- package/dist/{launch-verify-DCveSE6d.mjs → launch-verify-Dl78EVgR.mjs} +26 -7
- package/dist/{logs-Bao1HOMy.mjs → logs-Bh4eqCCc.mjs} +11 -11
- package/dist/{macos-ciyBdTY5.mjs → macos-D7irBW3A.mjs} +42 -50
- package/dist/macos-kivF8qKz.mjs +2 -0
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +13 -10
- package/dist/{metro-DI2AjPF_.mjs → metro-_ZIE3Axg.mjs} +18 -5
- package/dist/{metro-gateway-BxpJLIwh.mjs → metro-gateway-BzlQZQmU.mjs} +2 -2
- package/dist/miss-reason-SuTptY-I.mjs +238 -0
- package/dist/{named-ports-B4iWH4E-.mjs → named-ports-O1TIl7tx.mjs} +3 -3
- package/dist/native-android-D94RlJ-X.mjs +226 -0
- package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
- package/dist/{native-runtime-BGxsbfny.mjs → native-runtime-D024BAvm.mjs} +9 -8
- package/dist/native-xcode-inputs-UElfGs59.mjs +306 -0
- package/dist/native-xcode-ios-CjS-nDVY.mjs +434 -0
- package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
- package/dist/offload-worker.d.mts +41 -7
- package/dist/offload-worker.mjs +409 -74
- package/dist/{ownership-EB0ySdGK.mjs → ownership-BrjepDiv.mjs} +2 -2
- package/dist/{placement-log-Dxe34rJm.mjs → placement-log-BItXET1x.mjs} +1 -1
- package/dist/plan-Jwu_vr-F.mjs +260 -0
- package/dist/plan-placement-B466UYMU.mjs +73 -0
- package/dist/{ports-ByPc0_97.mjs → ports-BFRL6JTj.mjs} +3 -3
- package/dist/{prebuild-BKJ3wJI1.mjs → prebuild-DB_zLOLc.mjs} +4 -4
- package/dist/{preview-BbSottmO.mjs → preview-BRLFRhTz.mjs} +50 -32
- package/dist/project-DX8fXyw_.mjs +1131 -0
- package/dist/project-plan-BPwbLwQu.mjs +10 -0
- package/dist/{pull-request-IHNdMnG8.mjs → pull-request-vOhLNmIb.mjs} +1 -1
- package/dist/pull-requests.mjs +1 -1
- package/dist/react-native-android-CuXM3QS8.mjs +921 -0
- package/dist/react-native-build-JUF31S8U.mjs +239 -0
- package/dist/react-native-doctor-BQHqiXaQ.mjs +78 -0
- package/dist/react-native-ios-DtVO4qf2.mjs +595 -0
- package/dist/{recordings-Bv8McZN4.mjs → recordings-Bk-VivUV.mjs} +1 -1
- package/dist/{reload-rAPdqeRR.mjs → reload-D9V7D8gP.mjs} +17 -13
- package/dist/remote-CZiTijxG.mjs +192 -0
- package/dist/{remote-cache-N3hAEaAL.mjs → remote-cache-fxcl5C-L.mjs} +5 -5
- package/dist/result-BrfgXtdO.mjs +186 -0
- package/dist/{run-lAkle1-c.mjs → run-ClC8UVB5.mjs} +3 -3
- package/dist/{server-bare-CkIjnegq.mjs → server-bare-dWjAarki.mjs} +5 -5
- package/dist/server-command-DnJYLnr4.mjs +69 -0
- package/dist/{server-expo-n3tUi3Em.mjs → server-expo-CUuzP9Mp.mjs} +107 -82
- package/dist/server-expo-Dvkd7JcF.mjs +2 -0
- package/dist/{settings-CxgngUZk.mjs → settings-Bbztm4oV.mjs} +179 -129
- package/dist/{settings-2am7TSxp.mjs → settings-BuKMOANA.mjs} +6 -6
- package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
- package/dist/settings.schema.json +84 -0
- package/dist/simslim-ChMfe8rg.mjs +149 -0
- package/dist/{slot-launch-HV_KtPIw.mjs → slot-launch-CeCRr6MK.mjs} +6 -6
- package/dist/{stage-BgaZJVZY.mjs → stage-BNKKdDNc.mjs} +6 -3
- package/dist/{start-DtxthMfI.mjs → start-CRkG-mZ7.mjs} +97 -68
- package/dist/start-mh6bS27z.mjs +2 -0
- package/dist/{state-Tp4uImDS.mjs → state-BUpAmt2r.mjs} +1 -1
- package/dist/{state-D2KtnALG.mjs → state-lqxdOH_g.mjs} +32 -12
- package/dist/{stats-8EJ5ivFT.mjs → stats-CaB1MxMi.mjs} +5 -5
- package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
- package/dist/{status-BOyp0CUl.mjs → status-C7vzkInu.mjs} +81 -293
- package/dist/status-watch-ZaVqRdtW.mjs +296 -0
- package/dist/{stim-desktop-C65jk_q2.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
- package/dist/{stop-zDlknFoz.mjs → stop-BO80KJoT.mjs} +21 -20
- package/dist/{stop-DRipAg-e.mjs → stop-BU7JaFCg.mjs} +2 -2
- package/dist/{stop-D0UIpZuh.mjs → stop-Cftw_sXJ.mjs} +5 -5
- package/dist/supervisor-run.d.mts +7 -2
- package/dist/supervisor-run.mjs +32 -20
- package/dist/{support-DsNaFg6D.mjs → support-Ca6PZbpr.mjs} +17 -7
- package/dist/{support-CNqYkV0y.mjs → support-Co0wk8Cv.mjs} +61 -8
- package/dist/{swiftpm-macos-aJzPAj1t.mjs → swiftpm-macos-siFSkmCO.mjs} +5 -5
- package/dist/tailnet-D7-tE_ug.mjs +265 -0
- package/dist/toolchain-B12psPA2.mjs +2 -0
- package/dist/{toolchain-C3xSoyUl.mjs → toolchain-SqehyPLw.mjs} +206 -53
- package/dist/{trigger-CAhmG-4s.mjs → trigger-C0MWBi8O.mjs} +2 -2
- package/dist/trigger-SHLOWJ4O.mjs +2 -0
- package/dist/{warm-progress-pFLB2e6P.mjs → warm-progress-5FAXkXIg.mjs} +6 -6
- package/dist/{watchman-BL1IdOtq.mjs → watchman-CqmaGcIb.mjs} +6 -6
- package/dist/{web-DbbrPUn0.mjs → web-BTjzE8mc.mjs} +9 -13
- package/dist/web-CHN5gh34.mjs +2 -0
- package/dist/web-run.mjs +10 -10
- package/dist/{workspace-state-CR9X86xJ.mjs → workspace-state-CvgTFe-h.mjs} +36 -36
- package/dist/{workspaces-Djb2gfi9.mjs → workspaces-C4kqIOlx.mjs} +1260 -1258
- package/dist/worktree-Cgnm6vZt.mjs +2 -0
- package/dist/{worktree-BL4M1rhB.mjs → worktree-DtRsh6go.mjs} +86 -50
- package/package.json +6 -5
- package/shim/native-android.gradle +101 -0
- package/dist/android-BXc6BmEi.mjs +0 -3732
- package/dist/android-BvV1gGdN.mjs +0 -4
- package/dist/ios-ITYno9rO.mjs +0 -2241
- package/dist/macos-nIOATJmR.mjs +0 -2
- package/dist/metro-store-BAHFcJm6.mjs +0 -106
- package/dist/miss-reason-CjQCOKqq.mjs +0 -1743
- package/dist/plan-placement-BVY59qa8.mjs +0 -290
- package/dist/project-B5eQ1USs.mjs +0 -612
- package/dist/react-native-ios-BMh2i25I.mjs +0 -2
- package/dist/server-expo-DGsdUGX2.mjs +0 -2
- package/dist/start-D8NbxNBb.mjs +0 -2
- package/dist/trigger-CoalsMTV.mjs +0 -2
- package/dist/web-DFpFeZu5.mjs +0 -2
- package/dist/worktree-DV-A0LNL.mjs +0 -2
|
@@ -0,0 +1,1258 @@
|
|
|
1
|
+
import { n as ownership_claim_exports } from "./process-identity-Biv3arCO.mjs";
|
|
2
|
+
import { t as getExecutor } from "./exec-B81Qodjn.mjs";
|
|
3
|
+
import { E as upsertProject, L as deviceSlotFileKey, V as parseDeviceSlotOption, W as validateDeviceSlot, p as getProject, u as getConcurrencyLimits, z as deviceSlotPlatforms } from "./config-D0LhTY3S.mjs";
|
|
4
|
+
import { I as projectSettingsContext, K as resolveProjectSettings, L as publicUrlSetting, N as metroWarmupUrlSetting, V as remoteEasFallbackSetting, W as resolveCacheProviderConfig, a as SETTING_SHAPE_REMEDY, at as detectProviders, g as deviceReclaimIdleMinutesSetting, j as metroPortSetting, q as resolveSettings, tt as tunnelModeSetting } from "./settings-Bbztm4oV.mjs";
|
|
5
|
+
import { _ as waitForBuild, g as releaseBuildLock, m as acquireBuildLock } from "./device-lease-C309csaB.mjs";
|
|
6
|
+
import { c as writeWorkspaceState, f as emulatorLogFile, i as recordWorkspaceUse, r as readWorkspaceState$1, w as workspaceLogsDir$1 } from "./workspace-state-CvgTFe-h.mjs";
|
|
7
|
+
import { _ as stepTimer, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine } from "./command-output-CPDq_90S.mjs";
|
|
8
|
+
import { I as workspaceDevServerMode, R as writeWorkspaceLaunch, c as projectShortcut, d as projectRegistry, r as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-DX8fXyw_.mjs";
|
|
9
|
+
import { G as probeEmulatorSerial, J as resolveOwnedAvdSerial, O as listAdbDevices, U as physicalDeviceModel, Y as resolvePhysicalDevice, a as androidDeviceAbi, ht as configuredAndroidEmulatorApp, j as listInstalledSystemImages, tt as waitForBoot } from "./android-DE8-JfGW.mjs";
|
|
10
|
+
import { s as pidExists, u as resolveProjectMetro } from "./metro-_ZIE3Axg.mjs";
|
|
11
|
+
import { F as acquireBuildSlot, L as releaseBuildSlot } from "./workspaces-C4kqIOlx.mjs";
|
|
12
|
+
import { At as createRunRecorder, Ft as recordRunStats, It as statsProjectKey$1, Nt as recordCapacityRefusal, Ot as startBuildProgress, jt as readRunEstimates, kt as tapBuildLog, t as budgetGate, yt as NO_BUILD_PROGRESS } from "./budget-RFbwFpG5.mjs";
|
|
13
|
+
import { S as withNativeBuildRun, b as runCancellation, p as cancelledFailure, x as runCancellationSignal } from "./tailnet-D7-tE_ug.mjs";
|
|
14
|
+
import { r as createNdjsonWriter } from "./ndjson-GOnbyvso.mjs";
|
|
15
|
+
import { f as resolveEasCliBin, i as checkEasAuth, m as uploadRemote, p as resolveRemote } from "./remote-cache-fxcl5C-L.mjs";
|
|
16
|
+
import { f as setRemoteLogSink, g as parseBuildMachineOption, y as resolveBuildPlacement } from "./client-zqjFd79D.mjs";
|
|
17
|
+
import { c as startDevServer } from "./start-CRkG-mZ7.mjs";
|
|
18
|
+
import { d as verifyAndroidReleaseLaunch, f as verifyLaunch } from "./launch-verify-Dl78EVgR.mjs";
|
|
19
|
+
import { b as launchAndroidReleaseApp, h as installAndroidApp, r as DEFAULT_METRO_PORT, t as ADB_INSTALL_TIMEOUT_MS, y as launchAndroidApp } from "./app-install-Cg9YJy3x.mjs";
|
|
20
|
+
import { n as ensureWorkspaceStorageSafely, r as isPhysicalDeviceRequest, t as ensureDevServer } from "./native-runtime-D024BAvm.mjs";
|
|
21
|
+
import { n as devicePlacementRecord } from "./placement-log-BItXET1x.mjs";
|
|
22
|
+
import { t as CCACHE_NOT_RUN } from "./ccache-CtbqWxIC.mjs";
|
|
23
|
+
import { n as DeviceAdmissionRefusal, r as checkDeviceCapacity } from "./device-capacity-DrwHwR97.mjs";
|
|
24
|
+
import { S as pooledAndroidDevice, a as PLATFORM, b as noDeviceDiagnostic, g as dumpApkManifest, h as displayPath, o as androidBuildOptions, p as apkPackage, r as NO_DEVICE, s as androidDevClientScheme } from "./support-Ca6PZbpr.mjs";
|
|
25
|
+
import { r as resolveAndroidRunPlan, t as androidDeviceSelectorRefusal } from "./plan-Jwu_vr-F.mjs";
|
|
26
|
+
import { t as acquireAndroidArtifact } from "./artifact-DXk_Fle_.mjs";
|
|
27
|
+
import { a as acquireRunLease, d as runLease, f as waitFlagConflict, o as leaseExpiryText, u as releaseLeaseOnSignal } from "./device-lease-run-Baelt3nc.mjs";
|
|
28
|
+
import { a as reportAndroidResult, i as persistLastBuild, n as finishAndroidUpload } from "./result-BrfgXtdO.mjs";
|
|
29
|
+
import { r as hostedMetroSettings } from "./metro-gateway-BzlQZQmU.mjs";
|
|
30
|
+
import { a as writeHostedAndroid, i as writeDevicePlacement, t as readHostedAndroid } from "./ios-state-BsXMwo5y.mjs";
|
|
31
|
+
import { a as refusePlan, c as devicePlacementLine, i as printPlan, n as planFlagRefusal, o as checkEasFallback, s as automaticDevicePlacement } from "./build-plan-CRjAA6Qi.mjs";
|
|
32
|
+
import { n as prepareHostedAndroid, t as placeHostedAndroid } from "./hosted-android-C-D8Dj0_.mjs";
|
|
33
|
+
import { r as siblingPlatformSlots, t as launchSlotScope } from "./slot-launch-CeCRr6MK.mjs";
|
|
34
|
+
import { S as ownedSessionName, a as ensureMetroReachable, c as remoteAndroidDeps, n as binOnPath, o as ensureRemoteBootOwned, u as resolveRemoteContext } from "./device-remote-DjsNw8DD.mjs";
|
|
35
|
+
import { a as resolveEasDevelopmentBuild, r as isEasBuildFailure } from "./eas-build-0gadvrca.mjs";
|
|
36
|
+
import { n as verifyCollectorOwnership } from "./ownership-BrjepDiv.mjs";
|
|
37
|
+
import { a as AvdRecoveryError, i as AvdBootError, n as ensureBooted, o as warmMetro, r as ensureOwnedDevice, t as didSetUpDevice } from "./device-D4DqjSZZ.mjs";
|
|
38
|
+
import { t as selectFromPool } from "./device-pool-DtK9S6AD.mjs";
|
|
39
|
+
import { r as finishAndroidRun } from "./launch-V1zqiOYn.mjs";
|
|
40
|
+
import { t as unsupportedPlan } from "./project-plan-BPwbLwQu.mjs";
|
|
41
|
+
import { basename, join } from "node:path";
|
|
42
|
+
import { hostedAndroidStatus, parseMachine } from "@stim-cli/core/state";
|
|
43
|
+
import { loadCacheProvider } from "@stim-cli/cache";
|
|
44
|
+
import { workspaceId } from "@stim-cli/core";
|
|
45
|
+
import { InvalidArgumentError } from "commander";
|
|
46
|
+
import chalk from "chalk";
|
|
47
|
+
//#region src/commands/android/remote.ts
|
|
48
|
+
async function connectAndroidHosting({ root, slot, machine, release, appMode = release ? "release" : "development", metroCheck, selectors, read = readHostedAndroid, resolveSerial = resolveOwnedAvdSerial, prepare = prepareHostedAndroid }) {
|
|
49
|
+
let recorded;
|
|
50
|
+
try {
|
|
51
|
+
recorded = read(root, slot)[slot];
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { failure: {
|
|
54
|
+
code: "STIM_HOSTING_REFUSED",
|
|
55
|
+
message: error.message,
|
|
56
|
+
remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
|
|
57
|
+
} };
|
|
58
|
+
}
|
|
59
|
+
if (recorded && recorded.machine !== machine) return { failure: {
|
|
60
|
+
code: "STIM_BAD_ARG",
|
|
61
|
+
message: `This workspace's Android emulator runs on ${recorded.machine}; run stim stop first.`
|
|
62
|
+
} };
|
|
63
|
+
if (!machine) return { target: null };
|
|
64
|
+
if (appMode === "development" && !metroCheck) return { failure: {
|
|
65
|
+
code: "STIM_BAD_ARG",
|
|
66
|
+
message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
|
|
67
|
+
remedy: "Run stim stop; stim start, then retry without --no-metro-check."
|
|
68
|
+
} };
|
|
69
|
+
const local = deviceSlotPlatforms(getProject(root), slot)?.android;
|
|
70
|
+
if (local?.owned && local.avdName) {
|
|
71
|
+
let running = true;
|
|
72
|
+
try {
|
|
73
|
+
const resolved = resolveSerial(local.avdName, { timeoutMs: 5e3 });
|
|
74
|
+
running = Boolean(resolved.serial) || !resolved.notRunning && !resolved.missing;
|
|
75
|
+
} catch {}
|
|
76
|
+
if (running) return { failure: {
|
|
77
|
+
code: "STIM_BAD_ARG",
|
|
78
|
+
message: `This workspace's Android emulator for slot ${slot} runs on this Mac; run stim stop first.`
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return { target: await prepare(machine, selectors, recorded, appMode === "process" ? appMode : void 0) };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return { failure: {
|
|
85
|
+
code: error.code ?? "STIM_HOSTING_REFUSED",
|
|
86
|
+
message: error.message,
|
|
87
|
+
...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
|
|
88
|
+
} };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function selectAndroidPlacement({ target: selected, automatic = automaticDevicePlacement, checkBudget = budgetGate, note, phase, log, fromFlag, noWait, buildMachine, localSelectors, eas, ...args }) {
|
|
92
|
+
const appMode = args.appMode ?? (args.release ? "release" : "development");
|
|
93
|
+
const selectors = selected.kind === "hosted" ? {
|
|
94
|
+
...selected.systemImage ? { systemImage: selected.systemImage } : {},
|
|
95
|
+
...selected.deviceProfile ? { deviceProfile: selected.deviceProfile } : {}
|
|
96
|
+
} : {};
|
|
97
|
+
if (selected.kind !== "hosted" || selected.machine !== "auto") {
|
|
98
|
+
const connected = await connectAndroidHosting({
|
|
99
|
+
...args,
|
|
100
|
+
selectors,
|
|
101
|
+
machine: selected.kind === "hosted" ? selected.machine : null
|
|
102
|
+
});
|
|
103
|
+
if ("failure" in connected) return connected;
|
|
104
|
+
return {
|
|
105
|
+
target: selected,
|
|
106
|
+
hostedTarget: connected.target,
|
|
107
|
+
selectors,
|
|
108
|
+
budget: connected.target ? {
|
|
109
|
+
reclaimed: [],
|
|
110
|
+
refusal: null
|
|
111
|
+
} : await checkBudget({
|
|
112
|
+
root: args.root,
|
|
113
|
+
note
|
|
114
|
+
})
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const refusal = localSelectors(selected);
|
|
118
|
+
if (refusal) return { failure: refusal };
|
|
119
|
+
try {
|
|
120
|
+
const placed = await automatic({
|
|
121
|
+
root: args.root,
|
|
122
|
+
slot: args.slot,
|
|
123
|
+
platform: "android",
|
|
124
|
+
...appMode === "process" ? { appMode } : {},
|
|
125
|
+
selectors,
|
|
126
|
+
buildMachine,
|
|
127
|
+
noWait,
|
|
128
|
+
...eas ? { eas } : {}
|
|
129
|
+
});
|
|
130
|
+
phase("placement:", devicePlacementLine(placed.placement, placed.skipped));
|
|
131
|
+
log?.(devicePlacementRecord({
|
|
132
|
+
platform: "android",
|
|
133
|
+
fromFlag,
|
|
134
|
+
easFallback: Boolean(eas),
|
|
135
|
+
placed
|
|
136
|
+
}));
|
|
137
|
+
if (placed.placement.decision === "eas") return {
|
|
138
|
+
target: {
|
|
139
|
+
kind: "remote",
|
|
140
|
+
backend: "eas",
|
|
141
|
+
systemImage: selected.systemImage,
|
|
142
|
+
deviceProfile: selected.deviceProfile
|
|
143
|
+
},
|
|
144
|
+
hostedTarget: null,
|
|
145
|
+
selectors,
|
|
146
|
+
budget: await checkBudget({
|
|
147
|
+
root: args.root,
|
|
148
|
+
note
|
|
149
|
+
}),
|
|
150
|
+
devicePlacement: placed.placement
|
|
151
|
+
};
|
|
152
|
+
if (placed.target && appMode === "development" && !args.metroCheck) return { failure: {
|
|
153
|
+
code: "STIM_BAD_ARG",
|
|
154
|
+
message: "Hosted Debug runs require the local Metro supervisor; --no-metro-check cannot be used.",
|
|
155
|
+
remedy: "Run stim stop; stim start, then retry without --no-metro-check."
|
|
156
|
+
} };
|
|
157
|
+
return {
|
|
158
|
+
target: placed.target ? {
|
|
159
|
+
...selected,
|
|
160
|
+
machine: placed.target.host.machine
|
|
161
|
+
} : {
|
|
162
|
+
kind: "emulator",
|
|
163
|
+
systemImage: selected.systemImage,
|
|
164
|
+
deviceProfile: selected.deviceProfile
|
|
165
|
+
},
|
|
166
|
+
hostedTarget: placed.target,
|
|
167
|
+
selectors,
|
|
168
|
+
budget: placed.target ? {
|
|
169
|
+
reclaimed: [],
|
|
170
|
+
refusal: null
|
|
171
|
+
} : await checkBudget({
|
|
172
|
+
root: args.root,
|
|
173
|
+
note
|
|
174
|
+
}),
|
|
175
|
+
devicePlacement: placed.placement
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return { failure: {
|
|
179
|
+
code: error.code ?? "STIM_HOSTING_REFUSED",
|
|
180
|
+
message: error.message,
|
|
181
|
+
...error instanceof Error && "remedy" in error && typeof error.remedy === "string" ? { remedy: error.remedy } : {}
|
|
182
|
+
} };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/commands/android/hosted.ts
|
|
187
|
+
async function finishHostedAndroidRun({ target, artifact, root, slot = "default", release, appMode = release ? "release" : "development", isExpo, metroPort, logsDir, selectors, place, writePlacement, verifyLaunch: verify, writeLaunch, readApkPackage, resolveDevClientScheme, now, started, startedAt, fail, phase, enterPhase, out, recordBuild, ...report }) {
|
|
188
|
+
const packageName = artifact.androidPackage ?? readApkPackage(artifact.apkPath);
|
|
189
|
+
if (!artifact.apkPath || !packageName) return fail("STIM_INSTALL_FAILED", "The built APK has no package identity.");
|
|
190
|
+
const launchedAt = now();
|
|
191
|
+
try {
|
|
192
|
+
const run = await place(target, {
|
|
193
|
+
root,
|
|
194
|
+
slot,
|
|
195
|
+
bundle: artifact.apkPath,
|
|
196
|
+
handoff: artifact.handoff,
|
|
197
|
+
bundleId: packageName,
|
|
198
|
+
release,
|
|
199
|
+
...appMode === "process" ? { mode: appMode } : {},
|
|
200
|
+
metroPort,
|
|
201
|
+
selectors,
|
|
202
|
+
...appMode === "development" && isExpo ? { devClientScheme: resolveDevClientScheme(root, artifact.apkPath) ?? void 0 } : {},
|
|
203
|
+
reserved: (placement) => writePlacement(root, slot, placement),
|
|
204
|
+
note: out,
|
|
205
|
+
enterPhase
|
|
206
|
+
});
|
|
207
|
+
writePlacement(root, slot, run.placement);
|
|
208
|
+
writeLaunch(root, "android", {
|
|
209
|
+
appId: packageName,
|
|
210
|
+
deviceId: run.placement.session,
|
|
211
|
+
metroPort,
|
|
212
|
+
release,
|
|
213
|
+
...appMode === "process" ? { runtime: "process" } : {},
|
|
214
|
+
launchedAt: new Date(launchedAt).toISOString()
|
|
215
|
+
}, slot);
|
|
216
|
+
let launched = run.launched;
|
|
217
|
+
if (appMode === "development") {
|
|
218
|
+
const evidence = await verify({
|
|
219
|
+
requireBundleResponse: true,
|
|
220
|
+
platform: "android",
|
|
221
|
+
logsDir: logsDir ?? void 0,
|
|
222
|
+
since: launchedAt,
|
|
223
|
+
metroPort,
|
|
224
|
+
mode: workspaceDevServerMode(root, isExpo),
|
|
225
|
+
slot: launchSlotScope(root, slot),
|
|
226
|
+
platformShared: siblingPlatformSlots(root, "android", slot).length > 0
|
|
227
|
+
});
|
|
228
|
+
if (evidence.fatal) return fail("STIM_LAUNCH_FAILED", "Metro could not deliver the hosted app bundle.", "Run stim logs --errors and fix the bundle error.");
|
|
229
|
+
launched = evidence.verified ? true : evidence.requested ? "bundling" : "unverified";
|
|
230
|
+
}
|
|
231
|
+
if (launched === "unverified") out(`Launch on ${target.host.machine} is unverified; check the hosted app, then retry stim android --remote ${target.host.machine}.`);
|
|
232
|
+
const host = hostedAndroidStatus(run.placement);
|
|
233
|
+
report.record.deviceName = host.device?.name ?? null;
|
|
234
|
+
report.record.bundleId = packageName;
|
|
235
|
+
persistLastBuild({
|
|
236
|
+
recordBuild,
|
|
237
|
+
root,
|
|
238
|
+
record: report.record,
|
|
239
|
+
startedAt,
|
|
240
|
+
durationMs: now() - started,
|
|
241
|
+
status: "success",
|
|
242
|
+
out
|
|
243
|
+
});
|
|
244
|
+
await finishAndroidUpload(artifact.uploadPending, artifact.remote, phase);
|
|
245
|
+
await artifact.providerUpload;
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
facts: reportAndroidResult({
|
|
249
|
+
...report,
|
|
250
|
+
...appMode === "process" ? { runtimeKind: "process" } : {},
|
|
251
|
+
root,
|
|
252
|
+
slot,
|
|
253
|
+
release,
|
|
254
|
+
metroPort,
|
|
255
|
+
logsDir,
|
|
256
|
+
host,
|
|
257
|
+
serial: null,
|
|
258
|
+
apkPath: artifact.apkPath,
|
|
259
|
+
androidPackage: packageName,
|
|
260
|
+
installSkipped: false,
|
|
261
|
+
waitedForBuild: artifact.waitedForBuild,
|
|
262
|
+
remote: artifact.remote,
|
|
263
|
+
providerName: artifact.providerName,
|
|
264
|
+
launchState: launched,
|
|
265
|
+
launched: {},
|
|
266
|
+
ccache: artifact.ccache,
|
|
267
|
+
durationMs: now() - started
|
|
268
|
+
})
|
|
269
|
+
};
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return fail(error.code ?? "STIM_HOSTING_REFUSED", error.message, `Retry stim android --remote ${target.host.machine}, or run stim stop to reconcile the recorded placement.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/commands/android/next-build.ts
|
|
276
|
+
function runOnlyFlag(opts) {
|
|
277
|
+
if (isPhysicalDeviceRequest(opts.device)) return "--device";
|
|
278
|
+
if (opts.remote) return "--remote";
|
|
279
|
+
if (opts.wait !== void 0) return opts.wait === false ? "--no-wait" : "--wait";
|
|
280
|
+
if (opts.metroCheck === false) return "--no-metro-check";
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
async function planAndroid(opts, overrides = {}) {
|
|
284
|
+
const deps = {
|
|
285
|
+
findRoot: findProjectRoot,
|
|
286
|
+
projectRegistry,
|
|
287
|
+
...overrides
|
|
288
|
+
};
|
|
289
|
+
const json = Boolean(opts.json);
|
|
290
|
+
const flag = runOnlyFlag(opts);
|
|
291
|
+
if (flag) return refusePlan(planFlagRefusal(flag), json);
|
|
292
|
+
const root = deps.findRoot(process.cwd());
|
|
293
|
+
if (!root) return refusePlan(NO_PROJECT_REFUSAL, json);
|
|
294
|
+
const selected = deps.projectRegistry.selectAndroid(root);
|
|
295
|
+
if ("problem" in selected) return refusePlan({
|
|
296
|
+
code: "STIM_NO_PROJECT",
|
|
297
|
+
...selected.problem
|
|
298
|
+
}, json);
|
|
299
|
+
const resolved = resolveProjectSettings(root);
|
|
300
|
+
const project = await selected.load(resolved);
|
|
301
|
+
if (!project.plan) return refusePlan(unsupportedPlan("android"), json);
|
|
302
|
+
const result = await project.plan(opts, resolved);
|
|
303
|
+
if ("platform" in result) printPlan(result, json);
|
|
304
|
+
else refusePlan(result.refusal, json, result.lines);
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/commands/android.ts
|
|
308
|
+
function androidCommand(program) {
|
|
309
|
+
registerAndroid(program);
|
|
310
|
+
}
|
|
311
|
+
function registerAndroid(program) {
|
|
312
|
+
program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator. React Native and Expo Debug runs start 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("--no-metro-check", "Skip the reserved-port Metro health check and do not start it (the app will load no bundle unless something else serves it)").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("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device-profile <id>", "Hardware profile to create this workspace's owned AVD with, as an `avdmanager list device -c` id (e.g. \"pixel_fold\", \"pixel_tablet\"). Overrides the android.deviceProfile setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the available ids.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <target>", "Run on eas, proxy, or a named approved Mac from remote.machines; auto places on an approved Mac when this Mac is full or busy; local runs here, whatever android.remote says", (value) => {
|
|
313
|
+
if (!parseMachine(value)) throw new InvalidArgumentError("expected eas, proxy, auto, local, or a tailnet machine name");
|
|
314
|
+
return value;
|
|
315
|
+
}).option("--wait <seconds>", "How long to wait for another workspace to release the device 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 device 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) => {
|
|
316
|
+
if (opts.plan) return planAndroid(opts);
|
|
317
|
+
const root = findProjectRoot(process.cwd());
|
|
318
|
+
if (!root) {
|
|
319
|
+
refuseNoProject({ json: Boolean(opts.json) });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (!(await runAndroidOperation(root, {
|
|
323
|
+
...opts,
|
|
324
|
+
waitConflict: waitFlagConflict(process.argv)
|
|
325
|
+
})).ok) process.exit(runCancellation() ? 130 : 1);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function runAndroidOperation(root, opts = {}) {
|
|
329
|
+
return withNativeBuildRun(root, {
|
|
330
|
+
command: "android",
|
|
331
|
+
platform: PLATFORM,
|
|
332
|
+
slot: opts.slot ?? "default"
|
|
333
|
+
}, async (claim) => {
|
|
334
|
+
recordWorkspaceUse(root);
|
|
335
|
+
const progress = startBuildProgress({
|
|
336
|
+
root,
|
|
337
|
+
platform: PLATFORM,
|
|
338
|
+
slot: opts.slot ?? "default",
|
|
339
|
+
claim,
|
|
340
|
+
note: (line) => console.error(chalk.dim(line))
|
|
341
|
+
});
|
|
342
|
+
try {
|
|
343
|
+
return await runAndroid({
|
|
344
|
+
root,
|
|
345
|
+
buildMachine: opts.remoteBuild,
|
|
346
|
+
slot: opts.slot,
|
|
347
|
+
easProfile: opts.easProfile,
|
|
348
|
+
json: Boolean(opts.json),
|
|
349
|
+
metroCheck: opts.metroCheck !== false,
|
|
350
|
+
useBuildCache: opts.buildCache !== false,
|
|
351
|
+
variant: opts.variant ?? null,
|
|
352
|
+
systemImage: opts.systemImage ?? null,
|
|
353
|
+
deviceProfile: opts.deviceProfile ?? null,
|
|
354
|
+
remoteDevice: opts.remote ?? null,
|
|
355
|
+
device: opts.device ?? null,
|
|
356
|
+
wait: opts.wait,
|
|
357
|
+
waitConflict: opts.waitConflict,
|
|
358
|
+
progress
|
|
359
|
+
});
|
|
360
|
+
} finally {
|
|
361
|
+
progress.clear();
|
|
362
|
+
}
|
|
363
|
+
}, { write: (line) => console.error(chalk.dim(phaseLine("lock", line))) });
|
|
364
|
+
}
|
|
365
|
+
function resolveRunAndroidOptions({ root, json = false, remoteDevice: commandRemoteBackend = null, resolveSettingsFor = resolveSettings, resolveRemoteDeviceContext = resolveRemoteContext, remoteDeviceDeps: makeRemoteDeviceDeps = remoteAndroidDeps, resolveEasBin = resolveEasCliBin, ensureMetroReachable: ensureRemoteMetro = ensureMetroReachable, ensureRemoteBootOwned: ensureRemoteOwned = ensureRemoteBootOwned, detectRemoteProviders = detectProviders, metroCheck = true, useBuildCache = true, easProfile, resolveEasDevelopmentBuild: resolveEasBuild = resolveEasDevelopmentBuild, variant: variantFlag = null, systemImage: systemImageFlag = null, deviceProfile: deviceProfileFlag, device: deviceFlag = null, wait: waitFlag = void 0, waitConflict = false, acquireLease = acquireRunLease, makeRunLease = runLease, selectPool = selectFromPool, onLeaseSignal = releaseLeaseOnSignal, listDevices = listAdbDevices, deviceModel = physicalDeviceModel, deviceAbi = androidDeviceAbi, isEmulatorDevice = probeEmulatorSerial, readApkPackage = (apkPath) => apkPackage(dumpApkManifest(apkPath)), getLimits = getConcurrencyLimits, checkCapacity = checkDeviceCapacity, acquireSlot = acquireBuildSlot, releaseSlot = releaseBuildSlot, ensureDevice = ensureOwnedDevice, listSystemImages = listInstalledSystemImages, listDeviceProfiles, ensureDeviceBooted = ensureBooted, resolveAvdSerial = resolveOwnedAvdSerial, waitForDeviceBoot = waitForBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro = resolveProjectMetro, warmMetro: prewarmMetro = warmMetro, startServer = startDevServer, readState = readWorkspaceState$1, pidAlive = pidExists, verifyCollector = verifyCollectorOwnership, verifyLaunched = verifyLaunch, ensureStorage = ensureWorkspaceStorageSafely, acquireLock = acquireBuildLock, releaseLock = releaseBuildLock, waitForBuild: waitForBuild$1 = waitForBuild, easAuth = checkEasAuth, resolveRemoteBuild = resolveRemote, uploadRemoteBuild = uploadRemote, resolveCacheProvider = resolveCacheProviderConfig, loadCacheProviderModule = loadCacheProvider, install = installAndroidApp, launch = launchAndroidApp, launchRelease = launchAndroidReleaseApp, verifyReleaseLaunched = verifyAndroidReleaseLaunch, resolveDevClientScheme = androidDevClientScheme, spawn = (cmd, args, opts) => getExecutor().spawn(cmd, args, opts), kill = (pid, signal) => process.kill(pid, signal), createWriter = createNdjsonWriter, writeLaunch = writeWorkspaceLaunch, writeState = writeWorkspaceState, recordBuild, recordStats = recordRunStats, recordCapacityRefusal: recordRefusal, readEstimates = readRunEstimates, now = Date.now, out = (line) => console.error(line), emit = (line) => console.log(line) } = {}) {
|
|
366
|
+
return {
|
|
367
|
+
root,
|
|
368
|
+
json,
|
|
369
|
+
commandRemoteBackend,
|
|
370
|
+
resolveSettingsFor,
|
|
371
|
+
resolveRemoteDeviceContext,
|
|
372
|
+
makeRemoteDeviceDeps,
|
|
373
|
+
resolveEasBin,
|
|
374
|
+
ensureRemoteMetro,
|
|
375
|
+
ensureRemoteOwned,
|
|
376
|
+
detectRemoteProviders,
|
|
377
|
+
metroCheck,
|
|
378
|
+
useBuildCache,
|
|
379
|
+
easProfile,
|
|
380
|
+
resolveEasBuild,
|
|
381
|
+
variantFlag,
|
|
382
|
+
systemImageFlag,
|
|
383
|
+
deviceProfileFlag,
|
|
384
|
+
deviceFlag,
|
|
385
|
+
waitFlag,
|
|
386
|
+
waitConflict,
|
|
387
|
+
acquireLease,
|
|
388
|
+
makeRunLease,
|
|
389
|
+
selectPool,
|
|
390
|
+
onLeaseSignal,
|
|
391
|
+
listDevices,
|
|
392
|
+
deviceModel,
|
|
393
|
+
deviceAbi,
|
|
394
|
+
isEmulatorDevice,
|
|
395
|
+
readApkPackage,
|
|
396
|
+
getLimits,
|
|
397
|
+
checkCapacity,
|
|
398
|
+
acquireSlot,
|
|
399
|
+
releaseSlot,
|
|
400
|
+
ensureDevice,
|
|
401
|
+
listSystemImages,
|
|
402
|
+
listDeviceProfiles,
|
|
403
|
+
ensureDeviceBooted,
|
|
404
|
+
resolveAvdSerial,
|
|
405
|
+
waitForDeviceBoot,
|
|
406
|
+
dataFreeBytes,
|
|
407
|
+
trimCaches,
|
|
408
|
+
wipeDevice,
|
|
409
|
+
resolveMetro,
|
|
410
|
+
prewarmMetro,
|
|
411
|
+
startServer,
|
|
412
|
+
readState,
|
|
413
|
+
pidAlive,
|
|
414
|
+
verifyCollector,
|
|
415
|
+
verifyLaunched,
|
|
416
|
+
ensureStorage,
|
|
417
|
+
acquireLock,
|
|
418
|
+
releaseLock,
|
|
419
|
+
waitForBuild: waitForBuild$1,
|
|
420
|
+
easAuth,
|
|
421
|
+
resolveRemoteBuild,
|
|
422
|
+
uploadRemoteBuild,
|
|
423
|
+
resolveCacheProvider,
|
|
424
|
+
loadCacheProviderModule,
|
|
425
|
+
install,
|
|
426
|
+
launch,
|
|
427
|
+
launchRelease,
|
|
428
|
+
verifyReleaseLaunched,
|
|
429
|
+
resolveDevClientScheme,
|
|
430
|
+
spawn,
|
|
431
|
+
kill,
|
|
432
|
+
createWriter,
|
|
433
|
+
writeLaunch,
|
|
434
|
+
writeState,
|
|
435
|
+
recordBuild,
|
|
436
|
+
recordStats,
|
|
437
|
+
recordCapacityRefusal: recordRefusal,
|
|
438
|
+
readEstimates,
|
|
439
|
+
now,
|
|
440
|
+
out,
|
|
441
|
+
emit
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function androidSlotOptions(options) {
|
|
445
|
+
const base = resolveRunAndroidOptions(options);
|
|
446
|
+
const slot = validateDeviceSlot(options.slot);
|
|
447
|
+
if (slot === "default") return base;
|
|
448
|
+
return {
|
|
449
|
+
...base,
|
|
450
|
+
ensureDevice: (args) => base.ensureDevice({
|
|
451
|
+
...args,
|
|
452
|
+
slot
|
|
453
|
+
}),
|
|
454
|
+
ensureDeviceBooted: (args) => base.ensureDeviceBooted({
|
|
455
|
+
...args,
|
|
456
|
+
slot
|
|
457
|
+
}),
|
|
458
|
+
checkCapacity: (args) => base.checkCapacity({
|
|
459
|
+
...args,
|
|
460
|
+
slot
|
|
461
|
+
}),
|
|
462
|
+
selectPool: (args) => base.selectPool({
|
|
463
|
+
...args,
|
|
464
|
+
slot
|
|
465
|
+
}),
|
|
466
|
+
acquireLease: (args) => base.acquireLease({
|
|
467
|
+
...args,
|
|
468
|
+
slot
|
|
469
|
+
}),
|
|
470
|
+
makeRunLease: (args) => base.makeRunLease({
|
|
471
|
+
...args,
|
|
472
|
+
slot
|
|
473
|
+
}),
|
|
474
|
+
writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function avdSetupFailure(error, root, logFile) {
|
|
478
|
+
const refusal = (0, ownership_claim_exports.claimFailure)(error, "stim android");
|
|
479
|
+
if (refusal) return refusal;
|
|
480
|
+
if (error instanceof DeviceAdmissionRefusal) return {
|
|
481
|
+
code: error.code,
|
|
482
|
+
message: error.message,
|
|
483
|
+
remedy: error.remedy
|
|
484
|
+
};
|
|
485
|
+
const diag = noDeviceDiagnostic({
|
|
486
|
+
reason: `Could not ensure an owned Android emulator: ${error?.message || error}`,
|
|
487
|
+
logFile,
|
|
488
|
+
localEmulator: !(error instanceof AvdRecoveryError),
|
|
489
|
+
remedy: error instanceof AvdBootError ? error.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
|
|
490
|
+
});
|
|
491
|
+
return {
|
|
492
|
+
code: NO_DEVICE,
|
|
493
|
+
message: diag.message,
|
|
494
|
+
remedy: diag.remedy,
|
|
495
|
+
extra: {
|
|
496
|
+
lines: diag.lines,
|
|
497
|
+
logPath: diag.logPath ? displayPath(root, diag.logPath) : null
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
async function runAndroid(options = {}) {
|
|
502
|
+
let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceProfileFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, listDeviceProfiles, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, dataFreeBytes, trimCaches, wipeDevice, resolveMetro, prewarmMetro, startServer, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, acquireLock, releaseLock, waitForBuild, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, install, launch, launchRelease, verifyReleaseLaunched, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordBuild, recordStats, recordCapacityRefusal: recordRefusal = recordCapacityRefusal, readEstimates, now, out, emit } = androidSlotOptions(options);
|
|
503
|
+
const progress = options.progress ?? NO_BUILD_PROGRESS;
|
|
504
|
+
const slot = validateDeviceSlot(options.slot);
|
|
505
|
+
const started = now();
|
|
506
|
+
const startedAt = new Date(started).toISOString();
|
|
507
|
+
const selectedProject = (options.projectRegistry ?? projectRegistry).selectAndroid(root);
|
|
508
|
+
if ("problem" in selectedProject) {
|
|
509
|
+
const { message, remedy } = selectedProject.problem;
|
|
510
|
+
out(phaseLine("error", chalk.red(`STIM_NO_PROJECT: ${message}`)));
|
|
511
|
+
out(phaseLine("remedy", remedy));
|
|
512
|
+
if (json) emit(JSON.stringify({
|
|
513
|
+
code: "STIM_NO_PROJECT",
|
|
514
|
+
message,
|
|
515
|
+
remedy
|
|
516
|
+
}));
|
|
517
|
+
return {
|
|
518
|
+
ok: false,
|
|
519
|
+
error: {
|
|
520
|
+
code: "STIM_NO_PROJECT",
|
|
521
|
+
message,
|
|
522
|
+
remedy
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
try {
|
|
527
|
+
await ensureStorage(root, { note: out });
|
|
528
|
+
} catch (error) {
|
|
529
|
+
const code = error?.code || "STIM_WORKSPACE_STATE";
|
|
530
|
+
const message = `Could not prepare this workspace's Stim state: ${error?.message || error}`;
|
|
531
|
+
const remedy = "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`).";
|
|
532
|
+
out(phaseLine("error", chalk.red(`${code}: ${message}`)));
|
|
533
|
+
out(phaseLine("remedy", remedy));
|
|
534
|
+
if (json) emit(JSON.stringify({
|
|
535
|
+
code,
|
|
536
|
+
message,
|
|
537
|
+
remedy
|
|
538
|
+
}));
|
|
539
|
+
return {
|
|
540
|
+
ok: false,
|
|
541
|
+
error: {
|
|
542
|
+
code,
|
|
543
|
+
message,
|
|
544
|
+
remedy
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const logsDir = workspaceLogsDir$1(root);
|
|
549
|
+
const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
|
|
550
|
+
const writer = tapBuildLog(createWriter(buildLog, {
|
|
551
|
+
truncate: true,
|
|
552
|
+
fields: {
|
|
553
|
+
platform: PLATFORM,
|
|
554
|
+
slot
|
|
555
|
+
}
|
|
556
|
+
}), progress);
|
|
557
|
+
setRemoteLogSink((entry) => writer.write(entry));
|
|
558
|
+
const record = {
|
|
559
|
+
fingerprint: null,
|
|
560
|
+
cacheKey: null,
|
|
561
|
+
cacheHit: false,
|
|
562
|
+
appPath: null,
|
|
563
|
+
bundleId: null,
|
|
564
|
+
avdName: null,
|
|
565
|
+
deviceName: null
|
|
566
|
+
};
|
|
567
|
+
const phase = (label, text) => out(phaseLine(label, text));
|
|
568
|
+
const stats = createRunRecorder({
|
|
569
|
+
platform: PLATFORM,
|
|
570
|
+
write: recordStats,
|
|
571
|
+
now,
|
|
572
|
+
note: (line) => out(phaseLine("stats", chalk.dim(line))),
|
|
573
|
+
phases: () => progress.durations(),
|
|
574
|
+
deviceSetup: () => progress.deviceSetupKnown()
|
|
575
|
+
});
|
|
576
|
+
const recordRun = stats.record;
|
|
577
|
+
let ccacheActivity = CCACHE_NOT_RUN;
|
|
578
|
+
let reclaimed = [];
|
|
579
|
+
const fail = (code, message, remedy, { lastBuildStatus = false, setup = false, diagnostics = [], buildDiagnostics: rawDiagnostics = [], lines = [], logPath = null, lease } = {}) => {
|
|
580
|
+
const cancellation = cancelledFailure(PLATFORM, {
|
|
581
|
+
code,
|
|
582
|
+
message
|
|
583
|
+
});
|
|
584
|
+
if (cancellation) ({code, message, remedy, lines} = cancellation);
|
|
585
|
+
if (lastBuildStatus) persistLastBuild({
|
|
586
|
+
recordBuild,
|
|
587
|
+
root,
|
|
588
|
+
record,
|
|
589
|
+
startedAt,
|
|
590
|
+
durationMs: now() - started,
|
|
591
|
+
status: "failed",
|
|
592
|
+
errorCode: code,
|
|
593
|
+
diagnostics: rawDiagnostics,
|
|
594
|
+
out
|
|
595
|
+
});
|
|
596
|
+
out(phaseLine("error", chalk.red(`${code}: ${message}`)));
|
|
597
|
+
for (const diagnostic of diagnostics) out(phaseLine("error", chalk.red(diagnostic)));
|
|
598
|
+
for (const line of lines) out(phaseLine("", chalk.dim(line)));
|
|
599
|
+
if (remedy) out(phaseLine("remedy", remedy));
|
|
600
|
+
if (logPath) out(phaseLine("log", logPath));
|
|
601
|
+
if (!setup) recordRun({
|
|
602
|
+
failed: true,
|
|
603
|
+
durationMs: now() - started
|
|
604
|
+
});
|
|
605
|
+
if (json) emit(JSON.stringify({
|
|
606
|
+
code,
|
|
607
|
+
message,
|
|
608
|
+
remedy: remedy ?? null,
|
|
609
|
+
...record.devicePlacement ? { devicePlacement: record.devicePlacement } : {},
|
|
610
|
+
...ccacheActivity.status === "not-run" ? {} : { ccache: ccacheActivity },
|
|
611
|
+
...lease === void 0 ? {} : { lease },
|
|
612
|
+
...reclaimed.length ? { reclaimed } : {}
|
|
613
|
+
}));
|
|
614
|
+
writer.close();
|
|
615
|
+
return {
|
|
616
|
+
ok: false,
|
|
617
|
+
error: {
|
|
618
|
+
code,
|
|
619
|
+
message,
|
|
620
|
+
remedy: remedy ?? null
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
};
|
|
624
|
+
const settingsRoot = root;
|
|
625
|
+
const settingsContext = projectSettingsContext(root);
|
|
626
|
+
const projectKey = statsProjectKey$1({
|
|
627
|
+
root,
|
|
628
|
+
commonDir: settingsContext.gitCommonDir,
|
|
629
|
+
repoRoot: settingsContext.repoRoot
|
|
630
|
+
});
|
|
631
|
+
stats.setProject(projectKey);
|
|
632
|
+
progress.estimate(projectKey);
|
|
633
|
+
let estimatesRead = null;
|
|
634
|
+
const estimates = () => estimatesRead ??= readEstimates({
|
|
635
|
+
projectKey,
|
|
636
|
+
platform: PLATFORM
|
|
637
|
+
});
|
|
638
|
+
const settings = resolveSettingsFor(settingsContext);
|
|
639
|
+
const integration = await selectedProject.load({
|
|
640
|
+
context: settingsContext,
|
|
641
|
+
settings
|
|
642
|
+
});
|
|
643
|
+
const placement = resolveBuildPlacement(options.buildMachine);
|
|
644
|
+
record.buildMachine = placement.selected;
|
|
645
|
+
if (placement.failure) {
|
|
646
|
+
const { code, message, remedy } = placement.failure;
|
|
647
|
+
return fail(code, message, remedy, { setup: true });
|
|
648
|
+
}
|
|
649
|
+
const planned = resolveAndroidRunPlan({
|
|
650
|
+
settings,
|
|
651
|
+
settingsContext,
|
|
652
|
+
slot,
|
|
653
|
+
easProfile,
|
|
654
|
+
variant: variantFlag,
|
|
655
|
+
systemImage: systemImageFlag,
|
|
656
|
+
deviceProfile: deviceProfileFlag,
|
|
657
|
+
device: deviceFlag,
|
|
658
|
+
wait: waitFlag,
|
|
659
|
+
waitConflict,
|
|
660
|
+
remote: commandRemoteBackend,
|
|
661
|
+
buildCache: requestedBuildCache
|
|
662
|
+
}, {
|
|
663
|
+
resolveCacheProvider,
|
|
664
|
+
runtimeKind: integration.runtimeKind,
|
|
665
|
+
variantProblem: integration.variantProblem,
|
|
666
|
+
detectExpo: () => integration.isExpo,
|
|
667
|
+
listSystemImages,
|
|
668
|
+
listDeviceProfiles,
|
|
669
|
+
warn: (label, message) => out(phaseLine(label, chalk.yellow(message)))
|
|
670
|
+
});
|
|
671
|
+
if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
|
|
672
|
+
const { plan } = planned;
|
|
673
|
+
if (!integration.targets.includes(plan.target.kind)) return fail("STIM_BAD_ARG", `This project integration does not support the ${plan.target.kind} Android target.`, "Use a local Android emulator for this integration.");
|
|
674
|
+
if (easProfile !== void 0 && !integration.eas) return fail("STIM_BAD_ARG", "This project integration does not support EAS builds.", "Use its local Android build recipe.");
|
|
675
|
+
const deviceSlotWait = {
|
|
676
|
+
automatic: plan.target.kind === "hosted" && plan.target.machine === "auto",
|
|
677
|
+
signal: runCancellationSignal(),
|
|
678
|
+
waitMs: plan.deviceSlotWaitMs,
|
|
679
|
+
displayName: basename(root),
|
|
680
|
+
reclaimIdleMinutes: deviceReclaimIdleMinutesSetting(settings),
|
|
681
|
+
waitingFor: (info) => progress.waitingFor(info, "device-slot"),
|
|
682
|
+
onWait: (ms) => {
|
|
683
|
+
stats.addDeviceSlotWaitMs(ms);
|
|
684
|
+
if (record.devicePlacement?.decision === "local" && ms > 0) record.devicePlacement.decision = "waited-locally";
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
const { build: buildPlan, isExpo, cacheProviderConfig } = plan;
|
|
688
|
+
const { variant, release } = buildPlan;
|
|
689
|
+
const runtimeKind = integration.runtimeKind(buildPlan);
|
|
690
|
+
const appMode = runtimeKind === "process" ? "process" : runtimeKind === "metro" ? "development" : "release";
|
|
691
|
+
record.configuration = variant ?? "debug";
|
|
692
|
+
const hosting = await selectAndroidPlacement({
|
|
693
|
+
root,
|
|
694
|
+
slot,
|
|
695
|
+
target: plan.target,
|
|
696
|
+
release,
|
|
697
|
+
appMode,
|
|
698
|
+
metroCheck,
|
|
699
|
+
noWait: plan.deviceSlotWaitMs === 0,
|
|
700
|
+
buildMachine: options.buildMachine,
|
|
701
|
+
read: options.readHostedAndroid,
|
|
702
|
+
resolveSerial: resolveAvdSerial,
|
|
703
|
+
prepare: options.prepareHostedAndroid,
|
|
704
|
+
automatic: options.automaticDevicePlacement,
|
|
705
|
+
...integration.eas && remoteEasFallbackSetting(settings) ? { eas: () => (options.checkEasFallback ?? checkEasFallback)({
|
|
706
|
+
root,
|
|
707
|
+
platform: PLATFORM,
|
|
708
|
+
slot,
|
|
709
|
+
release,
|
|
710
|
+
isExpo,
|
|
711
|
+
tunnelMode: tunnelModeSetting(settings),
|
|
712
|
+
publicUrl: publicUrlSetting(settings),
|
|
713
|
+
localOnlyFlags: [...typeof systemImageFlag === "string" ? ["--system-image"] : [], ...typeof deviceProfileFlag === "string" ? ["--device-profile"] : []]
|
|
714
|
+
}) } : {},
|
|
715
|
+
localSelectors: (target) => androidDeviceSelectorRefusal({
|
|
716
|
+
settingsContext,
|
|
717
|
+
slot,
|
|
718
|
+
systemImageFlag,
|
|
719
|
+
deviceProfileFlag,
|
|
720
|
+
systemImage: target.systemImage,
|
|
721
|
+
deviceProfile: target.deviceProfile,
|
|
722
|
+
physical: false,
|
|
723
|
+
remoteBackend: null
|
|
724
|
+
}, {
|
|
725
|
+
listSystemImages,
|
|
726
|
+
listDeviceProfiles
|
|
727
|
+
}),
|
|
728
|
+
checkBudget: options.checkBudget,
|
|
729
|
+
note: out,
|
|
730
|
+
phase,
|
|
731
|
+
log: (entry) => writer.write(entry),
|
|
732
|
+
fromFlag: commandRemoteBackend !== null
|
|
733
|
+
});
|
|
734
|
+
if ("failure" in hosting) return fail(hosting.failure.code, hosting.failure.message, hosting.failure.remedy);
|
|
735
|
+
const { target, hostedTarget, budget, selectors } = hosting;
|
|
736
|
+
record.devicePlacement = hosting.devicePlacement;
|
|
737
|
+
writeDevicePlacement(root, slot, PLATFORM, record.devicePlacement);
|
|
738
|
+
const physical = target.kind === "physical";
|
|
739
|
+
const remoteBackend = target.kind === "remote" ? target.backend : null;
|
|
740
|
+
if (hostedTarget) {
|
|
741
|
+
if (settings.androidEmulatorApp !== void 0) phase("device", "androidEmulatorApp is ignored on a hosting Mac; the emulator boots headless.");
|
|
742
|
+
if (appMode === "development" && (publicUrlSetting(settings) || tunnelModeSetting(settings))) phase("metro", "metro.publicUrl and metro.tunnel are ignored on a hosting Mac; Metro uses the private tailnet bridge.");
|
|
743
|
+
}
|
|
744
|
+
reclaimed = budget.reclaimed;
|
|
745
|
+
if (budget.refusal) return fail(budget.refusal.code, budget.refusal.message, budget.refusal.remedy);
|
|
746
|
+
const remoteContext = remoteBackend ? await resolveRemoteDeviceContext({
|
|
747
|
+
root,
|
|
748
|
+
platform: PLATFORM,
|
|
749
|
+
backend: remoteBackend,
|
|
750
|
+
easBin: resolveEasBin(root)?.file ?? null
|
|
751
|
+
}) : null;
|
|
752
|
+
if (remoteContext && "failed" in remoteContext) return fail(remoteContext.code ?? "STIM_NO_REMOTE_SESSION", remoteContext.failed, remoteContext.remedy);
|
|
753
|
+
const easBuild = await resolveEasBuild({
|
|
754
|
+
root,
|
|
755
|
+
platform: PLATFORM,
|
|
756
|
+
profile: easProfile,
|
|
757
|
+
note: out,
|
|
758
|
+
isExpo,
|
|
759
|
+
physical,
|
|
760
|
+
selectors: [variantFlag],
|
|
761
|
+
buildCache: requestedBuildCache
|
|
762
|
+
});
|
|
763
|
+
if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
|
|
764
|
+
const appIds = integration.appIds();
|
|
765
|
+
let androidPackage = appIds.androidPackage;
|
|
766
|
+
record.bundleId = androidPackage;
|
|
767
|
+
const registerProject = () => upsertProject(root, {
|
|
768
|
+
bundleId: appIds.bundleId ?? void 0,
|
|
769
|
+
androidPackage: androidPackage ?? void 0,
|
|
770
|
+
isExpo
|
|
771
|
+
});
|
|
772
|
+
if (remoteBackend !== "eas") registerProject();
|
|
773
|
+
const project = getProject(root);
|
|
774
|
+
const label = projectShortcut(root, project);
|
|
775
|
+
let remoteDevice = null;
|
|
776
|
+
const reservedPort = project?.metroPort ?? null;
|
|
777
|
+
let metroPort = null;
|
|
778
|
+
let devServer = null;
|
|
779
|
+
async function prepareMetro() {
|
|
780
|
+
if (metroCheck) {
|
|
781
|
+
const gate = await ensureDevServer({
|
|
782
|
+
root,
|
|
783
|
+
port: reservedPort,
|
|
784
|
+
settings: hostedMetroSettings(settings, Boolean(hostedTarget)),
|
|
785
|
+
settingsContext,
|
|
786
|
+
remote: remoteContext !== null,
|
|
787
|
+
note: out,
|
|
788
|
+
resolve: resolveMetro,
|
|
789
|
+
start: startServer,
|
|
790
|
+
readState
|
|
791
|
+
});
|
|
792
|
+
reclaimed = [...reclaimed, ...gate.reclaimed];
|
|
793
|
+
if (!gate.ok) return {
|
|
794
|
+
ok: false,
|
|
795
|
+
error: {
|
|
796
|
+
code: gate.code,
|
|
797
|
+
message: gate.message,
|
|
798
|
+
remedy: gate.remedy,
|
|
799
|
+
lines: gate.lines
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
metroPort = gate.port;
|
|
803
|
+
devServer = gate.devServer;
|
|
804
|
+
phase("metro", `port ${metroPort} (${devServer ? `started: ${devServer.reason}` : `pid ${gate.pid ?? "unknown, started outside Stim"}`})`);
|
|
805
|
+
return {
|
|
806
|
+
ok: true,
|
|
807
|
+
prepared: { metroPort }
|
|
808
|
+
};
|
|
809
|
+
} else {
|
|
810
|
+
const pin = metroPortSetting(settings);
|
|
811
|
+
if (pin.error) return {
|
|
812
|
+
ok: false,
|
|
813
|
+
error: {
|
|
814
|
+
code: "STIM_BAD_ARG",
|
|
815
|
+
message: pin.error,
|
|
816
|
+
remedy: SETTING_SHAPE_REMEDY
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
metroPort = pin.port ?? reservedPort ?? 8081;
|
|
820
|
+
phase("metro", pin.port ?? reservedPort ? `port ${metroPort} (not checked)` : `no reservation; using ${DEFAULT_METRO_PORT} (not checked)`);
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
ok: true,
|
|
824
|
+
prepared: { metroPort }
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
const runtime = options.runtimePlan ?? integration.runtime({
|
|
828
|
+
build: buildPlan,
|
|
829
|
+
prepareMetro,
|
|
830
|
+
phase
|
|
831
|
+
});
|
|
832
|
+
const useBuildCache = buildPlan.cache.read;
|
|
833
|
+
const preparation = await runtime.prepare();
|
|
834
|
+
if (!preparation.ok) {
|
|
835
|
+
const { code, message, remedy, lines } = preparation.error;
|
|
836
|
+
return fail(code, message, remedy, { lines });
|
|
837
|
+
}
|
|
838
|
+
const runtimePreparation = preparation.prepared;
|
|
839
|
+
metroPort = runtimePreparation.metroPort;
|
|
840
|
+
if (remoteContext) {
|
|
841
|
+
remoteDevice = makeRemoteDeviceDeps(remoteContext.ctx);
|
|
842
|
+
if (metroPort !== null) {
|
|
843
|
+
const reachable = await ensureRemoteMetro({
|
|
844
|
+
ctx: remoteDevice.ctx,
|
|
845
|
+
metroPort,
|
|
846
|
+
isExpo,
|
|
847
|
+
tunnelMode: tunnelModeSetting(settings) ?? void 0,
|
|
848
|
+
publicUrl: publicUrlSetting(settings),
|
|
849
|
+
available: detectRemoteProviders(binOnPath, tunnelModeSetting(settings) ?? "auto")
|
|
850
|
+
});
|
|
851
|
+
if ("failed" in reachable) return fail(reachable.code ?? "STIM_NO_REMOTE_SESSION", reachable.failed, reachable.remedy);
|
|
852
|
+
}
|
|
853
|
+
checkCapacity = remoteDevice.checkCapacity;
|
|
854
|
+
ensureDevice = remoteDevice.ensureDevice;
|
|
855
|
+
ensureDeviceBooted = remoteDevice.ensureDeviceBooted;
|
|
856
|
+
install = remoteDevice.install;
|
|
857
|
+
launch = remoteDevice.launch;
|
|
858
|
+
}
|
|
859
|
+
const emuLog = emulatorLogFile(root);
|
|
860
|
+
const limits = getLimits();
|
|
861
|
+
let device;
|
|
862
|
+
let bootDuration = "";
|
|
863
|
+
let bootPending = false;
|
|
864
|
+
let bootPromise;
|
|
865
|
+
let startRemoteBoot = null;
|
|
866
|
+
if (hostedTarget) {
|
|
867
|
+
device = {
|
|
868
|
+
owned: false,
|
|
869
|
+
systemImage: hostedTarget.choice.systemImage,
|
|
870
|
+
deviceProfile: hostedTarget.choice.deviceProfile
|
|
871
|
+
};
|
|
872
|
+
bootPromise = Promise.resolve({ ok: true });
|
|
873
|
+
} else if (target.kind === "physical" && !target.serial) {
|
|
874
|
+
const pooled = await pooledAndroidDevice({
|
|
875
|
+
root,
|
|
876
|
+
selectPool,
|
|
877
|
+
listDevices,
|
|
878
|
+
isEmulatorDevice,
|
|
879
|
+
deviceModel,
|
|
880
|
+
waitSeconds: target.lease.waitSeconds,
|
|
881
|
+
noWait: target.lease.noWait,
|
|
882
|
+
now,
|
|
883
|
+
warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
|
|
884
|
+
});
|
|
885
|
+
if ("code" in pooled) return fail(pooled.code, pooled.message, pooled.remedy, pooled.extra);
|
|
886
|
+
device = pooled.device;
|
|
887
|
+
bootPromise = Promise.resolve({
|
|
888
|
+
ok: true,
|
|
889
|
+
serial: pooled.device.serial
|
|
890
|
+
});
|
|
891
|
+
} else if (target.kind === "physical") {
|
|
892
|
+
const resolved = resolvePhysicalDevice(target.serial, listDevices(), isEmulatorDevice);
|
|
893
|
+
if (!resolved.serial) return fail(NO_DEVICE, resolved.error, resolved.remedy);
|
|
894
|
+
device = {
|
|
895
|
+
serial: resolved.serial,
|
|
896
|
+
deviceName: deviceModel(resolved.serial) ?? resolved.serial,
|
|
897
|
+
owned: false
|
|
898
|
+
};
|
|
899
|
+
bootPromise = Promise.resolve({
|
|
900
|
+
ok: true,
|
|
901
|
+
serial: resolved.serial
|
|
902
|
+
});
|
|
903
|
+
} else {
|
|
904
|
+
if (!remoteDevice) try {
|
|
905
|
+
configuredAndroidEmulatorApp();
|
|
906
|
+
} catch (err) {
|
|
907
|
+
return fail("STIM_BAD_ARG", err.message, "Run `stim settings set androidEmulatorApp emulator` or `stim settings set androidEmulatorApp stim-desktop`.");
|
|
908
|
+
}
|
|
909
|
+
const capacity = plan.deviceSlotWaitMs === 0 ? checkCapacity({
|
|
910
|
+
platform: PLATFORM,
|
|
911
|
+
project,
|
|
912
|
+
max: limits.maxDevices
|
|
913
|
+
}) : null;
|
|
914
|
+
if (capacity) {
|
|
915
|
+
if (capacity.code === "STIM_AT_CAPACITY") recordRefusal({
|
|
916
|
+
platform: PLATFORM,
|
|
917
|
+
max: limits.maxDevices,
|
|
918
|
+
workspace: workspaceId(root)
|
|
919
|
+
}, now());
|
|
920
|
+
return fail(capacity.code, capacity.message, capacity.remedy);
|
|
921
|
+
}
|
|
922
|
+
const prepare = stepClock(now);
|
|
923
|
+
try {
|
|
924
|
+
device = await ensureDevice({
|
|
925
|
+
platform: PLATFORM,
|
|
926
|
+
project,
|
|
927
|
+
projectPath: root,
|
|
928
|
+
settingsRoot,
|
|
929
|
+
settings,
|
|
930
|
+
flags: {
|
|
931
|
+
deviceSlotWait,
|
|
932
|
+
systemImage: target.systemImage,
|
|
933
|
+
systemImageFlag: systemImageFlag?.trim() || null,
|
|
934
|
+
deviceProfile: target.deviceProfile
|
|
935
|
+
},
|
|
936
|
+
note: out,
|
|
937
|
+
out,
|
|
938
|
+
logFile: emuLog
|
|
939
|
+
});
|
|
940
|
+
} catch (err) {
|
|
941
|
+
const failure = avdSetupFailure(err, root, emuLog);
|
|
942
|
+
return fail(failure.code, failure.message, failure.remedy, failure.extra);
|
|
943
|
+
}
|
|
944
|
+
progress.deviceSetup(didSetUpDevice(device, Boolean(remoteDevice)));
|
|
945
|
+
const prepareMs = prepare();
|
|
946
|
+
if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
|
|
947
|
+
const boot = () => Promise.resolve(ensureDeviceBooted({
|
|
948
|
+
platform: PLATFORM,
|
|
949
|
+
device,
|
|
950
|
+
projectPath: root,
|
|
951
|
+
deviceSlotWait,
|
|
952
|
+
out,
|
|
953
|
+
logFile: emuLog
|
|
954
|
+
})).catch((e) => ({
|
|
955
|
+
failed: true,
|
|
956
|
+
reason: String(e?.message || e),
|
|
957
|
+
serial: void 0
|
|
958
|
+
}));
|
|
959
|
+
const startBoot = () => {
|
|
960
|
+
const bootTimer = stepTimer(now);
|
|
961
|
+
bootPending = remoteDevice?.ctx.backend !== "eas";
|
|
962
|
+
return (remoteDevice?.ctx.backend === "eas" ? ensureRemoteOwned({
|
|
963
|
+
root,
|
|
964
|
+
platform: PLATFORM,
|
|
965
|
+
sessionName: ownedSessionName(remoteDevice.ctx.label),
|
|
966
|
+
startedAt: new Date(now()).toISOString(),
|
|
967
|
+
boot,
|
|
968
|
+
createdSessionId: remoteDevice.createdSessionId,
|
|
969
|
+
abandonCreatedSession: remoteDevice.abandonCreatedSession,
|
|
970
|
+
webPreviewUrl: remoteDevice.webPreviewUrl,
|
|
971
|
+
writeState,
|
|
972
|
+
register: registerProject,
|
|
973
|
+
notice: (line) => out(chalk.dim(phaseLine("lock", line)))
|
|
974
|
+
}) : boot()).then((result) => {
|
|
975
|
+
bootDuration = bootTimer();
|
|
976
|
+
bootPending = false;
|
|
977
|
+
return result;
|
|
978
|
+
});
|
|
979
|
+
};
|
|
980
|
+
if (remoteDevice) startRemoteBoot = startBoot;
|
|
981
|
+
else bootPromise = startBoot();
|
|
982
|
+
}
|
|
983
|
+
record.avdName = device.avdName ?? null;
|
|
984
|
+
record.deviceName = device.deviceName ?? device.avdName ?? null;
|
|
985
|
+
record.systemImage = device.systemImage;
|
|
986
|
+
record.deviceProfile = device.deviceProfile;
|
|
987
|
+
let preparedArtifact;
|
|
988
|
+
const runFromFingerprint = async () => {
|
|
989
|
+
if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
|
|
990
|
+
port: metroPort,
|
|
991
|
+
platform: "android",
|
|
992
|
+
isExpo,
|
|
993
|
+
appId: androidPackage,
|
|
994
|
+
bundleUrl: metroWarmupUrlSetting(settings, "android")
|
|
995
|
+
});
|
|
996
|
+
const acquiredArtifact = await acquireAndroidArtifact({
|
|
997
|
+
root,
|
|
998
|
+
buildLog,
|
|
999
|
+
writer,
|
|
1000
|
+
recipe: integration.artifact({
|
|
1001
|
+
root,
|
|
1002
|
+
buildLog,
|
|
1003
|
+
writer,
|
|
1004
|
+
settings,
|
|
1005
|
+
buildPlan,
|
|
1006
|
+
target: androidBuildOptions({
|
|
1007
|
+
release,
|
|
1008
|
+
physical,
|
|
1009
|
+
device,
|
|
1010
|
+
variant,
|
|
1011
|
+
deviceAbi,
|
|
1012
|
+
targetAbiOnly: buildPlan.targetAbiOnly,
|
|
1013
|
+
hostedAbi: hostedTarget?.choice.architecture
|
|
1014
|
+
}),
|
|
1015
|
+
phase,
|
|
1016
|
+
out,
|
|
1017
|
+
step: progress.step,
|
|
1018
|
+
estimates
|
|
1019
|
+
}),
|
|
1020
|
+
targetOffloadRefusal: physical ? "device builds build here" : remoteDevice ? "remote device builds build here" : null,
|
|
1021
|
+
buildPlan,
|
|
1022
|
+
cacheProviderConfig,
|
|
1023
|
+
requestedBuildCache,
|
|
1024
|
+
easBuild,
|
|
1025
|
+
androidPackage,
|
|
1026
|
+
record,
|
|
1027
|
+
maxBuilds: limits.maxBuilds,
|
|
1028
|
+
progress: {
|
|
1029
|
+
phase,
|
|
1030
|
+
out,
|
|
1031
|
+
stats,
|
|
1032
|
+
step: progress.step,
|
|
1033
|
+
miss: progress.miss,
|
|
1034
|
+
hit: progress.hit,
|
|
1035
|
+
place: progress.place,
|
|
1036
|
+
waitingOn: progress.waitingOn,
|
|
1037
|
+
waitingFor: progress.waitingFor
|
|
1038
|
+
}
|
|
1039
|
+
}, {
|
|
1040
|
+
acquireLock,
|
|
1041
|
+
releaseLock,
|
|
1042
|
+
waitForBuild,
|
|
1043
|
+
easAuth,
|
|
1044
|
+
resolveRemoteBuild,
|
|
1045
|
+
uploadRemoteBuild,
|
|
1046
|
+
loadCacheProviderModule,
|
|
1047
|
+
acquireSlot,
|
|
1048
|
+
releaseSlot,
|
|
1049
|
+
now
|
|
1050
|
+
});
|
|
1051
|
+
if (!acquiredArtifact.ok) {
|
|
1052
|
+
ccacheActivity = acquiredArtifact.ccache;
|
|
1053
|
+
const { failure } = acquiredArtifact;
|
|
1054
|
+
return fail(failure.code, failure.message, failure.remedy, failure.extra);
|
|
1055
|
+
}
|
|
1056
|
+
const { artifact } = acquiredArtifact;
|
|
1057
|
+
preparedArtifact = artifact;
|
|
1058
|
+
const { apkPath } = artifact;
|
|
1059
|
+
ccacheActivity = artifact.ccache;
|
|
1060
|
+
androidPackage = artifact.androidPackage;
|
|
1061
|
+
record.appPath = apkPath;
|
|
1062
|
+
if (runCancellation()) return fail("STIM_CANCELLED", "before install");
|
|
1063
|
+
if (hostedTarget) return finishHostedAndroidRun({
|
|
1064
|
+
target: hostedTarget,
|
|
1065
|
+
artifact,
|
|
1066
|
+
root,
|
|
1067
|
+
slot,
|
|
1068
|
+
release,
|
|
1069
|
+
appMode,
|
|
1070
|
+
isExpo,
|
|
1071
|
+
metroPort,
|
|
1072
|
+
logsDir,
|
|
1073
|
+
selectors,
|
|
1074
|
+
place: options.placeHostedAndroid ?? placeHostedAndroid,
|
|
1075
|
+
writePlacement: options.writeHostedAndroid ?? writeHostedAndroid,
|
|
1076
|
+
verifyLaunch: verifyLaunched,
|
|
1077
|
+
writeLaunch,
|
|
1078
|
+
readApkPackage,
|
|
1079
|
+
resolveDevClientScheme,
|
|
1080
|
+
now,
|
|
1081
|
+
started,
|
|
1082
|
+
startedAt,
|
|
1083
|
+
record,
|
|
1084
|
+
fail,
|
|
1085
|
+
phase,
|
|
1086
|
+
enterPhase: progress.step,
|
|
1087
|
+
out,
|
|
1088
|
+
json,
|
|
1089
|
+
useBuildCache,
|
|
1090
|
+
variant,
|
|
1091
|
+
metroCheck,
|
|
1092
|
+
writer,
|
|
1093
|
+
emit,
|
|
1094
|
+
recordRun,
|
|
1095
|
+
reclaimed,
|
|
1096
|
+
devServer,
|
|
1097
|
+
recordBuild
|
|
1098
|
+
});
|
|
1099
|
+
if (startRemoteBoot) {
|
|
1100
|
+
progress.step("device");
|
|
1101
|
+
bootPromise = startRemoteBoot();
|
|
1102
|
+
const booted = await bootPromise;
|
|
1103
|
+
if (booted.failed) {
|
|
1104
|
+
if (booted.code) return fail(booted.code, booted.reason ?? "The remote device did not boot.", booted.remedy ?? null);
|
|
1105
|
+
const diag = noDeviceDiagnostic({
|
|
1106
|
+
reason: booted.reason ?? "The remote device did not boot.",
|
|
1107
|
+
logFile: emuLog,
|
|
1108
|
+
remedy: "Run `stim status` to inspect the remote device, then retry the command.",
|
|
1109
|
+
localEmulator: false
|
|
1110
|
+
});
|
|
1111
|
+
return fail(NO_DEVICE, diag.message, diag.remedy, {
|
|
1112
|
+
lines: diag.lines,
|
|
1113
|
+
logPath: diag.logPath ? displayPath(root, diag.logPath) : null
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
let leaseHandle = null;
|
|
1118
|
+
let stopLeaseSignals = null;
|
|
1119
|
+
const releaseLease = () => {
|
|
1120
|
+
const held = leaseHandle;
|
|
1121
|
+
const stopSignals = stopLeaseSignals;
|
|
1122
|
+
leaseHandle = null;
|
|
1123
|
+
stopLeaseSignals = null;
|
|
1124
|
+
stopSignals?.();
|
|
1125
|
+
try {
|
|
1126
|
+
held?.release();
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
out(phaseLine("lease", chalk.dim(`could not release this run's lease: ${err?.message || err}`)));
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
if (physical) {
|
|
1132
|
+
progress.step("device");
|
|
1133
|
+
const acquired = await acquireLease({
|
|
1134
|
+
root,
|
|
1135
|
+
platform: PLATFORM,
|
|
1136
|
+
id: device.serial,
|
|
1137
|
+
deviceName: device.deviceName ?? null,
|
|
1138
|
+
idLabel: "serial",
|
|
1139
|
+
waitSeconds: target.lease.waitSeconds,
|
|
1140
|
+
noWait: target.lease.noWait,
|
|
1141
|
+
installBoundMs: ADB_INSTALL_TIMEOUT_MS,
|
|
1142
|
+
appId: androidPackage,
|
|
1143
|
+
holderAppId: (holder) => getProject(holder)?.androidPackage ?? null,
|
|
1144
|
+
now,
|
|
1145
|
+
warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
|
|
1146
|
+
});
|
|
1147
|
+
if (acquired.status === "refused") return fail(acquired.refusal.code, acquired.refusal.message, acquired.refusal.remedy, { lease: acquired.refusal.lease });
|
|
1148
|
+
leaseHandle = makeRunLease({
|
|
1149
|
+
root,
|
|
1150
|
+
platform: PLATFORM,
|
|
1151
|
+
kind: acquired.status === "leased" ? acquired.kind : null,
|
|
1152
|
+
expiresAt: acquired.status === "leased" ? acquired.expiresAt : null
|
|
1153
|
+
});
|
|
1154
|
+
if (acquired.status === "leased") {
|
|
1155
|
+
stopLeaseSignals = onLeaseSignal(releaseLease);
|
|
1156
|
+
phase("lease", `${acquired.kind} lease on ${device.serial} until ${leaseExpiryText(acquired.expiresAt, now())}`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
try {
|
|
1160
|
+
return await finishAndroidRun({
|
|
1161
|
+
runtime,
|
|
1162
|
+
runtimePreparation,
|
|
1163
|
+
slot,
|
|
1164
|
+
lease: leaseHandle,
|
|
1165
|
+
releaseLease,
|
|
1166
|
+
root,
|
|
1167
|
+
json,
|
|
1168
|
+
metroCheck,
|
|
1169
|
+
useBuildCache,
|
|
1170
|
+
variant,
|
|
1171
|
+
release,
|
|
1172
|
+
isExpo,
|
|
1173
|
+
metroPort,
|
|
1174
|
+
logsDir,
|
|
1175
|
+
emuLog,
|
|
1176
|
+
device,
|
|
1177
|
+
physical,
|
|
1178
|
+
remoteDevice,
|
|
1179
|
+
bootPromise,
|
|
1180
|
+
resolveAvdSerial,
|
|
1181
|
+
waitForDeviceBoot,
|
|
1182
|
+
dataFreeBytes,
|
|
1183
|
+
trimCaches,
|
|
1184
|
+
wipeDevice,
|
|
1185
|
+
bootDuration: () => bootDuration,
|
|
1186
|
+
bootPending: () => bootPending,
|
|
1187
|
+
apkPath,
|
|
1188
|
+
androidPackage,
|
|
1189
|
+
androidPackageProblem: appIds.androidPackageProblem ?? null,
|
|
1190
|
+
record,
|
|
1191
|
+
waitedForBuild: artifact.waitedForBuild,
|
|
1192
|
+
ccache: ccacheActivity,
|
|
1193
|
+
uploadPending: artifact.uploadPending,
|
|
1194
|
+
providerUpload: artifact.providerUpload,
|
|
1195
|
+
providerName: artifact.providerName,
|
|
1196
|
+
remote: artifact.remote,
|
|
1197
|
+
abandonedRemote: artifact.abandonedRemote,
|
|
1198
|
+
started,
|
|
1199
|
+
startedAt,
|
|
1200
|
+
writer,
|
|
1201
|
+
phase,
|
|
1202
|
+
fail,
|
|
1203
|
+
readApkPackage,
|
|
1204
|
+
readProjectPackage: () => integration.appIds().androidPackage,
|
|
1205
|
+
packageRemedy: integration.packageRemedy,
|
|
1206
|
+
install,
|
|
1207
|
+
launch,
|
|
1208
|
+
launchRelease,
|
|
1209
|
+
resolveDevClientScheme,
|
|
1210
|
+
verifyLaunched,
|
|
1211
|
+
verifyReleaseLaunched,
|
|
1212
|
+
spawn,
|
|
1213
|
+
kill,
|
|
1214
|
+
pidAlive,
|
|
1215
|
+
verifyCollector,
|
|
1216
|
+
writeLaunch,
|
|
1217
|
+
recordBuild,
|
|
1218
|
+
now,
|
|
1219
|
+
out,
|
|
1220
|
+
emit,
|
|
1221
|
+
recordRun,
|
|
1222
|
+
reclaimed,
|
|
1223
|
+
devServer,
|
|
1224
|
+
enterPhase: progress.step,
|
|
1225
|
+
enterActivity: progress.activity,
|
|
1226
|
+
rebootDevice: () => Promise.resolve(ensureDeviceBooted({
|
|
1227
|
+
platform: PLATFORM,
|
|
1228
|
+
device: {
|
|
1229
|
+
...device,
|
|
1230
|
+
serial: void 0
|
|
1231
|
+
},
|
|
1232
|
+
projectPath: root,
|
|
1233
|
+
deviceSlotWait,
|
|
1234
|
+
out,
|
|
1235
|
+
logFile: emuLog
|
|
1236
|
+
})).catch((e) => ({
|
|
1237
|
+
failed: true,
|
|
1238
|
+
reason: String(e?.message || e)
|
|
1239
|
+
}))
|
|
1240
|
+
});
|
|
1241
|
+
} finally {
|
|
1242
|
+
releaseLease();
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
try {
|
|
1246
|
+
return await runFromFingerprint();
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
recordRun({
|
|
1249
|
+
failed: true,
|
|
1250
|
+
durationMs: now() - started
|
|
1251
|
+
});
|
|
1252
|
+
throw error;
|
|
1253
|
+
} finally {
|
|
1254
|
+
preparedArtifact?.release();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
//#endregion
|
|
1258
|
+
export { runAndroidOperation as i, registerAndroid as n, runAndroid as r, androidCommand as t };
|