stim 1.20.2 → 1.21.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 +88 -8
- package/dist/acquire-artifact-CD4lbiWE.mjs +153 -0
- package/dist/act-DHAw2A2S.mjs +2667 -0
- package/dist/activity-BzVfHSrY.mjs +1376 -0
- package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-zh1vhbLp.mjs} +21 -65
- package/dist/{android-HqP1_vB_.mjs → android-BEJ8Jpl7.mjs} +2 -2
- package/dist/{android-DCKZrNvV.mjs → android-BLgbNqox.mjs} +128 -78
- package/dist/{android-Dlzxshgp.mjs → android-CV_GCELZ.mjs} +5 -5
- package/dist/{android-Mz_Inn3w.mjs → android-DUYkNF4u.mjs} +184 -102
- package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-Ct5b1xCK.mjs} +7 -6
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/android-project-BCAAmMwz.mjs +12 -0
- package/dist/api.d.mts +740 -8
- package/dist/api.mjs +585 -1
- package/dist/{artifact-HSwkIWEQ.mjs → artifact-B8T3S5gi.mjs} +223 -201
- package/dist/{artifact-D3czlUvT.mjs → artifact-DAuZ-FNU.mjs} +158 -148
- package/dist/{browser-web-CA-F1GQo.mjs → browser-web-QQzVjz3O.mjs} +26 -24
- package/dist/budget-Bs6SxxzO.mjs +9154 -0
- package/dist/{build-DYDbfkk5.mjs → build-CoPzQRNw.mjs} +144 -96
- package/dist/build-cache-DmyR9Zzc.mjs +602 -0
- package/dist/build-plan-CaFskB7_.mjs +188 -0
- package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DkkqguoK.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-zdKiYQ3E.mjs → ccache-BJRZ9pG3.mjs} +10 -9
- package/dist/{cdp-Dq_ypAlJ.mjs → cdp-CWmIUMbw.mjs} +18 -5
- package/dist/{chrome-BnzZMqsR.mjs → chrome-B6tfCta1.mjs} +7 -4
- package/dist/{cli-CLP3diBm.mjs → cli-0DYf8QhQ.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/client-B7QVOlCr.d.mts +253 -0
- package/dist/client-UXD70ZbM.mjs +1406 -0
- package/dist/collector-run.d.mts +4 -40
- package/dist/collector-run.mjs +9 -8
- package/dist/{command-output-BTy8hZ0F.mjs → command-output-B1UrjZHL.mjs} +1 -1
- package/dist/config-CYcBF478.mjs +949 -0
- package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-CqeBlrEn.mjs} +3 -3
- package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-BbKQW83h.mjs} +20 -5
- package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-DFRyG1_o.mjs} +1 -1
- package/dist/{dev-client-DAWVIGBu.mjs → dev-client-UOeESKm6.mjs} +2 -2
- package/dist/device-DCxH7_By.mjs +1068 -0
- package/dist/{device-BECOiufl.mjs → device-ExJq8Ei7.mjs} +35 -35
- package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-CMlqEcvw.mjs} +43 -13
- package/dist/device-host-worker.mjs +65 -61
- package/dist/{device-ios-COSi6oS1.mjs → device-ios-DRUeKaFV.mjs} +48 -45
- package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-C8raGFoP.mjs} +14 -26
- package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-DMRpVX9a.mjs} +5 -5
- package/dist/doctor-BLHCYxzi.mjs +2 -0
- package/dist/{doctor-kfMBnUw6.mjs → doctor-DTvaDOda.mjs} +281 -317
- package/dist/{doctor-CB8oZKCk.mjs → doctor-Dhqebm0q.mjs} +183 -91
- package/dist/doctor-fingerprint.mjs +19 -0
- package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-Cv1F10Kj.mjs} +40 -21
- package/dist/{eas-build-JyfqyHfC.mjs → eas-build-BomfRnFT.mjs} +53 -20
- package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-DJwZpy0c.mjs} +26 -208
- package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-op6A54yi.mjs} +229 -11
- package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-Bf0rZvG9.mjs} +172 -82
- package/dist/exec-H20xSg2T.d.mts +65 -0
- package/dist/exec-_UbtCQcG.mjs +750 -0
- package/dist/gc-C-WwL0yi.mjs +3 -0
- package/dist/{gc-qWOMdnER.mjs → gc-_-dCXdyL.mjs} +584 -796
- package/dist/git-C3iTsJaZ.mjs +73 -0
- package/dist/{guide-G9cAbVAc.mjs → guide-Yqwt2bd6.mjs} +941 -269
- package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-Bw-honsd.mjs} +8 -6
- package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DB_gBPfY.mjs} +34 -19
- package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CUvOf4Vr.mjs} +1 -1
- package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-DL_P5AdN.mjs} +77 -43
- package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-QI141yKk.mjs} +21 -17
- package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-Ksyjgzq-.mjs} +30 -21
- package/dist/idle-BVeRGroT.d.mts +24 -0
- package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DaKmZi0z.mjs} +11 -10
- package/dist/{ios-BwVCF7oA.mjs → ios-CQ68hG9s.mjs} +8 -8
- package/dist/{ios-RkW4pnQf.mjs → ios-CpByZy12.mjs} +140 -80
- package/dist/{ios-device-B5veSDDQ.mjs → ios-device-CXGP1bsD.mjs} +1 -1
- package/dist/{ios-device-D7pi2Sca.mjs → ios-device-CwFMehUS.mjs} +51 -26
- package/dist/{ios-project-K9hY_Jab.mjs → ios-project-WuwwBdvW.mjs} +13 -13
- package/dist/{ios-nOLJGVdy.mjs → ios-qNJt4dFm.mjs} +1 -1
- package/dist/ios-signing-DTTIChGs.mjs +489 -0
- package/dist/{ios-state-CBLe1xYw.mjs → ios-state-DvFX-K_l.mjs} +1 -1
- package/dist/{ios-Ce7bc7br.mjs → ios-ubFsBzwq.mjs} +129 -86
- package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Cc1dttps.mjs} +53 -26
- package/dist/{launch-F4sOqAhB.mjs → launch-CD1yVc7U.mjs} +91 -90
- package/dist/{launch-C3qJ6Agb.mjs → launch-Zq2mOSDy.mjs} +82 -541
- package/dist/logs-BS00Anxf.mjs +352 -0
- package/dist/logs-CMm05HxN.mjs +255 -0
- package/dist/{macos-D7uDvO-z.mjs → macos-B65--pPd.mjs} +1 -1
- package/dist/{macos-Ch5VJmcS.mjs → macos-C-0qJ6zq.mjs} +225 -212
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +122 -247
- package/dist/{metro-DnCEjCxs.mjs → metro-DEXpIhHC.mjs} +67 -12
- package/dist/{metro-gateway-CX9Xlkq2.mjs → metro-gateway-BMJNlj3E.mjs} +20 -12
- package/dist/{named-ports-4WopRPg-.mjs → named-ports-BWTzt6mu.mjs} +8 -63
- package/dist/native-android-JACyjTZs.mjs +844 -0
- package/dist/{native-gradle-inputs-BsLRrl7u.mjs → native-gradle-inputs-Bep9JcSF.mjs} +128 -140
- package/dist/native-inputs-TRTTTRxt.mjs +91 -0
- package/dist/native-run-DY_RLx3C.mjs +277 -0
- package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-Dg1Q7tfF.mjs} +9 -8
- package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-CB2iQeWq.mjs} +27 -18
- package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-CXX39F7o.mjs} +96 -47
- package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
- package/dist/offload-worker.d.mts +1 -46
- package/dist/offload-worker.mjs +197 -66
- package/dist/{ownership-DCtw_2IA.mjs → ownership-DEmM5vhh.mjs} +3 -3
- package/dist/{page-DRHy329N.mjs → page-CPaHeYqj.mjs} +7 -4
- package/dist/paths-BsJMoPP2.mjs +12 -0
- package/dist/paths-DQtTgsBf.mjs +42 -0
- package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BqGbTa61.mjs} +5 -5
- package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-Bwj-IjY1.mjs} +6 -6
- package/dist/{ports-DdSUicVo.mjs → ports-D-MmCTdv.mjs} +3 -3
- package/dist/prebuild-uD_ipCxF.mjs +449 -0
- package/dist/{project-_phNIJfs.mjs → project-BZNjmW_8.mjs} +65 -37
- package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-Lfetn8Kr.mjs} +2 -2
- package/dist/pull-request-zjCSAryH.d.mts +120 -0
- package/dist/pull-requests.d.mts +1 -119
- package/dist/pull-requests.mjs +1 -1
- package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-DuzH0cn3.mjs} +74 -48
- package/dist/{gradle-DSrXb6lG.mjs → react-native-build-rhk4smwQ.mjs} +292 -15
- package/dist/react-native-doctor-CQpuIMso.mjs +234 -0
- package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-Bhq1rJeV.mjs} +46 -28
- package/dist/{recordings-D0d5aQt0.mjs → recordings-DYteQSu4.mjs} +12 -9
- package/dist/{reload-C8JhxjSh.mjs → reload-CmTRFJaX.mjs} +93 -47
- package/dist/reload-DzjSl5P_.mjs +2 -0
- package/dist/{remote-BiR6aYjy.mjs → remote-ByYkXdGD.mjs} +15 -10
- package/dist/result-DNiS-tTP.mjs +434 -0
- package/dist/{run-BtPgpKks.mjs → run-D23HmJMs.mjs} +4 -5
- package/dist/selection-CSl9i5qU.mjs +619 -0
- package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CxTrvDg8.mjs} +3 -4
- package/dist/{server-command-CTtCkmcQ.mjs → server-command-CtzloH3v.mjs} +14 -7
- package/dist/server-expo-C-90EhcG.mjs +2 -0
- package/dist/{server-expo-DsguMGUF.mjs → server-expo-DT_F_uVI.mjs} +50 -9
- package/dist/{settings-B717odTD.mjs → settings-CsJDEXgK.mjs} +234 -24
- package/dist/{settings-vk3mJGSm.d.mts → settings-DkdmlelE.d.mts} +6 -4
- package/dist/settings-T1gtc3bf.mjs +114 -0
- package/dist/settings-write-HyVZ8eQ8.mjs +359 -0
- package/dist/settings.schema.json +163 -16
- package/dist/{simslim-CCr3rE-A.mjs → simslim-Cgk17SXV.mjs} +14 -12
- package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-DF8VfT64.mjs} +15 -20
- package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
- package/dist/stage-BIS0ydsV.mjs +183 -0
- package/dist/{start-BWqzODpb.mjs → start-7lpH7hmN.mjs} +63 -44
- package/dist/{start-TXeMmmO2.mjs → start-RChynRDp.mjs} +1 -1
- package/dist/{state-D1Icyjlu.mjs → state-C4ZtUisW.mjs} +40 -13
- package/dist/{state-CG_OFoaA.mjs → state-XMziexv2.mjs} +3 -2
- package/dist/{stats-BoY5sEPU.mjs → stats-53T7OTb9.mjs} +7 -13
- package/dist/stats-report-BFfMpWWF.mjs +16 -0
- package/dist/{status-WgFbkRzj.mjs → status-BL7jVyAB.mjs} +535 -447
- package/dist/status-CSi6tlRq.mjs +2 -0
- package/dist/{status-BTA7kKiQ.mjs → status-DqEUSSxs.mjs} +6 -4
- package/dist/{status-watch-C23ILtf9.mjs → status-watch-GDCfqpTE.mjs} +187 -22
- package/dist/status-worker.d.mts +1 -0
- package/dist/status-worker.mjs +19 -0
- package/dist/stim-desktop-C12we7lz.mjs +112 -0
- package/dist/{stop-DR6VoDIk.mjs → stop-Bk_1qiV6.mjs} +2 -2
- package/dist/{stop-BNmBMi1F.mjs → stop-CPzlM1L-.mjs} +138 -98
- package/dist/{stop-DPEhunlw.mjs → stop-D7h6jAXQ.mjs} +11 -10
- package/dist/supervisor-run.d.mts +8 -17
- package/dist/supervisor-run.mjs +63 -27
- package/dist/{support-DMTqj3Hm.mjs → support-67vraa1Q.mjs} +18 -11
- package/dist/{support-CwjbFs9H.mjs → support-Dlu37g6H.mjs} +12 -12
- package/dist/{swiftpm-macos-DRNVJWlb.mjs → swiftpm-macos-p8WZbxKU.mjs} +35 -24
- package/dist/toolchain-DhKwp9iI.mjs +2 -0
- package/dist/toolchain-Hp9sCpK4.mjs +873 -0
- package/dist/{trigger-CigAYjeL.mjs → trigger-BAahxUPq.mjs} +20 -5
- package/dist/trigger-Bf3uDcpv.mjs +2 -0
- package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-BHX5r0Ei.mjs} +250 -156
- package/dist/{web-BDBMxDpx.mjs → web-CJmIj6AI.mjs} +18 -13
- package/dist/web-DknQETPs.mjs +2 -0
- package/dist/web-run.mjs +11 -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-process-lock-1_1OoVW5.mjs +57 -0
- package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-Cjuigeeu.mjs} +62 -58
- package/dist/{workspaces-C36s2jb9.mjs → workspaces-BccMFmGQ.mjs} +1188 -1079
- package/dist/worktree-CK2MpJW5.mjs +2 -0
- package/dist/{worktree-CgvJYJ-Q.mjs → worktree-DQ6Zi2Mv.mjs} +304 -53
- package/dist/xcode-Zlx3qjEs.mjs +896 -0
- package/package.json +7 -5
- 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/miss-reason-mKzZ4VvN.mjs +0 -238
- package/dist/native-android-D5qc3-_v.mjs +0 -226
- 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/spawn-claims-DVS5RkMS.mjs +0 -72
- package/dist/stage-KNTQopqv.mjs +0 -157
- package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
- 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
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { m as reportSignal, u as operationState } from "./exec-_UbtCQcG.mjs";
|
|
2
|
+
import { v as withDirLock$1 } from "./paths-DQtTgsBf.mjs";
|
|
3
|
+
import { Q as findPeer$1, Z as endpoint, _ as loadConfig$1, ct as readHostPermissions, d as getConfigDir, et as outdatedServer, nt as reachMachine, ot as setupRemedy, rt as realIo, tt as parseMachine$1 } from "./config-CYcBF478.mjs";
|
|
4
|
+
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { hostname } from "node:os";
|
|
6
|
+
import { ambientContext, ambientEnv, withDirLockAsync } from "@stim-cli/core";
|
|
7
|
+
import { MACHINE_CREDENTIALS_VERSION, OFFLOAD_MODES, acceptsShape, automaticMachineEnabled, buildMachinesFile, buildMachinesLock, cannotHost, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, isJsonObject, parseMachine, readBuildMachines, readDeviceHostMachines, settingDefinition } from "@stim-cli/core/state";
|
|
8
|
+
import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
|
|
9
|
+
import { InvalidArgumentError } from "commander";
|
|
10
|
+
//#region src/device-host/machines.ts
|
|
11
|
+
function note$1(title, detail, fix = null) {
|
|
12
|
+
return {
|
|
13
|
+
code: "device-host-machine",
|
|
14
|
+
level: "note",
|
|
15
|
+
title,
|
|
16
|
+
detail,
|
|
17
|
+
fix
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function configuredMachines() {
|
|
21
|
+
const remote = loadConfig$1()?.remote;
|
|
22
|
+
if (remote !== void 0 && !isJsonObject(remote)) return null;
|
|
23
|
+
const machines = remote?.machines;
|
|
24
|
+
if (machines === void 0) return [];
|
|
25
|
+
return acceptsShape(settingDefinition("remote.machines"), machines) ? machines : null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `remote.machines` entries this machine asked for device-host access, in setting order, without the ones the
|
|
29
|
+
* tailnet reports as running another OS than macOS, which can never host; null when the setting is invalid.
|
|
30
|
+
*/
|
|
31
|
+
function hostingMachines(status = realIo.status) {
|
|
32
|
+
const configured = configuredMachines();
|
|
33
|
+
if (configured === null) return null;
|
|
34
|
+
let candidates = configured;
|
|
35
|
+
try {
|
|
36
|
+
const asked = readDeviceHostMachines();
|
|
37
|
+
candidates = configured.filter((machine) => asked.some((each) => each.machine === machine));
|
|
38
|
+
} catch {
|
|
39
|
+
candidates = configured;
|
|
40
|
+
}
|
|
41
|
+
if (!candidates.length) return candidates;
|
|
42
|
+
const current = status();
|
|
43
|
+
return candidates.filter((machine) => !cannotHost(current, machine));
|
|
44
|
+
}
|
|
45
|
+
async function store(machines, operation) {
|
|
46
|
+
reportSignal(operation)?.throwIfAborted();
|
|
47
|
+
const write = () => {
|
|
48
|
+
reportSignal(operation)?.throwIfAborted();
|
|
49
|
+
const file = deviceHostMachinesFile();
|
|
50
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
51
|
+
try {
|
|
52
|
+
writeFileSync(tmp, `${JSON.stringify({
|
|
53
|
+
version: MACHINE_CREDENTIALS_VERSION,
|
|
54
|
+
machines
|
|
55
|
+
}, null, 2)}\n`, { mode: 384 });
|
|
56
|
+
renameSync(tmp, file);
|
|
57
|
+
} finally {
|
|
58
|
+
rmSync(tmp, { force: true });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const options = { ensureParent: () => mkdirSync(getConfigDir(), { recursive: true }) };
|
|
62
|
+
const signal = reportSignal(operation);
|
|
63
|
+
if (signal) await withDirLockAsync(deviceHostMachinesLock(), write, {
|
|
64
|
+
...options,
|
|
65
|
+
signal
|
|
66
|
+
});
|
|
67
|
+
else withDirLock$1(deviceHostMachinesLock(), write, options);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reports the device-host approval of each remote machine, separate from its build approval. Only --fix makes a setup
|
|
71
|
+
* ticket or forgets credentials; every server key stays pinned.
|
|
72
|
+
*/
|
|
73
|
+
async function inspectDeviceHostMachines({ fix, version = "<version>", tickets = /* @__PURE__ */ new Map(), operation }, io = realIo, entries = configuredMachines()) {
|
|
74
|
+
if (entries === null) return {
|
|
75
|
+
findings: [note$1("Invalid remote.machines setting", "Use a remote object with machines as an array of tailnet names. Existing hosting credentials and pinned keys are preserved.", "Run `stim guide settings` and correct the setting before running doctor again.")],
|
|
76
|
+
machines: []
|
|
77
|
+
};
|
|
78
|
+
const unavailable = (state, finding) => ({
|
|
79
|
+
findings: [finding],
|
|
80
|
+
machines: entries.map((machine) => ({
|
|
81
|
+
machine,
|
|
82
|
+
state
|
|
83
|
+
}))
|
|
84
|
+
});
|
|
85
|
+
const unreadable = () => unavailable("credentials-unavailable", note$1("Hosting credentials are unreadable", `Stim preserves ${deviceHostMachinesFile()} and its pinned keys.`, "Restore the existing file from a trusted backup or restore read access before running doctor again."));
|
|
86
|
+
if (!entries.length) {
|
|
87
|
+
if (!fix) return {
|
|
88
|
+
findings: [],
|
|
89
|
+
machines: []
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
if (!readDeviceHostMachines().length) return {
|
|
93
|
+
findings: [],
|
|
94
|
+
machines: []
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return unreadable();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const claim = tryAcquireClaim({
|
|
101
|
+
root: deviceHostMachinesClaims(),
|
|
102
|
+
mode: "exclusive",
|
|
103
|
+
label: "device-host approval"
|
|
104
|
+
});
|
|
105
|
+
if (!claim.acquired) {
|
|
106
|
+
releaseClaim(claim.pending);
|
|
107
|
+
return unavailable("busy", note$1("Device-host approval is in use", "Another doctor run holds the hosting credential claim. Run doctor again after it finishes."));
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
let credentials;
|
|
111
|
+
try {
|
|
112
|
+
credentials = readDeviceHostMachines();
|
|
113
|
+
} catch {
|
|
114
|
+
return unreadable();
|
|
115
|
+
}
|
|
116
|
+
if (fix) {
|
|
117
|
+
const kept = credentials.filter((each) => entries.includes(each.machine));
|
|
118
|
+
if (kept.length !== credentials.length) {
|
|
119
|
+
await store(kept, operation);
|
|
120
|
+
credentials = kept;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!entries.length) return {
|
|
124
|
+
findings: [],
|
|
125
|
+
machines: []
|
|
126
|
+
};
|
|
127
|
+
const status = await io.status();
|
|
128
|
+
if (!isJsonObject(status)) return unavailable("tailscale-off", note$1("Remote machines are unreachable", "remote.machines names remote machines, but Tailscale is not running."));
|
|
129
|
+
const self = isJsonObject(status.Self) ? status.Self : {};
|
|
130
|
+
const deviceName = (typeof self.HostName === "string" && self.HostName ? self.HostName : hostname()).replace(/[\p{Cc}\p{Cf}]/gu, "").trim().slice(0, 64) || "Mac";
|
|
131
|
+
const inspected = {
|
|
132
|
+
findings: [],
|
|
133
|
+
machines: []
|
|
134
|
+
};
|
|
135
|
+
const namedNodes = /* @__PURE__ */ new Set();
|
|
136
|
+
for (const machine of new Set(entries)) {
|
|
137
|
+
const parsed = parseMachine$1(machine);
|
|
138
|
+
if (!parsed) {
|
|
139
|
+
inspected.machines.push({
|
|
140
|
+
machine,
|
|
141
|
+
state: "invalid"
|
|
142
|
+
});
|
|
143
|
+
inspected.findings.push(note$1(`Remote machine ${machine} is not a tailnet name`, "Expected `name` or `name:port`."));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const current = await io.status();
|
|
147
|
+
if (!isJsonObject(current)) {
|
|
148
|
+
inspected.machines.push({
|
|
149
|
+
machine,
|
|
150
|
+
state: "tailscale-off"
|
|
151
|
+
});
|
|
152
|
+
inspected.findings.push(note$1(`Remote machine ${machine} is unreachable`, "Tailscale stopped before this connection."));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const peer = findPeer$1(current, parsed.name);
|
|
156
|
+
if (peer === "missing" || peer === "ambiguous") {
|
|
157
|
+
inspected.machines.push({
|
|
158
|
+
machine,
|
|
159
|
+
state: "not-on-tailnet"
|
|
160
|
+
});
|
|
161
|
+
inspected.findings.push(note$1(`Remote machine ${machine} is not on this tailnet`, peer === "missing" ? "No peer has that name." : "Several peers match that name."));
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const nodeRoute = `${peer.nodeId}:${parsed.port}`;
|
|
165
|
+
if (namedNodes.has(nodeRoute)) {
|
|
166
|
+
inspected.machines.push({
|
|
167
|
+
machine,
|
|
168
|
+
dnsName: peer.dnsName,
|
|
169
|
+
state: "invalid"
|
|
170
|
+
});
|
|
171
|
+
inspected.findings.push(note$1(`Remote machine ${machine} repeats a named node`, "Name each tailnet node and serve port only once in remote.machines."));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
namedNodes.add(nodeRoute);
|
|
175
|
+
if (cannotHost(current, machine)) {
|
|
176
|
+
inspected.machines.push({
|
|
177
|
+
machine,
|
|
178
|
+
dnsName: peer.dnsName,
|
|
179
|
+
state: "not-asked"
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const credential = credentials.find((each) => each.machine === machine);
|
|
184
|
+
reportSignal(operation)?.throwIfAborted();
|
|
185
|
+
const outcome = await reachMachine({
|
|
186
|
+
machine,
|
|
187
|
+
access: "device-host",
|
|
188
|
+
endpoint: endpoint(peer, parsed.port),
|
|
189
|
+
address: peer.dnsName,
|
|
190
|
+
credential,
|
|
191
|
+
other: () => readBuildMachines().find((each) => each.machine === machine),
|
|
192
|
+
persist: async (next) => {
|
|
193
|
+
credentials = [...credentials.filter((each) => each.machine !== machine), next];
|
|
194
|
+
await store(credentials, operation);
|
|
195
|
+
},
|
|
196
|
+
fix,
|
|
197
|
+
deviceName,
|
|
198
|
+
setupFlags: ["build", "device-host"],
|
|
199
|
+
serverVersion: version,
|
|
200
|
+
io,
|
|
201
|
+
tickets
|
|
202
|
+
});
|
|
203
|
+
reportSignal(operation)?.throwIfAborted();
|
|
204
|
+
if (outcome.state !== "not-asked" && outcome.credential) {
|
|
205
|
+
const next = outcome.credential;
|
|
206
|
+
if (JSON.stringify(next) !== JSON.stringify(credential)) {
|
|
207
|
+
credentials = [...credentials.filter((each) => each.machine !== machine), next];
|
|
208
|
+
await store(credentials, operation);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const saved = outcome.state !== "not-asked" ? outcome.credential : void 0;
|
|
212
|
+
const known = {
|
|
213
|
+
machine,
|
|
214
|
+
dnsName: peer.dnsName,
|
|
215
|
+
...saved?.deviceId ? { deviceId: saved.deviceId } : {},
|
|
216
|
+
...saved ? { requestedAt: saved.requestedAt } : {}
|
|
217
|
+
};
|
|
218
|
+
if (outcome.state === "approved") {
|
|
219
|
+
const host = readHostPermissions(outcome.result.host);
|
|
220
|
+
if (host && (host.screenRecording === false || host.accessibility === false)) {
|
|
221
|
+
const panes = [...host.screenRecording === false ? ["Screen & System Audio Recording (Screen Recording on macOS 14)"] : [], ...host.accessibility === false ? ["Device Control and Data Access (Accessibility on macOS 26 and earlier)"] : []];
|
|
222
|
+
const detail = [...host.screenRecording === false ? ["Viewing hosted macOS apps needs Screen Recording."] : [], ...host.accessibility === false ? ["Controlling them needs the control permission."] : []];
|
|
223
|
+
inspected.findings.push(note$1(`Remote Mac ${machine} needs ${panes.join(" and ")} for ${host.name}`, detail.join(" "), `On ${machine}, approve ${host.name} in System Settings > Privacy & Security, or run \`stim-server service install\` there again to show the requests.`));
|
|
224
|
+
}
|
|
225
|
+
inspected.machines.push({
|
|
226
|
+
...known,
|
|
227
|
+
state: "approved",
|
|
228
|
+
...host ? { host } : {}
|
|
229
|
+
});
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (outcome.state === "pending") {
|
|
233
|
+
inspected.machines.push({
|
|
234
|
+
...known,
|
|
235
|
+
state: "pending",
|
|
236
|
+
...outcome.credential.expiresAt ? { expiresAt: outcome.credential.expiresAt } : {}
|
|
237
|
+
});
|
|
238
|
+
inspected.findings.push(note$1(`Remote machine ${machine} has not approved this machine for device hosting yet`, `It does not hold this machine's setup ticket yet${outcome.credential.expiresAt ? `; the ticket expires at ${outcome.credential.expiresAt}` : ""}.`, setupRemedy(machine, outcome.command)));
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (outcome.state === "not-asked") {
|
|
242
|
+
inspected.machines.push({
|
|
243
|
+
...known,
|
|
244
|
+
state: "not-asked"
|
|
245
|
+
});
|
|
246
|
+
inspected.findings.push(note$1(`Remote machine ${machine} has not approved this machine for device hosting`, "This machine has not asked for hosting access.", "Run `stim doctor --fix` to make a setup ticket and print the command to run there."));
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (outcome.state === "outdated") {
|
|
250
|
+
const outdated = outdatedServer(machine, version, outcome.command);
|
|
251
|
+
inspected.machines.push({
|
|
252
|
+
...known,
|
|
253
|
+
state: "outdated"
|
|
254
|
+
});
|
|
255
|
+
inspected.findings.push(note$1(outdated.title, outdated.detail, outdated.fix));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
inspected.machines.push({
|
|
259
|
+
...known,
|
|
260
|
+
state: outcome.state
|
|
261
|
+
});
|
|
262
|
+
inspected.findings.push(outcome.state === "key-changed" ? note$1(`Remote machine ${machine} answered with a different server key`, "Stim refuses to connect to a stim-server that does not prove it holds the key pinned at approval.", `If that machine or its Stim home was replaced, remove ${machine} from remote.machines, run \`stim doctor --fix\`, then add it back and run \`stim doctor --fix\` again.`) : outcome.state === "lapsed" ? note$1(`Hosting request to ${machine} lapsed`, `${machine} did not approve this machine before its setup ticket expired at ${outcome.credential.expiresAt}.`, "Run `stim doctor --fix` to make a new ticket.") : outcome.state === "revoked" ? note$1(`Remote machine ${machine} no longer accepts this machine for device hosting`, `${outcome.reason} The pinned key is preserved.`, "Run `stim doctor --fix` to ask again.") : note$1(`Remote machine ${machine} did not confirm hosting access`, outcome.reason, `Check stim-server and its tailnet serve route on ${machine}, then run doctor again.`));
|
|
263
|
+
}
|
|
264
|
+
return inspected;
|
|
265
|
+
} finally {
|
|
266
|
+
releaseClaim(claim.acquired);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/offload/build-machines.ts
|
|
271
|
+
/** The `remote.machines` entries that approved this machine for builds, in that order. */
|
|
272
|
+
function pairedMachines(entries = configuredMachines() ?? []) {
|
|
273
|
+
const credentials = readBuildMachines().filter((each) => each.state === "approved");
|
|
274
|
+
return entries.flatMap((entry) => credentials.filter((each) => each.machine === entry));
|
|
275
|
+
}
|
|
276
|
+
async function updateCredentials(change, operation) {
|
|
277
|
+
reportSignal(operation)?.throwIfAborted();
|
|
278
|
+
const dir = getConfigDir();
|
|
279
|
+
const write = () => {
|
|
280
|
+
reportSignal(operation)?.throwIfAborted();
|
|
281
|
+
const file = buildMachinesFile();
|
|
282
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
283
|
+
writeFileSync(tmp, `${JSON.stringify({
|
|
284
|
+
version: MACHINE_CREDENTIALS_VERSION,
|
|
285
|
+
machines: change(readBuildMachines())
|
|
286
|
+
}, null, 2)}\n`, { mode: 384 });
|
|
287
|
+
try {
|
|
288
|
+
renameSync(tmp, file);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
rmSync(tmp, { force: true });
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const options = { ensureParent: () => mkdirSync(dir, { recursive: true }) };
|
|
295
|
+
const signal = reportSignal(operation);
|
|
296
|
+
if (signal) await withDirLockAsync(buildMachinesLock(), write, {
|
|
297
|
+
...options,
|
|
298
|
+
signal
|
|
299
|
+
});
|
|
300
|
+
else withDirLock$1(buildMachinesLock(), write, options);
|
|
301
|
+
}
|
|
302
|
+
function note(title, detail, fix = null) {
|
|
303
|
+
return {
|
|
304
|
+
code: "build-machine",
|
|
305
|
+
level: "note",
|
|
306
|
+
title,
|
|
307
|
+
detail,
|
|
308
|
+
fix
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const PROBLEM_TITLES = {
|
|
312
|
+
unreachable: "does not answer a build offer",
|
|
313
|
+
checkout: "cannot build an app outside a git checkout",
|
|
314
|
+
"stim-build": "runs another Stim build",
|
|
315
|
+
arch: "has another CPU architecture",
|
|
316
|
+
xcode: "has another Xcode",
|
|
317
|
+
"macos-sdk": "has another macOS SDK",
|
|
318
|
+
"simulator-sdk": "has another simulator SDK",
|
|
319
|
+
cocoapods: "has another CocoaPods",
|
|
320
|
+
bundler: "has no Bundler",
|
|
321
|
+
runtime: "has no simulator for this project's iOS runtime",
|
|
322
|
+
jdk: "runs another JDK major version",
|
|
323
|
+
"android-sdk": "has no Android SDK",
|
|
324
|
+
ndk: "lacks this project's NDK",
|
|
325
|
+
"build-tools": "lacks this project's Android build-tools",
|
|
326
|
+
"compile-sdk": "lacks this project's Android compile platform",
|
|
327
|
+
disk: "is low on disk",
|
|
328
|
+
busy: "is too busy to take builds",
|
|
329
|
+
platform: "cannot build this platform"
|
|
330
|
+
};
|
|
331
|
+
function problemFix(code, entry) {
|
|
332
|
+
switch (code) {
|
|
333
|
+
case "unreachable": return `Check that stim-server runs on ${entry} and that its tailscale serve route answers, then run \`stim doctor\` again. To keep it running there, run \`stim-server service install --serve\` on ${entry}.`;
|
|
334
|
+
case "checkout": return "Offload syncs the files git lists; run Stim from a git checkout of the app.";
|
|
335
|
+
case "stim-build": return `Update stim-server on ${entry} to the same Stim build as this machine: there, run \`stim-server service update --release <version>\` for this machine's release, or \`--from <dir>\` with the packed packages of this machine's checkout.`;
|
|
336
|
+
case "arch": return "Use a remote machine with the same CPU architecture as this machine.";
|
|
337
|
+
case "xcode":
|
|
338
|
+
case "simulator-sdk":
|
|
339
|
+
case "macos-sdk": return `Install and select the same Xcode on ${entry} and this Mac (\`xcode-select -p\` on each).`;
|
|
340
|
+
case "cocoapods": return `Install the same CocoaPods version on ${entry}, on the PATH its stim-server's login shell sets.`;
|
|
341
|
+
case "bundler": return `Install Bundler (\`gem install bundler\`) on ${entry}, on the PATH its stim-server's login shell sets.`;
|
|
342
|
+
case "runtime": return `Install that iOS simulator runtime on ${entry} with at least one iPhone simulator, or set ios.runtime here to one ${entry} has.`;
|
|
343
|
+
case "jdk": return `Start stim-server on ${entry} with JAVA_HOME at a JDK of the same major version as this machine's.`;
|
|
344
|
+
case "android-sdk":
|
|
345
|
+
case "ndk":
|
|
346
|
+
case "build-tools":
|
|
347
|
+
case "compile-sdk": return `Install the missing package with sdkmanager in the Android SDK stim-server on ${entry} uses (its ANDROID_HOME, else ~/Library/Android/sdk on macOS or ~/Android/Sdk on Linux).`;
|
|
348
|
+
case "disk": return `Free space on ${entry}'s worker root (server.workerRoot there).`;
|
|
349
|
+
case "platform": return `iOS and macOS builds need a Mac; ${entry} can take Android builds only.`;
|
|
350
|
+
case "busy": return `Builds stay on this machine until ${entry} has capacity. On ${entry}, server.maxLoadPerCore sets the load it accepts and concurrency.maxBuilds how many of its own builds it runs; it takes one offloaded build at a time.`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const TRANSIENT = /* @__PURE__ */ new Set(["unreachable", "busy"]);
|
|
354
|
+
function offloadFindings(entry, problems) {
|
|
355
|
+
return problems.map((problem) => ({
|
|
356
|
+
code: `build-machine-${problem.code}`,
|
|
357
|
+
level: TRANSIENT.has(problem.code) ? "note" : "cost",
|
|
358
|
+
title: `Remote machine ${entry} ${PROBLEM_TITLES[problem.code]}`,
|
|
359
|
+
detail: `Builds fall back to this machine: ${problem.reason}.`,
|
|
360
|
+
fix: problemFix(problem.code, entry)
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
async function inspectMachine(entry, status, deviceName, fix, version, tickets, io, check, operation) {
|
|
364
|
+
const parsed = parseMachine$1(entry);
|
|
365
|
+
if (!parsed) return {
|
|
366
|
+
report: {
|
|
367
|
+
machine: entry,
|
|
368
|
+
state: "invalid"
|
|
369
|
+
},
|
|
370
|
+
finding: note(`Remote machine ${entry} is not a tailnet name`, "Expected `name` or `name:port`.")
|
|
371
|
+
};
|
|
372
|
+
const peer = findPeer$1(status, parsed.name);
|
|
373
|
+
if (peer === "missing" || peer === "ambiguous") {
|
|
374
|
+
const detail = peer === "missing" ? "No peer on this tailnet has that name." : "Several peers match that name.";
|
|
375
|
+
return {
|
|
376
|
+
report: {
|
|
377
|
+
machine: entry,
|
|
378
|
+
state: "not-on-tailnet"
|
|
379
|
+
},
|
|
380
|
+
finding: note(`Remote machine ${entry} is not on this tailnet`, detail)
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const credential = readBuildMachines().find((each) => each.machine === entry);
|
|
384
|
+
const other = () => {
|
|
385
|
+
try {
|
|
386
|
+
return readDeviceHostMachines().find((each) => each.machine === entry);
|
|
387
|
+
} catch {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
const persist = (next) => updateCredentials((credentials) => [...credentials.filter((each) => each.machine !== entry), next], operation);
|
|
392
|
+
reportSignal(operation)?.throwIfAborted();
|
|
393
|
+
const outcome = await reachMachine({
|
|
394
|
+
machine: entry,
|
|
395
|
+
access: "build",
|
|
396
|
+
endpoint: endpoint(peer, parsed.port),
|
|
397
|
+
address: peer.dnsName,
|
|
398
|
+
credential,
|
|
399
|
+
other,
|
|
400
|
+
persist,
|
|
401
|
+
fix,
|
|
402
|
+
deviceName,
|
|
403
|
+
setupFlags: cannotHost(status, entry) ? ["build"] : ["build", "device-host"],
|
|
404
|
+
serverVersion: version,
|
|
405
|
+
io,
|
|
406
|
+
tickets
|
|
407
|
+
});
|
|
408
|
+
const known = {
|
|
409
|
+
machine: entry,
|
|
410
|
+
dnsName: peer.dnsName
|
|
411
|
+
};
|
|
412
|
+
reportSignal(operation)?.throwIfAborted();
|
|
413
|
+
if (outcome.state !== "not-asked" && outcome.credential) {
|
|
414
|
+
const next = outcome.credential;
|
|
415
|
+
if (JSON.stringify(next) !== JSON.stringify(credential)) await updateCredentials((credentials) => [...credentials.filter((each) => each.machine !== entry), next], operation);
|
|
416
|
+
}
|
|
417
|
+
const paired = {
|
|
418
|
+
...known,
|
|
419
|
+
...outcome.state !== "not-asked" && outcome.credential?.deviceId ? { deviceId: outcome.credential.deviceId } : {},
|
|
420
|
+
...outcome.state !== "not-asked" && outcome.credential ? { requestedAt: outcome.credential.requestedAt } : {}
|
|
421
|
+
};
|
|
422
|
+
switch (outcome.state) {
|
|
423
|
+
case "not-asked": return {
|
|
424
|
+
report: {
|
|
425
|
+
...known,
|
|
426
|
+
state: "not-asked"
|
|
427
|
+
},
|
|
428
|
+
finding: note(`Remote machine ${entry} has not approved this machine for builds`, "This machine has not asked it for build access.", "Run `stim doctor --fix` to make a setup ticket and print the command to run there.")
|
|
429
|
+
};
|
|
430
|
+
case "key-changed": return {
|
|
431
|
+
report: {
|
|
432
|
+
...paired,
|
|
433
|
+
state: "key-changed"
|
|
434
|
+
},
|
|
435
|
+
finding: note(`Remote machine ${entry} answered with a different server key`, `${peer.dnsName} did not prove it holds the stim-server key this machine pinned at approval. Stim does not connect to it.`, `If that machine or its Stim home was replaced, remove ${entry} from remote.machines, run \`stim doctor --fix\` to forget the old pairing, then add it back and run \`stim doctor --fix\` again.`)
|
|
436
|
+
};
|
|
437
|
+
case "pending": return {
|
|
438
|
+
report: {
|
|
439
|
+
...paired,
|
|
440
|
+
state: "pending",
|
|
441
|
+
...outcome.credential.expiresAt ? { expiresAt: outcome.credential.expiresAt } : {}
|
|
442
|
+
},
|
|
443
|
+
finding: note(`Remote machine ${entry} has not approved this machine for builds yet`, `It does not hold this machine's setup ticket yet${outcome.credential.expiresAt ? `; the ticket expires at ${outcome.credential.expiresAt}` : ""}.`, setupRemedy(entry, outcome.command))
|
|
444
|
+
};
|
|
445
|
+
case "lapsed": return {
|
|
446
|
+
report: {
|
|
447
|
+
...paired,
|
|
448
|
+
state: "lapsed"
|
|
449
|
+
},
|
|
450
|
+
finding: note(`Build request to ${entry} lapsed`, `${entry} did not approve this machine before its setup ticket expired at ${outcome.credential.expiresAt}.`, "Run `stim doctor --fix` to make a new ticket.")
|
|
451
|
+
};
|
|
452
|
+
case "revoked": return {
|
|
453
|
+
report: {
|
|
454
|
+
...paired,
|
|
455
|
+
state: "revoked"
|
|
456
|
+
},
|
|
457
|
+
finding: note(`Remote machine ${entry} no longer accepts this machine for builds`, `${outcome.reason} It was revoked.`, "Run `stim doctor --fix` to ask again.")
|
|
458
|
+
};
|
|
459
|
+
case "outdated": {
|
|
460
|
+
const outdated = outdatedServer(entry, version, outcome.command);
|
|
461
|
+
return {
|
|
462
|
+
report: {
|
|
463
|
+
...paired,
|
|
464
|
+
state: "outdated"
|
|
465
|
+
},
|
|
466
|
+
finding: note(outdated.title, outdated.detail, outdated.fix)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
case "unreachable": return {
|
|
470
|
+
report: {
|
|
471
|
+
...paired,
|
|
472
|
+
state: "unreachable"
|
|
473
|
+
},
|
|
474
|
+
finding: note(`Could not reach remote machine ${entry}`, outcome.reason, `Check that stim-server runs on ${entry}. To keep it running there, run \`stim-server service install --serve\` on ${entry}.`)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const permissions = readHostPermissions(outcome.result.host);
|
|
478
|
+
const host = permissions ? { host: permissions } : {};
|
|
479
|
+
if (!check) return {
|
|
480
|
+
report: {
|
|
481
|
+
...paired,
|
|
482
|
+
state: "approved",
|
|
483
|
+
...host
|
|
484
|
+
},
|
|
485
|
+
finding: null
|
|
486
|
+
};
|
|
487
|
+
const { capacity, problems } = await check(outcome.credential);
|
|
488
|
+
return {
|
|
489
|
+
report: {
|
|
490
|
+
...paired,
|
|
491
|
+
state: "approved",
|
|
492
|
+
...host,
|
|
493
|
+
offloadable: problems.length === 0,
|
|
494
|
+
reasons: problems.map((problem) => problem.reason),
|
|
495
|
+
problems,
|
|
496
|
+
...capacity ? { capacity } : {}
|
|
497
|
+
},
|
|
498
|
+
finding: null,
|
|
499
|
+
extra: offloadFindings(entry, problems)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Doctor findings and a report for each `remote.machines` entry. The worker's stim-server key is pinned on first
|
|
504
|
+
* contact; every later connection must prove it holds that key. With `fix`, makes a setup ticket for a named machine
|
|
505
|
+
* this machine holds no pairing for, or that revoked it, and forgets pairings of machines no longer named. With
|
|
506
|
+
* `check`, each approved machine is asked for one build offer and reports every reason it would not take this
|
|
507
|
+
* project's build.
|
|
508
|
+
*/
|
|
509
|
+
async function inspectBuildMachines({ fix, check = null, version = "<version>", tickets = /* @__PURE__ */ new Map(), operation }, io = realIo, entries = configuredMachines()) {
|
|
510
|
+
if (entries === null) return {
|
|
511
|
+
findings: [note("Invalid remote.machines setting", "Use a remote object with machines as an array of tailnet names. Existing credentials and pinned keys are preserved.", "Run `stim guide settings` and correct the setting before running doctor again.")],
|
|
512
|
+
machines: []
|
|
513
|
+
};
|
|
514
|
+
const unnamed = (each) => !entries.includes(each.machine);
|
|
515
|
+
if (fix && readBuildMachines().some(unnamed)) await updateCredentials((credentials) => credentials.filter((each) => !unnamed(each)), operation);
|
|
516
|
+
if (entries.length === 0) return {
|
|
517
|
+
findings: [],
|
|
518
|
+
machines: []
|
|
519
|
+
};
|
|
520
|
+
const status = await io.status();
|
|
521
|
+
if (!isJsonObject(status)) return {
|
|
522
|
+
findings: [note("Remote machines are unreachable", "remote.machines names remote machines, but Tailscale is not running.")],
|
|
523
|
+
machines: entries.map((machine) => ({
|
|
524
|
+
machine,
|
|
525
|
+
state: "tailscale-off"
|
|
526
|
+
}))
|
|
527
|
+
};
|
|
528
|
+
const self = isJsonObject(status.Self) ? status.Self : {};
|
|
529
|
+
const deviceName = (typeof self.HostName === "string" && self.HostName ? self.HostName : hostname()).replace(/[\p{Cc}\p{Cf}]/gu, "").trim().slice(0, 64) || "Mac";
|
|
530
|
+
const inspection = {
|
|
531
|
+
findings: [],
|
|
532
|
+
machines: []
|
|
533
|
+
};
|
|
534
|
+
const inspected = await Promise.all(entries.map((entry) => inspectMachine(entry, status, deviceName, fix, version, tickets, io, check, operation)));
|
|
535
|
+
for (const { report, finding, extra = [] } of inspected) {
|
|
536
|
+
inspection.machines.push(report);
|
|
537
|
+
if (finding) inspection.findings.push(finding);
|
|
538
|
+
inspection.findings.push(...extra);
|
|
539
|
+
}
|
|
540
|
+
return inspection;
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
543
|
+
//#region src/offload/selection.ts
|
|
544
|
+
var OffloadRefusal = class extends Error {
|
|
545
|
+
code = "STIM_OFFLOAD_REFUSED";
|
|
546
|
+
remedy = "Run stim doctor --fix to ask for build access if not paired; it prints the stim-server setup command a person runs on the worker to approve this machine. Check stim settings get remote.machines, or rerun with --remote-build auto or --remote-build local.";
|
|
547
|
+
constructor(machine, reason) {
|
|
548
|
+
super(reason.startsWith(`${machine}: `) ? reason : `${machine}: ${reason}`);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
function resolveBuildMachine(flag, env, setting) {
|
|
552
|
+
const selected = flag ?? (env?.trim() || void 0) ?? setting ?? "auto";
|
|
553
|
+
if (typeof selected !== "string" || !selected.trim() || !parseMachine(selected)) throw Object.assign(/* @__PURE__ */ new Error("--remote-build / remote.build must be auto, local, or a tailnet machine name."), { code: "STIM_BAD_ARG" });
|
|
554
|
+
const trimmed = selected.trim();
|
|
555
|
+
const reserved = trimmed.toLowerCase();
|
|
556
|
+
return reserved === "auto" || reserved === "local" ? reserved : trimmed;
|
|
557
|
+
}
|
|
558
|
+
function parseBuildMachineOption(value) {
|
|
559
|
+
try {
|
|
560
|
+
return resolveBuildMachine(value);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
throw new InvalidArgumentError(error.message);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function requireLocalBuild(selected) {
|
|
566
|
+
if (selected === "auto" && !automaticMachineEnabled("build", "local")) throw new OffloadRefusal("auto", "local is disabled in remote.buildPoolDisabled; enable it or select a machine explicitly");
|
|
567
|
+
}
|
|
568
|
+
function namedBuildMachine(selected) {
|
|
569
|
+
return selected !== "auto" && selected !== "local";
|
|
570
|
+
}
|
|
571
|
+
function requireConfiguredMachine(selected, machines) {
|
|
572
|
+
if (!namedBuildMachine(selected)) return selected;
|
|
573
|
+
const parsed = parseMachine(selected);
|
|
574
|
+
const entries = Array.isArray(machines) ? machines.filter((entry) => typeof entry === "string") : [];
|
|
575
|
+
const match = entries.find((entry) => {
|
|
576
|
+
const candidate = parseMachine(entry);
|
|
577
|
+
return candidate?.name === parsed.name && candidate.port === parsed.port;
|
|
578
|
+
});
|
|
579
|
+
if (!match) throw new OffloadRefusal(selected, `not listed in remote.machines (configured: ${entries.join(", ") || "none"})`);
|
|
580
|
+
return match;
|
|
581
|
+
}
|
|
582
|
+
const settingSource = (flag, env, setting) => flag !== void 0 ? "flag" : env?.trim() ? "env" : setting !== void 0 ? "setting" : "default";
|
|
583
|
+
/** The settings that decide where a build runs, with the layer each value came from, for this run's `--remote-build`. */
|
|
584
|
+
function buildPlacementSettings(selected) {
|
|
585
|
+
const flag = operationState(ambientContext()).buildMachineFlag;
|
|
586
|
+
const remote = loadConfig$1()?.remote;
|
|
587
|
+
const rawMode = ambientEnv().STIM_REMOTE_BUILD_MODE || remote?.buildMode;
|
|
588
|
+
return [{
|
|
589
|
+
key: "remote.build",
|
|
590
|
+
value: selected,
|
|
591
|
+
from: settingSource(flag, ambientEnv().STIM_REMOTE_BUILD, remote?.build)
|
|
592
|
+
}, {
|
|
593
|
+
key: "remote.buildMode",
|
|
594
|
+
value: selected === "local" ? "off" : OFFLOAD_MODES.includes(rawMode) ? rawMode : "auto",
|
|
595
|
+
from: settingSource(void 0, ambientEnv().STIM_REMOTE_BUILD_MODE, remote?.buildMode)
|
|
596
|
+
}];
|
|
597
|
+
}
|
|
598
|
+
function resolveBuildPlacement(flag) {
|
|
599
|
+
const remote = loadConfig$1()?.remote;
|
|
600
|
+
operationState(ambientContext()).buildMachineFlag = flag;
|
|
601
|
+
let selected = "auto";
|
|
602
|
+
try {
|
|
603
|
+
selected = resolveBuildMachine(flag, ambientEnv().STIM_REMOTE_BUILD, remote?.build);
|
|
604
|
+
selected = requireConfiguredMachine(selected, remote?.machines);
|
|
605
|
+
if (namedBuildMachine(selected) && pairedMachines([selected]).length === 0) throw new OffloadRefusal(selected, "no remote machine is paired");
|
|
606
|
+
return { selected };
|
|
607
|
+
} catch (error) {
|
|
608
|
+
return {
|
|
609
|
+
selected,
|
|
610
|
+
failure: {
|
|
611
|
+
code: error.code,
|
|
612
|
+
message: error.message,
|
|
613
|
+
remedy: error instanceof OffloadRefusal ? error.remedy : "Pass --remote-build auto, local, or an entry from stim settings get remote.machines."
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
export { requireLocalBuild as a, inspectBuildMachines as c, hostingMachines as d, inspectDeviceHostMachines as f, parseBuildMachineOption as i, pairedMachines as l, buildPlacementSettings as n, resolveBuildMachine as o, namedBuildMachine as r, resolveBuildPlacement as s, OffloadRefusal as t, configuredMachines as u };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { c as registerMetroStore, f as resolveMetroSharedCache, n as supervisorError, r as appendCacheStore, s as metroStoreRoot } from "./errors-DzkMYh8Q.mjs";
|
|
1
|
+
import { t as getExecutor, v as __commonJSMin, y as __require } from "./exec-_UbtCQcG.mjs";
|
|
2
|
+
import { q as resolveProjectSettings } from "./settings-CsJDEXgK.mjs";
|
|
3
|
+
import { c as registerMetroStore, f as resolveMetroSharedCache, n as supervisorError, r as appendCacheStore, s as metroStoreRoot } from "./errors-ccaPc4ox.mjs";
|
|
5
4
|
import { createRequire } from "node:module";
|
|
6
5
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
7
6
|
//#endregion
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
2
|
-
import { n as METRO_COMMAND_PORT } from "./settings-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { f as recordFromLine,
|
|
1
|
+
import { t as getExecutor } from "./exec-_UbtCQcG.mjs";
|
|
2
|
+
import { n as METRO_COMMAND_PORT } from "./settings-CsJDEXgK.mjs";
|
|
3
|
+
import { T as MODE_COMMAND } from "./project-BZNjmW_8.mjs";
|
|
4
|
+
import { r as createNdjsonWriter } from "./ndjson-CgE_o3Dl.mjs";
|
|
5
|
+
import { p as signalProcessTree } from "./metro-DEXpIhHC.mjs";
|
|
6
|
+
import { f as recordFromLine, h as superviseChildServer, m as withProjectNode } from "./server-expo-DT_F_uVI.mjs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { LOG_ROTATE_BYTES } from "@stim-cli/core";
|
|
9
9
|
//#region src/supervisor/server-command.ts
|
|
@@ -29,6 +29,13 @@ async function startCommandServer({ root, port, logsDir, command, writer = null,
|
|
|
29
29
|
msg: "metro.command starts the dev server, so Stim does not add its shared Metro transform store"
|
|
30
30
|
});
|
|
31
31
|
const group = platform !== "win32";
|
|
32
|
+
const node = withProjectNode(root, process.env);
|
|
33
|
+
if (node.pinned) log.write({
|
|
34
|
+
src: "metro",
|
|
35
|
+
level: "info",
|
|
36
|
+
event: "project_node",
|
|
37
|
+
msg: `Metro runs on Node ${node.pinned.version} from ${node.pinned.bin}, which ${node.pinned.pin} pins`
|
|
38
|
+
});
|
|
32
39
|
const child = spawn(program, args, {
|
|
33
40
|
cwd: root,
|
|
34
41
|
stdio: [
|
|
@@ -38,7 +45,7 @@ async function startCommandServer({ root, port, logsDir, command, writer = null,
|
|
|
38
45
|
],
|
|
39
46
|
detached: group,
|
|
40
47
|
env: {
|
|
41
|
-
...
|
|
48
|
+
...node.env,
|
|
42
49
|
FORCE_COLOR: "0"
|
|
43
50
|
}
|
|
44
51
|
});
|