stim 1.13.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-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
- package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
- package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
- package/dist/{android-qx_31wJo.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-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
- package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
- package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
- package/dist/build-progress-C2Jv1AtA.mjs +872 -0
- package/dist/build-slots-Df1ApSuH.mjs +127 -0
- package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
- 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-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
- package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
- package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
- package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
- package/dist/{deps-2Wo81np9.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-Cf7vl-so.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-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
- package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
- package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
- package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
- package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
- package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
- package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
- package/dist/{guide-status-BaMYu55M.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-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
- package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
- package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
- package/dist/{logs-DcSRbC2t.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-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
- package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
- package/dist/native-run-DFDoIRbY.mjs +147 -0
- package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
- package/dist/offload-worker.d.mts +121 -0
- package/dist/offload-worker.mjs +601 -0
- package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
- package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
- package/dist/prebuild-BvxDlD6C.mjs +213 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-DGeclhvO.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-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
- package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
- package/dist/run-B-xqRrsg.mjs +119 -0
- package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
- package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
- package/dist/server-expo-KJeD109I.mjs +2 -0
- package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
- package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
- package/dist/settings.schema.json +357 -2
- package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
- package/dist/slot-launch-BY0BrOLh.mjs +65 -0
- package/dist/{spawn-claims-PT8aQpCa.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-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/state-BxCgeTQu.mjs +41 -0
- package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
- package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
- package/dist/stats-DBLLFmKd.mjs +66 -0
- package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
- package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -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-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
- package/dist/stop-Bvf1MflS.mjs +48 -0
- package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
- package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
- package/dist/supervisor-run.d.mts +44 -12
- package/dist/supervisor-run.mjs +276 -33
- package/dist/support-CDQbDkL5.mjs +315 -0
- package/dist/support-WLOzVKUv.mjs +176 -0
- package/dist/teardown-BYuQafiA.mjs +718 -0
- package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
- package/dist/warm-progress-DVHLN4uC.mjs +463 -0
- package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
- package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
- package/dist/web-run.mjs +326 -13
- 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-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
- package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
- package/package.json +10 -5
- package/shim/bundle-response.cjs +55 -10
- package/shim/bundle-response.d.cts +1 -0
- package/shim/expo-metro-config.cjs +10 -2
- package/dist/build-progress-l7d-X-ZP.mjs +0 -247
- package/dist/in-use-wYEaKQM9.mjs +0 -249
- package/dist/ownership-PUP4Dwai.mjs +0 -1138
- package/dist/server-expo-BeYwP_Xy.mjs +0 -2
- package/dist/slot-launch-BIWEW-wI.mjs +0 -19
- package/dist/start-Rw2GD3nV.mjs +0 -2
- package/dist/stats-CaSwmsSZ.mjs +0 -62
- package/dist/stats-D8rUTPHd.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/worktree-3cBa-bQt.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,191 +821,151 @@ 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
|
|
@@ -982,6 +986,7 @@ function readStatus(gitMaxAgeMs, simctlListing = null) {
|
|
|
982
986
|
async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
983
987
|
const cfg = loadConfig$1();
|
|
984
988
|
const projects = Object.entries(cfg?.projects || {});
|
|
989
|
+
const macosReads = projects.map(([path]) => readHostedMacosStatus(readMacosRecord(path)));
|
|
985
990
|
const cwdRoot = findServerWorkspace(process.cwd())?.root ?? null;
|
|
986
991
|
const worktrees = linkedWorktrees([process.cwd(), ...projects.map(([path]) => path)]);
|
|
987
992
|
const orphanWorktrees = unprovisionedWorktrees(worktrees, projects.map(([p]) => p));
|
|
@@ -1028,11 +1033,15 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1028
1033
|
now: leaseNow
|
|
1029
1034
|
});
|
|
1030
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()));
|
|
1031
1038
|
for (const [i, [path, proj]] of projects.entries()) {
|
|
1032
1039
|
const { metro, supervisor } = running[i];
|
|
1033
1040
|
const saved = readWorkspaceState$1(path);
|
|
1034
1041
|
const builds = workspaceBuilds(path, saved, history);
|
|
1035
1042
|
const web = webFacts(readWebRecord(path));
|
|
1043
|
+
const macosFacts = await macosReads[i];
|
|
1044
|
+
const macos = macosAppState(macosFacts.record);
|
|
1036
1045
|
const activeBuild = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1037
1046
|
roots.push({
|
|
1038
1047
|
path,
|
|
@@ -1041,7 +1050,8 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1041
1050
|
platform: activeBuild.platform,
|
|
1042
1051
|
pid: activeBuild.claim.pid
|
|
1043
1052
|
} : null,
|
|
1044
|
-
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] : [])
|
|
1045
1055
|
});
|
|
1046
1056
|
const launches = readWorkspaceLaunches(path);
|
|
1047
1057
|
launchesByState.push(launches);
|
|
@@ -1058,25 +1068,50 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1058
1068
|
logs: logs[i],
|
|
1059
1069
|
remote: remoteDeviceState(readRemoteSession(path), easLedger, path),
|
|
1060
1070
|
idleStop: readIdleStop(saved),
|
|
1071
|
+
lastStop: metroLastStop(saved, supervisor),
|
|
1072
|
+
idleShutdowns: readDeviceIdleShutdowns(saved),
|
|
1061
1073
|
launches,
|
|
1062
1074
|
leasedIds: new Set(deviceLeaseStates(leaseFiles, {
|
|
1063
1075
|
root: path,
|
|
1064
1076
|
now: leaseNow
|
|
1065
1077
|
}).flatMap((lease) => lease.mine && !lease.expired && lease.id ? [lease.id] : [])),
|
|
1066
|
-
now: leaseNow
|
|
1078
|
+
now: leaseNow,
|
|
1079
|
+
workspaces: projects.map(([other]) => other)
|
|
1067
1080
|
}), web));
|
|
1068
1081
|
const state = states[states.length - 1];
|
|
1069
|
-
if (state)
|
|
1070
|
-
|
|
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));
|
|
1071
1103
|
}
|
|
1104
|
+
applyStatusMeasures(states, worktrees);
|
|
1105
|
+
readLogDerivedFacts(states);
|
|
1072
1106
|
const tables = createDeviceProcessTables();
|
|
1073
1107
|
readDeviceProcesses(states, tables, {
|
|
1074
1108
|
projects,
|
|
1075
1109
|
launchesByState
|
|
1076
1110
|
});
|
|
1111
|
+
readMetroReverses(states, launchesByState);
|
|
1077
1112
|
const sims = Object.values(simsByUdid);
|
|
1078
1113
|
const simNames = Object.fromEntries(sims.map((sim) => [sim.udid.toUpperCase(), sim.name]));
|
|
1079
|
-
const machine = readMachineUsage({
|
|
1114
|
+
const machine = await readMachineUsage({
|
|
1080
1115
|
states,
|
|
1081
1116
|
roots,
|
|
1082
1117
|
simNames,
|
|
@@ -1090,6 +1125,7 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1090
1125
|
leases,
|
|
1091
1126
|
leaseNow,
|
|
1092
1127
|
orphanWorktrees,
|
|
1128
|
+
worktrees,
|
|
1093
1129
|
simsAvailable,
|
|
1094
1130
|
simctlError,
|
|
1095
1131
|
cwdRoot,
|
|
@@ -1099,6 +1135,48 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1099
1135
|
simNames
|
|
1100
1136
|
};
|
|
1101
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
|
+
}
|
|
1102
1180
|
function browserPids(web) {
|
|
1103
1181
|
if (!web) return [];
|
|
1104
1182
|
const chrome = web.record.chromeProcess?.pid;
|
|
@@ -1106,32 +1184,43 @@ function browserPids(web) {
|
|
|
1106
1184
|
return web.status === "orphaned" && chrome ? [chrome] : [];
|
|
1107
1185
|
}
|
|
1108
1186
|
/**
|
|
1109
|
-
* Attributes the host process table
|
|
1110
|
-
* 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.
|
|
1111
1190
|
*/
|
|
1112
|
-
function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1191
|
+
async function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1113
1192
|
const processes = busy || states.some((state) => state.live || state.build?.state === "running") ? tables.host() : null;
|
|
1114
|
-
|
|
1193
|
+
if (!processes) return null;
|
|
1194
|
+
const footprints = await readFootprints();
|
|
1195
|
+
const machine = attributeMachineUsage({
|
|
1115
1196
|
processes,
|
|
1197
|
+
footprints,
|
|
1116
1198
|
environments: states,
|
|
1117
1199
|
roots,
|
|
1118
1200
|
simNames
|
|
1119
|
-
})
|
|
1201
|
+
});
|
|
1202
|
+
applyMachineMemory(states, machine);
|
|
1203
|
+
return machine;
|
|
1120
1204
|
}
|
|
1121
1205
|
/**
|
|
1122
1206
|
* Rereads only the error counts, device activity and machine usage. With `machine`, and when the snapshot measured
|
|
1123
1207
|
* machine usage, it reads the host process table again; it reuses every other subprocess fact of the full read.
|
|
1124
1208
|
*/
|
|
1125
|
-
function refreshLightFacts(snapshot, machine) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
|
1129
1216
|
};
|
|
1130
1217
|
withStateReadCache(() => {
|
|
1131
1218
|
for (const state of snapshot.states) state.logs = logFacts(state.path);
|
|
1219
|
+
applyStatusMeasures(snapshot.states, snapshot.worktrees);
|
|
1220
|
+
readLogDerivedFacts(snapshot.states);
|
|
1132
1221
|
readDeviceProcesses(snapshot.states, snapshot.tables, null);
|
|
1133
1222
|
});
|
|
1134
|
-
if (machine && snapshot.machine) snapshot.machine = readMachineUsage({
|
|
1223
|
+
if (machine && snapshot.machine) snapshot.machine = await readMachineUsage({
|
|
1135
1224
|
...snapshot,
|
|
1136
1225
|
busy: true
|
|
1137
1226
|
});
|
|
@@ -1153,13 +1242,13 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1153
1242
|
});
|
|
1154
1243
|
if (json) {
|
|
1155
1244
|
out.push(JSON.stringify({
|
|
1156
|
-
environments: states.map((state, i) => labelOnlyRoots[i] ? {
|
|
1245
|
+
environments: states.map((state, i) => withDerivedFacts(labelOnlyRoots[i] ? {
|
|
1157
1246
|
...state,
|
|
1158
1247
|
labelOnly: true
|
|
1159
|
-
} : state),
|
|
1248
|
+
} : state)),
|
|
1160
1249
|
capacity: cap,
|
|
1161
1250
|
deviceLeases: leases,
|
|
1162
|
-
unprovisionedWorktrees: orphanWorktrees,
|
|
1251
|
+
unprovisionedWorktrees: orphanWorktrees.map(withGitChip),
|
|
1163
1252
|
simctlAvailable: simsAvailable,
|
|
1164
1253
|
machine
|
|
1165
1254
|
}));
|
|
@@ -1180,12 +1269,16 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1180
1269
|
if (!state) continue;
|
|
1181
1270
|
const shortcut = projectShortcut(path, proj);
|
|
1182
1271
|
const marker = path === cwdRoot ? chalk.bold.cyan(`* ${shortcut}`) : shortcut;
|
|
1183
|
-
const idle = state
|
|
1272
|
+
const idle = phaseMarker(state);
|
|
1184
1273
|
out.push(`\n${marker}${idle} ${chalk.dim(`(${path})`)}`);
|
|
1185
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"})`));
|
|
1186
1275
|
if (state.metro) {
|
|
1187
|
-
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`) : chalk.dim("not running");
|
|
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");
|
|
1188
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
|
+
}
|
|
1189
1282
|
}
|
|
1190
1283
|
if (state.supervisor) {
|
|
1191
1284
|
const health = state.supervisor.healthy ? chalk.green("healthy") : chalk.yellow("not answering");
|
|
@@ -1198,6 +1291,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1198
1291
|
}
|
|
1199
1292
|
out.push(...lastBuildsLines(state.lastBuilds));
|
|
1200
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)}`));
|
|
1201
1295
|
if (state.logs) {
|
|
1202
1296
|
const n = state.logs.errorsSinceMarker;
|
|
1203
1297
|
const errs = n > 0 ? chalk.yellow(` (${n} error${n === 1 ? "" : "s"} since the last marker)`) : "";
|
|
@@ -1212,20 +1306,32 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1212
1306
|
if (deviceState.ios) {
|
|
1213
1307
|
const booted = deviceState.ios.state === "Booted" ? chalk.green("booted") : chalk.dim(deviceState.ios.state.toLowerCase());
|
|
1214
1308
|
const owned = deviceState.ios.owned ? chalk.dim(" (owned)") : "";
|
|
1215
|
-
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)}`);
|
|
1216
1310
|
}
|
|
1217
1311
|
if (deviceState.android) {
|
|
1218
1312
|
const kind = deviceState.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
|
|
1219
1313
|
const observed = deviceState.android.state ? ` ${deviceState.android.state}${deviceState.android.serial ? ` (${deviceState.android.serial})` : ""}` : "";
|
|
1220
|
-
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)}`);
|
|
1221
1315
|
}
|
|
1222
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
|
+
}
|
|
1223
1325
|
if (state.web) {
|
|
1224
1326
|
const browser = state.web.running ? chalk.green(`running (pid ${state.web.pid}, DevTools ${state.web.cdpEndpoint})`) : chalk.dim("not running");
|
|
1225
|
-
|
|
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}`);
|
|
1226
1330
|
}
|
|
1331
|
+
for (const device of state.physicalDevices ?? []) out.push(` ${physicalDeviceLine(device, leaseNow)}`);
|
|
1227
1332
|
for (const remote of state.remoteDevices ?? []) out.push(` ${remoteDeviceLine(remote)}`);
|
|
1228
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}`));
|
|
1229
1335
|
}
|
|
1230
1336
|
for (const pool of pools) {
|
|
1231
1337
|
if (pool.error) out.push(chalk.yellow(`\n${pool.error} ${POOL_SETTING_REMEDY}`));
|
|
@@ -1240,7 +1346,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1240
1346
|
out.push(chalk.dim(`\nWorktrees with no environment (${orphanWorktrees.length}):`));
|
|
1241
1347
|
for (const w of orphanWorktrees) out.push(chalk.dim(` ${orphanWorktreeLine(w)}`));
|
|
1242
1348
|
}
|
|
1243
|
-
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.`));
|
|
1244
1350
|
const volumes = readVolumes(cwdRoot || process.cwd());
|
|
1245
1351
|
const line = diskLine(volumes);
|
|
1246
1352
|
if (line) {
|
|
@@ -1257,14 +1363,16 @@ async function watchStatus(json) {
|
|
|
1257
1363
|
let last = null;
|
|
1258
1364
|
let snapshot = null;
|
|
1259
1365
|
let sources = null;
|
|
1366
|
+
const measurer = createStatusMeasurer({ updated: () => scheduler.trigger("light") });
|
|
1260
1367
|
const scheduler = createRefreshScheduler({
|
|
1261
1368
|
debounceMs: 250,
|
|
1262
1369
|
lightIntervalMs: WATCH_LIGHT_INTERVAL_MS,
|
|
1263
1370
|
run: async (kind) => {
|
|
1264
1371
|
let text;
|
|
1265
1372
|
try {
|
|
1266
|
-
if (kind === "light" && snapshot) refreshLightFacts(snapshot, json);
|
|
1373
|
+
if (kind === "light" && snapshot) await refreshLightFacts(snapshot, json);
|
|
1267
1374
|
else snapshot = await readStatus(WATCH_GIT_MAX_AGE_MS, sources?.simulatorListing());
|
|
1375
|
+
measurer.schedule(snapshot.states, snapshot.worktrees);
|
|
1268
1376
|
text = renderStatus(snapshot, json).join("\n");
|
|
1269
1377
|
} catch (error) {
|
|
1270
1378
|
console.error(chalk.red(String(error?.message || error)));
|
|
@@ -1282,13 +1390,14 @@ async function watchStatus(json) {
|
|
|
1282
1390
|
const finish = () => {
|
|
1283
1391
|
clearInterval(fallback);
|
|
1284
1392
|
clearInterval(machine);
|
|
1393
|
+
stopReader();
|
|
1285
1394
|
scheduler.stop();
|
|
1286
1395
|
sources?.stop();
|
|
1287
1396
|
process.exit(0);
|
|
1288
1397
|
};
|
|
1398
|
+
const stopReader = watchStdoutReader(finish);
|
|
1289
1399
|
process.on("SIGINT", finish);
|
|
1290
1400
|
process.on("SIGTERM", finish);
|
|
1291
|
-
process.stdout.on("error", finish);
|
|
1292
1401
|
sources = watchStatusSources({
|
|
1293
1402
|
home: getConfigDir(),
|
|
1294
1403
|
onChange: (kind) => scheduler.trigger(kind)
|
|
@@ -1304,6 +1413,10 @@ async function readGitInto(worktrees, orphans, maxAgeMs) {
|
|
|
1304
1413
|
});
|
|
1305
1414
|
for (const worktree of worktrees) worktree.git = byPath.get(worktree.path) ?? null;
|
|
1306
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
|
+
}
|
|
1307
1420
|
function orphanWorktreeLine(w) {
|
|
1308
1421
|
return `${w.path}${w.branch ? ` [${w.branch}]` : ""}${w.git ? ` -- ${gitSummaryText(w.git)}` : ""}`;
|
|
1309
1422
|
}
|
|
@@ -1314,7 +1427,11 @@ function lastBuildsLines(reports) {
|
|
|
1314
1427
|
function lastBuildText(report) {
|
|
1315
1428
|
const source = report.cacheHit ? `${report.cacheHit} cache` : report.status === "ok" ? "compiled" : "no cache hit";
|
|
1316
1429
|
const took = report.durationMs === null ? "" : ` in ${formatDuration(report.durationMs)}`;
|
|
1317
|
-
|
|
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)`) : "";
|
|
1318
1435
|
}
|
|
1319
1436
|
function activitySuffix(activity) {
|
|
1320
1437
|
const label = activityLabel(activity, Date.now());
|
|
@@ -1368,6 +1485,28 @@ function readDeviceProcesses(states, tables, apps) {
|
|
|
1368
1485
|
});
|
|
1369
1486
|
}
|
|
1370
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);
|
|
1371
1510
|
}
|
|
1372
1511
|
}
|
|
1373
1512
|
function appSuffix(app) {
|
|
@@ -1378,7 +1517,7 @@ function appSuffix(app) {
|
|
|
1378
1517
|
}
|
|
1379
1518
|
function linkedWorktrees(paths) {
|
|
1380
1519
|
return [...new Set(paths.flatMap((path) => gitCommonDirOnDisk(path) ?? []))].flatMap((common) => {
|
|
1381
|
-
const repository = basename(common) === ".git" ? dirname(common) : common;
|
|
1520
|
+
const repository = basename$1(common) === ".git" ? dirname$1(common) : common;
|
|
1382
1521
|
return linkedWorktreesOnDisk(common).map((entry) => Object.assign(entry, { repository }));
|
|
1383
1522
|
});
|
|
1384
1523
|
}
|
|
@@ -1394,7 +1533,7 @@ function workspaceBuilds(path, saved, history) {
|
|
|
1394
1533
|
function workspaceBuild(path, saved, history) {
|
|
1395
1534
|
const record = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1396
1535
|
if (!record) return null;
|
|
1397
|
-
const projectKey = statsProjectKey({
|
|
1536
|
+
const projectKey = statsProjectKey$1({
|
|
1398
1537
|
root: path,
|
|
1399
1538
|
commonDir: gitCommonDir(path),
|
|
1400
1539
|
repoRoot: repoRoot(path)
|
|
@@ -1404,6 +1543,32 @@ function workspaceBuild(path, saved, history) {
|
|
|
1404
1543
|
history: history?.[projectKey]
|
|
1405
1544
|
});
|
|
1406
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
|
+
}
|
|
1407
1572
|
function androidRuntimeReader() {
|
|
1408
1573
|
const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
|
|
1409
1574
|
return (avdName) => readAndroidRuntime(() => resolve(avdName));
|