stim 1.16.0 → 1.17.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/dist/{activity-BxwdD81S.mjs → activity-IRvJY_ru.mjs} +23 -3
- package/dist/{agent-device-usage-output-Dpih1Tfa.mjs → agent-device-usage-output-BK41vnzN.mjs} +3 -3
- package/dist/{android-Ift4kVvt.mjs → android--GorydsK.mjs} +679 -317
- package/dist/{android-B_NumVyQ.mjs → android-B_YNZy61.mjs} +7 -43
- package/dist/{android-r2rBL8kV.mjs → android-DE4v59Mb.mjs} +12 -3
- package/dist/{android-cas-8U4DPqeR.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
- package/dist/{app-install-CKWdbXIX.mjs → app-install-VPkI5WEg.mjs} +6 -5
- package/dist/{budget-DjrURfQn.mjs → budget-DM984mnd.mjs} +34 -18
- package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-CmQCoyT-.mjs} +345 -86
- package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
- package/dist/{cli-BkYZ_6hy.mjs → cli-Daxug5-K.mjs} +17 -17
- package/dist/cli.mjs +1 -1
- package/dist/collector-run.mjs +3 -3
- package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
- package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
- package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
- package/dist/{deps-urbmfXig.mjs → deps-DNWaQs4V.mjs} +1 -1
- package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
- package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
- package/dist/device-capacity-D0GWHnTM.mjs +580 -0
- package/dist/device-host-worker.mjs +436 -154
- package/dist/{device-ios-BadmeL2V.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
- package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
- package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
- package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
- package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
- package/dist/{doctor-Cimf-gGB.mjs → doctor-Cnk9d7Hm.mjs} +32 -51
- package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
- package/dist/{gc-Bqy4bjEu.mjs → gc-DxCL2A46.mjs} +308 -26
- package/dist/{gradle-BsO1MdsH.mjs → gradle-D7-L2Th3.mjs} +3 -3
- package/dist/{guide-B_uyKTxW.mjs → guide-DjrTQGik.mjs} +1662 -565
- package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
- package/dist/hosted-android-DwLS4Mse.mjs +32 -0
- package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-wk1W_CNW.mjs} +2 -3
- package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
- package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-DqYmWtXc.mjs} +16 -16
- package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-dlHXEeF5.mjs} +4 -4
- package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-C9xJ9rpA.mjs} +77 -42
- package/dist/{idle-DYVymG92.mjs → idle-B2m-Ovod.mjs} +122 -58
- package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
- package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
- package/dist/{ios-aq3QoGI3.mjs → ios-DE6duQoz.mjs} +170 -88
- package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
- package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
- package/dist/ios-state-8L3lgum6.mjs +89 -0
- package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
- package/dist/{logs-CHEVK8zD.mjs → logs-DUoKlDoS.mjs} +36 -23
- package/dist/{client-BTY686R6.mjs → machines-C2XiNwh9.mjs} +278 -20
- package/dist/{macos-CSYEAFmF.mjs → macos-32po3kUq.mjs} +14 -14
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +2 -2
- package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
- package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-DmslUM30.mjs} +22 -3
- package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
- package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
- package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
- package/dist/offload-worker.mjs +8 -8
- package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
- package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
- package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
- package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
- package/dist/{preview-DhVbvPAZ.mjs → preview-CxPRIEht.mjs} +7 -7
- package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
- package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
- package/dist/{reload-rM9jV1fz.mjs → reload-qZei6iWn.mjs} +62 -35
- package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
- package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
- package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
- package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
- package/dist/server-expo-CeakhI8P.mjs +2 -0
- package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
- package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
- package/dist/settings.schema.json +42 -13
- package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
- package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
- package/dist/{start-CX8Ig-U2.mjs → start-BopZcr4n.mjs} +1 -1
- package/dist/{start-ClDfwXa4.mjs → start-uee1wWjY.mjs} +11 -11
- package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
- package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
- package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
- package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
- package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
- package/dist/{status-Cs7VLg9D.mjs → status-dXX2JX1C.mjs} +125 -35
- package/dist/{stop-Dt_dM16A.mjs → stop-BCwvvs7F.mjs} +2 -2
- package/dist/{stop-Bffozsac.mjs → stop-BjizrQwv.mjs} +48 -27
- package/dist/{stop-B0Yxtmiy.mjs → stop-Cr1d8DSE.mjs} +4 -4
- package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
- package/dist/supervisor-run.d.mts +7 -4
- package/dist/supervisor-run.mjs +26 -28
- package/dist/{support-D4aYJniO.mjs → support-BJPgYCu-.mjs} +44 -19
- package/dist/{support-CAP8qDUF.mjs → support-F9-sr0Oi.mjs} +6 -6
- package/dist/{toolchain-DkUflduf.mjs → toolchain-CNIBTIOv.mjs} +7 -7
- package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
- package/dist/watchman-APYjXSQa.mjs +48 -0
- package/dist/{web-DWUaMp8P.mjs → web-exQtGkoc.mjs} +12 -13
- package/dist/web-run.mjs +4 -4
- package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
- package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
- package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
- package/dist/{worktree-jnG8Iph5.mjs → worktree-Dc98cE6_.mjs} +1 -1
- package/dist/{worktree-K-xj9Db0.mjs → worktree-ZpZR3S1k.mjs} +141 -40
- package/package.json +5 -5
- package/dist/device-capacity-BHwxNBlu.mjs +0 -136
- package/dist/ios-state-DS5UpYTH.mjs +0 -48
- package/dist/machines-BIBUdhpj.mjs +0 -265
- package/dist/ownership-hCkPzGmr.mjs +0 -571
- package/dist/server-expo-CVqO2c35.mjs +0 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-DqcCbpE7.mjs";
|
|
2
|
+
import { HOSTED_METRO_REQUESTS_KEY, hostedMetroRequests, hostedNativeRecords, isJsonObject, parseHostedNativePlacement, unreadableHostedNative } from "@stim-cli/core/state";
|
|
3
|
+
//#region src/device-host/ios-state.ts
|
|
4
|
+
function readHostedNative(root, platform, slot) {
|
|
5
|
+
const state = readWorkspaceState$1(root);
|
|
6
|
+
if (slot !== void 0) {
|
|
7
|
+
const record = hostedNativeRecords(state, platform)[slot];
|
|
8
|
+
if (record !== void 0 && !parseHostedNativePlacement(record, platform)) throw Object.assign(new Error(unreadableHostedNative(slot, platform)), {
|
|
9
|
+
code: "STIM_HOSTING_REFUSED",
|
|
10
|
+
remedy: `Restore that slot's recorded machine and session from the host, then run stim stop${slot === "default" ? "" : ` --slot ${slot}`} to reconcile it.`
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const placements = Object.fromEntries(Object.entries(hostedNativeRecords(state, platform)).flatMap(([name, record]) => {
|
|
14
|
+
const placement = parseHostedNativePlacement(record, platform);
|
|
15
|
+
return placement ? [[name, placement]] : [];
|
|
16
|
+
}));
|
|
17
|
+
return slot === void 0 ? placements : placements[slot] ? { [slot]: placements[slot] } : {};
|
|
18
|
+
}
|
|
19
|
+
function writeDevicePlacement(root, slot, platform, placement) {
|
|
20
|
+
if (!placement) return;
|
|
21
|
+
updateWorkspaceState(root, (state) => {
|
|
22
|
+
const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
|
|
23
|
+
const saved = isJsonObject(slots[slot]) ? slots[slot] : {};
|
|
24
|
+
const current = slot === "default" ? state[platform] : saved[platform];
|
|
25
|
+
const record = {
|
|
26
|
+
...isJsonObject(current) ? current : {},
|
|
27
|
+
devicePlacement: placement
|
|
28
|
+
};
|
|
29
|
+
if (slot === "default") return {
|
|
30
|
+
...state,
|
|
31
|
+
[platform]: record
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
...state,
|
|
35
|
+
deviceSlots: {
|
|
36
|
+
...slots,
|
|
37
|
+
[slot]: {
|
|
38
|
+
...saved,
|
|
39
|
+
[platform]: record
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function writeHostedNative(root, slot, host, platform) {
|
|
46
|
+
updateWorkspaceState(root, (state) => {
|
|
47
|
+
const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
|
|
48
|
+
const current = slot === "default" ? state[platform] : isJsonObject(slots[slot]) ? slots[slot][platform] : void 0;
|
|
49
|
+
const record = isJsonObject(current) ? { ...current } : {};
|
|
50
|
+
const previous = parseHostedNativePlacement(record.host, platform);
|
|
51
|
+
if (previous && previous.session !== host?.session) {
|
|
52
|
+
const requests = { ...hostedMetroRequests(state) };
|
|
53
|
+
delete requests[previous.session];
|
|
54
|
+
state = {
|
|
55
|
+
...state,
|
|
56
|
+
[HOSTED_METRO_REQUESTS_KEY]: requests
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (host) record.host = host;
|
|
60
|
+
else delete record.host;
|
|
61
|
+
return slot === "default" ? {
|
|
62
|
+
...state,
|
|
63
|
+
[platform]: record
|
|
64
|
+
} : {
|
|
65
|
+
...state,
|
|
66
|
+
deviceSlots: {
|
|
67
|
+
...slots,
|
|
68
|
+
[slot]: {
|
|
69
|
+
...isJsonObject(slots[slot]) ? slots[slot] : {},
|
|
70
|
+
[platform]: record
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function readHostedIos(root, slot) {
|
|
77
|
+
return readHostedNative(root, "ios", slot);
|
|
78
|
+
}
|
|
79
|
+
function writeHostedIos(root, slot, host) {
|
|
80
|
+
writeHostedNative(root, slot, host, "ios");
|
|
81
|
+
}
|
|
82
|
+
function readHostedAndroid(root, slot) {
|
|
83
|
+
return readHostedNative(root, "android", slot);
|
|
84
|
+
}
|
|
85
|
+
function writeHostedAndroid(root, slot, host) {
|
|
86
|
+
writeHostedNative(root, slot, host, "android");
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { writeHostedAndroid as a, writeDevicePlacement as i, readHostedIos as n, writeHostedIos as o, readHostedNative as r, writeHostedNative as s, readHostedAndroid as t };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
-
import { s as isAppLaunchError } from "./command-output-
|
|
2
|
+
import { s as isAppLaunchError } from "./command-output-CDAIFD-8.mjs";
|
|
3
3
|
import { o as readNdjsonGenerations } from "./ndjson-PclOIwVY.mjs";
|
|
4
4
|
import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-Ee7sDXfp.mjs";
|
|
5
|
-
import { f as deviceShellArg, n as ANDROID_DISABLE_AUTO_LAUNCH_EXTRA, s as androidAppProcess } from "./app-install-
|
|
5
|
+
import { f as deviceShellArg, n as ANDROID_DISABLE_AUTO_LAUNCH_EXTRA, s as androidAppProcess } from "./app-install-VPkI5WEg.mjs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
//#region src/engine/app-readiness.ts
|
|
8
8
|
const APP_READINESS_TIMEOUT_MS = 3e4;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { H as projectDeviceSlots, V as parseDeviceSlotOption, _ as loadConfig$1, g as isPathPrefix } from "./config-D0DbpZtM.mjs";
|
|
2
2
|
import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
3
|
-
import { f as refuseNoProject, l as phaseLine, v as launchErrorPreview } from "./command-output-
|
|
4
|
-
import {
|
|
5
|
-
import { r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-
|
|
6
|
-
import { n as agentDeviceLiveness, u as readAgentDeviceRecords } from "./activity-
|
|
3
|
+
import { f as refuseNoProject, l as phaseLine, v as launchErrorPreview } from "./command-output-CDAIFD-8.mjs";
|
|
4
|
+
import { o as findCommandWorkspace } from "./project-D47Rh0ly.mjs";
|
|
5
|
+
import { r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-DqcCbpE7.mjs";
|
|
6
|
+
import { n as agentDeviceLiveness, u as readAgentDeviceRecords } from "./activity-IRvJY_ru.mjs";
|
|
7
7
|
import { n as SOURCES, t as LEVELS } from "./ndjson-PclOIwVY.mjs";
|
|
8
|
-
import { o as connectHost } from "./hosted-client-
|
|
9
|
-
import { n as
|
|
10
|
-
import "./hosted-macos-
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
8
|
+
import { o as connectHost } from "./hosted-client-wk1W_CNW.mjs";
|
|
9
|
+
import { n as pullHostedNativeLogs, t as pullHostedMacosLogs } from "./hosted-logs-DqYmWtXc.mjs";
|
|
10
|
+
import "./hosted-macos-dlHXEeF5.mjs";
|
|
11
|
+
import { n as readHostedIos, t as readHostedAndroid } from "./ios-state-8L3lgum6.mjs";
|
|
12
|
+
import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-C-NMnhrk.mjs";
|
|
13
13
|
import { t as watchStdoutReader } from "./stdout-reader-C8yjre1x.mjs";
|
|
14
14
|
import { realpathSync } from "node:fs";
|
|
15
15
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
@@ -17,8 +17,15 @@ import { agentDeviceStateDirs, buildCriteria, compileGrep, createAgentActionRead
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
//#region src/devices/agent-actions.ts
|
|
20
|
-
function workspaceAgentTargets(project) {
|
|
20
|
+
function workspaceAgentTargets(project, macos) {
|
|
21
21
|
const targets = [];
|
|
22
|
+
if (macos?.app && macos.bundleId) targets.push({
|
|
23
|
+
platform: "macos",
|
|
24
|
+
id: macos.launchId,
|
|
25
|
+
slot: "default",
|
|
26
|
+
bundleId: macos.bundleId,
|
|
27
|
+
launchedAt: macos.app.startedAtMicros / 1e3
|
|
28
|
+
});
|
|
22
29
|
let slots;
|
|
23
30
|
try {
|
|
24
31
|
slots = projectDeviceSlots(project);
|
|
@@ -49,7 +56,6 @@ function canonicalDir(path) {
|
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
function createAgentActionReader$1({ targets, sinceTs, home = homedir(), now, startOf = process_identity_exports.inspectProcessStart }) {
|
|
52
|
-
const byId = new Map(targets.map((target) => [target.id, target]));
|
|
53
59
|
let claimed;
|
|
54
60
|
const read = createAgentActionReader({
|
|
55
61
|
sessionsDirs: () => agentDeviceStateDirs(home).map((root) => join(root, "sessions")),
|
|
@@ -57,9 +63,10 @@ function createAgentActionReader$1({ targets, sinceTs, home = homedir(), now, st
|
|
|
57
63
|
sinceTs,
|
|
58
64
|
now,
|
|
59
65
|
claimedDevice: (session, sessionsDir) => {
|
|
66
|
+
const byId = claimed ? /* @__PURE__ */ new Map() : new Map((typeof targets === "function" ? targets() : targets).map((target) => [target.id, target]));
|
|
60
67
|
claimed ??= new Map(readAgentDeviceRecords(home).filter((record) => record.kind === "claim" && record.session && record.deviceId).filter((record) => {
|
|
61
68
|
const target = byId.get(record.deviceId);
|
|
62
|
-
return target?.name === void 0 || target.name === record.deviceName;
|
|
69
|
+
return target?.platform !== "macos" && (target?.name === void 0 || target.name === record.deviceName);
|
|
63
70
|
}).filter((record) => agentDeviceLiveness(record, startOf) === "live").map((record) => [`${record.stateDir ? canonicalDir(record.stateDir) : ""}\0${record.session}`, record.deviceId]));
|
|
64
71
|
return claimed.get(`${canonicalDir(dirname(sessionsDir))}\0${session}`) ?? claimed.get(`\0${session}`) ?? null;
|
|
65
72
|
}
|
|
@@ -76,11 +83,11 @@ function warning(machine, error) {
|
|
|
76
83
|
const hint = message.includes("refused device-host.logs.query") ? ` If ${machine} runs an older stim-server, update it.` : "";
|
|
77
84
|
return phaseLine("device", `Could not read the app's logs from ${machine}: ${message}. Showing the logs already copied here.${hint}`);
|
|
78
85
|
}
|
|
79
|
-
async function syncHostedMacosLogs(root, placement, slot, warnings) {
|
|
86
|
+
async function syncHostedMacosLogs(root, placement, slot, warnings, platform = "ios") {
|
|
80
87
|
let host;
|
|
81
88
|
try {
|
|
82
89
|
host = await connectHost(placement.machine);
|
|
83
|
-
if (slot !== void 0) await
|
|
90
|
+
if (slot !== void 0) await pullHostedNativeLogs(root, slot, placement, host, false, platform);
|
|
84
91
|
else await pullHostedMacosLogs(root, placement, host);
|
|
85
92
|
return true;
|
|
86
93
|
} catch (error) {
|
|
@@ -93,7 +100,7 @@ async function syncHostedMacosLogs(root, placement, slot, warnings) {
|
|
|
93
100
|
host?.connection.close();
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
|
-
function followHostedMacosLogs(root, { failing = false, intervalMs = 500, retryMs = 5e3, slot } = {}) {
|
|
103
|
+
function followHostedMacosLogs(root, { failing = false, intervalMs = 500, retryMs = 5e3, slot, platform = "ios" } = {}) {
|
|
97
104
|
let host;
|
|
98
105
|
let retryAt = 0;
|
|
99
106
|
let stopped = false;
|
|
@@ -102,20 +109,20 @@ function followHostedMacosLogs(root, { failing = false, intervalMs = 500, retryM
|
|
|
102
109
|
if (running || stopped || Date.now() < retryAt) return;
|
|
103
110
|
running = true;
|
|
104
111
|
try {
|
|
105
|
-
const placement = slot !== void 0 ? readHostedIos(root)[slot] : readMacosRecord(root)?.host;
|
|
112
|
+
const placement = slot !== void 0 ? (platform === "ios" ? readHostedIos(root) : readHostedAndroid(root))[slot] : readMacosRecord(root)?.host;
|
|
106
113
|
if (!placement) {
|
|
107
114
|
host?.connection.close();
|
|
108
115
|
host = void 0;
|
|
109
116
|
return;
|
|
110
117
|
}
|
|
111
118
|
host ??= await connectHost(placement.machine);
|
|
112
|
-
if (slot !== void 0) await
|
|
119
|
+
if (slot !== void 0) await pullHostedNativeLogs(root, slot, placement, host, false, platform);
|
|
113
120
|
else await pullHostedMacosLogs(root, placement, host);
|
|
114
121
|
failing = false;
|
|
115
122
|
} catch (error) {
|
|
116
123
|
host?.connection.close();
|
|
117
124
|
host = void 0;
|
|
118
|
-
if (!failing) process.stderr.write(`${warning((slot !== void 0 ? readHostedIos(root)[slot] : readMacosRecord(root)?.host)?.machine ?? "the host", error)}\n`);
|
|
125
|
+
if (!failing) process.stderr.write(`${warning((slot !== void 0 ? (platform === "ios" ? readHostedIos(root) : readHostedAndroid(root))[slot] : readMacosRecord(root)?.host)?.machine ?? "the host", error)}\n`);
|
|
119
126
|
failing = true;
|
|
120
127
|
retryAt = Date.now() + retryMs;
|
|
121
128
|
} finally {
|
|
@@ -132,17 +139,22 @@ function followHostedMacosLogs(root, { failing = false, intervalMs = 500, retryM
|
|
|
132
139
|
async function syncHostedLogs(root) {
|
|
133
140
|
const macos = readMacosRecord(root)?.host;
|
|
134
141
|
const warnings = /* @__PURE__ */ new Set();
|
|
135
|
-
return (await Promise.all([
|
|
142
|
+
return (await Promise.all([
|
|
143
|
+
...macos ? [syncHostedMacosLogs(root, macos)] : [],
|
|
144
|
+
...Object.entries(readHostedIos(root)).map(([slot, placement]) => syncHostedMacosLogs(root, placement, slot, warnings)),
|
|
145
|
+
...Object.entries(readHostedAndroid(root)).map(([slot, placement]) => syncHostedMacosLogs(root, placement, slot, warnings, "android"))
|
|
146
|
+
])).every(Boolean);
|
|
136
147
|
}
|
|
137
148
|
function followHostedLogs(root, failing) {
|
|
138
149
|
const stops = /* @__PURE__ */ new Map();
|
|
139
150
|
const refresh = () => {
|
|
140
151
|
if (readMacosRecord(root)?.host && !stops.has("macos")) stops.set("macos", followHostedMacosLogs(root, { failing }));
|
|
141
|
-
for (const slot of Object.keys(readHostedIos(root))) {
|
|
142
|
-
const key =
|
|
152
|
+
for (const platform of ["ios", "android"]) for (const slot of Object.keys(platform === "ios" ? readHostedIos(root) : readHostedAndroid(root))) {
|
|
153
|
+
const key = `${platform}:${slot}`;
|
|
143
154
|
if (!stops.has(key)) stops.set(key, followHostedMacosLogs(root, {
|
|
144
155
|
failing,
|
|
145
|
-
slot
|
|
156
|
+
slot,
|
|
157
|
+
platform
|
|
146
158
|
}));
|
|
147
159
|
}
|
|
148
160
|
};
|
|
@@ -301,8 +313,9 @@ function logsCommand(program) {
|
|
|
301
313
|
const offsets = opts.follow ? fileSizes(dir) : null;
|
|
302
314
|
const workspaceTimeline = opts.follow ? readLogRecords(dir) : captured;
|
|
303
315
|
const agentSince = workspaceTimeline[0]?.ts ?? (opts.follow ? Date.now() : void 0);
|
|
316
|
+
const project = loadConfig$1()?.projects?.[root];
|
|
304
317
|
const readAgent = (sources ? sources.includes("agent") : !opts.errors) && agentSince !== void 0 ? createAgentActionReader$1({
|
|
305
|
-
targets: workspaceAgentTargets(
|
|
318
|
+
targets: () => workspaceAgentTargets(project, readMacosRecord(root)),
|
|
306
319
|
sinceTs: agentSince
|
|
307
320
|
}) : null;
|
|
308
321
|
const timeline = readAgent ? sortByTs([...workspaceTimeline, ...readAgent()]) : workspaceTimeline;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
2
|
import { _ as loadConfig$1, d as getConfigDir } from "./config-D0DbpZtM.mjs";
|
|
3
|
-
import { w as withDirLock } from "./workspace-state-
|
|
4
|
-
import { T as CCACHE_UNAVAILABLE, a as toolchainMismatches, l as COMPILATION_CACHE_UNAVAILABLE, s as manifestDigest } from "./toolchain-
|
|
3
|
+
import { w as withDirLock } from "./workspace-state-DqcCbpE7.mjs";
|
|
4
|
+
import { T as CCACHE_UNAVAILABLE, a as toolchainMismatches, l as COMPILATION_CACHE_UNAVAILABLE, s as manifestDigest } from "./toolchain-CNIBTIOv.mjs";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readlinkSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, join, relative } from "node:path";
|
|
8
8
|
import { InvalidArgumentError } from "commander";
|
|
9
|
-
import { OFFLOAD_MODES, buildMachinesFile, buildMachinesLock, endpoint, findPeer as findPeer$1, isJsonObject, parseMachine, parseMachine as parseMachine$1, pinnedEndpoint, readBuildMachines, saturation } from "@stim-cli/core/state";
|
|
9
|
+
import { OFFLOAD_MODES, acceptsShape, buildMachinesFile, buildMachinesLock, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, endpoint, findPeer as findPeer$1, isJsonObject, parseMachine, parseMachine as parseMachine$1, pinnedEndpoint, readBuildMachines, readDeviceHostMachines, saturation, settingDefinition } from "@stim-cli/core/state";
|
|
10
|
+
import { releaseClaim, tryAcquireClaim } from "@stim-cli/core/ownership-claim";
|
|
10
11
|
import { hostname } from "node:os";
|
|
11
12
|
import { WebSocket } from "ws";
|
|
12
13
|
//#region src/offload/access-ticket.ts
|
|
@@ -95,11 +96,11 @@ function pinnedEndpoint$1(credential, status = tailscaleStatus) {
|
|
|
95
96
|
//#endregion
|
|
96
97
|
//#region src/offload/build-machines.ts
|
|
97
98
|
/** The build machines named in `offload.machines` that this Mac asked for build access, in that order. */
|
|
98
|
-
function pairedMachines(entries = configuredMachines()) {
|
|
99
|
+
function pairedMachines(entries = configuredMachines$1()) {
|
|
99
100
|
const credentials = readBuildMachines();
|
|
100
101
|
return entries.flatMap((entry) => credentials.filter((each) => each.machine === entry));
|
|
101
102
|
}
|
|
102
|
-
function configuredMachines() {
|
|
103
|
+
function configuredMachines$1() {
|
|
103
104
|
const machines = loadConfig$1()?.offload?.machines;
|
|
104
105
|
return Array.isArray(machines) ? machines.filter((entry) => typeof entry === "string") : [];
|
|
105
106
|
}
|
|
@@ -120,7 +121,7 @@ function updateCredentials(change) {
|
|
|
120
121
|
}
|
|
121
122
|
}, { ensureParent: () => mkdirSync(dir, { recursive: true }) });
|
|
122
123
|
}
|
|
123
|
-
function note(title, detail, fix = null) {
|
|
124
|
+
function note$1(title, detail, fix = null) {
|
|
124
125
|
return {
|
|
125
126
|
code: "build-machine",
|
|
126
127
|
level: "note",
|
|
@@ -129,7 +130,7 @@ function note(title, detail, fix = null) {
|
|
|
129
130
|
fix
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
|
-
const approval = (entry, deviceId) => `A person on ${entry} approves it with \`stim-server devices grant ${deviceId} --build\`.`;
|
|
133
|
+
const approval$1 = (entry, deviceId) => `A person on ${entry} approves it with \`stim-server devices grant ${deviceId} --build\`.`;
|
|
133
134
|
const PROBLEM_TITLES = {
|
|
134
135
|
unreachable: "does not answer a build offer",
|
|
135
136
|
checkout: "cannot build an app outside a git checkout",
|
|
@@ -195,7 +196,7 @@ async function requestAccess(entry, peer, port, deviceName, io) {
|
|
|
195
196
|
state: "unreachable",
|
|
196
197
|
dnsName: peer.dnsName
|
|
197
198
|
},
|
|
198
|
-
finding: note(`Could not ask ${entry} for build access`, reason)
|
|
199
|
+
finding: note$1(`Could not ask ${entry} for build access`, reason)
|
|
199
200
|
};
|
|
200
201
|
}
|
|
201
202
|
const credential = {
|
|
@@ -217,7 +218,7 @@ async function requestAccess(entry, peer, port, deviceName, io) {
|
|
|
217
218
|
deviceId: credential.deviceId,
|
|
218
219
|
requestedAt: credential.requestedAt
|
|
219
220
|
},
|
|
220
|
-
finding: note(`Asked ${entry} for build access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval(entry, credential.deviceId))
|
|
221
|
+
finding: note$1(`Asked ${entry} for build access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval$1(entry, credential.deviceId))
|
|
221
222
|
};
|
|
222
223
|
}
|
|
223
224
|
async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
@@ -227,7 +228,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
227
228
|
machine: entry,
|
|
228
229
|
state: "invalid"
|
|
229
230
|
},
|
|
230
|
-
finding: note(`Build machine ${entry} is not a tailnet name`, "Expected `name` or `name:port`.")
|
|
231
|
+
finding: note$1(`Build machine ${entry} is not a tailnet name`, "Expected `name` or `name:port`.")
|
|
231
232
|
};
|
|
232
233
|
const peer = findPeer$1(status, parsed.name);
|
|
233
234
|
if (peer === "missing" || peer === "ambiguous") {
|
|
@@ -237,7 +238,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
237
238
|
machine: entry,
|
|
238
239
|
state: "not-on-tailnet"
|
|
239
240
|
},
|
|
240
|
-
finding: note(`Build machine ${entry} is not on this tailnet`, detail)
|
|
241
|
+
finding: note$1(`Build machine ${entry} is not on this tailnet`, detail)
|
|
241
242
|
};
|
|
242
243
|
}
|
|
243
244
|
const credential = readBuildMachines().find((each) => each.machine === entry);
|
|
@@ -252,7 +253,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
252
253
|
deviceId: credential.deviceId,
|
|
253
254
|
requestedAt: credential.requestedAt
|
|
254
255
|
},
|
|
255
|
-
finding: note(`Build machine ${entry} is a different tailnet node`, `This Mac paired with node ${credential.nodeId}, but ${peer.dnsName} is now node ${peer.nodeId}. Stim does not connect to it.`, `If that Mac was replaced, remove ${entry} from offload.machines, run \`stim doctor --fix\` to forget the old pairing, then add it back and run \`stim doctor --fix\` again.`)
|
|
256
|
+
finding: note$1(`Build machine ${entry} is a different tailnet node`, `This Mac paired with node ${credential.nodeId}, but ${peer.dnsName} is now node ${peer.nodeId}. Stim does not connect to it.`, `If that Mac was replaced, remove ${entry} from offload.machines, run \`stim doctor --fix\` to forget the old pairing, then add it back and run \`stim doctor --fix\` again.`)
|
|
256
257
|
};
|
|
257
258
|
if (!credential) {
|
|
258
259
|
if (fix) return requestAccess(entry, peer, parsed.port, deviceName, io);
|
|
@@ -261,7 +262,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
261
262
|
...known,
|
|
262
263
|
state: "not-asked"
|
|
263
264
|
},
|
|
264
|
-
finding: note(`Build machine ${entry} has not approved this Mac`, "This Mac has not asked it for build access.", "Run `stim doctor --fix` to ask.")
|
|
265
|
+
finding: note$1(`Build machine ${entry} has not approved this Mac`, "This Mac has not asked it for build access.", "Run `stim doctor --fix` to ask.")
|
|
265
266
|
};
|
|
266
267
|
}
|
|
267
268
|
const paired = {
|
|
@@ -311,7 +312,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
311
312
|
...paired,
|
|
312
313
|
state: "pending"
|
|
313
314
|
},
|
|
314
|
-
finding: note(`Build machine ${entry} has not approved this Mac yet`, `Requested at ${credential.requestedAt}.`, approval(entry, credential.deviceId))
|
|
315
|
+
finding: note$1(`Build machine ${entry} has not approved this Mac yet`, `Requested at ${credential.requestedAt}.`, approval$1(entry, credential.deviceId))
|
|
315
316
|
};
|
|
316
317
|
}
|
|
317
318
|
if ("error" in reply && reply.error.code === "unauthorized") {
|
|
@@ -321,7 +322,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
321
322
|
...paired,
|
|
322
323
|
state: "revoked"
|
|
323
324
|
},
|
|
324
|
-
finding: note(`Build machine ${entry} no longer accepts this Mac`, `${reply.error.message} It was revoked, or the request lapsed before approval.`, "Run `stim doctor --fix` to ask again.")
|
|
325
|
+
finding: note$1(`Build machine ${entry} no longer accepts this Mac`, `${reply.error.message} It was revoked, or the request lapsed before approval.`, "Run `stim doctor --fix` to ask again.")
|
|
325
326
|
};
|
|
326
327
|
}
|
|
327
328
|
const reason = "failed" in reply ? reply.failed : "error" in reply ? reply.error.message : "unexpected reply";
|
|
@@ -330,7 +331,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
330
331
|
...paired,
|
|
331
332
|
state: "unreachable"
|
|
332
333
|
},
|
|
333
|
-
finding: note(`Could not reach build machine ${entry}`, reason, `Check that stim-server runs on ${entry}. To keep it running there, run \`stim-server service install --serve\` on ${entry}.`)
|
|
334
|
+
finding: note$1(`Could not reach build machine ${entry}`, reason, `Check that stim-server runs on ${entry}. To keep it running there, run \`stim-server service install --serve\` on ${entry}.`)
|
|
334
335
|
};
|
|
335
336
|
}
|
|
336
337
|
/**
|
|
@@ -340,7 +341,7 @@ async function inspectMachine(entry, status, deviceName, fix, io, check) {
|
|
|
340
341
|
* pinned node forgot this Mac or, with `STIM_ACCESS_TICKET` set, when a pending request carries another ticket, and forgets pairings of machines no longer named. With `check`, each approved
|
|
341
342
|
* machine is asked for one build offer and reports every reason it would not take this project's build.
|
|
342
343
|
*/
|
|
343
|
-
async function inspectBuildMachines({ fix, check = null }, io = realIo, entries = configuredMachines()) {
|
|
344
|
+
async function inspectBuildMachines({ fix, check = null }, io = realIo, entries = configuredMachines$1()) {
|
|
344
345
|
const unnamed = (each) => !entries.includes(each.machine);
|
|
345
346
|
if (fix && readBuildMachines().some(unnamed)) updateCredentials((credentials) => credentials.filter((each) => !unnamed(each)));
|
|
346
347
|
if (entries.length === 0) return {
|
|
@@ -349,7 +350,7 @@ async function inspectBuildMachines({ fix, check = null }, io = realIo, entries
|
|
|
349
350
|
};
|
|
350
351
|
const status = io.status();
|
|
351
352
|
if (!isJsonObject(status)) return {
|
|
352
|
-
findings: [note("Build machines are unreachable", "offload.machines names build machines, but Tailscale is not running.")],
|
|
353
|
+
findings: [note$1("Build machines are unreachable", "offload.machines names build machines, but Tailscale is not running.")],
|
|
353
354
|
machines: entries.map((machine) => ({
|
|
354
355
|
machine,
|
|
355
356
|
state: "tailscale-off"
|
|
@@ -1240,7 +1241,7 @@ async function offloadBuild({ choice, expectedFingerprint, request, stagingDir,
|
|
|
1240
1241
|
ok: true,
|
|
1241
1242
|
machine,
|
|
1242
1243
|
artifactPath,
|
|
1243
|
-
...
|
|
1244
|
+
...typeof declared.handoff === "string" && /^[0-9a-f]{64}$/.test(declared.handoff) ? { handoff: {
|
|
1244
1245
|
nodeId: choice.credential.nodeId,
|
|
1245
1246
|
token: declared.handoff,
|
|
1246
1247
|
sha256: digest
|
|
@@ -1365,4 +1366,261 @@ function offloadCheck(projectRoot, targets) {
|
|
|
1365
1366
|
};
|
|
1366
1367
|
}
|
|
1367
1368
|
//#endregion
|
|
1368
|
-
|
|
1369
|
+
//#region src/device-host/machines.ts
|
|
1370
|
+
function note(title, detail, fix = null) {
|
|
1371
|
+
return {
|
|
1372
|
+
code: "device-host-machine",
|
|
1373
|
+
level: "note",
|
|
1374
|
+
title,
|
|
1375
|
+
detail,
|
|
1376
|
+
fix
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
const approval = (machine, id) => `A person on ${machine} approves it with \`stim-server devices grant ${id} --device-host\`.`;
|
|
1380
|
+
function configuredMachines() {
|
|
1381
|
+
const hosting = loadConfig$1()?.hosting;
|
|
1382
|
+
if (hosting !== void 0 && !isJsonObject(hosting)) return null;
|
|
1383
|
+
const machines = hosting?.machines;
|
|
1384
|
+
if (machines === void 0) return [];
|
|
1385
|
+
return acceptsShape(settingDefinition("hosting.machines"), machines) ? machines : null;
|
|
1386
|
+
}
|
|
1387
|
+
function store(machines) {
|
|
1388
|
+
withDirLock(deviceHostMachinesLock(), () => {
|
|
1389
|
+
const file = deviceHostMachinesFile();
|
|
1390
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
1391
|
+
try {
|
|
1392
|
+
writeFileSync(tmp, `${JSON.stringify({
|
|
1393
|
+
version: 1,
|
|
1394
|
+
machines
|
|
1395
|
+
}, null, 2)}\n`, { mode: 384 });
|
|
1396
|
+
renameSync(tmp, file);
|
|
1397
|
+
} finally {
|
|
1398
|
+
rmSync(tmp, { force: true });
|
|
1399
|
+
}
|
|
1400
|
+
}, { ensureParent: () => mkdirSync(getConfigDir(), { recursive: true }) });
|
|
1401
|
+
}
|
|
1402
|
+
async function request(machine, peer, port, deviceName, io) {
|
|
1403
|
+
const ticket = readAccessTicket();
|
|
1404
|
+
const reply = await io.hello(endpoint(peer, port), {
|
|
1405
|
+
request: "device-host",
|
|
1406
|
+
deviceName,
|
|
1407
|
+
...ticket ? { setupTicket: ticket.ticket } : {}
|
|
1408
|
+
});
|
|
1409
|
+
if (!("result" in reply) || reply.result?.approval?.state !== "pending" || typeof reply.result.deviceToken !== "string" || !reply.result.deviceToken || typeof reply.result.device?.id !== "string" || !reply.result.device.id || typeof reply.result.approval.expiresAt !== "string" || !reply.result.approval.expiresAt) return {
|
|
1410
|
+
report: {
|
|
1411
|
+
machine,
|
|
1412
|
+
dnsName: peer.dnsName,
|
|
1413
|
+
state: "unreachable"
|
|
1414
|
+
},
|
|
1415
|
+
finding: note(`Could not ask ${machine} for hosting access`, "The worker did not return a pending request.")
|
|
1416
|
+
};
|
|
1417
|
+
const credential = {
|
|
1418
|
+
machine,
|
|
1419
|
+
nodeId: peer.nodeId,
|
|
1420
|
+
dnsName: peer.dnsName,
|
|
1421
|
+
deviceId: reply.result.device.id,
|
|
1422
|
+
deviceToken: reply.result.deviceToken,
|
|
1423
|
+
state: "pending",
|
|
1424
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1425
|
+
...ticket ? { ticketHash: ticket.ticketHash } : {}
|
|
1426
|
+
};
|
|
1427
|
+
return {
|
|
1428
|
+
report: {
|
|
1429
|
+
machine,
|
|
1430
|
+
dnsName: peer.dnsName,
|
|
1431
|
+
state: "pending",
|
|
1432
|
+
deviceId: credential.deviceId,
|
|
1433
|
+
requestedAt: credential.requestedAt
|
|
1434
|
+
},
|
|
1435
|
+
finding: note(`Asked ${machine} for hosting access`, `The request lapses at ${reply.result.approval.expiresAt}.`, approval(machine, credential.deviceId)),
|
|
1436
|
+
credential
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
/** Reports separately approved hosting machines. Only --fix asks or forgets credentials; every token stays pinned. */
|
|
1440
|
+
async function inspectDeviceHostMachines({ fix }, io = realIo, entries = configuredMachines()) {
|
|
1441
|
+
if (entries === null) return {
|
|
1442
|
+
findings: [note("Invalid hosting.machines setting", "Use a hosting object with machines as an array of tailnet names. Existing hosting credentials and pinned nodes are preserved.", "Run `stim guide settings` and correct the setting before running doctor again.")],
|
|
1443
|
+
machines: []
|
|
1444
|
+
};
|
|
1445
|
+
const unavailable = (state, finding) => ({
|
|
1446
|
+
findings: [finding],
|
|
1447
|
+
machines: entries.map((machine) => ({
|
|
1448
|
+
machine,
|
|
1449
|
+
state
|
|
1450
|
+
}))
|
|
1451
|
+
});
|
|
1452
|
+
const unreadable = () => unavailable("credentials-unavailable", note("Hosting credentials are unreadable", `Stim preserves ${deviceHostMachinesFile()} and its pinned nodes.`, "Restore the existing file from a trusted backup or restore read access before running doctor again."));
|
|
1453
|
+
if (!entries.length) {
|
|
1454
|
+
if (!fix) return {
|
|
1455
|
+
findings: [],
|
|
1456
|
+
machines: []
|
|
1457
|
+
};
|
|
1458
|
+
try {
|
|
1459
|
+
if (!readDeviceHostMachines().length) return {
|
|
1460
|
+
findings: [],
|
|
1461
|
+
machines: []
|
|
1462
|
+
};
|
|
1463
|
+
} catch {
|
|
1464
|
+
return unreadable();
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
const claim = tryAcquireClaim({
|
|
1468
|
+
root: deviceHostMachinesClaims(),
|
|
1469
|
+
mode: "exclusive",
|
|
1470
|
+
label: "hosting machine approval"
|
|
1471
|
+
});
|
|
1472
|
+
if (!claim.acquired) {
|
|
1473
|
+
releaseClaim(claim.pending);
|
|
1474
|
+
return unavailable("busy", note("Hosting machine approval is in use", "Another doctor run holds the hosting credential claim. Run doctor again after it finishes."));
|
|
1475
|
+
}
|
|
1476
|
+
try {
|
|
1477
|
+
let credentials;
|
|
1478
|
+
try {
|
|
1479
|
+
credentials = readDeviceHostMachines();
|
|
1480
|
+
} catch {
|
|
1481
|
+
return unreadable();
|
|
1482
|
+
}
|
|
1483
|
+
if (fix) {
|
|
1484
|
+
const kept = credentials.filter((each) => entries.includes(each.machine));
|
|
1485
|
+
if (kept.length !== credentials.length) {
|
|
1486
|
+
store(kept);
|
|
1487
|
+
credentials = kept;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (!entries.length) return {
|
|
1491
|
+
findings: [],
|
|
1492
|
+
machines: []
|
|
1493
|
+
};
|
|
1494
|
+
const status = io.status();
|
|
1495
|
+
if (!isJsonObject(status)) return unavailable("tailscale-off", note("Hosting machines are unreachable", "hosting.machines names hosting machines, but Tailscale is not running."));
|
|
1496
|
+
const self = isJsonObject(status.Self) ? status.Self : {};
|
|
1497
|
+
const deviceName = (typeof self.HostName === "string" && self.HostName ? self.HostName : hostname()).replace(/[\p{Cc}\p{Cf}]/gu, "").trim().slice(0, 64) || "Mac";
|
|
1498
|
+
const inspected = {
|
|
1499
|
+
findings: [],
|
|
1500
|
+
machines: []
|
|
1501
|
+
};
|
|
1502
|
+
const namedNodes = /* @__PURE__ */ new Set();
|
|
1503
|
+
for (const machine of new Set(entries)) {
|
|
1504
|
+
const parsed = parseMachine$1(machine);
|
|
1505
|
+
if (!parsed) {
|
|
1506
|
+
inspected.machines.push({
|
|
1507
|
+
machine,
|
|
1508
|
+
state: "invalid"
|
|
1509
|
+
});
|
|
1510
|
+
inspected.findings.push(note(`Hosting machine ${machine} is not a tailnet name`, "Expected `name` or `name:port`."));
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
const current = io.status();
|
|
1514
|
+
if (!isJsonObject(current)) {
|
|
1515
|
+
inspected.machines.push({
|
|
1516
|
+
machine,
|
|
1517
|
+
state: "tailscale-off"
|
|
1518
|
+
});
|
|
1519
|
+
inspected.findings.push(note(`Hosting machine ${machine} is unreachable`, "Tailscale stopped before this connection."));
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
const peer = findPeer$1(current, parsed.name);
|
|
1523
|
+
if (peer === "missing" || peer === "ambiguous") {
|
|
1524
|
+
inspected.machines.push({
|
|
1525
|
+
machine,
|
|
1526
|
+
state: "not-on-tailnet"
|
|
1527
|
+
});
|
|
1528
|
+
inspected.findings.push(note(`Hosting machine ${machine} is not on this tailnet`, peer === "missing" ? "No peer has that name." : "Several peers match that name."));
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
const nodeRoute = `${peer.nodeId}:${parsed.port}`;
|
|
1532
|
+
if (namedNodes.has(nodeRoute)) {
|
|
1533
|
+
inspected.machines.push({
|
|
1534
|
+
machine,
|
|
1535
|
+
dnsName: peer.dnsName,
|
|
1536
|
+
state: "invalid"
|
|
1537
|
+
});
|
|
1538
|
+
inspected.findings.push(note(`Hosting machine ${machine} repeats a named node`, "Name each tailnet node and serve port only once in hosting.machines."));
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
namedNodes.add(nodeRoute);
|
|
1542
|
+
const credential = credentials.find((each) => each.machine === machine);
|
|
1543
|
+
const known = {
|
|
1544
|
+
machine,
|
|
1545
|
+
dnsName: peer.dnsName,
|
|
1546
|
+
...credential ? {
|
|
1547
|
+
deviceId: credential.deviceId,
|
|
1548
|
+
requestedAt: credential.requestedAt
|
|
1549
|
+
} : {}
|
|
1550
|
+
};
|
|
1551
|
+
if (credential && credential.nodeId !== peer.nodeId) {
|
|
1552
|
+
inspected.machines.push({
|
|
1553
|
+
...known,
|
|
1554
|
+
state: "node-changed"
|
|
1555
|
+
});
|
|
1556
|
+
inspected.findings.push(note(`Hosting machine ${machine} is a different tailnet node`, "Stim refuses to connect or send the saved token.", `If that Mac was replaced, remove ${machine} from hosting.machines, run \`stim doctor --fix\`, then add it back and run \`stim doctor --fix\` again to request approval on the new node.`));
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
let ask = !credential && fix;
|
|
1560
|
+
if (credential) {
|
|
1561
|
+
const reply = await io.hello(endpoint(peer, parsed.port), { deviceToken: credential.deviceToken });
|
|
1562
|
+
if ("result" in reply && reply.result?.device?.id === credential.deviceId && Array.isArray(reply.result.capabilities) && reply.result.capabilities.includes("device-host")) {
|
|
1563
|
+
if (credential.state !== "approved") {
|
|
1564
|
+
credentials = credentials.map((each) => each.machine === machine ? {
|
|
1565
|
+
...each,
|
|
1566
|
+
state: "approved"
|
|
1567
|
+
} : each);
|
|
1568
|
+
store(credentials);
|
|
1569
|
+
}
|
|
1570
|
+
const host = readHostPermissions(reply.result.host);
|
|
1571
|
+
if (host && (host.screenRecording === false || host.accessibility === false)) {
|
|
1572
|
+
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)"] : []];
|
|
1573
|
+
const detail = [...host.screenRecording === false ? ["Viewing hosted macOS apps needs Screen Recording."] : [], ...host.accessibility === false ? ["Controlling them needs the control permission."] : []];
|
|
1574
|
+
inspected.findings.push(note(`Hosting machine ${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.`));
|
|
1575
|
+
}
|
|
1576
|
+
inspected.machines.push({
|
|
1577
|
+
...known,
|
|
1578
|
+
state: "approved",
|
|
1579
|
+
...host ? { host } : {}
|
|
1580
|
+
});
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
const pending = "error" in reply && reply.error?.code === "approval-pending";
|
|
1584
|
+
const revoked = "error" in reply && reply.error?.code === "unauthorized";
|
|
1585
|
+
const ticket = fix && pending && credential.state !== "approved" ? readAccessTicket() : void 0;
|
|
1586
|
+
ask = fix && (revoked || !!ticket && credential.ticketHash !== ticket.ticketHash);
|
|
1587
|
+
if (pending && !ask) {
|
|
1588
|
+
inspected.machines.push({
|
|
1589
|
+
...known,
|
|
1590
|
+
state: "pending"
|
|
1591
|
+
});
|
|
1592
|
+
inspected.findings.push(note(`Hosting machine ${machine} has not approved this Mac yet`, `Requested at ${credential.requestedAt}.`, approval(machine, credential.deviceId)));
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (!ask) {
|
|
1596
|
+
inspected.machines.push({
|
|
1597
|
+
...known,
|
|
1598
|
+
state: revoked ? "revoked" : "unreachable"
|
|
1599
|
+
});
|
|
1600
|
+
inspected.findings.push(note(`Hosting machine ${machine} ${revoked ? "no longer accepts this Mac" : "did not confirm hosting access"}`, "The saved token and pinned node are preserved.", revoked ? "Run `stim doctor --fix` to ask again." : `Check stim-server and its tailnet serve route on ${machine}, then run doctor again.`));
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (ask) {
|
|
1605
|
+
const result = await request(machine, peer, parsed.port, deviceName, io);
|
|
1606
|
+
if (result.credential) {
|
|
1607
|
+
credentials = [...credentials.filter((each) => each.machine !== machine), result.credential];
|
|
1608
|
+
store(credentials);
|
|
1609
|
+
}
|
|
1610
|
+
inspected.machines.push(result.report);
|
|
1611
|
+
inspected.findings.push(result.finding);
|
|
1612
|
+
} else {
|
|
1613
|
+
inspected.machines.push({
|
|
1614
|
+
...known,
|
|
1615
|
+
state: "not-asked"
|
|
1616
|
+
});
|
|
1617
|
+
inspected.findings.push(note(`Hosting machine ${machine} has not approved this Mac`, "This Mac has not asked for hosting access.", "Run `stim doctor --fix` to ask."));
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return inspected;
|
|
1621
|
+
} finally {
|
|
1622
|
+
releaseClaim(claim.acquired);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
//#endregion
|
|
1626
|
+
export { resolveBuildPlacement as _, chooseBuildMachine as a, pinnedEndpoint$1 as b, offloadCheck as c, remotePhaseText as d, simulatorRuntime as f, resolveBuildMachine as g, parseBuildMachineOption as h, buildPlacementCandidates as i, offloadPlacement as l, namedBuildMachine as m, inspectDeviceHostMachines as n, closeOffload as o, OffloadRefusal as p, BuildConnection as r, offloadBuild as s, configuredMachines as t, placementLoad as u, inspectBuildMachines as v, realIo as x, parseMachine$1 as y };
|