stim 1.14.0 → 1.15.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-CYVDcrt3.mjs → activity-B2-kb7k0.mjs} +281 -41
- package/dist/{android-XpvCrV7q.mjs → android-CYmq0jNi.mjs} +2 -2
- package/dist/{android-LbPFRWVL.mjs → android-Cu_L4RpL.mjs} +502 -407
- package/dist/{android-d6rGq1pA.mjs → android-Db2eEMaf.mjs} +152 -65
- package/dist/android-cas-PnKfAdB2.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CAAplXAt.mjs} +95 -161
- package/dist/{budget-CGDkhvv_.mjs → budget-CMiqghig.mjs} +1038 -73
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-aeGMFX7e.mjs} +193 -653
- package/dist/build-progress-C2Jv1AtA.mjs +872 -0
- package/dist/build-slots-Df1ApSuH.mjs +127 -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-0cX5s4NM.mjs +45 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-C2IS36Qf.mjs +1333 -0
- package/dist/collector-run.mjs +8 -7
- package/dist/{command-output-DVcpw6eE.mjs → command-output-CliHHn7a.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-BwVb3liH.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-BFlKbOIp.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CjzLoNs0.mjs +80 -0
- package/dist/{device-CjuuAJjQ.mjs → device-BZ1IyS5A.mjs} +7 -7
- package/dist/device-capacity-DMuKC7nx.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +769 -0
- package/dist/device-ios-DMQBUPus.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-W5Pi1q38.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-pool-B2r38YjS.mjs} +4 -4
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-9ulPsdhY.mjs} +68 -162
- package/dist/{doctor-x9pnU9fq.mjs → doctor-BcE5xsBn.mjs} +191 -99
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-BuL1Genr.mjs} +429 -101
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-CmdOBq1d.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-cCSGRFd3.mjs} +1959 -222
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CIoWK6mG.mjs} +3 -3
- package/dist/hosted-macos-DwowNU7W.mjs +447 -0
- package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
- package/dist/in-use-CndPMilN.mjs +110 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-3CtCRhwr.mjs} +201 -10
- package/dist/{ios-P1f5nm2n.mjs → ios-Chob8ny7.mjs} +512 -263
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-DERmMmk-.mjs} +12 -7
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-CLlofxRt.mjs} +4 -4
- package/dist/{logs-Civ4gbBH.mjs → logs-h9h6MUvE.mjs} +95 -13
- package/dist/machine-usage-D_YYMzfR.mjs +283 -0
- package/dist/machines-CYtOCqcj.mjs +259 -0
- package/dist/macos-BwtbUtTN.mjs +468 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
- package/dist/{metro-CcEo8Fl8.mjs → metro-Di8EN2Tb.mjs} +20 -120
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Dj_XBNaL.mjs} +6 -59
- package/dist/native-run-DFDoIRbY.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-i6OkVg-w.mjs} +9 -5
- package/dist/offload-worker.d.mts +121 -0
- package/dist/offload-worker.mjs +601 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-BkYxUtuj.mjs} +3 -2
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-DddwttKM.mjs} +3 -3
- package/dist/prebuild-BvxDlD6C.mjs +213 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-CV3tDFHl.mjs} +9 -18
- 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-GEXp4iBT.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-ChPiwvot.mjs} +70 -22
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-CO2elm_b.mjs} +4 -4
- package/dist/run-B-xqRrsg.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-X7PUgizL.mjs} +33 -15
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-DhtX6bom.mjs} +6 -10
- package/dist/server-expo-KJeD109I.mjs +2 -0
- package/dist/{settings-D61iNOsB.mjs → settings-CduVcoxj.mjs} +77 -26
- package/dist/{settings-B-fsC-Om.mjs → settings-DbL6uhH8.mjs} +16 -10
- package/dist/settings.schema.json +357 -2
- package/dist/{simslim-DrDN4WuV.mjs → simslim-Ciluxlq4.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-BY0BrOLh.mjs} +6 -6
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-DncpJvWL.mjs +16 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CPO4wRg7.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-CUD9nasA.mjs} +108 -61
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/state-BxCgeTQu.mjs +41 -0
- package/dist/{state-DHqvsbqC.mjs → state-CaeaHbDA.mjs} +7 -4
- package/dist/{state-Dk9RVXI7.mjs → state-DFm77wRn.mjs} +67 -5
- package/dist/stats-DBLLFmKd.mjs +66 -0
- package/dist/{status-BsPAnfOI.mjs → status-DYXxNOT0.mjs} +423 -268
- package/dist/{status-BgAuxESg.mjs → status-l84Gbvp2.mjs} +116 -20
- 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-BzreOWJp.mjs → stop-Bad61z_2.mjs} +38 -12
- package/dist/stop-Bvf1MflS.mjs +48 -0
- package/dist/{stop-BWIF_6BK.mjs → stop-CceYh1yG.mjs} +2 -2
- package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +227 -19
- package/dist/support-CDQbDkL5.mjs +315 -0
- package/dist/support-WLOzVKUv.mjs +176 -0
- package/dist/teardown-BYuQafiA.mjs +718 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DmeYjXD9.mjs} +363 -13
- package/dist/warm-progress-DVHLN4uC.mjs +463 -0
- package/dist/{idle-DXtx3RbX.mjs → watchman-Bx0wceoF.mjs} +55 -8
- package/dist/{web-BsQs1WlO.mjs → web-k6w8-HnE.mjs} +37 -20
- package/dist/web-run.mjs +309 -12
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-CL2V2Ons.mjs +371 -0
- package/dist/worktree-BT_uULFx.mjs +2 -0
- package/dist/{worktree-BVrHsYZG.mjs → worktree-C0c5Udwa.mjs} +55 -54
- package/dist/{worktree-UrQWXSeb.mjs → worktree-qXKzADSC.mjs} +154 -252
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- 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-CHWWvm5v.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,32 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
+
import { B as parseDeviceSlotKey, F as volumeRootFor, H as projectDeviceSlots, R as deviceSlotKey, _ as loadConfig$1, d as getConfigDir } from "./config-D0DbpZtM.mjs";
|
|
3
|
+
import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
3
4
|
import { a as parseNdjsonLine } from "./ndjson-CDY9IkVC.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { c as gitCommonDirOnDisk, p as linkedWorktreesOnDisk, s as gitCommonDir, y as repoRoot } from "./worktree-
|
|
6
|
-
import { l as projectShortcut, o as findServerWorkspace, r as detectIsExpo } from "./project-
|
|
7
|
-
import { f as readWorkspaceLaunches, u as readRemoteSession } from "./state-
|
|
8
|
-
import {
|
|
9
|
-
import { i as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { a as readWebRecord,
|
|
14
|
-
import {
|
|
15
|
-
import { a as buildStatusLine, i as buildReport, r as activeBuildState, s as parseActiveBuild, t as ACTIVE_BUILD_KEY } from "./build-progress-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { i as
|
|
5
|
+
import { C as workspaceLogsDir, S as workspaceLogErrorIndex, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
|
|
6
|
+
import { c as gitCommonDirOnDisk, p as linkedWorktreesOnDisk, s as gitCommonDir, y as repoRoot } from "./worktree-C0c5Udwa.mjs";
|
|
7
|
+
import { l as projectShortcut, o as findServerWorkspace, r as detectIsExpo } from "./project-CV3tDFHl.mjs";
|
|
8
|
+
import { c as readMetroTunnel, f as readWorkspaceLaunches, u as readRemoteSession } from "./state-CaeaHbDA.mjs";
|
|
9
|
+
import { c as tailLines, n as createActivityReader, r as createDeviceProcessTables, s as readWebActivity } from "./activity-B2-kb7k0.mjs";
|
|
10
|
+
import { a as formatElapsed, i as formatDuration, r as clockTime } from "./command-output-CliHHn7a.mjs";
|
|
11
|
+
import { i as listLeaseFiles, r as leaseIsExpired, s as parseWorkspaceLeases } from "./device-lease-W5Pi1q38.mjs";
|
|
12
|
+
import { F as ownedAvdDeviceProfile, O as listAdbDevices, at as POOL_SETTING_REMEDY, d as androidToolPath, dt as parkedMaxSetting, ft as readParked, z as ownedAvdSerialResolver } from "./android-Db2eEMaf.mjs";
|
|
13
|
+
import { a as listeningPidsByPort, c as processCwds, d as isMetroRunning, i as listeningPids, l as resolveProjectMetro, o as pidExists, s as processCwd } from "./metro-Di8EN2Tb.mjs";
|
|
14
|
+
import { a as readWebPage, o as readWebRecord, u as webFacts } from "./state-DFm77wRn.mjs";
|
|
15
|
+
import { d as listAllIosSimsAsync, v as parseSimctlList } from "./ios-3CtCRhwr.mjs";
|
|
16
|
+
import { a as buildStatusLine, h as statsProjectKey$1, i as buildReport, p as readStats, r as activeBuildState, s as parseActiveBuild, t as ACTIVE_BUILD_KEY } from "./build-progress-C2Jv1AtA.mjs";
|
|
17
|
+
import { n as describeMetroLastStop, o as metroLastStop, u as resolveSupervisorTarget } from "./stop-cause-CZ_Mr-v9.mjs";
|
|
18
|
+
import { m as hasMetroReverse } from "./app-install-CAAplXAt.mjs";
|
|
19
|
+
import { r as probeHostedMacos } from "./hosted-macos-DwowNU7W.mjs";
|
|
20
|
+
import { m as readEasSessionLedger$1, p as easMachineStateRoot$1 } from "./device-remote-9ulPsdhY.mjs";
|
|
21
|
+
import { _ as statusActivity, a as capacity, b as withWebFacts, c as diskLine, d as gitSummaryText, f as metroReverseTargets, g as remoteDeviceState, h as remoteDeviceLine, i as applyMachineMemory, l as environmentState, m as poolLine, n as activityLabel, o as deviceLeaseLines, p as parseDfFree, r as addReverseMissingIssue, s as deviceLeaseStates, v as tightVolumes, y as unprovisionedWorktrees } from "./status-l84Gbvp2.mjs";
|
|
22
|
+
import { n as attributeMachineUsage, r as readFootprints } from "./machine-usage-D_YYMzfR.mjs";
|
|
23
|
+
import { d as readIosDevices, s as isReachable } from "./ios-device-DERmMmk-.mjs";
|
|
24
|
+
import { n as mergeState } from "./merge-state-BnxLNQiM.mjs";
|
|
25
|
+
import { i as createStatusMeasurer, n as workspacePhase, r as applyStatusMeasures } from "./warm-progress-DVHLN4uC.mjs";
|
|
26
|
+
import { t as watchStdoutReader } from "./stdout-reader-C8yjre1x.mjs";
|
|
27
|
+
import { r as workspaceRecordingEnabled } from "./recordings-GEXp4iBT.mjs";
|
|
28
|
+
import { i as readSupervisorState } from "./stop-Bad61z_2.mjs";
|
|
29
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
21
30
|
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
31
|
+
import { join, posix, resolve } from "node:path";
|
|
22
32
|
import { closeSync as closeSync$1, existsSync as existsSync$1, fstatSync as fstatSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readSync as readSync$1, renameSync as renameSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
23
|
-
import { basename, dirname, join } from "path";
|
|
24
|
-
import { rotatedLogPath } from "@stim-cli/core";
|
|
25
|
-
import { ERROR_SOURCES, MACHINE_OWNER_KINDS, gitMergeCacheDir, logFiles, queryLogs, readBuildHistory, readIdleStop, readLastBuilds, recordMatches, withStateReadCache } from "@stim-cli/core/state";
|
|
26
|
-
import { join as join$1, posix, resolve as resolve$1 } from "node:path";
|
|
27
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
28
33
|
import { homedir, totalmem } from "os";
|
|
34
|
+
import { basename as basename$1, dirname as dirname$1, join as join$1 } from "path";
|
|
35
|
+
import { rotatedLogPath } from "@stim-cli/core";
|
|
36
|
+
import { ERROR_SOURCES, bundleMarkerPlatform, countErrorEntries, gitMergeCacheDir, logFiles, macosAppState, queryLogs, readBuildDetail, readBuildHistory, readDeviceIdleShutdowns, readIdleStop, readLastBuilds, readMacosRecord, recordMatches, sortByTs, withDerivedFacts, withGitChip, withStateReadCache } from "@stim-cli/core/state";
|
|
29
37
|
import chalk from "chalk";
|
|
38
|
+
//#region src/device-host/hosted-macos-status.ts
|
|
39
|
+
function applyHostedMacosProbe(record, probe) {
|
|
40
|
+
if (!record.host || probe.state === "ready") return { record };
|
|
41
|
+
const { machine, session } = record.host;
|
|
42
|
+
if (probe.state === "stopped") return {
|
|
43
|
+
record: {
|
|
44
|
+
...record,
|
|
45
|
+
hostLaunched: false
|
|
46
|
+
},
|
|
47
|
+
warning: `The macOS app on ${machine} is no longer running (the host reports session ${session} stopped or no longer holds it, for example after a stim-server restart there). Run stim macos --remote ${machine} to launch it again, or stim stop to clear the placement.`
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
record: {
|
|
51
|
+
...record,
|
|
52
|
+
hostLaunched: "unverified"
|
|
53
|
+
},
|
|
54
|
+
warning: probe.state === "unknown" ? `${probe.notice ?? `The host cannot confirm session ${session} on ${machine}.`} Run stim stop to reconcile it.` : `${machine} could not be checked (${probe.reason}), so the macOS app there is unverified. The placement stays recorded; run stim stop when the host answers.`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function readHostedMacosStatus(record, options) {
|
|
58
|
+
if (!record?.host || record.supervisor || !record.hostLaunched) return { record };
|
|
59
|
+
return applyHostedMacosProbe(record, await probeHostedMacos(record.host, options));
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
30
62
|
//#region src/status-watch.ts
|
|
31
63
|
const WATCH_FALLBACK_MS = 3e4;
|
|
32
64
|
const WATCH_LIGHT_INTERVAL_MS = 15e3;
|
|
@@ -88,9 +120,9 @@ function createRefreshScheduler({ debounceMs, lightIntervalMs = 0, run }) {
|
|
|
88
120
|
};
|
|
89
121
|
}
|
|
90
122
|
/**
|
|
91
|
-
* Which refresh a change to `name` in a watched `$STIM_HOME` directory needs: `light` for a log append
|
|
92
|
-
* change only the log-derived fields, `full` for anything else that can change the payload,
|
|
93
|
-
* name means the platform did not report one.
|
|
123
|
+
* Which refresh a change to `name` in a watched `$STIM_HOME` directory needs: `light` for a log append or a running
|
|
124
|
+
* build's detail, which can change only the log-derived fields, `full` for anything else that can change the payload,
|
|
125
|
+
* null for none. A null name means the platform did not report one.
|
|
94
126
|
*/
|
|
95
127
|
function statusChange(dir, name) {
|
|
96
128
|
if (name?.includes(".lock")) return null;
|
|
@@ -98,7 +130,9 @@ function statusChange(dir, name) {
|
|
|
98
130
|
if (name === null) return "full";
|
|
99
131
|
switch (dir) {
|
|
100
132
|
case "home": return name.startsWith("config.json") || name === "workspaces" || name === "device-locks" ? "full" : null;
|
|
101
|
-
case "workspace":
|
|
133
|
+
case "workspace":
|
|
134
|
+
if (name.startsWith("build-detail.json")) return "light";
|
|
135
|
+
return name.startsWith("state.json") || name.startsWith("ended-agents.json") || name === "logs" ? "full" : null;
|
|
102
136
|
case "eas": return name.startsWith("sessions.json") ? "full" : null;
|
|
103
137
|
default: return "full";
|
|
104
138
|
}
|
|
@@ -122,18 +156,18 @@ function watchStatusSources({ home, onChange, platform = process.platform, simct
|
|
|
122
156
|
const desiredDirs = () => {
|
|
123
157
|
const dirs = /* @__PURE__ */ new Map([
|
|
124
158
|
[home, "home"],
|
|
125
|
-
[join
|
|
126
|
-
[join
|
|
159
|
+
[join(home, "workspaces"), "workspaces"],
|
|
160
|
+
[join(home, "device-locks"), "leases"],
|
|
127
161
|
[easMachineStateRoot$1(), "eas"]
|
|
128
162
|
]);
|
|
129
163
|
let names = [];
|
|
130
164
|
try {
|
|
131
|
-
names = readdirSync(join
|
|
165
|
+
names = readdirSync(join(home, "workspaces"));
|
|
132
166
|
} catch {}
|
|
133
167
|
for (const name of names) {
|
|
134
|
-
const dir = join
|
|
168
|
+
const dir = join(home, "workspaces", name);
|
|
135
169
|
dirs.set(dir, "workspace");
|
|
136
|
-
dirs.set(join
|
|
170
|
+
dirs.set(join(dir, "logs"), "logs");
|
|
137
171
|
}
|
|
138
172
|
return dirs;
|
|
139
173
|
};
|
|
@@ -266,7 +300,7 @@ function trackAdbDevices(onChange) {
|
|
|
266
300
|
}
|
|
267
301
|
//#endregion
|
|
268
302
|
//#region src/diagnostics/error-index.ts
|
|
269
|
-
const INDEX_VERSION =
|
|
303
|
+
const INDEX_VERSION = 7;
|
|
270
304
|
const HEAD_BYTES = 1024;
|
|
271
305
|
const TAIL_BYTES = 256;
|
|
272
306
|
const CHUNK_BYTES = 4194304;
|
|
@@ -282,7 +316,7 @@ const ERROR_CRITERIA = {
|
|
|
282
316
|
includeAppDeviceErrors: true
|
|
283
317
|
};
|
|
284
318
|
/**
|
|
285
|
-
* The number of
|
|
319
|
+
* The number of rows the apps list for `queryLogs({ dir, errorsOnly: true })`, one per failure, without parsing every
|
|
286
320
|
* line on every call. `indexFile` keeps, per log file generation, the markers and error records
|
|
287
321
|
* already read and the byte offset they were read through; a later call reads only what was
|
|
288
322
|
* appended since. The index is a cache: losing it or a concurrent writer only costs a rescan.
|
|
@@ -292,7 +326,7 @@ function countErrorsSinceMarker(dir, indexFile) {
|
|
|
292
326
|
const files = {};
|
|
293
327
|
let changed = false;
|
|
294
328
|
for (const name of logFiles(dir)) {
|
|
295
|
-
const current = join(dir, name);
|
|
329
|
+
const current = join$1(dir, name);
|
|
296
330
|
for (const path of [rotatedLogPath(current), current]) {
|
|
297
331
|
const result = summarize(path, previous.files);
|
|
298
332
|
if (!result) continue;
|
|
@@ -306,10 +340,10 @@ function countErrorsSinceMarker(dir, indexFile) {
|
|
|
306
340
|
});
|
|
307
341
|
const records = [];
|
|
308
342
|
for (const summary of Object.values(files)) records.push(...Object.values(summary.markers), ...summary.errors);
|
|
309
|
-
return queryLogs({
|
|
343
|
+
return countErrorEntries(sortByTs(queryLogs({
|
|
310
344
|
records,
|
|
311
345
|
errorsOnly: true
|
|
312
|
-
})
|
|
346
|
+
})));
|
|
313
347
|
}
|
|
314
348
|
function summarize(path, known) {
|
|
315
349
|
let fd;
|
|
@@ -401,11 +435,17 @@ function collect(record, markers, errors) {
|
|
|
401
435
|
const metro = record.src === "metro";
|
|
402
436
|
const web = !metro && record.platform === "web";
|
|
403
437
|
const slot = record.slot ?? "default";
|
|
404
|
-
const
|
|
438
|
+
const platform = metro ? bundleMarkerPlatform(record) : record.platform ?? null;
|
|
439
|
+
const key = web ? "web" : JSON.stringify([
|
|
440
|
+
metro ? "metro" : "launch",
|
|
441
|
+
slot,
|
|
442
|
+
platform
|
|
443
|
+
]);
|
|
405
444
|
if ((markers[key]?.ts ?? -Infinity) < ts) markers[key] = metro ? {
|
|
406
445
|
src: "metro",
|
|
407
446
|
marker: true,
|
|
408
|
-
ts
|
|
447
|
+
ts,
|
|
448
|
+
...platform === null ? {} : { platform }
|
|
409
449
|
} : web ? {
|
|
410
450
|
platform: "web",
|
|
411
451
|
marker: true,
|
|
@@ -413,16 +453,20 @@ function collect(record, markers, errors) {
|
|
|
413
453
|
} : {
|
|
414
454
|
marker: true,
|
|
415
455
|
ts,
|
|
416
|
-
slot
|
|
456
|
+
slot,
|
|
457
|
+
...platform === null ? {} : { platform }
|
|
417
458
|
};
|
|
418
459
|
}
|
|
419
460
|
if (recordMatches(record, ERROR_CRITERIA)) errors.push({
|
|
420
461
|
src: record.src,
|
|
421
462
|
level: record.level,
|
|
422
463
|
event: record.event,
|
|
423
|
-
platform: record.platform,
|
|
464
|
+
platform: record.src === "metro" && record.marker === true ? bundleMarkerPlatform(record) : record.platform,
|
|
424
465
|
ts: ts ?? void 0,
|
|
425
|
-
slot: record.slot
|
|
466
|
+
slot: record.slot,
|
|
467
|
+
raw: record.raw,
|
|
468
|
+
marker: record.marker,
|
|
469
|
+
msg: record.marker === true ? record.msg : void 0
|
|
426
470
|
});
|
|
427
471
|
}
|
|
428
472
|
function readIndex(file) {
|
|
@@ -513,7 +557,7 @@ async function defaultBranchOf(repository) {
|
|
|
513
557
|
};
|
|
514
558
|
}
|
|
515
559
|
function mergeCacheFile(path) {
|
|
516
|
-
return join
|
|
560
|
+
return join(gitMergeCacheDir(), `${createHash("sha256").update(path).digest("hex").slice(0, 32)}.json`);
|
|
517
561
|
}
|
|
518
562
|
function readMergeCache(path) {
|
|
519
563
|
try {
|
|
@@ -587,14 +631,14 @@ const mergeTimeouts = /* @__PURE__ */ new Map();
|
|
|
587
631
|
const recent = /* @__PURE__ */ new Map();
|
|
588
632
|
function gitDirsOf(worktree) {
|
|
589
633
|
try {
|
|
590
|
-
const dotGit = join
|
|
634
|
+
const dotGit = join(worktree, ".git");
|
|
591
635
|
const pointer = statSync(dotGit).isDirectory() ? null : /^gitdir: (.+)$/m.exec(readFileSync(dotGit, "utf-8"))?.[1]?.trim();
|
|
592
|
-
const gitDir = pointer ? resolve
|
|
636
|
+
const gitDir = pointer ? resolve(worktree, pointer) : pointer === null ? dotGit : null;
|
|
593
637
|
if (!gitDir) return null;
|
|
594
638
|
try {
|
|
595
639
|
return {
|
|
596
640
|
gitDir,
|
|
597
|
-
commonDir: resolve
|
|
641
|
+
commonDir: resolve(gitDir, readFileSync(join(gitDir, "commondir"), "utf-8").trim())
|
|
598
642
|
};
|
|
599
643
|
} catch {
|
|
600
644
|
return {
|
|
@@ -623,21 +667,21 @@ function fileStamp(path) {
|
|
|
623
667
|
function gitFilesStamp(worktree, refs) {
|
|
624
668
|
const dirs = gitDirsOf(worktree.path);
|
|
625
669
|
if (!dirs) return {
|
|
626
|
-
stamp: fileStamp(join
|
|
670
|
+
stamp: fileStamp(join(worktree.path, ".git")),
|
|
627
671
|
missing: true
|
|
628
672
|
};
|
|
629
673
|
const { gitDir, commonDir } = dirs;
|
|
630
674
|
return {
|
|
631
675
|
stamp: [
|
|
632
|
-
join
|
|
633
|
-
join
|
|
634
|
-
join
|
|
635
|
-
join
|
|
636
|
-
join
|
|
637
|
-
join
|
|
638
|
-
...refs.target ? [join
|
|
639
|
-
...worktree.branch ? [join
|
|
640
|
-
...refs.upstream ? [join
|
|
676
|
+
join(gitDir, "index"),
|
|
677
|
+
join(gitDir, "HEAD"),
|
|
678
|
+
join(gitDir, "logs", "HEAD"),
|
|
679
|
+
join(commonDir, "packed-refs"),
|
|
680
|
+
join(commonDir, "FETCH_HEAD"),
|
|
681
|
+
join(commonDir, "refs", "remotes", "origin", "HEAD"),
|
|
682
|
+
...refs.target ? [join(commonDir, refs.target)] : [],
|
|
683
|
+
...worktree.branch ? [join(commonDir, "refs", "heads", worktree.branch)] : [],
|
|
684
|
+
...refs.upstream ? [join(commonDir, "refs", "remotes", refs.upstream), join(commonDir, "refs", "heads", refs.upstream)] : []
|
|
641
685
|
].map(fileStamp).join(" "),
|
|
642
686
|
missing: false
|
|
643
687
|
};
|
|
@@ -753,7 +797,7 @@ function plistBundleId(appPath) {
|
|
|
753
797
|
"raw",
|
|
754
798
|
"-o",
|
|
755
799
|
"-",
|
|
756
|
-
join
|
|
800
|
+
join(appPath, "Info.plist")
|
|
757
801
|
], { timeoutMs: 2e3 })?.trim() || null;
|
|
758
802
|
if (id) bundleIdsByApp.set(appPath, id);
|
|
759
803
|
return id;
|
|
@@ -777,196 +821,155 @@ function createAppProcessReader(tables, bundleIdOf = plistBundleId) {
|
|
|
777
821
|
};
|
|
778
822
|
}
|
|
779
823
|
//#endregion
|
|
780
|
-
//#region src/
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
{
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
name: "Android emulator services",
|
|
792
|
-
pattern: /\/emulator\/netsimd(\s|$)/
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
name: "Gradle daemon",
|
|
796
|
-
pattern: /\borg\.gradle\.launcher\.daemon\.bootstrap\.GradleDaemon\b/
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
name: "Kotlin daemon",
|
|
800
|
-
pattern: /\bKotlinCompileDaemon\b/
|
|
801
|
-
},
|
|
802
|
-
{
|
|
803
|
-
name: "Watchman",
|
|
804
|
-
pattern: /(^|\/)watchman(\s|$)/
|
|
824
|
+
//#region src/metro-bundle.ts
|
|
825
|
+
const IN_FLIGHT_MAX_MS = 6e5;
|
|
826
|
+
const RESTART_EVENTS = /* @__PURE__ */ new Set(["supervisor_started", "server_started"]);
|
|
827
|
+
const BUNDLE_EVENT = /^bundle_(?:response|prefetch)_(started|progress|finished|failed)$/;
|
|
828
|
+
function parseRecord(line) {
|
|
829
|
+
if (!line.includes("\"bundle_") && !line.includes("_started\"")) return null;
|
|
830
|
+
try {
|
|
831
|
+
const value = JSON.parse(line);
|
|
832
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
805
835
|
}
|
|
806
|
-
];
|
|
807
|
-
const SERVER = /(^|[\s/])stim-server(\.mjs)?(\s|$)/;
|
|
808
|
-
const LAUNCHD_SIM = /^launchd_sim\s.*\/Devices\/([^/\s]+)\//;
|
|
809
|
-
const MAX_DEPTH = 64;
|
|
810
|
-
const EMULATOR = /(?:^|\/)(?:qemu-system-[^\s/]+|emulator)(?:\s.*)?\s(?:-avd\s+|@)(\S+)/;
|
|
811
|
-
function emulatorAvd(command) {
|
|
812
|
-
return EMULATOR.exec(command)?.[1] ?? null;
|
|
813
|
-
}
|
|
814
|
-
function workspaceOwner(kind, env, name, id) {
|
|
815
|
-
return {
|
|
816
|
-
kind,
|
|
817
|
-
name,
|
|
818
|
-
workspace: env.path,
|
|
819
|
-
id,
|
|
820
|
-
owned: true
|
|
821
|
-
};
|
|
822
836
|
}
|
|
823
|
-
function
|
|
824
|
-
return
|
|
837
|
+
function platformOf(value) {
|
|
838
|
+
return value === "ios" || value === "android" ? value : null;
|
|
825
839
|
}
|
|
826
840
|
/**
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
841
|
+
* The bundle state of a workspace's Metro from the tail of its `metro.ndjson`: the records Stim's middleware writes
|
|
842
|
+
* for each native bundle request, an app's (`bundle_response_*`) or Stim's own prefetch (`bundle_prefetch_*`),
|
|
843
|
+
* matched by `requestId`. A request counts as in flight only while
|
|
844
|
+
* Metro runs, when it started under the current dev server and less than 10 minutes ago, since a dev server that
|
|
845
|
+
* died mid-request never records its end. Null when the tail holds no bundle request.
|
|
832
846
|
*/
|
|
833
|
-
function
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
slot: "default",
|
|
845
|
-
ios: env.ios,
|
|
846
|
-
android: env.android
|
|
847
|
-
}, ...env.slots ?? []]) {
|
|
848
|
-
const slot = device.slot === "default" ? {} : { slot: device.slot };
|
|
849
|
-
if (device.ios?.state === "Booted") sims.set(device.ios.udid.toUpperCase(), {
|
|
850
|
-
kind: "simulator",
|
|
851
|
-
name: device.ios.name ?? device.ios.udid,
|
|
852
|
-
workspace: env.path,
|
|
853
|
-
...slot,
|
|
854
|
-
id: device.ios.udid,
|
|
855
|
-
owned: device.ios.owned
|
|
856
|
-
});
|
|
857
|
-
if (device.android?.name && !device.android.physical && device.android.serial) emulators.set(device.android.name, {
|
|
858
|
-
kind: "emulator",
|
|
859
|
-
name: device.android.name,
|
|
860
|
-
workspace: env.path,
|
|
861
|
-
...slot,
|
|
862
|
-
id: device.android.name,
|
|
863
|
-
owned: device.android.owned
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
for (const p of processes) {
|
|
867
|
-
const shared = SHARED.find(({ pattern }) => pattern.test(p.command));
|
|
868
|
-
if (shared) {
|
|
869
|
-
const owner = {
|
|
870
|
-
kind: "shared",
|
|
871
|
-
name: shared.name,
|
|
872
|
-
workspace: null,
|
|
873
|
-
id: null,
|
|
874
|
-
owned: false
|
|
875
|
-
};
|
|
876
|
-
claim(p.pid, `shared:${shared.name}`, owner, fallbackOf);
|
|
847
|
+
function metroBundleState(lines, { running, now }) {
|
|
848
|
+
let open = /* @__PURE__ */ new Map();
|
|
849
|
+
let last;
|
|
850
|
+
let seen = false;
|
|
851
|
+
for (const line of lines) {
|
|
852
|
+
const record = parseRecord(line);
|
|
853
|
+
if (!record) continue;
|
|
854
|
+
const { event, requestId, ts } = record;
|
|
855
|
+
if (typeof event !== "string") continue;
|
|
856
|
+
if (RESTART_EVENTS.has(event)) {
|
|
857
|
+
open = /* @__PURE__ */ new Map();
|
|
877
858
|
continue;
|
|
878
859
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
const avd = emulatorAvd(p.command);
|
|
902
|
-
if (avd) {
|
|
903
|
-
const owner = emulators.get(avd) ?? {
|
|
904
|
-
kind: "emulator",
|
|
905
|
-
name: avd,
|
|
906
|
-
workspace: null,
|
|
907
|
-
id: avd,
|
|
908
|
-
owned: false
|
|
860
|
+
const platform = platformOf(record.platform);
|
|
861
|
+
const stage = BUNDLE_EVENT.exec(event)?.[1];
|
|
862
|
+
if (!stage || typeof requestId !== "string" || typeof ts !== "number" || !platform) continue;
|
|
863
|
+
seen = true;
|
|
864
|
+
if (stage === "started") open.set(requestId, {
|
|
865
|
+
platform,
|
|
866
|
+
startedAt: ts
|
|
867
|
+
});
|
|
868
|
+
else if (stage === "progress") {
|
|
869
|
+
const request = open.get(requestId);
|
|
870
|
+
const percent = record.percent;
|
|
871
|
+
if (request && typeof percent === "number" && percent >= 0 && percent <= 100) request.percent = percent;
|
|
872
|
+
} else {
|
|
873
|
+
const request = open.get(requestId);
|
|
874
|
+
open.delete(requestId);
|
|
875
|
+
if (!request) continue;
|
|
876
|
+
last = {
|
|
877
|
+
platform,
|
|
878
|
+
status: stage === "finished" ? "ok" : "failed",
|
|
879
|
+
durationMs: Math.max(0, ts - request.startedAt),
|
|
880
|
+
finishedAt: new Date(ts).toISOString()
|
|
909
881
|
};
|
|
910
|
-
claim(p.pid, `emulator:${avd}`, owner);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const present = new Set(processes.map((p) => p.pid));
|
|
914
|
-
const byPath = new Map(environments.map((env) => [env.path, env]));
|
|
915
|
-
for (const root of roots) {
|
|
916
|
-
const env = byPath.get(root.path);
|
|
917
|
-
if (!env) continue;
|
|
918
|
-
const metroPids = [root.supervisorPid, env.metro?.running ? env.metro.pid : null];
|
|
919
|
-
for (const pid of metroPids) if (pid && present.has(pid)) {
|
|
920
|
-
const port = env.metro?.port ?? null;
|
|
921
|
-
claim(pid, `metro:${env.path}`, workspaceOwner("metro", env, port ? `Metro :${port}` : "Metro", port ? String(port) : null));
|
|
922
|
-
}
|
|
923
|
-
if (root.build && present.has(root.build.pid)) {
|
|
924
|
-
const label = root.build.platform === "ios" ? "iOS build" : "Android build";
|
|
925
|
-
claim(root.build.pid, `build:${env.path}`, workspaceOwner("build", env, label, root.build.platform));
|
|
926
|
-
}
|
|
927
|
-
for (const pid of root.browserPids) if (present.has(pid)) claim(pid, `browser:${env.path}`, workspaceOwner("browser", env, "Chrome", null));
|
|
928
|
-
}
|
|
929
|
-
const parentOf = new Map(processes.map((p) => [p.pid, p.ppid]));
|
|
930
|
-
const totals = /* @__PURE__ */ new Map();
|
|
931
|
-
for (const p of processes) {
|
|
932
|
-
let pid = p.pid;
|
|
933
|
-
let key;
|
|
934
|
-
let fallback;
|
|
935
|
-
for (let depth = 0; pid !== void 0 && pid > 1 && depth < MAX_DEPTH; depth++) {
|
|
936
|
-
key = rootOf.get(pid);
|
|
937
|
-
if (key) break;
|
|
938
|
-
fallback ??= fallbackOf.get(pid);
|
|
939
|
-
pid = parentOf.get(pid);
|
|
940
882
|
}
|
|
941
|
-
key ??= fallback;
|
|
942
|
-
if (!key) continue;
|
|
943
|
-
const total = totals.get(key) ?? {
|
|
944
|
-
rssKb: 0,
|
|
945
|
-
cpu: 0,
|
|
946
|
-
processes: 0
|
|
947
|
-
};
|
|
948
|
-
total.rssKb += p.rssKb;
|
|
949
|
-
total.cpu += p.cpuPercent;
|
|
950
|
-
total.processes += 1;
|
|
951
|
-
totals.set(key, total);
|
|
952
883
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
884
|
+
if (!seen) return null;
|
|
885
|
+
const active = running ? [...open.values()].filter((request) => now - request.startedAt < IN_FLIGHT_MAX_MS).toSorted((a, b) => b.startedAt - a.startedAt)[0] : void 0;
|
|
886
|
+
return {
|
|
887
|
+
bundling: Boolean(active),
|
|
888
|
+
...active ? {
|
|
889
|
+
platform: active.platform,
|
|
890
|
+
startedAt: new Date(active.startedAt).toISOString(),
|
|
891
|
+
...active.percent === void 0 ? {} : { percent: active.percent }
|
|
892
|
+
} : {},
|
|
893
|
+
...last ? { last } : {}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/physical-devices.ts
|
|
898
|
+
const EMULATOR_SERIAL = /^emulator-\d+$/;
|
|
899
|
+
/**
|
|
900
|
+
* The physical devices a workspace holds: each lease its state records whose file still carries the same token,
|
|
901
|
+
* names this workspace as holder, and has not expired. A lease on a simulator (a UDID in `simulatorUdids`) or an
|
|
902
|
+
* emulator (an `emulator-<port>` serial), which `device lock` also takes, is not a physical device. `read` is
|
|
903
|
+
* called once per device.
|
|
904
|
+
*/
|
|
905
|
+
function physicalDeviceStates(entries, held, { root, now, simulatorUdids, read }) {
|
|
906
|
+
const out = [];
|
|
907
|
+
for (const [key, record] of Object.entries(held)) {
|
|
908
|
+
const parsed = parseDeviceSlotKey(key);
|
|
909
|
+
const lease = entries.find((entry) => entry.lease?.token === record.token)?.lease;
|
|
910
|
+
if (!parsed || !lease || lease.platform !== parsed.platform || lease.id !== record.id) continue;
|
|
911
|
+
if (lease.holder !== root || leaseIsExpired(lease, now)) continue;
|
|
912
|
+
if (parsed.platform === "ios" ? simulatorUdids.has(lease.id.toUpperCase()) : EMULATOR_SERIAL.test(lease.id)) continue;
|
|
913
|
+
const reading = read(parsed.platform, lease.id);
|
|
914
|
+
out.push({
|
|
915
|
+
platform: parsed.platform,
|
|
916
|
+
slot: parsed.slot,
|
|
917
|
+
id: lease.id,
|
|
918
|
+
name: reading.name ?? lease.deviceName,
|
|
919
|
+
model: reading.model ?? (parsed.platform === "android" && lease.deviceName !== lease.id ? lease.deviceName : null),
|
|
920
|
+
owned: false,
|
|
921
|
+
physical: true,
|
|
922
|
+
connection: reading.connection,
|
|
923
|
+
lease: {
|
|
924
|
+
holder: lease.holder,
|
|
925
|
+
kind: record.kind,
|
|
926
|
+
grantedAt: lease.grantedAt,
|
|
927
|
+
expiresAt: lease.expiresAt
|
|
928
|
+
}
|
|
961
929
|
});
|
|
962
930
|
}
|
|
963
|
-
|
|
964
|
-
|
|
931
|
+
return out;
|
|
932
|
+
}
|
|
933
|
+
/** A phone's reading from devicectl's device list; null `devices` means devicectl could not be read. */
|
|
934
|
+
function iosPhysicalReading(devices, udid) {
|
|
935
|
+
if (!devices) return {
|
|
936
|
+
name: null,
|
|
937
|
+
model: null,
|
|
938
|
+
connection: "unknown"
|
|
939
|
+
};
|
|
940
|
+
const device = devices.find((entry) => entry.udid.toUpperCase() === udid.toUpperCase());
|
|
941
|
+
if (!device) return {
|
|
942
|
+
name: null,
|
|
943
|
+
model: null,
|
|
944
|
+
connection: "disconnected"
|
|
945
|
+
};
|
|
946
|
+
return {
|
|
947
|
+
name: device.name,
|
|
948
|
+
model: device.model ?? null,
|
|
949
|
+
connection: isReachable(device) ? "connected" : "disconnected"
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* A phone's reading from `adb devices`; null `devices` means adb could not be read. `android --device` and `device lock`
|
|
954
|
+
* record the phone's model as the lease's device name, so the model comes from there.
|
|
955
|
+
*/
|
|
956
|
+
function androidPhysicalReading(devices, serial) {
|
|
957
|
+
return {
|
|
958
|
+
name: null,
|
|
959
|
+
model: null,
|
|
960
|
+
connection: !devices ? "unknown" : devices.physical.some((entry) => entry.serial === serial) ? "connected" : "disconnected"
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function physicalDeviceLine(device, now) {
|
|
964
|
+
const slot = device.slot === "default" ? "" : ` [${device.slot}]`;
|
|
965
|
+
const name = device.name ?? device.id;
|
|
966
|
+
const model = device.model && device.model !== name ? `, ${device.model}` : "";
|
|
967
|
+
const remaining = formatElapsed(Date.parse(device.lease.expiresAt) - now);
|
|
968
|
+
return `${device.platform}${slot}: ${name} (physical${model}) ${device.connection} -- leased until ${clockTime(device.lease.expiresAt)} (${remaining} left)`;
|
|
965
969
|
}
|
|
966
970
|
//#endregion
|
|
967
971
|
//#region src/commands/status.ts
|
|
968
972
|
const WATCH_GIT_MAX_AGE_MS = 6e4;
|
|
969
|
-
const WATCH_READER_CHECK_MS = 2e3;
|
|
970
973
|
function formatGb(mb) {
|
|
971
974
|
return `${(mb / 1024).toFixed(1)} GB`;
|
|
972
975
|
}
|
|
@@ -983,6 +986,7 @@ function readStatus(gitMaxAgeMs, simctlListing = null) {
|
|
|
983
986
|
async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
984
987
|
const cfg = loadConfig$1();
|
|
985
988
|
const projects = Object.entries(cfg?.projects || {});
|
|
989
|
+
const macosReads = projects.map(([path]) => readHostedMacosStatus(readMacosRecord(path)));
|
|
986
990
|
const cwdRoot = findServerWorkspace(process.cwd())?.root ?? null;
|
|
987
991
|
const worktrees = linkedWorktrees([process.cwd(), ...projects.map(([path]) => path)]);
|
|
988
992
|
const orphanWorktrees = unprovisionedWorktrees(worktrees, projects.map(([p]) => p));
|
|
@@ -1029,11 +1033,15 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1029
1033
|
now: leaseNow
|
|
1030
1034
|
});
|
|
1031
1035
|
const roots = [];
|
|
1036
|
+
const readPhysicalDevice = physicalDeviceReader();
|
|
1037
|
+
const simulatorUdids = new Set([...Object.keys(simsByUdid), ...projects.flatMap(([, proj]) => projectDeviceSlots(proj).flatMap(({ platforms }) => platforms.ios?.deviceUdid ?? []))].map((udid) => udid.toUpperCase()));
|
|
1032
1038
|
for (const [i, [path, proj]] of projects.entries()) {
|
|
1033
1039
|
const { metro, supervisor } = running[i];
|
|
1034
1040
|
const saved = readWorkspaceState$1(path);
|
|
1035
1041
|
const builds = workspaceBuilds(path, saved, history);
|
|
1036
1042
|
const web = webFacts(readWebRecord(path));
|
|
1043
|
+
const macosFacts = await macosReads[i];
|
|
1044
|
+
const macos = macosAppState(macosFacts.record);
|
|
1037
1045
|
const activeBuild = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1038
1046
|
roots.push({
|
|
1039
1047
|
path,
|
|
@@ -1042,7 +1050,8 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1042
1050
|
platform: activeBuild.platform,
|
|
1043
1051
|
pid: activeBuild.claim.pid
|
|
1044
1052
|
} : null,
|
|
1045
|
-
browserPids: browserPids(web)
|
|
1053
|
+
browserPids: browserPids(web),
|
|
1054
|
+
macosPids: [macos?.supervisor, macos?.app].flatMap((owner) => owner && (0, process_identity_exports.inspectProcessIdentity)(owner) === "same" ? [owner.pid] : [])
|
|
1046
1055
|
});
|
|
1047
1056
|
const launches = readWorkspaceLaunches(path);
|
|
1048
1057
|
launchesByState.push(launches);
|
|
@@ -1060,25 +1069,49 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1060
1069
|
remote: remoteDeviceState(readRemoteSession(path), easLedger, path),
|
|
1061
1070
|
idleStop: readIdleStop(saved),
|
|
1062
1071
|
lastStop: metroLastStop(saved, supervisor),
|
|
1072
|
+
idleShutdowns: readDeviceIdleShutdowns(saved),
|
|
1063
1073
|
launches,
|
|
1064
1074
|
leasedIds: new Set(deviceLeaseStates(leaseFiles, {
|
|
1065
1075
|
root: path,
|
|
1066
1076
|
now: leaseNow
|
|
1067
1077
|
}).flatMap((lease) => lease.mine && !lease.expired && lease.id ? [lease.id] : [])),
|
|
1068
|
-
now: leaseNow
|
|
1078
|
+
now: leaseNow,
|
|
1079
|
+
workspaces: projects.map(([other]) => other)
|
|
1069
1080
|
}), web));
|
|
1070
1081
|
const state = states[states.length - 1];
|
|
1071
|
-
if (state)
|
|
1072
|
-
|
|
1082
|
+
if (state) {
|
|
1083
|
+
const tunnel = readMetroTunnel(path);
|
|
1084
|
+
if (state.metro && tunnel?.kind === "managed" && tunnel.port === state.metro.port && pidExists(tunnel.pid)) state.metro.tunnel = {
|
|
1085
|
+
provider: tunnel.provider,
|
|
1086
|
+
url: tunnel.url
|
|
1087
|
+
};
|
|
1088
|
+
if (macos) {
|
|
1089
|
+
state.macos = macos;
|
|
1090
|
+
if (macosFacts.warning) state.warnings.push(macosFacts.warning);
|
|
1091
|
+
state.live ||= macos.state === "running" || macos.state === "orphaned" || macos.build.state === "running";
|
|
1092
|
+
}
|
|
1093
|
+
Object.assign(state, builds, workspacePhase(state.live, saved, { now: leaseNow }), { recording: { enabled: workspaceRecordingEnabled(path, proj, cfg, process.env) } });
|
|
1094
|
+
}
|
|
1095
|
+
const physicalDevices = physicalDeviceStates(leaseFiles, parseWorkspaceLeases(saved?.deviceLeases), {
|
|
1096
|
+
root: path,
|
|
1097
|
+
now: leaseNow,
|
|
1098
|
+
simulatorUdids,
|
|
1099
|
+
read: readPhysicalDevice
|
|
1100
|
+
});
|
|
1101
|
+
if (state && physicalDevices.length) state.physicalDevices = physicalDevices;
|
|
1102
|
+
labelOnlyRoots.push(Boolean(proj.worktreeRoot && !proj.bundleId && !state?.metro && !state?.ios && !state?.android && !state?.web && !state?.macos));
|
|
1073
1103
|
}
|
|
1104
|
+
applyStatusMeasures(states, worktrees);
|
|
1105
|
+
readLogDerivedFacts(states);
|
|
1074
1106
|
const tables = createDeviceProcessTables();
|
|
1075
1107
|
readDeviceProcesses(states, tables, {
|
|
1076
1108
|
projects,
|
|
1077
1109
|
launchesByState
|
|
1078
1110
|
});
|
|
1111
|
+
readMetroReverses(states, launchesByState);
|
|
1079
1112
|
const sims = Object.values(simsByUdid);
|
|
1080
1113
|
const simNames = Object.fromEntries(sims.map((sim) => [sim.udid.toUpperCase(), sim.name]));
|
|
1081
|
-
const machine = readMachineUsage({
|
|
1114
|
+
const machine = await readMachineUsage({
|
|
1082
1115
|
states,
|
|
1083
1116
|
roots,
|
|
1084
1117
|
simNames,
|
|
@@ -1092,6 +1125,7 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1092
1125
|
leases,
|
|
1093
1126
|
leaseNow,
|
|
1094
1127
|
orphanWorktrees,
|
|
1128
|
+
worktrees,
|
|
1095
1129
|
simsAvailable,
|
|
1096
1130
|
simctlError,
|
|
1097
1131
|
cwdRoot,
|
|
@@ -1101,6 +1135,48 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1101
1135
|
simNames
|
|
1102
1136
|
};
|
|
1103
1137
|
}
|
|
1138
|
+
/** Sets the facts status derives from workspace logs, the running build's record and its detail file. */
|
|
1139
|
+
function readLogDerivedFacts(states) {
|
|
1140
|
+
const now = Date.now();
|
|
1141
|
+
for (const state of states) {
|
|
1142
|
+
if (state.metro) {
|
|
1143
|
+
const bundle = metroBundleState(tailLines(join$1(workspaceLogsDir(state.path), "metro.ndjson")) ?? [], {
|
|
1144
|
+
running: state.metro.running,
|
|
1145
|
+
now
|
|
1146
|
+
});
|
|
1147
|
+
if (bundle) state.metro.bundle = bundle;
|
|
1148
|
+
else delete state.metro.bundle;
|
|
1149
|
+
}
|
|
1150
|
+
if (state.build?.state === "running") {
|
|
1151
|
+
const record = parseActiveBuild(readWorkspaceState$1(state.path)?.[ACTIVE_BUILD_KEY]);
|
|
1152
|
+
if (!record) {
|
|
1153
|
+
state.build = null;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
const report = buildReport(record, {
|
|
1157
|
+
state: activeBuildState(record.claim),
|
|
1158
|
+
history: void 0
|
|
1159
|
+
});
|
|
1160
|
+
const { outcome, expectedMs, expectedPhaseMs, basis, plannedPhases } = state.build;
|
|
1161
|
+
state.build = record.estimate || record.startedAt !== state.build.startedAt ? report : {
|
|
1162
|
+
...report,
|
|
1163
|
+
outcome,
|
|
1164
|
+
expectedMs,
|
|
1165
|
+
expectedPhaseMs: report.phase === state.build.phase ? expectedPhaseMs : null,
|
|
1166
|
+
basis,
|
|
1167
|
+
plannedPhases
|
|
1168
|
+
};
|
|
1169
|
+
const detail = record.phase === "compile" ? readBuildDetail(state.path, record.claim.claimId) : null;
|
|
1170
|
+
if (detail) state.build.detail = detail;
|
|
1171
|
+
else delete state.build.detail;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
function phaseMarker({ phase, live, warmStep }) {
|
|
1176
|
+
if (phase === "warming") return chalk.cyan(` [warming: ${warmStep}]`);
|
|
1177
|
+
if (phase === "ready") return chalk.green(" [ready]");
|
|
1178
|
+
return live ? "" : chalk.dim(" [idle]");
|
|
1179
|
+
}
|
|
1104
1180
|
function browserPids(web) {
|
|
1105
1181
|
if (!web) return [];
|
|
1106
1182
|
const chrome = web.record.chromeProcess?.pid;
|
|
@@ -1108,32 +1184,43 @@ function browserPids(web) {
|
|
|
1108
1184
|
return web.status === "orphaned" && chrome ? [chrome] : [];
|
|
1109
1185
|
}
|
|
1110
1186
|
/**
|
|
1111
|
-
* Attributes the host process table
|
|
1112
|
-
* live or a build runs, so an idle
|
|
1187
|
+
* Attributes the host process table and the processes' footprints to their owners, and sets each environment's
|
|
1188
|
+
* `memoryMb` from them. Reads both only when a simulator is booted, a workspace is live or a build runs, so an idle
|
|
1189
|
+
* machine runs no `ps` and keeps the estimate.
|
|
1113
1190
|
*/
|
|
1114
|
-
function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1191
|
+
async function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1115
1192
|
const processes = busy || states.some((state) => state.live || state.build?.state === "running") ? tables.host() : null;
|
|
1116
|
-
|
|
1193
|
+
if (!processes) return null;
|
|
1194
|
+
const footprints = await readFootprints();
|
|
1195
|
+
const machine = attributeMachineUsage({
|
|
1117
1196
|
processes,
|
|
1197
|
+
footprints,
|
|
1118
1198
|
environments: states,
|
|
1119
1199
|
roots,
|
|
1120
1200
|
simNames
|
|
1121
|
-
})
|
|
1201
|
+
});
|
|
1202
|
+
applyMachineMemory(states, machine);
|
|
1203
|
+
return machine;
|
|
1122
1204
|
}
|
|
1123
1205
|
/**
|
|
1124
1206
|
* Rereads only the error counts, device activity and machine usage. With `machine`, and when the snapshot measured
|
|
1125
1207
|
* machine usage, it reads the host process table again; it reuses every other subprocess fact of the full read.
|
|
1126
1208
|
*/
|
|
1127
|
-
function refreshLightFacts(snapshot, machine) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1209
|
+
async function refreshLightFacts(snapshot, machine) {
|
|
1210
|
+
const fresh = createDeviceProcessTables();
|
|
1211
|
+
snapshot.tables = {
|
|
1212
|
+
host: machine && snapshot.machine ? fresh.host : snapshot.tables.host,
|
|
1213
|
+
android: snapshot.tables.android,
|
|
1214
|
+
tcpConnections: fresh.tcpConnections,
|
|
1215
|
+
clients: fresh.host
|
|
1131
1216
|
};
|
|
1132
1217
|
withStateReadCache(() => {
|
|
1133
1218
|
for (const state of snapshot.states) state.logs = logFacts(state.path);
|
|
1219
|
+
applyStatusMeasures(snapshot.states, snapshot.worktrees);
|
|
1220
|
+
readLogDerivedFacts(snapshot.states);
|
|
1134
1221
|
readDeviceProcesses(snapshot.states, snapshot.tables, null);
|
|
1135
1222
|
});
|
|
1136
|
-
if (machine && snapshot.machine) snapshot.machine = readMachineUsage({
|
|
1223
|
+
if (machine && snapshot.machine) snapshot.machine = await readMachineUsage({
|
|
1137
1224
|
...snapshot,
|
|
1138
1225
|
busy: true
|
|
1139
1226
|
});
|
|
@@ -1155,13 +1242,13 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1155
1242
|
});
|
|
1156
1243
|
if (json) {
|
|
1157
1244
|
out.push(JSON.stringify({
|
|
1158
|
-
environments: states.map((state, i) => labelOnlyRoots[i] ? {
|
|
1245
|
+
environments: states.map((state, i) => withDerivedFacts(labelOnlyRoots[i] ? {
|
|
1159
1246
|
...state,
|
|
1160
1247
|
labelOnly: true
|
|
1161
|
-
} : state),
|
|
1248
|
+
} : state)),
|
|
1162
1249
|
capacity: cap,
|
|
1163
1250
|
deviceLeases: leases,
|
|
1164
|
-
unprovisionedWorktrees: orphanWorktrees,
|
|
1251
|
+
unprovisionedWorktrees: orphanWorktrees.map(withGitChip),
|
|
1165
1252
|
simctlAvailable: simsAvailable,
|
|
1166
1253
|
machine
|
|
1167
1254
|
}));
|
|
@@ -1182,12 +1269,16 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1182
1269
|
if (!state) continue;
|
|
1183
1270
|
const shortcut = projectShortcut(path, proj);
|
|
1184
1271
|
const marker = path === cwdRoot ? chalk.bold.cyan(`* ${shortcut}`) : shortcut;
|
|
1185
|
-
const idle = state
|
|
1272
|
+
const idle = phaseMarker(state);
|
|
1186
1273
|
out.push(`\n${marker}${idle} ${chalk.dim(`(${path})`)}`);
|
|
1187
1274
|
out.push(labelOnlyRoots[i] ? chalk.dim(" worktree root (holds the label; the app registers its own entry)") : chalk.dim(` app: ${proj.bundleId ?? "?"} (${proj.isExpo ?? detectIsExpo(path) ? "expo" : "bare"})`));
|
|
1188
1275
|
if (state.metro) {
|
|
1189
1276
|
const label = state.metro.running ? chalk.green(`running (pid ${state.metro.pid})`) : state.metro.idleStop ? chalk.dim(`stopped (idle) after ${state.metro.idleStop.idleMinutes}m; \`stim start\`, \`ios\` or \`android\` restarts it`) : state.metro.lastStop ? chalk.dim(`not running${state.metro.lastStop.reason === "vanished" ? "" : ` since ${state.metro.lastStop.at}`}; ${describeMetroLastStop(state.metro.lastStop)}`) : chalk.dim("not running");
|
|
1190
1277
|
out.push(` metro: port ${state.metro.port} ${label}`);
|
|
1278
|
+
if (state.metro.tunnel) {
|
|
1279
|
+
const { provider, url } = state.metro.tunnel;
|
|
1280
|
+
out.push(` tunnel: ${provider}${provider === "tailscale" ? " (tailnet-only)" : ""} ${url}`);
|
|
1281
|
+
}
|
|
1191
1282
|
}
|
|
1192
1283
|
if (state.supervisor) {
|
|
1193
1284
|
const health = state.supervisor.healthy ? chalk.green("healthy") : chalk.yellow("not answering");
|
|
@@ -1200,6 +1291,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1200
1291
|
}
|
|
1201
1292
|
out.push(...lastBuildsLines(state.lastBuilds));
|
|
1202
1293
|
if (state.worktree?.git) out.push(chalk.dim(` git: ${gitSummaryText(state.worktree.git)}`));
|
|
1294
|
+
for (const agent of state.agents ?? []) out.push(chalk.dim(` agent: ${agentLine(agent)}`));
|
|
1203
1295
|
if (state.logs) {
|
|
1204
1296
|
const n = state.logs.errorsSinceMarker;
|
|
1205
1297
|
const errs = n > 0 ? chalk.yellow(` (${n} error${n === 1 ? "" : "s"} since the last marker)`) : "";
|
|
@@ -1214,20 +1306,32 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1214
1306
|
if (deviceState.ios) {
|
|
1215
1307
|
const booted = deviceState.ios.state === "Booted" ? chalk.green("booted") : chalk.dim(deviceState.ios.state.toLowerCase());
|
|
1216
1308
|
const owned = deviceState.ios.owned ? chalk.dim(" (owned)") : "";
|
|
1217
|
-
out.push(` ios${slotLabel}: ${chalk.cyan(deviceState.ios.name ?? deviceState.ios.udid)} ${booted}${owned}${activitySuffix(deviceState.ios.activity)}${appSuffix(deviceState.ios.app)}`);
|
|
1309
|
+
out.push(` ios${slotLabel}: ${chalk.cyan(deviceState.ios.name ?? deviceState.ios.udid)} ${booted}${owned}${activitySuffix(deviceState.ios.activity)}${appSuffix(deviceState.ios.app)}${idleShutdownSuffix(deviceState.ios.idleShutdown)}`);
|
|
1218
1310
|
}
|
|
1219
1311
|
if (deviceState.android) {
|
|
1220
1312
|
const kind = deviceState.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
|
|
1221
1313
|
const observed = deviceState.android.state ? ` ${deviceState.android.state}${deviceState.android.serial ? ` (${deviceState.android.serial})` : ""}` : "";
|
|
1222
|
-
out.push(` android${slotLabel}: ${chalk.cyan(deviceState.android.name)} ${kind}${observed}${deviceState.android.owned ? chalk.dim(" (owned)") : ""}${activitySuffix(deviceState.android.activity)}${appSuffix(deviceState.android.app)}`);
|
|
1314
|
+
out.push(` android${slotLabel}: ${chalk.cyan(deviceState.android.name)} ${kind}${observed}${deviceState.android.owned ? chalk.dim(" (owned)") : ""}${activitySuffix(deviceState.android.activity)}${appSuffix(deviceState.android.app)}${idleShutdownSuffix(deviceState.android.idleShutdown)}`);
|
|
1223
1315
|
}
|
|
1224
1316
|
}
|
|
1317
|
+
if (state.macos) {
|
|
1318
|
+
const host = state.macos.host;
|
|
1319
|
+
const where = host ? ` on ${host.machine} as ${host.bundleId}${chalk.dim(` (session ${host.session})`)}` : "";
|
|
1320
|
+
const build = state.macos.build;
|
|
1321
|
+
const placement = build.buildMachine === void 0 ? "" : `; build machine ${build.buildMachine}, built on ${build.builtOn ?? "none"}`;
|
|
1322
|
+
out.push(` macOS ${state.macos.product}: ${state.macos.state}${where}; build ${build.state}${placement}`);
|
|
1323
|
+
if (host?.agent.driver === "agent-device") out.push(chalk.dim(` agent: ${host.agent.command}`));
|
|
1324
|
+
}
|
|
1225
1325
|
if (state.web) {
|
|
1226
1326
|
const browser = state.web.running ? chalk.green(`running (pid ${state.web.pid}, DevTools ${state.web.cdpEndpoint})`) : chalk.dim("not running");
|
|
1227
|
-
|
|
1327
|
+
const page = state.web.page;
|
|
1328
|
+
const failed = page?.state === "failed" ? ` -- ${chalk.yellow(`page failed: ${page.error}`)}` : "";
|
|
1329
|
+
out.push(` web: ${chalk.cyan(page?.url ?? state.web.url)} ${browser}${state.web.headless ? "" : chalk.dim(" (headed)")}${activitySuffix(state.web.activity)}${failed}`);
|
|
1228
1330
|
}
|
|
1331
|
+
for (const device of state.physicalDevices ?? []) out.push(` ${physicalDeviceLine(device, leaseNow)}`);
|
|
1229
1332
|
for (const remote of state.remoteDevices ?? []) out.push(` ${remoteDeviceLine(remote)}`);
|
|
1230
1333
|
for (const w of state.warnings) out.push(chalk.yellow(` ! ${w}`));
|
|
1334
|
+
for (const note of state.issues.filter((issue) => issue.severity === "info")) out.push(chalk.dim(` - ${note.message}`));
|
|
1231
1335
|
}
|
|
1232
1336
|
for (const pool of pools) {
|
|
1233
1337
|
if (pool.error) out.push(chalk.yellow(`\n${pool.error} ${POOL_SETTING_REMEDY}`));
|
|
@@ -1242,7 +1346,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1242
1346
|
out.push(chalk.dim(`\nWorktrees with no environment (${orphanWorktrees.length}):`));
|
|
1243
1347
|
for (const w of orphanWorktrees) out.push(chalk.dim(` ${orphanWorktreeLine(w)}`));
|
|
1244
1348
|
}
|
|
1245
|
-
out.push(chalk.dim(`\n${cap.liveCount} live environment(s), roughly ${formatGb(cap.committedMb)} of ${formatGb(cap.totalMemoryMb)} committed.`));
|
|
1349
|
+
out.push(chalk.dim(machine?.memorySource === "footprint" ? `\n${cap.liveCount} live environment(s) use ${formatGb(cap.committedMb)} of ${formatGb(cap.totalMemoryMb)}.` : `\n${cap.liveCount} live environment(s), roughly ${formatGb(cap.committedMb)} of ${formatGb(cap.totalMemoryMb)} committed.`));
|
|
1246
1350
|
const volumes = readVolumes(cwdRoot || process.cwd());
|
|
1247
1351
|
const line = diskLine(volumes);
|
|
1248
1352
|
if (line) {
|
|
@@ -1259,14 +1363,16 @@ async function watchStatus(json) {
|
|
|
1259
1363
|
let last = null;
|
|
1260
1364
|
let snapshot = null;
|
|
1261
1365
|
let sources = null;
|
|
1366
|
+
const measurer = createStatusMeasurer({ updated: () => scheduler.trigger("light") });
|
|
1262
1367
|
const scheduler = createRefreshScheduler({
|
|
1263
1368
|
debounceMs: 250,
|
|
1264
1369
|
lightIntervalMs: WATCH_LIGHT_INTERVAL_MS,
|
|
1265
1370
|
run: async (kind) => {
|
|
1266
1371
|
let text;
|
|
1267
1372
|
try {
|
|
1268
|
-
if (kind === "light" && snapshot) refreshLightFacts(snapshot, json);
|
|
1373
|
+
if (kind === "light" && snapshot) await refreshLightFacts(snapshot, json);
|
|
1269
1374
|
else snapshot = await readStatus(WATCH_GIT_MAX_AGE_MS, sources?.simulatorListing());
|
|
1375
|
+
measurer.schedule(snapshot.states, snapshot.worktrees);
|
|
1270
1376
|
text = renderStatus(snapshot, json).join("\n");
|
|
1271
1377
|
} catch (error) {
|
|
1272
1378
|
console.error(chalk.red(String(error?.message || error)));
|
|
@@ -1281,24 +1387,17 @@ async function watchStatus(json) {
|
|
|
1281
1387
|
});
|
|
1282
1388
|
const fallback = setInterval(() => scheduler.trigger("full"), WATCH_FALLBACK_MS);
|
|
1283
1389
|
const machine = setInterval(() => json && snapshot?.machine && scheduler.trigger("light"), WATCH_LIGHT_INTERVAL_MS);
|
|
1284
|
-
const parent = process.ppid;
|
|
1285
|
-
const stdout = fstatSync$1(1);
|
|
1286
|
-
const linuxPipe = process.platform === "linux" && (stdout.isFIFO() || stdout.isSocket());
|
|
1287
|
-
const reader = setInterval(() => {
|
|
1288
|
-
if (linuxPipe && process.ppid !== parent) finish();
|
|
1289
|
-
else process.stdout.write("");
|
|
1290
|
-
}, WATCH_READER_CHECK_MS);
|
|
1291
1390
|
const finish = () => {
|
|
1292
1391
|
clearInterval(fallback);
|
|
1293
1392
|
clearInterval(machine);
|
|
1294
|
-
|
|
1393
|
+
stopReader();
|
|
1295
1394
|
scheduler.stop();
|
|
1296
1395
|
sources?.stop();
|
|
1297
1396
|
process.exit(0);
|
|
1298
1397
|
};
|
|
1398
|
+
const stopReader = watchStdoutReader(finish);
|
|
1299
1399
|
process.on("SIGINT", finish);
|
|
1300
1400
|
process.on("SIGTERM", finish);
|
|
1301
|
-
process.stdout.on("error", finish);
|
|
1302
1401
|
sources = watchStatusSources({
|
|
1303
1402
|
home: getConfigDir(),
|
|
1304
1403
|
onChange: (kind) => scheduler.trigger(kind)
|
|
@@ -1314,6 +1413,10 @@ async function readGitInto(worktrees, orphans, maxAgeMs) {
|
|
|
1314
1413
|
});
|
|
1315
1414
|
for (const worktree of worktrees) worktree.git = byPath.get(worktree.path) ?? null;
|
|
1316
1415
|
}
|
|
1416
|
+
function agentLine({ tool, title, sessionId, lastActiveAt }) {
|
|
1417
|
+
const at = lastActiveAt ? `, active ${lastActiveAt}` : "";
|
|
1418
|
+
return `${tool === "codex" ? "Codex" : "Claude Code"} ${title ? `"${title}"` : sessionId}${at}`;
|
|
1419
|
+
}
|
|
1317
1420
|
function orphanWorktreeLine(w) {
|
|
1318
1421
|
return `${w.path}${w.branch ? ` [${w.branch}]` : ""}${w.git ? ` -- ${gitSummaryText(w.git)}` : ""}`;
|
|
1319
1422
|
}
|
|
@@ -1324,7 +1427,11 @@ function lastBuildsLines(reports) {
|
|
|
1324
1427
|
function lastBuildText(report) {
|
|
1325
1428
|
const source = report.cacheHit ? `${report.cacheHit} cache` : report.status === "ok" ? "compiled" : "no cache hit";
|
|
1326
1429
|
const took = report.durationMs === null ? "" : ` in ${formatDuration(report.durationMs)}`;
|
|
1327
|
-
|
|
1430
|
+
const placement = report.buildMachine === void 0 ? "" : `; build machine ${report.buildMachine}, built on ${report.builtOn ?? (report.cacheHit ? "none (cache)" : "none")}`;
|
|
1431
|
+
return `${report.platform} ${report.status === "ok" ? source : `failed (${report.errorCode ?? "error"}), ${source}`}${took}${placement}`;
|
|
1432
|
+
}
|
|
1433
|
+
function idleShutdownSuffix(record) {
|
|
1434
|
+
return record ? chalk.dim(` -- shut down after ${record.idleMinutes}m idle at ${record.at} (devices.idleShutdownMinutes)`) : "";
|
|
1328
1435
|
}
|
|
1329
1436
|
function activitySuffix(activity) {
|
|
1330
1437
|
const label = activityLabel(activity, Date.now());
|
|
@@ -1378,6 +1485,28 @@ function readDeviceProcesses(states, tables, apps) {
|
|
|
1378
1485
|
});
|
|
1379
1486
|
}
|
|
1380
1487
|
}
|
|
1488
|
+
const web = state.web;
|
|
1489
|
+
const port = web?.cdpEndpoint ? Number(new URL(web.cdpEndpoint).port) : null;
|
|
1490
|
+
if (web?.running && port) {
|
|
1491
|
+
web.page = readWebPage(state.path);
|
|
1492
|
+
const ownPids = [web.pid, web.supervisorPid].filter((pid) => pid !== null);
|
|
1493
|
+
web.activity = statusActivity(readWebActivity({
|
|
1494
|
+
port,
|
|
1495
|
+
ownPids,
|
|
1496
|
+
workspace: state.path
|
|
1497
|
+
}, tables));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
function readMetroReverses(states, launchesByState) {
|
|
1502
|
+
for (const [i, state] of states.entries()) for (const target of metroReverseTargets(state, launchesByState[i])) {
|
|
1503
|
+
const listing = getExecutor().runFileQuiet("adb", [
|
|
1504
|
+
"-s",
|
|
1505
|
+
target.serial,
|
|
1506
|
+
"reverse",
|
|
1507
|
+
"--list"
|
|
1508
|
+
], { timeoutMs: 5e3 });
|
|
1509
|
+
if (listing !== null && !hasMetroReverse(listing, target.metroPort)) addReverseMissingIssue(state, target);
|
|
1381
1510
|
}
|
|
1382
1511
|
}
|
|
1383
1512
|
function appSuffix(app) {
|
|
@@ -1388,7 +1517,7 @@ function appSuffix(app) {
|
|
|
1388
1517
|
}
|
|
1389
1518
|
function linkedWorktrees(paths) {
|
|
1390
1519
|
return [...new Set(paths.flatMap((path) => gitCommonDirOnDisk(path) ?? []))].flatMap((common) => {
|
|
1391
|
-
const repository = basename(common) === ".git" ? dirname(common) : common;
|
|
1520
|
+
const repository = basename$1(common) === ".git" ? dirname$1(common) : common;
|
|
1392
1521
|
return linkedWorktreesOnDisk(common).map((entry) => Object.assign(entry, { repository }));
|
|
1393
1522
|
});
|
|
1394
1523
|
}
|
|
@@ -1404,7 +1533,7 @@ function workspaceBuilds(path, saved, history) {
|
|
|
1404
1533
|
function workspaceBuild(path, saved, history) {
|
|
1405
1534
|
const record = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1406
1535
|
if (!record) return null;
|
|
1407
|
-
const projectKey = statsProjectKey({
|
|
1536
|
+
const projectKey = statsProjectKey$1({
|
|
1408
1537
|
root: path,
|
|
1409
1538
|
commonDir: gitCommonDir(path),
|
|
1410
1539
|
repoRoot: repoRoot(path)
|
|
@@ -1414,6 +1543,32 @@ function workspaceBuild(path, saved, history) {
|
|
|
1414
1543
|
history: history?.[projectKey]
|
|
1415
1544
|
});
|
|
1416
1545
|
}
|
|
1546
|
+
const PHYSICAL_DEVICE_LIST_TIMEOUT_MS = 5e3;
|
|
1547
|
+
const PHYSICAL_DEVICE_LIST_MAX_AGE_MS = 3e4;
|
|
1548
|
+
const physicalDeviceLists = {};
|
|
1549
|
+
function freshList(entry, read) {
|
|
1550
|
+
const now = Date.now();
|
|
1551
|
+
return entry && now - entry.at < PHYSICAL_DEVICE_LIST_MAX_AGE_MS ? entry : {
|
|
1552
|
+
at: now,
|
|
1553
|
+
devices: read()
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
function physicalDeviceReader() {
|
|
1557
|
+
return (platform, id) => {
|
|
1558
|
+
if (platform === "ios") {
|
|
1559
|
+
physicalDeviceLists.ios = freshList(physicalDeviceLists.ios, () => readIosDevices({ timeoutMs: PHYSICAL_DEVICE_LIST_TIMEOUT_MS }));
|
|
1560
|
+
return iosPhysicalReading(physicalDeviceLists.ios.devices, id);
|
|
1561
|
+
}
|
|
1562
|
+
physicalDeviceLists.android = freshList(physicalDeviceLists.android, () => {
|
|
1563
|
+
try {
|
|
1564
|
+
return listAdbDevices({ timeoutMs: PHYSICAL_DEVICE_LIST_TIMEOUT_MS });
|
|
1565
|
+
} catch {
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
return androidPhysicalReading(physicalDeviceLists.android.devices, id);
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1417
1572
|
function androidRuntimeReader() {
|
|
1418
1573
|
const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
|
|
1419
1574
|
return (avdName) => readAndroidRuntime(() => resolve(avdName));
|