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
package/dist/start-BWqzODpb.mjs
DELETED
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
import { E as upsertProject, k as process_identity_exports, p as getProject } from "./config-BYDxlMY3.mjs";
|
|
2
|
-
import { t as getExecutor } from "./exec-DqKo9GKJ.mjs";
|
|
3
|
-
import { t as spawnEntry } from "./spawn-entry-Dcb7sDUz.mjs";
|
|
4
|
-
import { A as metroIdleStopMinutesSetting, B as remoteAndroidSetting, G as resolveIosProjectDir, H as remoteIosSetting, K as resolveProjectSettings, L as publicUrlSetting, M as metroTunnelSettingError, O as metroCommandSetting, P as ngrokUrlSetting, Q as settingShapeErrors, U as resolveAndroidLayout, W as resolveCacheProviderConfig, a as SETTING_SHAPE_REMEDY, at as PUBLIC_METRO_ENV, h as deviceIdleShutdownMinutesSetting, j as metroPortSetting, k as metroCommandSettingError, m as defaultAndroidLayout, nt as unknownSettingKeys, ot as detectProviders, p as cacheProviderSettingError, st as planMetroReach, tt as tunnelModeSetting } from "./settings-B717odTD.mjs";
|
|
5
|
-
import { _ as supervisorLogFile, c as writeWorkspaceState, i as recordWorkspaceUse, n as clearWorkspaceStateKeys, p as ensureWorkspaceStorage, r as readWorkspaceState$1, w as workspaceLogsDir$1, y as workspaceAgentDeviceDir } from "./workspace-state-DJt_MMvf.mjs";
|
|
6
|
-
import { _ as stepTimer, l as phaseLine } from "./command-output-BTy8hZ0F.mjs";
|
|
7
|
-
import { C as MODE_BARE, D as clearManagedMetroTunnel, T as MODE_EXPO, _ as detectIsExpo, b as readAppConfigText, j as readMetroTunnel, r as findProjectRoot, s as projectProblem, t as NO_PROJECT_REFUSAL, w as MODE_COMMAND, x as readAppJson } from "./project-_phNIJfs.mjs";
|
|
8
|
-
import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-BzBN2Em2.mjs";
|
|
9
|
-
import { d as signalProcessTree, g as PortInspectionError, h as PORT_INSPECTION_REMEDY, m as reserveMetroPort, s as pidExists, u as resolveProjectMetro } from "./metro-DnCEjCxs.mjs";
|
|
10
|
-
import { D as stopTunnel, E as startTunnelSequence, O as terminateChild, P as resolveSupervisorTarget, j as withManagedTunnelLock, k as withManagedRemoteWorktreeLock } from "./workspaces-C36s2jb9.mjs";
|
|
11
|
-
import { ct as stopOwnedMetroForReset, t as budgetGate } from "./budget-f7516Zk0.mjs";
|
|
12
|
-
import { a as withWorkspaceProcessLock, o as workspaceProcessLockError } from "./named-ports-4WopRPg-.mjs";
|
|
13
|
-
import { t as cancellableSleep } from "./cancellation-C_22VQTH.mjs";
|
|
14
|
-
import { i as levelRank } from "./ndjson-GOnbyvso.mjs";
|
|
15
|
-
import { d as readProjectConfig } from "./remote-cache-OyOyVIS3.mjs";
|
|
16
|
-
import { n as windowsLauncherArgs } from "./detached-entry-B9C_zeQC.mjs";
|
|
17
|
-
import { appendFileSync, mkdirSync, openSync, readFileSync } from "node:fs";
|
|
18
|
-
import { dirname } from "node:path";
|
|
19
|
-
import { join as join$1 } from "path";
|
|
20
|
-
import { IDLE_STOP_KEY, queryLogs } from "@stim-cli/core/state";
|
|
21
|
-
import { CACHE_PROVIDER_ENV, cacheProviderEnv } from "@stim-cli/cache";
|
|
22
|
-
import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1 } from "fs";
|
|
23
|
-
import chalk from "chalk";
|
|
24
|
-
import { EventEmitter } from "node:events";
|
|
25
|
-
//#region src/workspace/app-id.ts
|
|
26
|
-
const ANDROID_PACKAGE_NAME = /^[A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)+$/;
|
|
27
|
-
function androidPackageFilter(problems) {
|
|
28
|
-
return (id, source) => {
|
|
29
|
-
if (id === null || ANDROID_PACKAGE_NAME.test(id)) return id;
|
|
30
|
-
problems.push(`Invalid Android package ${JSON.stringify(id)} in ${source}. Expected dot-separated letters, digits and underscores, such as com.example.app.`);
|
|
31
|
-
return null;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function configString(config, platform, key) {
|
|
35
|
-
if (!config || typeof config !== "object") return null;
|
|
36
|
-
const expoField = config.expo;
|
|
37
|
-
const section = (expoField && typeof expoField === "object" ? expoField : config)[platform];
|
|
38
|
-
if (!section || typeof section !== "object") return null;
|
|
39
|
-
const value = section[key];
|
|
40
|
-
return typeof value === "string" && value ? value : null;
|
|
41
|
-
}
|
|
42
|
-
function literalString(text, key) {
|
|
43
|
-
return text?.match(new RegExp(`${key}\\s*:\\s*["']([^"']+)["']`))?.[1] ?? null;
|
|
44
|
-
}
|
|
45
|
-
function idsFromConfig(projectRoot) {
|
|
46
|
-
const read = readProjectConfig(projectRoot);
|
|
47
|
-
if (!read.unavailable) return {
|
|
48
|
-
bundleId: configString(read.config, "ios", "bundleIdentifier"),
|
|
49
|
-
androidPackage: configString(read.config, "android", "package")
|
|
50
|
-
};
|
|
51
|
-
const appJson = readAppJson(projectRoot);
|
|
52
|
-
const text = readAppConfigText(projectRoot);
|
|
53
|
-
return {
|
|
54
|
-
bundleId: configString(appJson, "ios", "bundleIdentifier") ?? literalString(text, "bundleIdentifier"),
|
|
55
|
-
androidPackage: configString(appJson, "android", "package") ?? literalString(text, "package")
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function detectAppIds(projectRoot, iosDir = join$1(projectRoot, "ios"), androidModuleDir = defaultAndroidLayout(projectRoot).moduleDir) {
|
|
59
|
-
const ids = idsFromConfig(projectRoot);
|
|
60
|
-
const problems = [];
|
|
61
|
-
const accept = androidPackageFilter(problems);
|
|
62
|
-
return {
|
|
63
|
-
bundleId: ids.bundleId ?? detectBundleIdFromPbxproj(iosDir),
|
|
64
|
-
androidPackage: accept(ids.androidPackage, "the Expo config android.package") ?? detectAndroidPackageFromGradle(androidModuleDir, accept),
|
|
65
|
-
androidPackageProblem: problems.length > 0 ? problems.join(" ") : null
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function detectBundleId(projectRoot, iosDir = join$1(projectRoot, "ios")) {
|
|
69
|
-
return detectAppIds(projectRoot, iosDir).bundleId;
|
|
70
|
-
}
|
|
71
|
-
function detectAndroidPackage(projectRoot, androidModuleDir = defaultAndroidLayout(projectRoot).moduleDir) {
|
|
72
|
-
return detectAppIds(projectRoot, void 0, androidModuleDir).androidPackage;
|
|
73
|
-
}
|
|
74
|
-
function detectBundleIdFromPbxproj(iosDir) {
|
|
75
|
-
if (!existsSync$1(iosDir)) return null;
|
|
76
|
-
let entries;
|
|
77
|
-
try {
|
|
78
|
-
entries = readdirSync$1(iosDir, { withFileTypes: true });
|
|
79
|
-
} catch {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
for (const entry of entries) {
|
|
83
|
-
if (!entry.isDirectory() || !entry.name.endsWith(".xcodeproj")) continue;
|
|
84
|
-
const pbx = join$1(iosDir, entry.name, "project.pbxproj");
|
|
85
|
-
if (!existsSync$1(pbx)) continue;
|
|
86
|
-
let text;
|
|
87
|
-
try {
|
|
88
|
-
text = readFileSync$1(pbx, "utf-8");
|
|
89
|
-
} catch {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const concrete = [...text.matchAll(/PRODUCT_BUNDLE_IDENTIFIER\s*=\s*([^;\s"]+)\s*;/g)].map((m) => m[1]).filter((id) => !!id && !id.startsWith("$") && !id.includes("("));
|
|
93
|
-
if (concrete.length === 0) continue;
|
|
94
|
-
const counts = {};
|
|
95
|
-
for (const id of concrete) counts[id] = (counts[id] || 0) + 1;
|
|
96
|
-
let best = null, bestCount = 0, bestLen = Infinity;
|
|
97
|
-
for (const [id, count] of Object.entries(counts)) if (count > bestCount || count === bestCount && id.length < bestLen) {
|
|
98
|
-
best = id;
|
|
99
|
-
bestCount = count;
|
|
100
|
-
bestLen = id.length;
|
|
101
|
-
}
|
|
102
|
-
return best;
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
function detectAndroidPackageFromGradle(moduleDir, accept) {
|
|
107
|
-
const gradle = [join$1(moduleDir, "build.gradle"), join$1(moduleDir, "build.gradle.kts")].find((file) => existsSync$1(file));
|
|
108
|
-
if (!gradle) return null;
|
|
109
|
-
let text;
|
|
110
|
-
try {
|
|
111
|
-
text = readFileSync$1(gradle, "utf-8");
|
|
112
|
-
} catch {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
const nsId = text.match(/namespace\s*=?\s*["']([^"']+)["']/)?.[1];
|
|
116
|
-
const namespace = nsId ? accept(nsId, gradle) : null;
|
|
117
|
-
if (namespace) return namespace;
|
|
118
|
-
const appId = text.match(/applicationId\s*=?\s*["']([^"']+)["']/)?.[1];
|
|
119
|
-
return appId ? accept(appId, gradle) : null;
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
|
-
//#region src/commands/start.ts
|
|
123
|
-
const DEFAULT_WAIT_SECONDS = 60;
|
|
124
|
-
const POLL_MS = 500;
|
|
125
|
-
const LOG_TAIL_LINES = 5;
|
|
126
|
-
function writeNote(line) {
|
|
127
|
-
console.error(line);
|
|
128
|
-
}
|
|
129
|
-
function supervisorEntry() {
|
|
130
|
-
return spawnEntry("supervisor-run");
|
|
131
|
-
}
|
|
132
|
-
function wantsExpoOwnTunnel({ isExpo, remote, mode, publicUrl }) {
|
|
133
|
-
return remote && isExpo && !publicUrl && mode === "expo";
|
|
134
|
-
}
|
|
135
|
-
function parseWait(value) {
|
|
136
|
-
if (value === void 0 || value === null) return { seconds: DEFAULT_WAIT_SECONDS };
|
|
137
|
-
const seconds = Number(value);
|
|
138
|
-
if (!Number.isFinite(seconds) || seconds <= 0) return { error: `Invalid --wait value ${JSON.stringify(value)}. Pass a number of seconds, e.g. --wait 90.` };
|
|
139
|
-
return { seconds };
|
|
140
|
-
}
|
|
141
|
-
function liveSupervisor({ state, project, port, isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity } = {}) {
|
|
142
|
-
const target = resolveSupervisorTarget({
|
|
143
|
-
state: state?.supervisor,
|
|
144
|
-
record: project?.supervisor,
|
|
145
|
-
reservedPort: port,
|
|
146
|
-
isAlive,
|
|
147
|
-
inspectIdentity
|
|
148
|
-
});
|
|
149
|
-
return target.status === "ours" && target.port === port ? {
|
|
150
|
-
pid: target.pid,
|
|
151
|
-
port: target.port,
|
|
152
|
-
mode: target.mode ?? null,
|
|
153
|
-
startedAt: target.startedAt ?? null
|
|
154
|
-
} : null;
|
|
155
|
-
}
|
|
156
|
-
function startFacts({ root, port, supervisor, logsDir, alreadyRunning }) {
|
|
157
|
-
return {
|
|
158
|
-
port,
|
|
159
|
-
supervisorPid: supervisor?.pid ?? null,
|
|
160
|
-
mode: supervisor?.mode ?? null,
|
|
161
|
-
logsDir,
|
|
162
|
-
...root ? { agentDevice: { stateDir: workspaceAgentDeviceDir(root) } } : {},
|
|
163
|
-
alreadyRunning: Boolean(alreadyRunning)
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function startError({ code, message, remedy = null }) {
|
|
167
|
-
return {
|
|
168
|
-
code,
|
|
169
|
-
message,
|
|
170
|
-
remedy: remedy ?? null
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
function tailLines(text, n = LOG_TAIL_LINES) {
|
|
174
|
-
return String(text || "").split("\n").filter((l) => l.trim() !== "").slice(-n);
|
|
175
|
-
}
|
|
176
|
-
function readLogTail(file, n = LOG_TAIL_LINES) {
|
|
177
|
-
try {
|
|
178
|
-
return tailLines(readFileSync(file, "utf-8"), n);
|
|
179
|
-
} catch {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
function startCommand(program) {
|
|
184
|
-
registerStart(program);
|
|
185
|
-
}
|
|
186
|
-
function supervisorSignalable(child, inspectIdentity = process_identity_exports.inspectProcessIdentity) {
|
|
187
|
-
return !child.recordedIdentity || inspectIdentity(child.recordedIdentity) === "same";
|
|
188
|
-
}
|
|
189
|
-
function recordedSupervisorProcess(recorded) {
|
|
190
|
-
const pid = typeof recorded?.pid === "number" ? recorded.pid : void 0;
|
|
191
|
-
return Object.assign(new EventEmitter(), {
|
|
192
|
-
pid,
|
|
193
|
-
...recorded ? { recordedIdentity: recorded } : {},
|
|
194
|
-
stdout: null,
|
|
195
|
-
stderr: null,
|
|
196
|
-
kill: (signal = "SIGTERM") => pid !== void 0 && signalProcessTree(pid, typeof signal === "number" ? "SIGTERM" : signal),
|
|
197
|
-
unref() {}
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
const RECORDED_SUPERVISOR_WAIT_MS = 15e3;
|
|
201
|
-
function providersOnPath(mode) {
|
|
202
|
-
return detectProviders((bin) => {
|
|
203
|
-
try {
|
|
204
|
-
return Boolean(getExecutor().findExecutable(bin));
|
|
205
|
-
} catch {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}, mode);
|
|
209
|
-
}
|
|
210
|
-
const DEFAULT_START_DEPS = {
|
|
211
|
-
providers: providersOnPath,
|
|
212
|
-
startTunnelSequence,
|
|
213
|
-
isTunnelAlive: pidExists,
|
|
214
|
-
writeTunnelRecord: writeWorkspaceState,
|
|
215
|
-
stopTunnel,
|
|
216
|
-
writeSupervisorRecord: writeWorkspaceState,
|
|
217
|
-
terminateSupervisorChild: (child) => terminateChild(child, {
|
|
218
|
-
alreadyExited: false,
|
|
219
|
-
timeoutMs: 1e3,
|
|
220
|
-
now: Date.now,
|
|
221
|
-
sleep: cancellableSleep,
|
|
222
|
-
isAlive: pidExists
|
|
223
|
-
}),
|
|
224
|
-
withWorktreeLock: withManagedRemoteWorktreeLock,
|
|
225
|
-
withTunnelLock: withManagedTunnelLock,
|
|
226
|
-
clearTunnelRecord: clearManagedMetroTunnel,
|
|
227
|
-
budgetGate,
|
|
228
|
-
platform: process.platform
|
|
229
|
-
};
|
|
230
|
-
function normalizeManagedTunnelUrl(url) {
|
|
231
|
-
return url.trim().replace(/\/+$/, "");
|
|
232
|
-
}
|
|
233
|
-
function registerStart(program, overrides = {}) {
|
|
234
|
-
const d = {
|
|
235
|
-
...DEFAULT_START_DEPS,
|
|
236
|
-
...overrides
|
|
237
|
-
};
|
|
238
|
-
program.command("start").description("Start this workspace's dev server under a detached supervisor and wait until it verifies as this project's. Idempotent: a healthy dev server on the reserved port is a no-op. Structured logs land in the global workspace logs directory.").option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--wait <seconds>", `How long to wait for the dev server to answer (default ${DEFAULT_WAIT_SECONDS})`).option("--remote", "Prepare the dev server for a remote device").option("--reset-cache", "Restart owned Metro and clear this app's Metro caches").action(async (opts) => {
|
|
239
|
-
const json = Boolean(opts.json);
|
|
240
|
-
const out = (line) => {
|
|
241
|
-
if (json) console.error(line);
|
|
242
|
-
else console.log(line);
|
|
243
|
-
};
|
|
244
|
-
const note = writeNote;
|
|
245
|
-
const fail = ({ code, message, remedy = null, lines = [], reclaimed = [] }) => {
|
|
246
|
-
note(chalk.red(message));
|
|
247
|
-
for (const line of lines) note(chalk.dim(` ${line}`));
|
|
248
|
-
if (remedy) note(chalk.dim(remedy));
|
|
249
|
-
note(chalk.red(`failed: ${code}`));
|
|
250
|
-
if (json) console.log(JSON.stringify({
|
|
251
|
-
...startError({
|
|
252
|
-
code,
|
|
253
|
-
message,
|
|
254
|
-
remedy
|
|
255
|
-
}),
|
|
256
|
-
...reclaimed.length ? { reclaimed } : {}
|
|
257
|
-
}));
|
|
258
|
-
process.exit(1);
|
|
259
|
-
};
|
|
260
|
-
const wait = parseWait(opts.wait);
|
|
261
|
-
if (wait.error) return fail({
|
|
262
|
-
code: "STIM_BAD_ARG",
|
|
263
|
-
message: wait.error,
|
|
264
|
-
remedy: "Pass a whole number of seconds, e.g. --wait 90."
|
|
265
|
-
});
|
|
266
|
-
const waitSeconds = wait.seconds;
|
|
267
|
-
const root = findProjectRoot(process.cwd());
|
|
268
|
-
if (!root) return fail(NO_PROJECT_REFUSAL);
|
|
269
|
-
const problem = projectProblem(root, "dev-server");
|
|
270
|
-
if (problem) return fail({
|
|
271
|
-
code: "STIM_NO_PROJECT",
|
|
272
|
-
message: problem.message,
|
|
273
|
-
remedy: problem.remedy
|
|
274
|
-
});
|
|
275
|
-
try {
|
|
276
|
-
ensureWorkspaceStorage(root);
|
|
277
|
-
} catch (error) {
|
|
278
|
-
return fail({
|
|
279
|
-
code: error?.code || "STIM_WORKSPACE_STATE",
|
|
280
|
-
message: `Could not prepare this workspace's Stim state: ${error?.message || error}`,
|
|
281
|
-
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`)."
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
recordWorkspaceUse(root);
|
|
285
|
-
const { context: settingsContext, settings } = resolveProjectSettings(root);
|
|
286
|
-
const [shapeError, ...moreShapeErrors] = settingShapeErrors(settings);
|
|
287
|
-
if (shapeError) return fail({
|
|
288
|
-
code: "STIM_BAD_ARG",
|
|
289
|
-
message: shapeError,
|
|
290
|
-
lines: moreShapeErrors,
|
|
291
|
-
remedy: SETTING_SHAPE_REMEDY
|
|
292
|
-
});
|
|
293
|
-
for (const key of unknownSettingKeys(settings)) note(chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`));
|
|
294
|
-
const cacheProviderError = cacheProviderSettingError(settings);
|
|
295
|
-
if (cacheProviderError) note(chalk.yellow(`Warning: ${cacheProviderError} No cache provider is used.`));
|
|
296
|
-
const result = await startDevServer({
|
|
297
|
-
root,
|
|
298
|
-
settings,
|
|
299
|
-
settingsContext,
|
|
300
|
-
waitSeconds,
|
|
301
|
-
remote: Boolean(opts.remote),
|
|
302
|
-
resetCache: Boolean(opts.resetCache),
|
|
303
|
-
out,
|
|
304
|
-
note
|
|
305
|
-
}, d);
|
|
306
|
-
if (!result.ok) return fail({
|
|
307
|
-
...result.error,
|
|
308
|
-
lines: result.lines,
|
|
309
|
-
reclaimed: result.reclaimed
|
|
310
|
-
});
|
|
311
|
-
report({
|
|
312
|
-
json,
|
|
313
|
-
out,
|
|
314
|
-
facts: result.facts,
|
|
315
|
-
waited: result.waited,
|
|
316
|
-
reclaimed: result.reclaimed,
|
|
317
|
-
links: workspaceLinks(root)
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
var StartRefusal = class extends Error {
|
|
322
|
-
refusal;
|
|
323
|
-
constructor(refusal) {
|
|
324
|
-
super(refusal.message);
|
|
325
|
-
this.refusal = refusal;
|
|
326
|
-
}
|
|
327
|
-
};
|
|
328
|
-
async function startDevServer({ root, settings, settingsContext, waitSeconds = DEFAULT_WAIT_SECONDS, remote: remoteFlag = false, resetCache = false, out, note }, overrides = {}) {
|
|
329
|
-
const d = {
|
|
330
|
-
...DEFAULT_START_DEPS,
|
|
331
|
-
...overrides
|
|
332
|
-
};
|
|
333
|
-
const waitTimer = stepTimer();
|
|
334
|
-
let reclaimed = [];
|
|
335
|
-
const fail = (refusal) => {
|
|
336
|
-
throw new StartRefusal(refusal);
|
|
337
|
-
};
|
|
338
|
-
const isExpo = detectIsExpo(root);
|
|
339
|
-
const metroCommand = metroCommandSetting(settings);
|
|
340
|
-
const serverMode = metroCommand ? MODE_COMMAND : isExpo ? MODE_EXPO : MODE_BARE;
|
|
341
|
-
const expoServer = serverMode === MODE_EXPO;
|
|
342
|
-
const worktreeRoot = settingsContext.repoRoot ?? root;
|
|
343
|
-
const cacheProvider = resolveCacheProviderConfig(settingsContext);
|
|
344
|
-
const run = async () => {
|
|
345
|
-
const settingError = metroTunnelSettingError(settings);
|
|
346
|
-
if (settingError) return fail({
|
|
347
|
-
code: "STIM_BAD_ARG",
|
|
348
|
-
message: settingError,
|
|
349
|
-
remedy: "Set metro.tunnel to \"ngrok\" and metro.ngrokUrl to an HTTPS URL, or remove metro.ngrokUrl."
|
|
350
|
-
});
|
|
351
|
-
if (metroCommand && d.platform === "win32") return fail({
|
|
352
|
-
code: "STIM_BAD_ARG",
|
|
353
|
-
message: "metro.command is not supported on Windows: Stim cannot read a process working directory there, so it cannot prove the Metro a wrapper starts belongs to this app.",
|
|
354
|
-
remedy: "Remove metro.command on Windows, or run this app from macOS or Linux."
|
|
355
|
-
});
|
|
356
|
-
if (metroCommand && resetCache) return fail({
|
|
357
|
-
code: "STIM_BAD_ARG",
|
|
358
|
-
message: "--reset-cache cannot reach a dev server started by metro.command; Stim does not know its reset flag.",
|
|
359
|
-
remedy: "Add the command's own cache-reset flag to metro.command (for React Native, --reset-cache), then run stim stop and stim start."
|
|
360
|
-
});
|
|
361
|
-
const commandError = metroCommandSettingError(settings);
|
|
362
|
-
if (commandError) return fail({
|
|
363
|
-
code: "STIM_BAD_ARG",
|
|
364
|
-
message: commandError,
|
|
365
|
-
remedy: "Set metro.command to the argv that starts the dev server, e.g. [\"npx\", \"react-native\", \"start\", \"--port\", \"{port}\"], or remove it."
|
|
366
|
-
});
|
|
367
|
-
const remote = remoteFlag || remoteIosSetting(settings)?.kind === "backend" || remoteAndroidSetting(settings)?.kind === "backend";
|
|
368
|
-
const tunnelMode = tunnelModeSetting(settings) ?? "auto";
|
|
369
|
-
const publicUrl = publicUrlSetting(settings);
|
|
370
|
-
const tunnel = wantsExpoOwnTunnel({
|
|
371
|
-
isExpo: expoServer,
|
|
372
|
-
remote,
|
|
373
|
-
mode: tunnelMode,
|
|
374
|
-
publicUrl
|
|
375
|
-
});
|
|
376
|
-
if (tunnel) note(chalk.dim("note requesting an Expo tunnel for this workspace's dev server"));
|
|
377
|
-
const managedRemote = remote && !tunnel && !publicUrl && tunnelMode !== "off";
|
|
378
|
-
const runStart = async () => {
|
|
379
|
-
const appIds = detectAppIds(root, resolveIosProjectDir(settings, root).dir, resolveAndroidLayout(settings, root, settingsContext.repoRoot ?? root).moduleDir);
|
|
380
|
-
upsertProject(root, {
|
|
381
|
-
bundleId: appIds.bundleId ?? void 0,
|
|
382
|
-
androidPackage: appIds.androidPackage ?? void 0,
|
|
383
|
-
isExpo
|
|
384
|
-
});
|
|
385
|
-
const gateBudget = async () => {
|
|
386
|
-
const budget = await d.budgetGate({
|
|
387
|
-
root,
|
|
388
|
-
note
|
|
389
|
-
});
|
|
390
|
-
reclaimed = budget.reclaimed;
|
|
391
|
-
if (budget.refusal) return fail(budget.refusal);
|
|
392
|
-
};
|
|
393
|
-
if (resetCache) {
|
|
394
|
-
await gateBudget();
|
|
395
|
-
const pin = metroPortSetting(settings);
|
|
396
|
-
if (pin.port !== null || pin.error) {
|
|
397
|
-
const pinCheck = await resolveWorkspaceMetroPort(root, settings, note, "start", false);
|
|
398
|
-
if (typeof pinCheck !== "number") return fail(pinCheck);
|
|
399
|
-
}
|
|
400
|
-
try {
|
|
401
|
-
await stopOwnedMetroForReset(root);
|
|
402
|
-
} catch (error) {
|
|
403
|
-
const failure = error;
|
|
404
|
-
return fail({
|
|
405
|
-
code: failure.code ?? "STIM_WORKSPACE_STATE",
|
|
406
|
-
message: failure.message,
|
|
407
|
-
remedy: failure.remedy
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
note(phaseLine("cache", "clearing this app's Metro transform and file-map caches; devices preserved"));
|
|
411
|
-
}
|
|
412
|
-
const logsDir = workspaceLogsDir$1(root);
|
|
413
|
-
const logFile = supervisorLogFile(root);
|
|
414
|
-
const port = await resolvePort(root, settings, note, fail);
|
|
415
|
-
let publicOrigin = remote ? publicUrl : null;
|
|
416
|
-
let resolution = await resolveProjectMetro(port, root);
|
|
417
|
-
let supervisor = liveSupervisor({
|
|
418
|
-
state: readWorkspaceState$1(root),
|
|
419
|
-
project: getProject(root),
|
|
420
|
-
port
|
|
421
|
-
});
|
|
422
|
-
const recordedTarget = resolveSupervisorTarget({
|
|
423
|
-
state: readWorkspaceState$1(root)?.supervisor,
|
|
424
|
-
record: getProject(root)?.supervisor,
|
|
425
|
-
reservedPort: port
|
|
426
|
-
});
|
|
427
|
-
if (recordedTarget.status === "unverified") return fail({
|
|
428
|
-
code: "STIM_SUPERVISOR_EXITED",
|
|
429
|
-
message: `Cannot reuse or replace the recorded supervisor: ${recordedTarget.reason}.`,
|
|
430
|
-
remedy: "Stop it with the tool that started it, then retry `stim start`. Stim leaves unverified processes alone."
|
|
431
|
-
});
|
|
432
|
-
if (!resetCache && !resolution.metro && !supervisor) await gateBudget();
|
|
433
|
-
let managedTunnel = null;
|
|
434
|
-
let spawnedChild = null;
|
|
435
|
-
let spawnedTs = null;
|
|
436
|
-
let childExit = null;
|
|
437
|
-
const spawnDirect = (supervisorArgs, childEnv) => {
|
|
438
|
-
const fd = openSync(logFile, "a");
|
|
439
|
-
const child = getExecutor().spawn(process.execPath, supervisorArgs, {
|
|
440
|
-
cwd: root,
|
|
441
|
-
detached: true,
|
|
442
|
-
stdio: [
|
|
443
|
-
"ignore",
|
|
444
|
-
fd,
|
|
445
|
-
fd
|
|
446
|
-
],
|
|
447
|
-
env: childEnv
|
|
448
|
-
});
|
|
449
|
-
child.unref?.();
|
|
450
|
-
child.on?.("exit", (code, signal) => {
|
|
451
|
-
childExit = {
|
|
452
|
-
code,
|
|
453
|
-
signal
|
|
454
|
-
};
|
|
455
|
-
});
|
|
456
|
-
child.on?.("error", (err) => {
|
|
457
|
-
childExit = {
|
|
458
|
-
code: null,
|
|
459
|
-
signal: null,
|
|
460
|
-
error: err
|
|
461
|
-
};
|
|
462
|
-
});
|
|
463
|
-
return child;
|
|
464
|
-
};
|
|
465
|
-
const recordedSupervisor = async (since) => {
|
|
466
|
-
const deadline = Date.now() + RECORDED_SUPERVISOR_WAIT_MS;
|
|
467
|
-
while (Date.now() < deadline) {
|
|
468
|
-
const record = readWorkspaceState$1(root)?.supervisor;
|
|
469
|
-
if (record?.pid && record.port === port && Date.parse(String(record.startedAt)) >= since && pidExists(record.pid)) return {
|
|
470
|
-
pid: record.pid,
|
|
471
|
-
processToken: record.processToken
|
|
472
|
-
};
|
|
473
|
-
await cancellableSleep(25);
|
|
474
|
-
}
|
|
475
|
-
return null;
|
|
476
|
-
};
|
|
477
|
-
const spawnThroughWindowsShell = async (supervisorArgs, childEnv) => {
|
|
478
|
-
const [entry, ...args] = supervisorArgs;
|
|
479
|
-
const launcher = windowsLauncherArgs({
|
|
480
|
-
entry,
|
|
481
|
-
args,
|
|
482
|
-
cwd: root,
|
|
483
|
-
logFile
|
|
484
|
-
});
|
|
485
|
-
const since = Date.now();
|
|
486
|
-
const shell = getExecutor().spawn(launcher.file, launcher.args, {
|
|
487
|
-
cwd: root,
|
|
488
|
-
stdio: [
|
|
489
|
-
"ignore",
|
|
490
|
-
"ignore",
|
|
491
|
-
"pipe"
|
|
492
|
-
],
|
|
493
|
-
env: {
|
|
494
|
-
...childEnv,
|
|
495
|
-
...launcher.env
|
|
496
|
-
},
|
|
497
|
-
windowsHide: true
|
|
498
|
-
});
|
|
499
|
-
const stderr = [];
|
|
500
|
-
shell.stderr?.on("data", (chunk) => stderr.push(String(chunk)));
|
|
501
|
-
const exit = await new Promise((resolve) => {
|
|
502
|
-
shell.on?.("close", (code, signal) => resolve({
|
|
503
|
-
code,
|
|
504
|
-
signal
|
|
505
|
-
}));
|
|
506
|
-
shell.on?.("error", (error) => resolve({
|
|
507
|
-
code: null,
|
|
508
|
-
signal: null,
|
|
509
|
-
error
|
|
510
|
-
}));
|
|
511
|
-
});
|
|
512
|
-
const launcherFailed = exit.code !== 0 || exit.error;
|
|
513
|
-
const recorded = launcherFailed ? null : await recordedSupervisor(since);
|
|
514
|
-
if (recorded === null) {
|
|
515
|
-
const reason = launcherFailed ? `the supervisor launcher exited (${exit.error ? exit.error.message : `code ${exit.code}`})` : `the supervisor did not record itself within ${RECORDED_SUPERVISOR_WAIT_MS / 1e3}s`;
|
|
516
|
-
appendFileSync(logFile, `Stim start: ${reason}.\n${stderr.join("")}`);
|
|
517
|
-
childExit = {
|
|
518
|
-
code: exit.code,
|
|
519
|
-
signal: exit.signal,
|
|
520
|
-
...exit.error ? { error: exit.error } : {}
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
return recordedSupervisorProcess(recorded);
|
|
524
|
-
};
|
|
525
|
-
const spawnSupervisor = async (origin) => {
|
|
526
|
-
mkdirSync(logsDir, { recursive: true });
|
|
527
|
-
spawnedTs = Date.now();
|
|
528
|
-
const supervisorArgs = [
|
|
529
|
-
supervisorEntry(),
|
|
530
|
-
"--root",
|
|
531
|
-
root,
|
|
532
|
-
"--port",
|
|
533
|
-
String(port),
|
|
534
|
-
...tunnel ? ["--tunnel"] : [],
|
|
535
|
-
...resetCache ? ["--reset-cache"] : [],
|
|
536
|
-
"--idle-stop-minutes",
|
|
537
|
-
String(metroIdleStopMinutesSetting(settings)),
|
|
538
|
-
"--device-idle-minutes",
|
|
539
|
-
String(deviceIdleShutdownMinutesSetting(settings))
|
|
540
|
-
];
|
|
541
|
-
const childEnv = {
|
|
542
|
-
...process.env,
|
|
543
|
-
...origin ? {
|
|
544
|
-
[PUBLIC_METRO_ENV]: origin,
|
|
545
|
-
EXPO_PACKAGER_PROXY_URL: origin
|
|
546
|
-
} : {}
|
|
547
|
-
};
|
|
548
|
-
childEnv[CACHE_PROVIDER_ENV] = cacheProviderEnv(cacheProvider);
|
|
549
|
-
const child = d.platform === "win32" ? await spawnThroughWindowsShell(supervisorArgs, childEnv) : spawnDirect(supervisorArgs, childEnv);
|
|
550
|
-
out(chalk.dim(phaseLine("metro", `starting on port ${port} (${serverMode}, supervisor pid ${child.pid})`)));
|
|
551
|
-
spawnedChild = child;
|
|
552
|
-
return child;
|
|
553
|
-
};
|
|
554
|
-
const waitForSupervisorHandoff = async (child) => {
|
|
555
|
-
const deadline = Date.now() + 5e3;
|
|
556
|
-
while (Date.now() < deadline) {
|
|
557
|
-
const found = liveSupervisor({
|
|
558
|
-
state: readWorkspaceState$1(root),
|
|
559
|
-
project: getProject(root),
|
|
560
|
-
port
|
|
561
|
-
});
|
|
562
|
-
if (found?.pid === child.pid) return found;
|
|
563
|
-
if (childExit !== null || (child.pid ? !pidExists(child.pid) : true)) return null;
|
|
564
|
-
await cancellableSleep(25);
|
|
565
|
-
}
|
|
566
|
-
return null;
|
|
567
|
-
};
|
|
568
|
-
if (remote && !tunnel && !publicUrl && tunnelMode !== "off") {
|
|
569
|
-
const available = d.providers(tunnelMode);
|
|
570
|
-
const plan = planMetroReach({
|
|
571
|
-
mode: tunnelMode,
|
|
572
|
-
metroPort: port,
|
|
573
|
-
publicUrl,
|
|
574
|
-
isExpo: expoServer,
|
|
575
|
-
available
|
|
576
|
-
});
|
|
577
|
-
if ("failed" in plan) return fail({
|
|
578
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
579
|
-
message: plan.failed,
|
|
580
|
-
remedy: plan.remedy
|
|
581
|
-
});
|
|
582
|
-
if ("start" in plan) {
|
|
583
|
-
const candidates = tunnelMode === "auto" ? available.filter((provider) => provider !== "tailscale") : [plan.start];
|
|
584
|
-
const expectedStableUrl = ngrokUrlSetting(settings);
|
|
585
|
-
let acquisition;
|
|
586
|
-
try {
|
|
587
|
-
acquisition = await d.withTunnelLock(root, async () => {
|
|
588
|
-
let tracking;
|
|
589
|
-
let startedCleanup = null;
|
|
590
|
-
const recorded = readMetroTunnel(root);
|
|
591
|
-
if (recorded?.kind === "managed" && d.isTunnelAlive(recorded.pid)) {
|
|
592
|
-
if (!recorded.processToken) return { failed: {
|
|
593
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
594
|
-
message: "The recorded managed Metro tunnel has no process identity token.",
|
|
595
|
-
remedy: "Inspect the process, stop it with the provider tooling, remove the stale metroTunnel state, and retry."
|
|
596
|
-
} };
|
|
597
|
-
if (!(recorded.port === port && candidates.includes(recorded.provider) && (!expectedStableUrl || normalizeManagedTunnelUrl(recorded.url) === expectedStableUrl))) return { failed: {
|
|
598
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
599
|
-
message: `A different managed Metro tunnel is already running for this workspace.`,
|
|
600
|
-
remedy: "Run `stim stop`, then `stim start --remote`."
|
|
601
|
-
} };
|
|
602
|
-
tracking = {
|
|
603
|
-
record: {
|
|
604
|
-
provider: recorded.provider,
|
|
605
|
-
pid: recorded.pid,
|
|
606
|
-
url: normalizeManagedTunnelUrl(recorded.url),
|
|
607
|
-
port: recorded.port,
|
|
608
|
-
startedAt: recorded.startedAt,
|
|
609
|
-
processToken: recorded.processToken,
|
|
610
|
-
logFile: recorded.logFile
|
|
611
|
-
},
|
|
612
|
-
startedHere: false
|
|
613
|
-
};
|
|
614
|
-
} else {
|
|
615
|
-
const currentResolution = await resolveProjectMetro(port, root);
|
|
616
|
-
const currentSupervisor = liveSupervisor({
|
|
617
|
-
state: readWorkspaceState$1(root),
|
|
618
|
-
project: getProject(root),
|
|
619
|
-
port
|
|
620
|
-
});
|
|
621
|
-
if (currentResolution.metro || currentSupervisor) return { failed: {
|
|
622
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
623
|
-
message: `The dev server on port ${port} is local-only and cannot gain a managed tunnel while it is running.`,
|
|
624
|
-
remedy: "Run `stim stop`, then `stim start --remote`."
|
|
625
|
-
} };
|
|
626
|
-
const started = await d.startTunnelSequence({
|
|
627
|
-
providers: candidates,
|
|
628
|
-
port,
|
|
629
|
-
ngrokUrl: expectedStableUrl,
|
|
630
|
-
requireReachable: false
|
|
631
|
-
});
|
|
632
|
-
if ("failed" in started) return { failed: {
|
|
633
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
634
|
-
message: `Could not start a managed Metro tunnel for port ${port}.`,
|
|
635
|
-
remedy: started.reason
|
|
636
|
-
} };
|
|
637
|
-
const record = {
|
|
638
|
-
provider: started.provider,
|
|
639
|
-
pid: started.pid,
|
|
640
|
-
url: normalizeManagedTunnelUrl(started.url),
|
|
641
|
-
port,
|
|
642
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
643
|
-
processToken: started.processToken,
|
|
644
|
-
logFile: started.logFile
|
|
645
|
-
};
|
|
646
|
-
startedCleanup = started.cleanup;
|
|
647
|
-
try {
|
|
648
|
-
d.writeTunnelRecord(root, { metroTunnel: {
|
|
649
|
-
kind: "managed",
|
|
650
|
-
...record
|
|
651
|
-
} });
|
|
652
|
-
} catch (err) {
|
|
653
|
-
const stopped = await started.cleanup();
|
|
654
|
-
return { failed: {
|
|
655
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
656
|
-
message: `Could not record the managed Metro tunnel: ${err?.message || err}`,
|
|
657
|
-
remedy: stopped.status === "failed" ? `Cleanup failed. Unmanaged pid ${record.pid} may still be running: ${stopped.reason ?? "unknown error"}` : "The tunnel process was stopped. Fix the workspace write error, then retry `stim start --remote`."
|
|
658
|
-
} };
|
|
659
|
-
}
|
|
660
|
-
tracking = {
|
|
661
|
-
record,
|
|
662
|
-
startedHere: true
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
const currentResolution = await resolveProjectMetro(port, root);
|
|
666
|
-
const currentSupervisor = liveSupervisor({
|
|
667
|
-
state: readWorkspaceState$1(root),
|
|
668
|
-
project: getProject(root),
|
|
669
|
-
port
|
|
670
|
-
});
|
|
671
|
-
if (currentResolution.metro || currentSupervisor) {
|
|
672
|
-
if (tracking.startedHere && startedCleanup) {
|
|
673
|
-
const stopped = await startedCleanup();
|
|
674
|
-
if (stopped.status === "failed") return { failed: {
|
|
675
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
676
|
-
message: `The dev server on port ${port} started before the managed tunnel was ready.`,
|
|
677
|
-
remedy: `Tunnel cleanup failed for pid ${tracking.record.pid}: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
|
|
678
|
-
} };
|
|
679
|
-
d.clearTunnelRecord(root, tracking.record);
|
|
680
|
-
return { failed: {
|
|
681
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
682
|
-
message: `The dev server on port ${port} started before the managed tunnel was ready.`,
|
|
683
|
-
remedy: "Run `stim stop`, then `stim start --remote`."
|
|
684
|
-
} };
|
|
685
|
-
}
|
|
686
|
-
return {
|
|
687
|
-
origin: tracking.record.url,
|
|
688
|
-
tunnel: tracking
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
if (!d.isTunnelAlive(tracking.record.pid)) {
|
|
692
|
-
if (tracking.startedHere && startedCleanup) {
|
|
693
|
-
const stopped = await startedCleanup();
|
|
694
|
-
if (stopped.status === "failed") return { failed: {
|
|
695
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
696
|
-
message: `The managed ${tracking.record.provider} tunnel exited before the supervisor started.`,
|
|
697
|
-
remedy: `Cleanup failed. Unmanaged pid ${tracking.record.pid} may still be running: ${stopped.reason ?? "unknown error"}. The tunnel record remains available to \`stim stop\`.`
|
|
698
|
-
} };
|
|
699
|
-
}
|
|
700
|
-
d.clearTunnelRecord(root, tracking.record);
|
|
701
|
-
return { failed: {
|
|
702
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
703
|
-
message: `The managed ${tracking.record.provider} tunnel exited before the supervisor started.`,
|
|
704
|
-
remedy: "Retry `stim start --remote`."
|
|
705
|
-
} };
|
|
706
|
-
}
|
|
707
|
-
let child;
|
|
708
|
-
try {
|
|
709
|
-
child = await spawnSupervisor(tracking.record.url);
|
|
710
|
-
} catch (err) {
|
|
711
|
-
return { failed: {
|
|
712
|
-
code: "STIM_SUPERVISOR_EXITED",
|
|
713
|
-
message: `Could not spawn the dev server supervisor: ${err?.message || err}`,
|
|
714
|
-
remedy: "Fix the spawn error, then retry `stim start --remote`."
|
|
715
|
-
} };
|
|
716
|
-
}
|
|
717
|
-
const handoffRecord = {
|
|
718
|
-
pid: child.pid,
|
|
719
|
-
processToken: child.recordedIdentity ? child.recordedIdentity.processToken ?? null : child.pid ? (0, process_identity_exports.captureProcessToken)(child.pid) : null,
|
|
720
|
-
port,
|
|
721
|
-
mode: serverMode,
|
|
722
|
-
startedAt: new Date(spawnedTs ?? Date.now()).toISOString()
|
|
723
|
-
};
|
|
724
|
-
try {
|
|
725
|
-
d.writeSupervisorRecord(root, { supervisor: handoffRecord });
|
|
726
|
-
} catch {
|
|
727
|
-
if (!await waitForSupervisorHandoff(child)) {
|
|
728
|
-
const stopped = supervisorSignalable(child) && await d.terminateSupervisorChild(child);
|
|
729
|
-
return { failed: {
|
|
730
|
-
code: "STIM_SUPERVISOR_EXITED",
|
|
731
|
-
message: `Could not record supervisor pid ${child.pid ?? "unknown"} before releasing the managed start lock.`,
|
|
732
|
-
remedy: stopped ? "The supervisor process was stopped. Retry `stim start --remote`." : `Cleanup failed. Unmanaged supervisor pid ${child.pid ?? "unknown"} may still be running. Stop it with \`stim stop\`${child.pid === void 0 ? "" : `, or \`${d.platform === "win32" ? `taskkill /PID ${child.pid} /T /F` : `kill ${child.pid}`}\``} before retrying.`
|
|
733
|
-
} };
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return {
|
|
737
|
-
origin: tracking.record.url,
|
|
738
|
-
tunnel: tracking
|
|
739
|
-
};
|
|
740
|
-
});
|
|
741
|
-
} catch (err) {
|
|
742
|
-
return fail({
|
|
743
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
744
|
-
message: `Could not acquire the managed Metro tunnel lock: ${err?.message || err}`,
|
|
745
|
-
remedy: "Retry `stim start --remote` after the other start command finishes."
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
if ("failed" in acquisition) return fail(acquisition.failed);
|
|
749
|
-
publicOrigin = acquisition.origin;
|
|
750
|
-
managedTunnel = acquisition.tunnel;
|
|
751
|
-
if (managedTunnel.record.provider === "tailscale") note(phaseLine("lan", `tailnet-only Metro: ${publicOrigin}`));
|
|
752
|
-
resolution = await resolveProjectMetro(port, root);
|
|
753
|
-
supervisor = liveSupervisor({
|
|
754
|
-
state: readWorkspaceState$1(root),
|
|
755
|
-
project: getProject(root),
|
|
756
|
-
port
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
const managedTunnelExited = () => managedTunnel !== null && !d.isTunnelAlive(managedTunnel.record.pid);
|
|
761
|
-
const failExitedManagedTunnel = async () => {
|
|
762
|
-
const tracked = managedTunnel;
|
|
763
|
-
const record = tracked.record;
|
|
764
|
-
const stopped = tracked.startedHere ? await d.stopTunnel(record) : { status: "missing" };
|
|
765
|
-
if (stopped.status !== "failed") d.clearTunnelRecord(root, record);
|
|
766
|
-
return fail({
|
|
767
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
768
|
-
message: `The managed ${record.provider} tunnel exited before the dev server became ready.`,
|
|
769
|
-
remedy: stopped.status === "failed" ? `The tunnel cleanup also failed: ${stopped.reason ?? "unknown error"}. Run \`stim stop\`, then retry.` : "Run `stim stop`, then `stim start --remote`."
|
|
770
|
-
});
|
|
771
|
-
};
|
|
772
|
-
const requireExpoTunnel = () => {
|
|
773
|
-
if (tunnel && (!supervisor || readMetroTunnel(root)?.kind !== "expo")) fail({
|
|
774
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
775
|
-
message: `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.`,
|
|
776
|
-
remedy: "Run `stim stop`, then `stim start --remote`."
|
|
777
|
-
});
|
|
778
|
-
};
|
|
779
|
-
if (!spawnedChild && resolution.metro) {
|
|
780
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
781
|
-
if (tunnel && supervisor) {
|
|
782
|
-
if (!await waitForExpoTunnel({
|
|
783
|
-
root,
|
|
784
|
-
seconds: waitSeconds,
|
|
785
|
-
aborted: () => !liveSupervisor({
|
|
786
|
-
state: readWorkspaceState$1(root),
|
|
787
|
-
project: getProject(root),
|
|
788
|
-
port
|
|
789
|
-
})
|
|
790
|
-
})) {
|
|
791
|
-
const stillLive = liveSupervisor({
|
|
792
|
-
state: readWorkspaceState$1(root),
|
|
793
|
-
project: getProject(root),
|
|
794
|
-
port
|
|
795
|
-
});
|
|
796
|
-
return fail({
|
|
797
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
798
|
-
message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
|
|
799
|
-
remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
supervisor = liveSupervisor({
|
|
803
|
-
state: readWorkspaceState$1(root),
|
|
804
|
-
project: getProject(root),
|
|
805
|
-
port
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
requireExpoTunnel();
|
|
809
|
-
if (!supervisor) {
|
|
810
|
-
note(chalk.dim(`A dev server for this project already answers on port ${port}, started outside Stim.`));
|
|
811
|
-
note(chalk.dim("Leaving it alone: Stim will not start a second bundler over a working one."));
|
|
812
|
-
}
|
|
813
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
814
|
-
clearWorkspaceStateKeys(root, [IDLE_STOP_KEY]);
|
|
815
|
-
return startFacts({
|
|
816
|
-
root,
|
|
817
|
-
port,
|
|
818
|
-
supervisor,
|
|
819
|
-
logsDir,
|
|
820
|
-
alreadyRunning: true
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
if (!spawnedChild && supervisor) {
|
|
824
|
-
note(chalk.dim(`Supervisor pid ${supervisor.pid} is already running for this workspace; waiting for it to answer on port ${port}...`));
|
|
825
|
-
if (!await waitForMetro({
|
|
826
|
-
root,
|
|
827
|
-
port,
|
|
828
|
-
seconds: waitSeconds,
|
|
829
|
-
aborted: managedTunnel ? managedTunnelExited : void 0
|
|
830
|
-
})) {
|
|
831
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
832
|
-
return fail({
|
|
833
|
-
code: "STIM_METRO_TIMEOUT",
|
|
834
|
-
message: `Supervisor pid ${supervisor.pid} did not serve port ${port} within ${waitSeconds}s.`,
|
|
835
|
-
lines: logTailLines(logFile),
|
|
836
|
-
remedy: "Run `stim stop` to halt it, then `stim start` again."
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
if (tunnel) {
|
|
840
|
-
if (!await waitForExpoTunnel({
|
|
841
|
-
root,
|
|
842
|
-
seconds: waitSeconds,
|
|
843
|
-
aborted: () => !liveSupervisor({
|
|
844
|
-
state: readWorkspaceState$1(root),
|
|
845
|
-
project: getProject(root),
|
|
846
|
-
port
|
|
847
|
-
})
|
|
848
|
-
})) {
|
|
849
|
-
const stillLive = liveSupervisor({
|
|
850
|
-
state: readWorkspaceState$1(root),
|
|
851
|
-
project: getProject(root),
|
|
852
|
-
port
|
|
853
|
-
});
|
|
854
|
-
return fail({
|
|
855
|
-
code: "STIM_REMOTE_START_REQUIRED",
|
|
856
|
-
message: stillLive ? `The Expo dev server on port ${port} is local-only and cannot gain a tunnel while it is running.` : `The Expo dev server on port ${port} stopped before its tunnel became ready.`,
|
|
857
|
-
remedy: stillLive ? "Run `stim stop`, then `stim start --remote`." : "Run `stim start --remote` again."
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
supervisor = liveSupervisor({
|
|
862
|
-
state: readWorkspaceState$1(root),
|
|
863
|
-
project: getProject(root),
|
|
864
|
-
port
|
|
865
|
-
}) || (tunnel ? null : supervisor);
|
|
866
|
-
requireExpoTunnel();
|
|
867
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
868
|
-
return startFacts({
|
|
869
|
-
root,
|
|
870
|
-
port,
|
|
871
|
-
supervisor,
|
|
872
|
-
logsDir,
|
|
873
|
-
alreadyRunning: true
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
877
|
-
const child = spawnedChild ?? await spawnSupervisor(publicOrigin);
|
|
878
|
-
const attemptStartedTs = spawnedTs ?? Date.now();
|
|
879
|
-
if (!await waitForMetro({
|
|
880
|
-
root,
|
|
881
|
-
port,
|
|
882
|
-
seconds: waitSeconds,
|
|
883
|
-
aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false) || managedTunnelExited()
|
|
884
|
-
})) {
|
|
885
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
886
|
-
const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
|
|
887
|
-
const exitInfo = childExit;
|
|
888
|
-
const how = exitInfo ? exitInfo.signal ? `signal ${exitInfo.signal}` : `code ${exitInfo.code}` : "without being observed";
|
|
889
|
-
return fail(gone ? {
|
|
890
|
-
code: "STIM_SUPERVISOR_EXITED",
|
|
891
|
-
message: `The supervisor exited (${how}) before the dev server came up on port ${port}.`,
|
|
892
|
-
lines: failureEvidence({
|
|
893
|
-
logFile,
|
|
894
|
-
logsDir,
|
|
895
|
-
sinceTs: attemptStartedTs
|
|
896
|
-
}),
|
|
897
|
-
remedy: "Fix the error above and run `stim start` again; `stim logs --errors` has the full records."
|
|
898
|
-
} : {
|
|
899
|
-
code: "STIM_METRO_TIMEOUT",
|
|
900
|
-
message: `The dev server did not answer on port ${port} within ${waitSeconds}s.`,
|
|
901
|
-
lines: failureEvidence({
|
|
902
|
-
logFile,
|
|
903
|
-
logsDir,
|
|
904
|
-
sinceTs: attemptStartedTs
|
|
905
|
-
}),
|
|
906
|
-
remedy: "It may still be starting. Run `stim stop` to halt it, or `stim logs` to follow along."
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (managedTunnelExited()) return failExitedManagedTunnel();
|
|
910
|
-
if (tunnel) {
|
|
911
|
-
if (!await waitForExpoTunnel({
|
|
912
|
-
root,
|
|
913
|
-
seconds: waitSeconds,
|
|
914
|
-
aborted: () => childExit !== null || (child.pid ? !pidExists(child.pid) : false)
|
|
915
|
-
})) {
|
|
916
|
-
const gone = childExit !== null || (child.pid ? !pidExists(child.pid) : false);
|
|
917
|
-
return fail({
|
|
918
|
-
code: gone ? "STIM_SUPERVISOR_EXITED" : "STIM_METRO_TIMEOUT",
|
|
919
|
-
message: gone ? `The supervisor exited before the Expo tunnel became ready on port ${port}.` : `Expo did not report a tunnel URL within ${waitSeconds}s.`,
|
|
920
|
-
remedy: "Run `stim stop`, then `stim start --remote`."
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
supervisor = liveSupervisor({
|
|
925
|
-
state: readWorkspaceState$1(root),
|
|
926
|
-
project: getProject(root),
|
|
927
|
-
port
|
|
928
|
-
}) || {
|
|
929
|
-
pid: child.pid,
|
|
930
|
-
port,
|
|
931
|
-
mode: null,
|
|
932
|
-
startedAt: null
|
|
933
|
-
};
|
|
934
|
-
return startFacts({
|
|
935
|
-
root,
|
|
936
|
-
port,
|
|
937
|
-
supervisor,
|
|
938
|
-
logsDir,
|
|
939
|
-
alreadyRunning: false
|
|
940
|
-
});
|
|
941
|
-
};
|
|
942
|
-
const startLocked = async () => {
|
|
943
|
-
try {
|
|
944
|
-
return await withWorkspaceProcessLock(dirname(workspaceLogsDir$1(root)), "metro-start", runStart, { external: true });
|
|
945
|
-
} catch (error) {
|
|
946
|
-
if (workspaceProcessLockError(error) !== "timeout") throw error;
|
|
947
|
-
return fail({
|
|
948
|
-
code: "STIM_METRO_TIMEOUT",
|
|
949
|
-
message: "Another Metro start or reset is still running for this app.",
|
|
950
|
-
remedy: "Wait for it to finish, then retry `stim start`."
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
if (!managedRemote) return startLocked();
|
|
955
|
-
try {
|
|
956
|
-
return await d.withWorktreeLock(worktreeRoot, startLocked);
|
|
957
|
-
} catch (err) {
|
|
958
|
-
const lockError = workspaceProcessLockError(err);
|
|
959
|
-
if (lockError === "refused") return fail({
|
|
960
|
-
code: "STIM_WORKTREE_REMOVAL_IN_PROGRESS",
|
|
961
|
-
message: `The worktree at ${worktreeRoot} is being removed.`,
|
|
962
|
-
remedy: "Retry `stim start --remote` after `stim worktree remove` finishes."
|
|
963
|
-
});
|
|
964
|
-
if (lockError === "timeout") return fail({
|
|
965
|
-
code: "STIM_REMOTE_METRO_UNREACHABLE",
|
|
966
|
-
message: `Could not acquire the managed remote worktree lock: ${err?.message || err}`,
|
|
967
|
-
remedy: "Retry `stim start --remote` after the other remote start command finishes."
|
|
968
|
-
});
|
|
969
|
-
throw err;
|
|
970
|
-
}
|
|
971
|
-
};
|
|
972
|
-
try {
|
|
973
|
-
return {
|
|
974
|
-
ok: true,
|
|
975
|
-
facts: await run(),
|
|
976
|
-
waited: waitTimer(),
|
|
977
|
-
reclaimed
|
|
978
|
-
};
|
|
979
|
-
} catch (error) {
|
|
980
|
-
if (!(error instanceof StartRefusal)) throw error;
|
|
981
|
-
return {
|
|
982
|
-
ok: false,
|
|
983
|
-
error: startError(error.refusal),
|
|
984
|
-
lines: error.refusal.lines ?? [],
|
|
985
|
-
reclaimed
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
async function resolvePort(root, settings, note, fail) {
|
|
990
|
-
const result = await resolveWorkspaceMetroPort(root, settings, note, "start");
|
|
991
|
-
return typeof result === "number" ? result : fail(result);
|
|
992
|
-
}
|
|
993
|
-
async function resolveWorkspaceMetroPort(root, settings, note, command, reserve = true) {
|
|
994
|
-
const setting = metroPortSetting(settings);
|
|
995
|
-
if (setting.error) return {
|
|
996
|
-
code: "STIM_BAD_ARG",
|
|
997
|
-
message: setting.error,
|
|
998
|
-
remedy: SETTING_SHAPE_REMEDY
|
|
999
|
-
};
|
|
1000
|
-
const pinned = setting.port;
|
|
1001
|
-
const project = getProject(root);
|
|
1002
|
-
const recorded = project?.metroPort;
|
|
1003
|
-
if (pinned === null && command === "web") return recorded ?? await reservePort(root, null);
|
|
1004
|
-
if (recorded) {
|
|
1005
|
-
const supervisor = resolveSupervisorTarget({
|
|
1006
|
-
state: readWorkspaceState$1(root)?.supervisor,
|
|
1007
|
-
record: project.supervisor,
|
|
1008
|
-
reservedPort: recorded
|
|
1009
|
-
});
|
|
1010
|
-
if (supervisor.status !== "none" && supervisor.status !== "stale") {
|
|
1011
|
-
if (pinned === null || recorded === pinned) return recorded;
|
|
1012
|
-
if (supervisor.status === "unverified") return {
|
|
1013
|
-
code: "STIM_BAD_ARG",
|
|
1014
|
-
message: `Cannot move this workspace's Metro to metro.port ${pinned}: ${supervisor.reason}.`,
|
|
1015
|
-
remedy: `Stop that supervisor with the tool that started it, then retry, or restore metro.port to ${recorded}. Stim leaves unverified processes alone.`
|
|
1016
|
-
};
|
|
1017
|
-
return {
|
|
1018
|
-
code: "STIM_BAD_ARG",
|
|
1019
|
-
message: `This workspace's dev server is running on port ${recorded}, and metro.port pins it to ${pinned}.`,
|
|
1020
|
-
remedy: "Run `stim stop`, then retry, or unset or restore metro.port."
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
if (pinned !== null) {
|
|
1025
|
-
const held = await resolveProjectMetro(pinned, root);
|
|
1026
|
-
if (held.notOurs) return {
|
|
1027
|
-
code: "STIM_BAD_ARG",
|
|
1028
|
-
message: `Port ${pinned} is held by something else (${held.notOurs}), and metro.port pins this workspace's Metro to it.`,
|
|
1029
|
-
remedy: "Stop the process on that port, or set another metro.port."
|
|
1030
|
-
};
|
|
1031
|
-
if (recorded === pinned) return recorded;
|
|
1032
|
-
} else if (recorded) {
|
|
1033
|
-
const held = await resolveProjectMetro(recorded, root);
|
|
1034
|
-
if (!held.notOurs) return recorded;
|
|
1035
|
-
const fresh = await reservePort(root, null);
|
|
1036
|
-
if (typeof fresh === "number" && fresh !== recorded) {
|
|
1037
|
-
note(chalk.yellow(`Port ${recorded} is held by something else (${held.notOurs}).`));
|
|
1038
|
-
note(chalk.dim(`Reserved port ${fresh} for this project instead.`));
|
|
1039
|
-
}
|
|
1040
|
-
return fresh;
|
|
1041
|
-
}
|
|
1042
|
-
if (!reserve && pinned !== null) return pinned;
|
|
1043
|
-
return reservePort(root, pinned);
|
|
1044
|
-
}
|
|
1045
|
-
async function reservePort(root, pinned) {
|
|
1046
|
-
try {
|
|
1047
|
-
return await reserveMetroPort(root, void 0, void 0, pinned);
|
|
1048
|
-
} catch (error) {
|
|
1049
|
-
if (error instanceof PortInspectionError) return {
|
|
1050
|
-
code: error.code,
|
|
1051
|
-
message: error.reason,
|
|
1052
|
-
remedy: PORT_INSPECTION_REMEDY
|
|
1053
|
-
};
|
|
1054
|
-
return {
|
|
1055
|
-
code: "STIM_BAD_ARG",
|
|
1056
|
-
message: error.message,
|
|
1057
|
-
remedy: "Give each workspace its own metro.port, in the workspace layer or the environment."
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
async function waitForMetro({ root, port, seconds, aborted = () => false, probe = resolveProjectMetro }) {
|
|
1062
|
-
const deadline = Date.now() + seconds * 1e3;
|
|
1063
|
-
while (Date.now() < deadline) {
|
|
1064
|
-
if ((await probe(port, root)).metro) return true;
|
|
1065
|
-
if (aborted()) return false;
|
|
1066
|
-
await cancellableSleep(POLL_MS);
|
|
1067
|
-
}
|
|
1068
|
-
const last = await probe(port, root);
|
|
1069
|
-
return Boolean(last.metro);
|
|
1070
|
-
}
|
|
1071
|
-
async function waitForExpoTunnel({ root, seconds, aborted = () => false }) {
|
|
1072
|
-
const deadline = Date.now() + seconds * 1e3;
|
|
1073
|
-
while (Date.now() < deadline) {
|
|
1074
|
-
if (aborted()) return false;
|
|
1075
|
-
if (readMetroTunnel(root)?.kind === "expo") return true;
|
|
1076
|
-
await cancellableSleep(POLL_MS);
|
|
1077
|
-
}
|
|
1078
|
-
return !aborted() && readMetroTunnel(root)?.kind === "expo";
|
|
1079
|
-
}
|
|
1080
|
-
function logTailLines(logFile) {
|
|
1081
|
-
return [...readLogTail(logFile), `Supervisor log: ${logFile}`];
|
|
1082
|
-
}
|
|
1083
|
-
function failureEvidence({ logFile, logsDir, sinceTs }) {
|
|
1084
|
-
const supTail = readLogTail(logFile);
|
|
1085
|
-
const lines = supTail.length > 0 ? [...supTail, `Supervisor log: ${logFile}`] : [];
|
|
1086
|
-
let errors = [];
|
|
1087
|
-
let all = [];
|
|
1088
|
-
try {
|
|
1089
|
-
all = queryLogs({ dir: logsDir });
|
|
1090
|
-
errors = all.filter((record) => levelRank(record.level) >= levelRank("error"));
|
|
1091
|
-
} catch {}
|
|
1092
|
-
const since = (rs) => rs.filter((r) => typeof r.ts === "number" && r.ts >= sinceTs).slice(-8);
|
|
1093
|
-
let recent = since(errors);
|
|
1094
|
-
const fellBack = recent.length === 0;
|
|
1095
|
-
if (fellBack) recent = since(all);
|
|
1096
|
-
for (const r of recent) lines.push(`${String(r.src ?? "?")}: ${String(r.msg ?? "").split("\n")[0] ?? ""}`);
|
|
1097
|
-
if (recent.length > 0) lines.push(fellBack ? "Full records: `stim logs`" : "Full records: `stim logs --errors`");
|
|
1098
|
-
return lines;
|
|
1099
|
-
}
|
|
1100
|
-
function report({ json, out, facts, waited, reclaimed, links }) {
|
|
1101
|
-
if (json) {
|
|
1102
|
-
console.log(JSON.stringify({
|
|
1103
|
-
...facts,
|
|
1104
|
-
...links ? { links } : {},
|
|
1105
|
-
...reclaimed.length ? { reclaimed } : {}
|
|
1106
|
-
}));
|
|
1107
|
-
return;
|
|
1108
|
-
}
|
|
1109
|
-
const who = facts.supervisorPid ? `supervisor pid ${facts.supervisorPid}${facts.mode ? ` (${facts.mode})` : ""}` : "started outside Stim";
|
|
1110
|
-
out(chalk.green(`OK: dev server on port ${facts.port}, ${who}${facts.alreadyRunning ? " (already running)" : ""} ${waited}`));
|
|
1111
|
-
out(chalk.dim(phaseLine("logs", facts.logsDir)));
|
|
1112
|
-
if (facts.agentDevice) out(chalk.dim(phaseLine("agent-device", `AGENT_DEVICE_STATE_DIR=${facts.agentDevice.stateDir}`)));
|
|
1113
|
-
if (links) console.error(chalk.dim(workspaceLinkLine(links)));
|
|
1114
|
-
}
|
|
1115
|
-
//#endregion
|
|
1116
|
-
export { registerStart as a, startDevServer as c, supervisorSignalable as d, tailLines as f, detectBundleId as g, detectAppIds as h, readLogTail as i, startFacts as l, detectAndroidPackage as m, liveSupervisor as n, resolveWorkspaceMetroPort as o, wantsExpoOwnTunnel as p, parseWait as r, startCommand as s, failureEvidence as t, supervisorEntry as u };
|