stim 1.14.0 → 1.16.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 +2 -0
- package/dist/activity-BxwdD81S.mjs +609 -0
- package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
- package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
- package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
- package/dist/android-cas-8U4DPqeR.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/budget-DjrURfQn.mjs +2700 -0
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
- package/dist/build-slots-rUk77xqh.mjs +132 -0
- package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
- package/dist/cli-BkYZ_6hy.mjs +51 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-BTY686R6.mjs +1368 -0
- package/dist/collector-run.mjs +9 -195
- package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CroAIjjj.mjs +43 -0
- package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
- package/dist/device-capacity-BHwxNBlu.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +964 -0
- package/dist/device-ios-BadmeL2V.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
- package/dist/device-pool-CUaEWMMm.mjs +125 -0
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
- package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
- package/dist/hosted-client-a-WmqE2L.mjs +197 -0
- package/dist/hosted-ios-CIWelIqf.mjs +220 -0
- package/dist/hosted-logs-D5g041Q3.mjs +187 -0
- package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
- package/dist/idle-DYVymG92.mjs +1099 -0
- package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
- package/dist/in-use-DvBw0Kxo.mjs +130 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
- package/dist/ios-state-DS5UpYTH.mjs +48 -0
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
- package/dist/logs-CHEVK8zD.mjs +371 -0
- package/dist/machines-BIBUdhpj.mjs +265 -0
- package/dist/macos-CSYEAFmF.mjs +473 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
- package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
- package/dist/native-run-NaBrRFtQ.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +122 -0
- package/dist/offload-worker.mjs +606 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
- package/dist/ownership-hCkPzGmr.mjs +571 -0
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
- package/dist/prebuild-D2fsca_Z.mjs +213 -0
- package/dist/preview-DhVbvPAZ.mjs +318 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-BIXOzVnr.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
- package/dist/run-f4vI6EnK.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
- package/dist/server-expo-CVqO2c35.mjs +2 -0
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
- package/dist/settings-oTSkkNrM.mjs +1439 -0
- package/dist/settings.schema.json +845 -97
- package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CX8Ig-U2.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
- package/dist/state-3DwSQIBK.mjs +41 -0
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
- package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
- package/dist/stats-CEZ_i5je.mjs +77 -0
- package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
- package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/stop-B0Yxtmiy.mjs +48 -0
- package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
- package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
- package/dist/stop-cause-CidxIqup.mjs +97 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +363 -24
- package/dist/support-CAP8qDUF.mjs +316 -0
- package/dist/support-D4aYJniO.mjs +201 -0
- package/dist/teardown-CL6vDs2Y.mjs +727 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
- package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
- package/dist/web-run.mjs +310 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
- package/dist/workspaces-BV4pdHFz.mjs +306 -0
- package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
- package/dist/worktree-jnG8Iph5.mjs +2 -0
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/activity-CYVDcrt3.mjs +0 -356
- package/dist/budget-CGDkhvv_.mjs +0 -1745
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/idle-DXtx3RbX.mjs +0 -191
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/logs-Civ4gbBH.mjs +0 -486
- package/dist/merge-state-ytFQCeh4.mjs +0 -262
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- package/dist/settings-D61iNOsB.mjs +0 -629
- package/dist/start-Cetfvy8l.mjs +0 -2
- package/dist/stats-CFpFGlJV.mjs +0 -62
- package/dist/stats-CX1Z8Tca.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
- package/dist/worktree-BVrHsYZG.mjs +0 -752
- package/dist/worktree-CHWWvm5v.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as appProjectProblem } from "./project-
|
|
3
|
-
import { t as compareStimVersions } from "./stim-installations-
|
|
1
|
+
import { E as upsertProject, d as getConfigDir, p as getProject } from "./config-D0DbpZtM.mjs";
|
|
2
|
+
import { n as appProjectProblem } from "./project-Du91Ilvc.mjs";
|
|
3
|
+
import { t as compareStimVersions } from "./stim-installations-QMEI32v1.mjs";
|
|
4
4
|
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
//#region src/guide-status.ts
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { t as BuildConnection, y as pinnedEndpoint$1 } from "./client-BTY686R6.mjs";
|
|
2
|
+
import { t as configuredMachines } from "./machines-BIBUdhpj.mjs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { lstatSync, readFileSync, readdirSync, readlinkSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { HOSTED_APP_CHUNK_BYTES, hostedMacosAppSlot, isJsonObject, readDeviceHostMachines } from "@stim-cli/core/state";
|
|
7
|
+
//#region src/device-host/hosted-client.ts
|
|
8
|
+
const CONNECT_TIMEOUT_MS = 1e4;
|
|
9
|
+
/** Covers 90s stop + 15s final logs + three 10s worker group-settle bounds, with 45s for polling and transport. */
|
|
10
|
+
const SESSION_TIMEOUT_MS = 18e4;
|
|
11
|
+
const INSTALL_TIMEOUT_MS = 3e5;
|
|
12
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
13
|
+
const sha256 = (content) => createHash("sha256").update(content).digest("hex");
|
|
14
|
+
function credentialRefusal(message, remedy = "Run stim doctor.") {
|
|
15
|
+
return Object.assign(new Error(message), {
|
|
16
|
+
code: "STIM_HOSTING_REFUSED",
|
|
17
|
+
remedy
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function hostingCredential(machine) {
|
|
21
|
+
const configured = configuredMachines();
|
|
22
|
+
if (configured === null) throw credentialRefusal("hosting.machines is invalid. Run stim guide settings and correct it.", "Run stim guide settings and correct hosting.machines.");
|
|
23
|
+
if (!configured.includes(machine)) throw Object.assign(/* @__PURE__ */ new Error(`${machine} is not in hosting.machines. Add it with stim settings set hosting.machines, then run stim doctor --fix to ask it for hosting access.`), { code: "STIM_BAD_ARG" });
|
|
24
|
+
let credentials;
|
|
25
|
+
try {
|
|
26
|
+
credentials = readDeviceHostMachines();
|
|
27
|
+
} catch {
|
|
28
|
+
throw credentialRefusal("The hosting credentials are unreadable. Run stim doctor.");
|
|
29
|
+
}
|
|
30
|
+
const credential = credentials.find((each) => each.machine === machine);
|
|
31
|
+
if (!credential) throw credentialRefusal(`This Mac has not asked ${machine} for hosting access. Run stim doctor --fix.`, "Run stim doctor --fix.");
|
|
32
|
+
if (credential.state !== "approved") {
|
|
33
|
+
const remedy = `A person on ${machine} approves it with stim-server devices grant ${credential.deviceId} --device-host; then run stim doctor.`;
|
|
34
|
+
throw credentialRefusal(`${machine} has not confirmed hosting access for this Mac. ${remedy}`, remedy);
|
|
35
|
+
}
|
|
36
|
+
return credential;
|
|
37
|
+
}
|
|
38
|
+
/** Connects only to the pinned node of an approved hosting machine, and only when it grants `device-host`. */
|
|
39
|
+
async function connectHost(machine, timeoutMs = CONNECT_TIMEOUT_MS, strict = false) {
|
|
40
|
+
const credential = hostingCredential(machine);
|
|
41
|
+
const target = pinnedEndpoint$1(credential);
|
|
42
|
+
if (typeof target === "string") throw Object.assign(/* @__PURE__ */ new Error(`Stim does not connect to ${machine}: ${target}.`), { code: "STIM_HOSTING_REFUSED" });
|
|
43
|
+
const opened = await BuildConnection.open(target, credential.deviceToken, timeoutMs, "device-host").catch((error) => {
|
|
44
|
+
throw Object.assign(new Error(`${machine} is unreachable. Check stim-server and its tailnet serve route on ${machine}.`, { cause: error }), { code: strict ? "STIM_HOSTING_REFUSED" : "closed" });
|
|
45
|
+
});
|
|
46
|
+
if (!(opened instanceof BuildConnection)) throw Object.assign(/* @__PURE__ */ new Error(opened.refused ? `${machine} refused this Mac: ${opened.failure.replaceAll(credential.deviceToken, "[redacted]").replace(/\.+$/, "")}. Run stim doctor.` : `${machine} is unreachable: ${opened.failure.replaceAll(credential.deviceToken, "[redacted]")}. Check stim-server and its tailnet serve route on ${machine}.`), {
|
|
47
|
+
code: strict ? "STIM_HOSTING_REFUSED" : opened.refused ? "STIM_HOSTING_REFUSED" : opened.code ?? "closed",
|
|
48
|
+
hostCode: opened.code
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
machine,
|
|
52
|
+
credential,
|
|
53
|
+
connection: opened
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const gatewaySecrets = /* @__PURE__ */ new WeakMap();
|
|
57
|
+
function redactHostText(host, message) {
|
|
58
|
+
for (const secret of [host.credential.deviceToken, gatewaySecrets.get(host)]) if (secret) message = message.replaceAll(secret, "[redacted]");
|
|
59
|
+
return message;
|
|
60
|
+
}
|
|
61
|
+
async function call(host, method, params, timeoutMs) {
|
|
62
|
+
if (method === "device-host.metro.open" && isJsonObject(params) && typeof params.secret === "string") gatewaySecrets.set(host, params.secret);
|
|
63
|
+
const reply = await host.connection.request(method, params, timeoutMs);
|
|
64
|
+
if ("error" in reply) throw Object.assign(/* @__PURE__ */ new Error(`${host.machine} refused ${method}: ${redactHostText(host, reply.error.message)}`), { code: reply.error.code });
|
|
65
|
+
if (!isJsonObject(reply.result)) throw new Error(`${host.machine} answered ${method} without a result.`);
|
|
66
|
+
return typeof reply.result.notice === "string" ? {
|
|
67
|
+
...reply.result,
|
|
68
|
+
notice: redactHostText(host, reply.result.notice)
|
|
69
|
+
} : reply.result;
|
|
70
|
+
}
|
|
71
|
+
function hostedSession(host, value, platform = "macos") {
|
|
72
|
+
if (typeof value.id !== "string" || typeof value.state !== "string" || value.platform !== platform) throw new Error(`${host.machine} answered with a session that is not a hosted ${platform} session.`);
|
|
73
|
+
return {
|
|
74
|
+
id: value.id,
|
|
75
|
+
state: value.state,
|
|
76
|
+
device: value.device,
|
|
77
|
+
...hostedMacosAppSlot(value.appSlot) ? { appSlot: value.appSlot } : {},
|
|
78
|
+
...typeof value.notice === "string" ? { notice: value.notice } : {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const heldNoLonger = (error) => error instanceof Error && "code" in error && error.code === "unknown-session";
|
|
82
|
+
async function attach(host, session, timeoutMs, platform = "macos") {
|
|
83
|
+
return hostedSession(host, await call(host, "device-host.attach", { session }, timeoutMs), platform);
|
|
84
|
+
}
|
|
85
|
+
const probeCache = /* @__PURE__ */ new Map();
|
|
86
|
+
function probeHostedSession(placement, { timeoutMs = 3e3, ttlMs = 1e4 } = {}) {
|
|
87
|
+
const key = JSON.stringify([placement.machine, placement.session]);
|
|
88
|
+
const cached = probeCache.get(key);
|
|
89
|
+
if (ttlMs > 0 && cached && cached.expiresAt > Date.now()) return cached.promise;
|
|
90
|
+
const promise = probeSession(placement, timeoutMs);
|
|
91
|
+
if (ttlMs > 0) {
|
|
92
|
+
const entry = {
|
|
93
|
+
expiresAt: Infinity,
|
|
94
|
+
promise
|
|
95
|
+
};
|
|
96
|
+
probeCache.delete(key);
|
|
97
|
+
probeCache.set(key, entry);
|
|
98
|
+
if (probeCache.size > 64) probeCache.delete(probeCache.keys().next().value);
|
|
99
|
+
promise.then((result) => {
|
|
100
|
+
entry.expiresAt = Date.now() + ttlMs;
|
|
101
|
+
return result;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return promise;
|
|
105
|
+
}
|
|
106
|
+
async function probeSession(placement, timeoutMs) {
|
|
107
|
+
let host;
|
|
108
|
+
try {
|
|
109
|
+
host = await connectHost(placement.machine, timeoutMs);
|
|
110
|
+
const session = await attach(host, placement.session, timeoutMs, placement.device === void 0 ? "macos" : "ios");
|
|
111
|
+
if (session.state === "ready" || session.state === "stopped") return { state: session.state };
|
|
112
|
+
return {
|
|
113
|
+
state: "unknown",
|
|
114
|
+
notice: session.state === "unknown" ? session.notice : `The hosted session ${session.id} on ${host.machine} is ${session.state}.${session.notice ? ` ${session.notice}` : ""}`
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (heldNoLonger(error)) return { state: "stopped" };
|
|
118
|
+
return {
|
|
119
|
+
state: "unreachable",
|
|
120
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
121
|
+
};
|
|
122
|
+
} finally {
|
|
123
|
+
host?.connection.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function settle(host, session, passing, timeoutMs, platform = "macos") {
|
|
127
|
+
const deadline = Date.now() + timeoutMs;
|
|
128
|
+
while (passing.includes(session.state)) {
|
|
129
|
+
if (Date.now() > deadline) throw new Error(`The hosted session ${session.id} on ${host.machine} stayed ${session.state}.`);
|
|
130
|
+
await sleep(500);
|
|
131
|
+
session = await attach(host, session.id, void 0, platform);
|
|
132
|
+
}
|
|
133
|
+
return session;
|
|
134
|
+
}
|
|
135
|
+
function unknownSession(host, session) {
|
|
136
|
+
return /* @__PURE__ */ new Error(`The hosted session ${session.id} on ${host.machine} is in an unknown state${session.notice ? `: ${session.notice}` : ""}. Run stim stop to reconcile it.`);
|
|
137
|
+
}
|
|
138
|
+
function bundleManifest(bundle) {
|
|
139
|
+
const files = [];
|
|
140
|
+
const content = /* @__PURE__ */ new Map();
|
|
141
|
+
const walk = (dir, prefix) => {
|
|
142
|
+
for (const name of readdirSync(dir)) {
|
|
143
|
+
const absolute = join(dir, name);
|
|
144
|
+
const path = prefix ? `${prefix}/${name}` : name;
|
|
145
|
+
const stat = lstatSync(absolute);
|
|
146
|
+
let read;
|
|
147
|
+
let kind;
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
walk(absolute, path);
|
|
150
|
+
continue;
|
|
151
|
+
} else if (stat.isSymbolicLink()) {
|
|
152
|
+
read = () => Buffer.from(readlinkSync(absolute));
|
|
153
|
+
kind = "link";
|
|
154
|
+
} else if (stat.isFile()) {
|
|
155
|
+
read = () => readFileSync(absolute);
|
|
156
|
+
kind = stat.mode & 73 ? "exec" : "file";
|
|
157
|
+
} else continue;
|
|
158
|
+
const bytes = read();
|
|
159
|
+
const digest = sha256(bytes);
|
|
160
|
+
files.push({
|
|
161
|
+
path,
|
|
162
|
+
kind,
|
|
163
|
+
size: bytes.length,
|
|
164
|
+
sha256: digest
|
|
165
|
+
});
|
|
166
|
+
content.set(digest, read);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
walk(bundle, "");
|
|
170
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
171
|
+
return {
|
|
172
|
+
files,
|
|
173
|
+
content
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function upload(host, ids, missing, content) {
|
|
177
|
+
if (!Array.isArray(missing)) throw new Error(`${host.machine} answered device-host.app.offer without missing content.`);
|
|
178
|
+
for (const entry of missing) {
|
|
179
|
+
const read = isJsonObject(entry) && typeof entry.sha256 === "string" ? content.get(entry.sha256) : void 0;
|
|
180
|
+
if (!read || !isJsonObject(entry) || typeof entry.offset !== "number") throw new Error(`${host.machine} asked for content this app does not have.`);
|
|
181
|
+
const bytes = read();
|
|
182
|
+
let offset = entry.offset;
|
|
183
|
+
do {
|
|
184
|
+
const data = bytes.subarray(offset, offset + HOSTED_APP_CHUNK_BYTES).toString("base64");
|
|
185
|
+
const next = (await call(host, "device-host.app.chunk", {
|
|
186
|
+
...ids,
|
|
187
|
+
sha256: entry.sha256,
|
|
188
|
+
offset,
|
|
189
|
+
data
|
|
190
|
+
})).offset;
|
|
191
|
+
if (typeof next !== "number" || next <= offset && next !== bytes.length) throw new Error(`${host.machine} did not accept app content at byte ${offset}.`);
|
|
192
|
+
offset = next;
|
|
193
|
+
} while (offset < bytes.length);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { call as a, hostedSession as c, sha256 as d, sleep as f, bundleManifest as i, probeHostedSession as l, upload as m, SESSION_TIMEOUT_MS as n, connectHost as o, unknownSession as p, attach as r, heldNoLonger as s, INSTALL_TIMEOUT_MS as t, settle as u };
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
|
|
2
|
+
import { a as call, c as hostedSession, d as sha256, f as sleep, i as bundleManifest, m as upload, n as SESSION_TIMEOUT_MS, o as connectHost, p as unknownSession, r as attach, s as heldNoLonger, t as INSTALL_TIMEOUT_MS, u as settle } from "./hosted-client-a-WmqE2L.mjs";
|
|
3
|
+
import { i as requireHostedMetro, n as closeHostedMetro, r as requestHostedMetro } from "./metro-gateway-BJ-rxQAB.mjs";
|
|
4
|
+
import { i as agentAccess, r as closeAgentConnection, t as pullHostedIosLogs } from "./hosted-logs-D5g041Q3.mjs";
|
|
5
|
+
import { n as writeHostedIos } from "./ios-state-DS5UpYTH.mjs";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { rmSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { hostedIosRecords, isJsonObject, parseHostedChoice, parseHostedDevice, parseHostedIosPlacement, unreadableHostedIos } from "@stim-cli/core/state";
|
|
10
|
+
//#region src/device-host/hosted-ios.ts
|
|
11
|
+
function hostingRefusal(machine, error) {
|
|
12
|
+
return Object.assign(/* @__PURE__ */ new Error(`${machine}: ${error instanceof Error ? error.message : String(error)}`), {
|
|
13
|
+
code: "STIM_HOSTING_REFUSED",
|
|
14
|
+
...error instanceof Error && "remedy" in error ? { remedy: error.remedy } : {}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const iosAgentRemoteConfig = (root, slot) => join(workspaceDir(root), "hosted-ios", slot, "agent-device-remote.json");
|
|
18
|
+
async function prepareHostedIos(machine, selectors, recorded) {
|
|
19
|
+
let host;
|
|
20
|
+
try {
|
|
21
|
+
host = await connectHost(machine, void 0, true);
|
|
22
|
+
if (recorded) {
|
|
23
|
+
let session = null;
|
|
24
|
+
try {
|
|
25
|
+
session = await settle(host, await attach(host, recorded.session, void 0, "ios"), ["preparing", "stopping"], SESSION_TIMEOUT_MS, "ios");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (!heldNoLonger(error)) throw error;
|
|
28
|
+
}
|
|
29
|
+
if (session && session.state !== "stopped") {
|
|
30
|
+
if (session.state !== "ready") throw unknownSession(host, session);
|
|
31
|
+
const device = parseHostedDevice(session.device);
|
|
32
|
+
if (!device) throw new Error("The ready session has no simulator identity.");
|
|
33
|
+
if (selectors.deviceType && selectors.deviceType !== device.deviceType || selectors.runtime && selectors.runtime.replace(/^iOS /, "") !== device.runtime.replace(/^iOS /, "")) throw new Error(`This session uses ${device.deviceType} (iOS ${device.runtime.replace(/^iOS /, "")}); run stim stop first to change it.`);
|
|
34
|
+
return {
|
|
35
|
+
host,
|
|
36
|
+
choice: device,
|
|
37
|
+
session
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const offer = await call(host, "device-host.offer", {
|
|
42
|
+
platform: "ios",
|
|
43
|
+
...selectors
|
|
44
|
+
}, 3e3);
|
|
45
|
+
const choice = offer.platform === "ios" ? parseHostedChoice(offer.choice) : null;
|
|
46
|
+
if (typeof offer.declined === "string") throw new Error(offer.declined);
|
|
47
|
+
if (!choice) throw new Error("No compatible installed iOS simulator runtime was offered.");
|
|
48
|
+
if (!isJsonObject(offer.capacity) || offer.capacity.available === 0) throw new Error("No hosted device capacity is available.");
|
|
49
|
+
if (!isJsonObject(offer.resources) || offer.resources.memoryPressure !== "normal") throw new Error("Host memory pressure is unknown or elevated.");
|
|
50
|
+
return {
|
|
51
|
+
host,
|
|
52
|
+
choice,
|
|
53
|
+
session: null
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof Error && "code" in error && error.code === "STIM_BAD_ARG") throw error;
|
|
57
|
+
throw hostingRefusal(machine, error);
|
|
58
|
+
} finally {
|
|
59
|
+
host?.connection.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function placeHostedIos(target, { root, slot, bundle, handoff, bundleId, selectors, release, devClientScheme, reserved, note, metro = requestHostedMetro }) {
|
|
63
|
+
let host = target.host;
|
|
64
|
+
try {
|
|
65
|
+
if (!release && metro === requestHostedMetro) requireHostedMetro(root);
|
|
66
|
+
host = await connectHost(host.machine, void 0, true);
|
|
67
|
+
target.host = host;
|
|
68
|
+
let session = (target.session ? await attach(host, target.session.id, void 0, "ios") : null) ?? hostedSession(host, await call(host, "device-host.reserve", {
|
|
69
|
+
platform: "ios",
|
|
70
|
+
workspace: root,
|
|
71
|
+
slot,
|
|
72
|
+
attempt: randomUUID(),
|
|
73
|
+
...selectors
|
|
74
|
+
}), "ios");
|
|
75
|
+
let placement = {
|
|
76
|
+
machine: host.machine,
|
|
77
|
+
selected: host.machine,
|
|
78
|
+
session: session.id,
|
|
79
|
+
appAttempt: randomUUID(),
|
|
80
|
+
device: parseHostedDevice(session.device),
|
|
81
|
+
agent: {
|
|
82
|
+
driver: "none",
|
|
83
|
+
setting: "hosting.agentDriver"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
reserved(placement);
|
|
87
|
+
session = await settle(host, session, ["preparing"], SESSION_TIMEOUT_MS, "ios");
|
|
88
|
+
if (session.state !== "ready") throw unknownSession(host, session);
|
|
89
|
+
const device = parseHostedDevice(session.device);
|
|
90
|
+
if (!device || device.architecture !== target.choice.architecture) throw new Error("The reserved simulator does not match the offered architecture.");
|
|
91
|
+
placement = {
|
|
92
|
+
...placement,
|
|
93
|
+
device
|
|
94
|
+
};
|
|
95
|
+
reserved(placement);
|
|
96
|
+
if (!release) {
|
|
97
|
+
const gateway = await metro(root, session.id, host.credential);
|
|
98
|
+
await call(host, "device-host.metro.open", {
|
|
99
|
+
session: session.id,
|
|
100
|
+
...gateway
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
await call(host, "device-host.metro.close", { session: session.id });
|
|
104
|
+
await closeHostedMetro(root, session.id);
|
|
105
|
+
}
|
|
106
|
+
const { files, content } = bundleManifest(bundle);
|
|
107
|
+
const manifest = Buffer.from(JSON.stringify(files));
|
|
108
|
+
content.set(sha256(manifest), () => manifest);
|
|
109
|
+
const ids = {
|
|
110
|
+
session: session.id,
|
|
111
|
+
attempt: placement.appAttempt
|
|
112
|
+
};
|
|
113
|
+
const offer = {
|
|
114
|
+
...ids,
|
|
115
|
+
bundleId,
|
|
116
|
+
mode: release ? "release" : "development",
|
|
117
|
+
...!release && devClientScheme ? { devClientScheme } : {},
|
|
118
|
+
manifest: {
|
|
119
|
+
sha256: sha256(manifest),
|
|
120
|
+
size: manifest.length
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
note(`Delivering ${files.length} files to ${host.machine} (${device.runtime}, ${device.architecture})`);
|
|
124
|
+
await upload(host, ids, (await call(host, "device-host.app.offer", offer)).missing, content);
|
|
125
|
+
let missing = (await call(host, "device-host.app.offer", offer)).missing;
|
|
126
|
+
if (handoff && handoff.nodeId === host.credential.nodeId && Array.isArray(missing) && missing.length) {
|
|
127
|
+
if (!host.connection.supports("hosted-ios-data")) note(`${host.machine} needs a newer stim-server for iOS build handoff; uploading the app instead`);
|
|
128
|
+
else try {
|
|
129
|
+
const taken = await call(host, "device-host.app.handoff", {
|
|
130
|
+
...ids,
|
|
131
|
+
build: {
|
|
132
|
+
handoff: handoff.token,
|
|
133
|
+
sha256: handoff.sha256
|
|
134
|
+
}
|
|
135
|
+
}, 6e4);
|
|
136
|
+
if (taken.files) note(`${host.machine} took ${String(taken.files)} files from the build it ran`);
|
|
137
|
+
missing = (await call(host, "device-host.app.offer", offer)).missing;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
note(`${error instanceof Error ? error.message : String(error)}; uploading the app instead`);
|
|
140
|
+
const fallbackDeadline = Date.now() + 6e4;
|
|
141
|
+
for (;;) try {
|
|
142
|
+
missing = (await call(host, "device-host.app.offer", offer, Math.max(1, Math.min(2e4, fallbackDeadline - Date.now())))).missing;
|
|
143
|
+
break;
|
|
144
|
+
} catch (offerError) {
|
|
145
|
+
if (!String(offerError).includes("native operation in progress") || Date.now() >= fallbackDeadline) throw offerError;
|
|
146
|
+
await sleep(Math.min(500, Math.max(0, fallbackDeadline - Date.now())));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await upload(host, ids, missing, content);
|
|
151
|
+
note(`Launching on ${host.machine}`);
|
|
152
|
+
let delivery = await call(host, "device-host.app.launch", ids);
|
|
153
|
+
const deadline = Date.now() + INSTALL_TIMEOUT_MS;
|
|
154
|
+
while (delivery.state !== "installed") {
|
|
155
|
+
if (delivery.state === "unknown") throw new Error(typeof delivery.notice === "string" ? delivery.notice : "App installation or launch is unknown.");
|
|
156
|
+
if (Date.now() > deadline) throw new Error("App installation did not finish.");
|
|
157
|
+
await sleep(500);
|
|
158
|
+
delivery = await call(host, "device-host.app.attach", ids);
|
|
159
|
+
}
|
|
160
|
+
if (typeof delivery.notice === "string") note(delivery.notice);
|
|
161
|
+
placement = {
|
|
162
|
+
...placement,
|
|
163
|
+
agent: agentAccess(iosAgentRemoteConfig(root, slot), host.credential, host.connection.supports("hosted-ios-agent") ? delivery.agent : void 0, note, "ios")
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
placement,
|
|
167
|
+
launched: release && delivery.launched === true ? true : "unverified"
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
throw hostingRefusal(host.machine, error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function stopHostedIos(root, slot) {
|
|
174
|
+
const failures = [];
|
|
175
|
+
for (const [name, record] of Object.entries(hostedIosRecords(readWorkspaceState$1(root)))) {
|
|
176
|
+
if (slot !== void 0 && name !== slot) continue;
|
|
177
|
+
const placement = parseHostedIosPlacement(record);
|
|
178
|
+
if (!placement) {
|
|
179
|
+
failures.push(unreadableHostedIos(name));
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (placement.agent.driver === "agent-device") closeAgentConnection(placement.agent.remoteConfig);
|
|
183
|
+
let host;
|
|
184
|
+
try {
|
|
185
|
+
host = await connectHost(placement.machine);
|
|
186
|
+
try {
|
|
187
|
+
await pullHostedIosLogs(root, name, placement, host, true);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
process.stderr.write(`Could not copy final native logs from ${placement.machine}: ${error.message}\n`);
|
|
190
|
+
}
|
|
191
|
+
const stopped = await settle(host, hostedSession(host, await call(host, "device-host.stop", { session: placement.session }), "ios"), ["stopping"], SESSION_TIMEOUT_MS, "ios");
|
|
192
|
+
if (stopped.state !== "stopped") throw unknownSession(host, stopped);
|
|
193
|
+
try {
|
|
194
|
+
await pullHostedIosLogs(root, name, placement, host, true);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
process.stderr.write(`Could not copy the host's final native logs from ${placement.machine}: ${error.message}\n`);
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
200
|
+
const hostCode = error instanceof Error && "hostCode" in error ? error.hostCode : code;
|
|
201
|
+
if (hostCode !== "unknown-session" && hostCode !== "forbidden") {
|
|
202
|
+
const remedy = code === "closed" || code === "timeout" ? "rerun stim stop when that machine answers." : code === "STIM_BAD_ARG" || code === "STIM_HOSTING_REFUSED" ? "run stim doctor, restore hosting access, then rerun stim stop." : "run stim stop to reconcile the session.";
|
|
203
|
+
failures.push(`Could not stop the iOS simulator on ${placement.machine}: ${(error instanceof Error ? error.message : String(error)).replace(/\.+$/, "")}. The placement is kept; ${remedy}`);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
} finally {
|
|
207
|
+
host?.connection.close();
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await closeHostedMetro(root, placement.session);
|
|
211
|
+
rmSync(iosAgentRemoteConfig(root, name), { force: true });
|
|
212
|
+
writeHostedIos(root, name, null);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
failures.push(`${placement.machine}: ${error.message}. The placement is kept; rerun stim stop.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (failures.length) throw new Error(failures.join("\n"));
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
export { prepareHostedIos as n, stopHostedIos as r, placeHostedIos as t };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
+
import { L as deviceSlotFileKey, W as validateDeviceSlot } from "./config-D0DbpZtM.mjs";
|
|
3
|
+
import { l as phaseLine } from "./command-output-DnJMdEY-.mjs";
|
|
4
|
+
import { v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
|
|
5
|
+
import { t as macosDir } from "./state-3DwSQIBK.mjs";
|
|
6
|
+
import { r as createNdjsonWriter } from "./ndjson-PclOIwVY.mjs";
|
|
7
|
+
import { v as parseMachine$1 } from "./client-BTY686R6.mjs";
|
|
8
|
+
import { a as call } from "./hosted-client-a-WmqE2L.mjs";
|
|
9
|
+
import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { LOG_ROTATE_BYTES, withDirLock } from "@stim-cli/core";
|
|
12
|
+
import { isJsonObject, parseHostedAgentGrant, parseHostedLogsCursor } from "@stim-cli/core/state";
|
|
13
|
+
//#region src/device-host/hosted-agent.ts
|
|
14
|
+
function agentAccess(file, credential, grant, note, platform = "macos") {
|
|
15
|
+
const parsed = grant === void 0 ? null : parseHostedAgentGrant(grant);
|
|
16
|
+
if (grant !== void 0 && !parsed) note(`${credential.machine} offered agent control this Stim does not support.`);
|
|
17
|
+
if (!parsed || parsed.driver === "none" || parsed.lease.backend === "ios-instance" !== (platform === "ios")) {
|
|
18
|
+
rmSync(file, { force: true });
|
|
19
|
+
return {
|
|
20
|
+
driver: "none",
|
|
21
|
+
setting: "hosting.agentDriver"
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const port = parseMachine$1(credential.machine)?.port ?? 443;
|
|
25
|
+
const config = {
|
|
26
|
+
daemonBaseUrl: `https://${credential.dnsName}${port === 443 ? "" : `:${port}`}${parsed.path}`,
|
|
27
|
+
daemonAuthToken: parsed.token,
|
|
28
|
+
tenant: parsed.lease.tenant,
|
|
29
|
+
sessionIsolation: "tenant",
|
|
30
|
+
runId: parsed.lease.runId,
|
|
31
|
+
...platform === "macos" ? { leaseId: parsed.scope } : {},
|
|
32
|
+
leaseBackend: platform === "ios" ? "ios-instance" : "macos-app",
|
|
33
|
+
leaseProvider: "proxy",
|
|
34
|
+
clientId: parsed.lease.clientId,
|
|
35
|
+
deviceKey: parsed.lease.deviceKey,
|
|
36
|
+
platform
|
|
37
|
+
};
|
|
38
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
39
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
40
|
+
try {
|
|
41
|
+
writeFileSync(tmp, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
42
|
+
renameSync(tmp, file);
|
|
43
|
+
} finally {
|
|
44
|
+
rmSync(tmp, { force: true });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
driver: "agent-device",
|
|
48
|
+
remoteConfig: file,
|
|
49
|
+
command: `agent-device <command> --remote-config ${file}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/device-host/agent-connection.ts
|
|
54
|
+
function canonical(path) {
|
|
55
|
+
try {
|
|
56
|
+
return realpathSync(path);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function report(message) {
|
|
62
|
+
process.stderr.write(`${phaseLine("device", message.replace(/\s+/g, " "))}\n`);
|
|
63
|
+
}
|
|
64
|
+
function closeAgentConnection(remoteConfig) {
|
|
65
|
+
const exec = getExecutor();
|
|
66
|
+
const deadline = Date.now() + 3e4;
|
|
67
|
+
const run = (args) => {
|
|
68
|
+
const remaining = deadline - Date.now();
|
|
69
|
+
if (remaining <= 0) throw new Error("agent-device cleanup timed out");
|
|
70
|
+
const result = JSON.parse(exec.runFile("agent-device", [...args, "--json"], {
|
|
71
|
+
timeoutMs: Math.min(1e4, remaining),
|
|
72
|
+
killSignal: "SIGKILL"
|
|
73
|
+
}));
|
|
74
|
+
if (result?.success !== true) throw new Error("agent-device did not confirm the command");
|
|
75
|
+
return result.data;
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
if (!exec.findExecutable("agent-device")) return;
|
|
79
|
+
const status = run(["connection", "status"]);
|
|
80
|
+
if (status?.connected !== true || typeof status.session !== "string" || typeof status.remoteConfig !== "string") return;
|
|
81
|
+
const config = canonical(remoteConfig);
|
|
82
|
+
if (config === null || canonical(status.remoteConfig) !== config) return;
|
|
83
|
+
const failures = [];
|
|
84
|
+
for (const args of [[
|
|
85
|
+
"close",
|
|
86
|
+
"--remote-config",
|
|
87
|
+
remoteConfig,
|
|
88
|
+
"--session",
|
|
89
|
+
status.session
|
|
90
|
+
], [
|
|
91
|
+
"disconnect",
|
|
92
|
+
"--session",
|
|
93
|
+
status.session
|
|
94
|
+
]]) try {
|
|
95
|
+
run(args);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
failures.push(error.message);
|
|
98
|
+
}
|
|
99
|
+
report(failures.length ? `could not clean up agent-device connection for ${status.session}: ${failures.join("; ")}` : `closed agent-device connection for ${status.session}`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
report(`could not clean up agent-device connection: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/device-host/hosted-logs.ts
|
|
106
|
+
const MAX_PAGES = 64;
|
|
107
|
+
const MACOS = {
|
|
108
|
+
platform: "macos",
|
|
109
|
+
slot: "default"
|
|
110
|
+
};
|
|
111
|
+
const logsStateDir = (root, target) => target.platform === "macos" ? macosDir(root) : join(workspaceDir(root), "ios", validateDeviceSlot(target.slot));
|
|
112
|
+
const cursorFile = (root, target) => join(logsStateDir(root, target), "host-logs-cursor.json");
|
|
113
|
+
const hostLogFile = (root, target) => join(workspaceLogsDir$1(root), `${target.platform === "macos" ? "macos" : deviceSlotFileKey("ios", target.slot)}-host.ndjson`);
|
|
114
|
+
function readCursor(root, session, target) {
|
|
115
|
+
try {
|
|
116
|
+
const stored = JSON.parse(readFileSync(cursorFile(root, target), "utf8"));
|
|
117
|
+
if (isJsonObject(stored) && stored.session === session) return parseHostedLogsCursor(stored.cursor) ?? void 0;
|
|
118
|
+
} catch {}
|
|
119
|
+
}
|
|
120
|
+
const sameCursor = (a, b) => JSON.stringify(Object.entries(a ?? {}).toSorted()) === JSON.stringify(Object.entries(b ?? {}).toSorted());
|
|
121
|
+
function commitPage(root, session, before, records, cursor, target) {
|
|
122
|
+
if (!records.length && sameCursor(before, cursor)) return;
|
|
123
|
+
const directory = logsStateDir(root, target);
|
|
124
|
+
withDirLock(join(directory, "host-logs.lock"), () => {
|
|
125
|
+
if (!sameCursor(readCursor(root, session, target), before)) return;
|
|
126
|
+
if (records.length) {
|
|
127
|
+
const writer = createNdjsonWriter(hostLogFile(root, target), { maxBytes: LOG_ROTATE_BYTES });
|
|
128
|
+
try {
|
|
129
|
+
for (const record of records) writer.write(target.platform === "ios" ? {
|
|
130
|
+
...record,
|
|
131
|
+
src: "device",
|
|
132
|
+
platform: "ios",
|
|
133
|
+
slot: target.slot,
|
|
134
|
+
event: "hosted_native_log"
|
|
135
|
+
} : record);
|
|
136
|
+
} finally {
|
|
137
|
+
writer.close();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const temporary = `${cursorFile(root, target)}.${process.pid}.tmp`;
|
|
141
|
+
try {
|
|
142
|
+
writeFileSync(temporary, JSON.stringify({
|
|
143
|
+
session,
|
|
144
|
+
cursor
|
|
145
|
+
}), { mode: 384 });
|
|
146
|
+
renameSync(temporary, cursorFile(root, target));
|
|
147
|
+
} finally {
|
|
148
|
+
rmSync(temporary, { force: true });
|
|
149
|
+
}
|
|
150
|
+
}, { ensureParent: () => mkdirSync(directory, { recursive: true }) });
|
|
151
|
+
}
|
|
152
|
+
async function pullHostedMacosLogs(root, placement, host, target = MACOS, maxPages = MAX_PAGES, final = false) {
|
|
153
|
+
const deadline = Date.now() + (final ? 3e4 : 12e4);
|
|
154
|
+
let checkpoint;
|
|
155
|
+
let progressAt = 0;
|
|
156
|
+
for (let page = 0; page < maxPages && Date.now() < deadline; page++) {
|
|
157
|
+
const before = readCursor(root, placement.session, target);
|
|
158
|
+
if (final && Date.now() >= progressAt) {
|
|
159
|
+
process.stderr.write(`Copying final native logs from ${host.machine} (page ${page + 1})\n`);
|
|
160
|
+
progressAt = Date.now() + 5e3;
|
|
161
|
+
}
|
|
162
|
+
const result = await call(host, "device-host.logs.query", {
|
|
163
|
+
session: placement.session,
|
|
164
|
+
...before ? { cursor: before } : {}
|
|
165
|
+
}, Math.max(1, Math.min(2e4, deadline - Date.now())));
|
|
166
|
+
const cursor = isJsonObject(result) ? parseHostedLogsCursor(result.cursor) : null;
|
|
167
|
+
if (!isJsonObject(result) || !cursor || !Array.isArray(result.records) || !result.records.every(isJsonObject) || typeof result.more !== "boolean") throw new Error(`${host.machine} answered device-host.logs.query with an unexpected result.`);
|
|
168
|
+
commitPage(root, placement.session, before, result.records, cursor, target);
|
|
169
|
+
if (!result.more) return;
|
|
170
|
+
const nextCheckpoint = typeof result.checkpoint === "number" ? result.checkpoint : void 0;
|
|
171
|
+
if (sameCursor(cursor, before) && nextCheckpoint === checkpoint) {
|
|
172
|
+
if (target.platform === "macos" && !final) return;
|
|
173
|
+
throw new Error("Hosted native logs made no progress; keeping the logs already copied here.");
|
|
174
|
+
}
|
|
175
|
+
checkpoint = nextCheckpoint;
|
|
176
|
+
}
|
|
177
|
+
if (target.platform === "ios") throw new Error(final ? "Final native log copy reached its time or page bound; the host keeps collected logs after stop." : "Hosted native logs still have unread pages; run stim logs again.");
|
|
178
|
+
}
|
|
179
|
+
async function pullHostedIosLogs(root, slot, placement, host, final = false) {
|
|
180
|
+
if (!host.connection.supports("hosted-ios-data")) throw new Error(`${host.machine} needs a newer stim-server to collect hosted iOS native logs.`);
|
|
181
|
+
await pullHostedMacosLogs(root, placement, host, {
|
|
182
|
+
platform: "ios",
|
|
183
|
+
slot
|
|
184
|
+
}, final ? 1024 : MAX_PAGES, final);
|
|
185
|
+
}
|
|
186
|
+
//#endregion
|
|
187
|
+
export { agentAccess as i, pullHostedMacosLogs as n, closeAgentConnection as r, pullHostedIosLogs as t };
|