stim 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/activity-BxwdD81S.mjs +609 -0
- package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
- package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
- package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
- package/dist/android-cas-8U4DPqeR.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/budget-DjrURfQn.mjs +2700 -0
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
- package/dist/build-slots-rUk77xqh.mjs +132 -0
- package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
- package/dist/cli-BkYZ_6hy.mjs +51 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-BTY686R6.mjs +1368 -0
- package/dist/collector-run.mjs +9 -195
- package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CroAIjjj.mjs +43 -0
- package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
- package/dist/device-capacity-BHwxNBlu.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +964 -0
- package/dist/device-ios-BadmeL2V.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
- package/dist/device-pool-CUaEWMMm.mjs +125 -0
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
- package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
- package/dist/hosted-client-a-WmqE2L.mjs +197 -0
- package/dist/hosted-ios-CIWelIqf.mjs +220 -0
- package/dist/hosted-logs-D5g041Q3.mjs +187 -0
- package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
- package/dist/idle-DYVymG92.mjs +1099 -0
- package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
- package/dist/in-use-DvBw0Kxo.mjs +130 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
- package/dist/ios-state-DS5UpYTH.mjs +48 -0
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
- package/dist/logs-CHEVK8zD.mjs +371 -0
- package/dist/machines-BIBUdhpj.mjs +265 -0
- package/dist/macos-CSYEAFmF.mjs +473 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
- package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
- package/dist/native-run-NaBrRFtQ.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +122 -0
- package/dist/offload-worker.mjs +606 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
- package/dist/ownership-hCkPzGmr.mjs +571 -0
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
- package/dist/prebuild-D2fsca_Z.mjs +213 -0
- package/dist/preview-DhVbvPAZ.mjs +318 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-BIXOzVnr.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
- package/dist/run-f4vI6EnK.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
- package/dist/server-expo-CVqO2c35.mjs +2 -0
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
- package/dist/settings-oTSkkNrM.mjs +1439 -0
- package/dist/settings.schema.json +845 -97
- package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CX8Ig-U2.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
- package/dist/state-3DwSQIBK.mjs +41 -0
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
- package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
- package/dist/stats-CEZ_i5je.mjs +77 -0
- package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
- package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/stop-B0Yxtmiy.mjs +48 -0
- package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
- package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
- package/dist/stop-cause-CidxIqup.mjs +97 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +363 -24
- package/dist/support-CAP8qDUF.mjs +316 -0
- package/dist/support-D4aYJniO.mjs +201 -0
- package/dist/teardown-CL6vDs2Y.mjs +727 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
- package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
- package/dist/web-run.mjs +310 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
- package/dist/workspaces-BV4pdHFz.mjs +306 -0
- package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
- package/dist/worktree-jnG8Iph5.mjs +2 -0
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/activity-CYVDcrt3.mjs +0 -356
- package/dist/budget-CGDkhvv_.mjs +0 -1745
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/idle-DXtx3RbX.mjs +0 -191
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/logs-Civ4gbBH.mjs +0 -486
- package/dist/merge-state-ytFQCeh4.mjs +0 -262
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- package/dist/settings-D61iNOsB.mjs +0 -629
- package/dist/start-Cetfvy8l.mjs +0 -2
- package/dist/stats-CFpFGlJV.mjs +0 -62
- package/dist/stats-CX1Z8Tca.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
- package/dist/worktree-BVrHsYZG.mjs +0 -752
- package/dist/worktree-CHWWvm5v.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,32 +1,89 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { n as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
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, j as formatBytes } from "./config-D0DbpZtM.mjs";
|
|
3
|
+
import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
4
|
+
import { a as formatElapsed, i as formatDuration, r as clockTime, u as plural } from "./command-output-DnJMdEY-.mjs";
|
|
5
|
+
import { P as resolveSettings, at as gitCommonDirOnDisk, gt as repoRoot, it as gitCommonDir, ut as linkedWorktreesOnDisk } from "./settings-oTSkkNrM.mjs";
|
|
6
|
+
import { i as detectPlatforms, r as detectIsExpo, s as findServerWorkspace, u as projectShortcut } from "./project-Du91Ilvc.mjs";
|
|
7
|
+
import { a as listeningPidsByPort, c as processCwds, d as isMetroRunning, i as listeningPids, l as resolveProjectMetro, o as pidExists, s as processCwd } from "./metro-BeK1ZPz1.mjs";
|
|
8
|
+
import { b as workspaceLogErrorIndex, r as readWorkspaceState$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
|
|
9
|
+
import { a as readWebPage, o as readWebRecord, u as webFacts } from "./state-C1jB7Q8q.mjs";
|
|
10
|
+
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-BuvBFeII.mjs";
|
|
11
|
+
import { J as attributeMachineUsage, Y as readFootprints, a as machineNumber } from "./budget-DjrURfQn.mjs";
|
|
12
|
+
import { d as readWebActivity, f as tailLines, i as createDeviceProcessTables, r as createActivityReader } from "./activity-BxwdD81S.mjs";
|
|
13
|
+
import { i as listLeaseFiles, r as leaseIsExpired, s as parseWorkspaceLeases } from "./device-lease-D-hLqx1b.mjs";
|
|
14
|
+
import { c as readMetroTunnel, f as readWorkspaceLaunches, u as readRemoteSession } from "./state-DcW6vOd_.mjs";
|
|
15
|
+
import { S as statsProjectKey$1, b as readStats$1, c as ACTIVE_BUILD_KEY, d as buildReport, f as buildStatusLine, m as parseActiveBuild, u as activeBuildState } from "./idle-DYVymG92.mjs";
|
|
16
|
+
import { F as ownedAvdDeviceProfile, O as listAdbDevices, d as androidToolPath, ft as parkedMaxSetting, ot as POOL_SETTING_REMEDY, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
|
|
17
|
+
import { d as listAllIosSimsAsync, v as parseSimctlList } from "./ios-BaInn3sW.mjs";
|
|
18
|
+
import { t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
|
|
19
|
+
import { a as parseNdjsonLine } from "./ndjson-PclOIwVY.mjs";
|
|
20
|
+
import { n as describeMetroLastStop, o as metroLastStop } from "./stop-cause-CidxIqup.mjs";
|
|
21
|
+
import { l as probeHostedSession } from "./hosted-client-a-WmqE2L.mjs";
|
|
22
|
+
import { m as hasMetroReverse } from "./app-install-CKWdbXIX.mjs";
|
|
23
|
+
import "./hosted-macos-BSZyIb3y.mjs";
|
|
24
|
+
import { m as readEasSessionLedger$1, p as easMachineStateRoot$1 } from "./device-remote-D5WVWqOq.mjs";
|
|
25
|
+
import { d as readIosDevices, s as isReachable } from "./ios-device-FfCN_C8p.mjs";
|
|
26
|
+
import { n as maintenanceLines, r as maintenanceStatus } from "./status-DlKvVJBE.mjs";
|
|
27
|
+
import { i as createStatusMeasurer, n as workspacePhase, r as applyStatusMeasures, s as mergeState } from "./warm-progress-CpGv3Wb6.mjs";
|
|
28
|
+
import { r as workspaceRecordingEnabled } from "./recordings-BIXOzVnr.mjs";
|
|
29
|
+
import { t as watchStdoutReader } from "./stdout-reader-C8yjre1x.mjs";
|
|
30
|
+
import { t as triggerMaintenance } from "./trigger-Bemu8ytw.mjs";
|
|
31
|
+
import { i as readSupervisorState } from "./stop-Bffozsac.mjs";
|
|
27
32
|
import { createHash, randomUUID } from "node:crypto";
|
|
33
|
+
import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
34
|
+
import { join, posix, resolve } from "node:path";
|
|
35
|
+
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, statSync as statSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
28
36
|
import { homedir, totalmem } from "os";
|
|
37
|
+
import { basename as basename$1, dirname as dirname$1, join as join$1 } from "path";
|
|
38
|
+
import { rotatedLogPath } from "@stim-cli/core";
|
|
39
|
+
import { ERROR_SOURCES, archiveRoot, archivedUsage, bundleMarkerPlatform, countErrorEntries, gitMergeCacheDir, hostedIosPlacements, hostedIosRecords, hostedIosStatus, linkReplacedArchives, logFiles, macosAppState, parseHostedIosPlacement, queryLogs, readArchives, readBuildDetail, readBuildHistory, readDeviceIdleShutdowns, readIdleStop, readLastBuilds, readMacosRecord, recordMatches, sortByTs, unreadableHostedIos, withDerivedFacts, withGitChip, withStateReadCache } from "@stim-cli/core/state";
|
|
29
40
|
import chalk from "chalk";
|
|
41
|
+
//#region src/device-host/hosted-ios-status.ts
|
|
42
|
+
function applyHostedIosProbe(placement, probe, slot = "default") {
|
|
43
|
+
const rerun = `stim ios --remote ${placement.machine}${slot === "default" ? "" : ` --slot ${slot}`}`;
|
|
44
|
+
const state = probe.state === "ready" ? "ready" : probe.state === "stopped" ? "stopped" : "unverified";
|
|
45
|
+
return {
|
|
46
|
+
ios: {
|
|
47
|
+
host: {
|
|
48
|
+
...hostedIosStatus(placement),
|
|
49
|
+
state
|
|
50
|
+
},
|
|
51
|
+
name: placement.device?.name ?? null,
|
|
52
|
+
udid: "",
|
|
53
|
+
owned: false,
|
|
54
|
+
state
|
|
55
|
+
},
|
|
56
|
+
...probe.state === "ready" ? {} : { warning: probe.state === "stopped" ? `The iOS session on ${placement.machine} stopped or no longer exists. Run ${rerun} to launch it again, or stim stop to clear the placement.` : `${placement.machine} could not confirm its iOS session${probe.state === "unknown" ? probe.notice ? `: ${probe.notice}` : "" : `: ${probe.reason.replace(/\.+$/, "")}`}. The placement stays recorded; run stim stop when the host answers.` }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function readHostedIosStatus(placements) {
|
|
60
|
+
return Object.fromEntries(await Promise.all(Object.entries(placements).map(async ([slot, placement]) => [slot, applyHostedIosProbe(placement, await probeHostedSession(placement), slot)])));
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/device-host/hosted-macos-status.ts
|
|
64
|
+
function applyHostedMacosProbe(record, probe) {
|
|
65
|
+
if (!record.host || probe.state === "ready") return { record };
|
|
66
|
+
const { machine, session } = record.host;
|
|
67
|
+
if (probe.state === "stopped") return {
|
|
68
|
+
record: {
|
|
69
|
+
...record,
|
|
70
|
+
hostLaunched: false
|
|
71
|
+
},
|
|
72
|
+
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.`
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
record: {
|
|
76
|
+
...record,
|
|
77
|
+
hostLaunched: "unverified"
|
|
78
|
+
},
|
|
79
|
+
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.`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function readHostedMacosStatus(record, options) {
|
|
83
|
+
if (!record?.host || record.supervisor || !record.hostLaunched) return { record };
|
|
84
|
+
return applyHostedMacosProbe(record, await probeHostedSession(record.host, options));
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
30
87
|
//#region src/status-watch.ts
|
|
31
88
|
const WATCH_FALLBACK_MS = 3e4;
|
|
32
89
|
const WATCH_LIGHT_INTERVAL_MS = 15e3;
|
|
@@ -88,17 +145,23 @@ function createRefreshScheduler({ debounceMs, lightIntervalMs = 0, run }) {
|
|
|
88
145
|
};
|
|
89
146
|
}
|
|
90
147
|
/**
|
|
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.
|
|
148
|
+
* Which refresh a change to `name` in a watched `$STIM_HOME` directory needs: `light` for a log append or a running
|
|
149
|
+
* build's detail, which can change only the log-derived fields, `full` for anything else that can change the payload,
|
|
150
|
+
* null for none. A null name means the platform did not report one.
|
|
94
151
|
*/
|
|
95
152
|
function statusChange(dir, name) {
|
|
153
|
+
if (dir === "archive" && (name?.startsWith(".incoming-") || name?.startsWith(".removing-") || name?.startsWith(".lock"))) return null;
|
|
96
154
|
if (name?.includes(".lock")) return null;
|
|
97
155
|
if (dir === "logs") return "light";
|
|
98
156
|
if (name === null) return "full";
|
|
99
157
|
switch (dir) {
|
|
100
|
-
case "home":
|
|
101
|
-
|
|
158
|
+
case "home":
|
|
159
|
+
if (name === "maintenance") return "light";
|
|
160
|
+
return name.startsWith("config.json") || name === "archive" || name === "workspaces" || name === "device-locks" ? "full" : null;
|
|
161
|
+
case "workspace":
|
|
162
|
+
if (name.startsWith("build-detail.json")) return "light";
|
|
163
|
+
return name.startsWith("state.json") || name.startsWith("ended-agents.json") || name === "logs" ? "full" : null;
|
|
164
|
+
case "maintenance": return name === "state.json" ? "light" : null;
|
|
102
165
|
case "eas": return name.startsWith("sessions.json") ? "full" : null;
|
|
103
166
|
default: return "full";
|
|
104
167
|
}
|
|
@@ -122,18 +185,20 @@ function watchStatusSources({ home, onChange, platform = process.platform, simct
|
|
|
122
185
|
const desiredDirs = () => {
|
|
123
186
|
const dirs = /* @__PURE__ */ new Map([
|
|
124
187
|
[home, "home"],
|
|
125
|
-
[join
|
|
126
|
-
[join
|
|
188
|
+
[join(home, "archive"), "archive"],
|
|
189
|
+
[join(home, "workspaces"), "workspaces"],
|
|
190
|
+
[join(home, "maintenance"), "maintenance"],
|
|
191
|
+
[join(home, "device-locks"), "leases"],
|
|
127
192
|
[easMachineStateRoot$1(), "eas"]
|
|
128
193
|
]);
|
|
129
194
|
let names = [];
|
|
130
195
|
try {
|
|
131
|
-
names = readdirSync(join
|
|
196
|
+
names = readdirSync(join(home, "workspaces"));
|
|
132
197
|
} catch {}
|
|
133
198
|
for (const name of names) {
|
|
134
|
-
const dir = join
|
|
199
|
+
const dir = join(home, "workspaces", name);
|
|
135
200
|
dirs.set(dir, "workspace");
|
|
136
|
-
dirs.set(join
|
|
201
|
+
dirs.set(join(dir, "logs"), "logs");
|
|
137
202
|
}
|
|
138
203
|
return dirs;
|
|
139
204
|
};
|
|
@@ -266,7 +331,7 @@ function trackAdbDevices(onChange) {
|
|
|
266
331
|
}
|
|
267
332
|
//#endregion
|
|
268
333
|
//#region src/diagnostics/error-index.ts
|
|
269
|
-
const INDEX_VERSION =
|
|
334
|
+
const INDEX_VERSION = 7;
|
|
270
335
|
const HEAD_BYTES = 1024;
|
|
271
336
|
const TAIL_BYTES = 256;
|
|
272
337
|
const CHUNK_BYTES = 4194304;
|
|
@@ -282,7 +347,7 @@ const ERROR_CRITERIA = {
|
|
|
282
347
|
includeAppDeviceErrors: true
|
|
283
348
|
};
|
|
284
349
|
/**
|
|
285
|
-
* The number of
|
|
350
|
+
* The number of rows the apps list for `queryLogs({ dir, errorsOnly: true })`, one per failure, without parsing every
|
|
286
351
|
* line on every call. `indexFile` keeps, per log file generation, the markers and error records
|
|
287
352
|
* already read and the byte offset they were read through; a later call reads only what was
|
|
288
353
|
* appended since. The index is a cache: losing it or a concurrent writer only costs a rescan.
|
|
@@ -292,7 +357,7 @@ function countErrorsSinceMarker(dir, indexFile) {
|
|
|
292
357
|
const files = {};
|
|
293
358
|
let changed = false;
|
|
294
359
|
for (const name of logFiles(dir)) {
|
|
295
|
-
const current = join(dir, name);
|
|
360
|
+
const current = join$1(dir, name);
|
|
296
361
|
for (const path of [rotatedLogPath(current), current]) {
|
|
297
362
|
const result = summarize(path, previous.files);
|
|
298
363
|
if (!result) continue;
|
|
@@ -306,10 +371,10 @@ function countErrorsSinceMarker(dir, indexFile) {
|
|
|
306
371
|
});
|
|
307
372
|
const records = [];
|
|
308
373
|
for (const summary of Object.values(files)) records.push(...Object.values(summary.markers), ...summary.errors);
|
|
309
|
-
return queryLogs({
|
|
374
|
+
return countErrorEntries(sortByTs(queryLogs({
|
|
310
375
|
records,
|
|
311
376
|
errorsOnly: true
|
|
312
|
-
})
|
|
377
|
+
})));
|
|
313
378
|
}
|
|
314
379
|
function summarize(path, known) {
|
|
315
380
|
let fd;
|
|
@@ -401,11 +466,17 @@ function collect(record, markers, errors) {
|
|
|
401
466
|
const metro = record.src === "metro";
|
|
402
467
|
const web = !metro && record.platform === "web";
|
|
403
468
|
const slot = record.slot ?? "default";
|
|
404
|
-
const
|
|
469
|
+
const platform = metro ? bundleMarkerPlatform(record) : record.platform ?? null;
|
|
470
|
+
const key = web ? "web" : JSON.stringify([
|
|
471
|
+
metro ? "metro" : "launch",
|
|
472
|
+
slot,
|
|
473
|
+
platform
|
|
474
|
+
]);
|
|
405
475
|
if ((markers[key]?.ts ?? -Infinity) < ts) markers[key] = metro ? {
|
|
406
476
|
src: "metro",
|
|
407
477
|
marker: true,
|
|
408
|
-
ts
|
|
478
|
+
ts,
|
|
479
|
+
...platform === null ? {} : { platform }
|
|
409
480
|
} : web ? {
|
|
410
481
|
platform: "web",
|
|
411
482
|
marker: true,
|
|
@@ -413,16 +484,20 @@ function collect(record, markers, errors) {
|
|
|
413
484
|
} : {
|
|
414
485
|
marker: true,
|
|
415
486
|
ts,
|
|
416
|
-
slot
|
|
487
|
+
slot,
|
|
488
|
+
...platform === null ? {} : { platform }
|
|
417
489
|
};
|
|
418
490
|
}
|
|
419
491
|
if (recordMatches(record, ERROR_CRITERIA)) errors.push({
|
|
420
492
|
src: record.src,
|
|
421
493
|
level: record.level,
|
|
422
494
|
event: record.event,
|
|
423
|
-
platform: record.platform,
|
|
495
|
+
platform: record.src === "metro" && record.marker === true ? bundleMarkerPlatform(record) : record.platform,
|
|
424
496
|
ts: ts ?? void 0,
|
|
425
|
-
slot: record.slot
|
|
497
|
+
slot: record.slot,
|
|
498
|
+
raw: record.raw,
|
|
499
|
+
marker: record.marker,
|
|
500
|
+
msg: record.marker === true ? record.msg : void 0
|
|
426
501
|
});
|
|
427
502
|
}
|
|
428
503
|
function readIndex(file) {
|
|
@@ -513,7 +588,7 @@ async function defaultBranchOf(repository) {
|
|
|
513
588
|
};
|
|
514
589
|
}
|
|
515
590
|
function mergeCacheFile(path) {
|
|
516
|
-
return join
|
|
591
|
+
return join(gitMergeCacheDir(), `${createHash("sha256").update(path).digest("hex").slice(0, 32)}.json`);
|
|
517
592
|
}
|
|
518
593
|
function readMergeCache(path) {
|
|
519
594
|
try {
|
|
@@ -587,14 +662,14 @@ const mergeTimeouts = /* @__PURE__ */ new Map();
|
|
|
587
662
|
const recent = /* @__PURE__ */ new Map();
|
|
588
663
|
function gitDirsOf(worktree) {
|
|
589
664
|
try {
|
|
590
|
-
const dotGit = join
|
|
665
|
+
const dotGit = join(worktree, ".git");
|
|
591
666
|
const pointer = statSync(dotGit).isDirectory() ? null : /^gitdir: (.+)$/m.exec(readFileSync(dotGit, "utf-8"))?.[1]?.trim();
|
|
592
|
-
const gitDir = pointer ? resolve
|
|
667
|
+
const gitDir = pointer ? resolve(worktree, pointer) : pointer === null ? dotGit : null;
|
|
593
668
|
if (!gitDir) return null;
|
|
594
669
|
try {
|
|
595
670
|
return {
|
|
596
671
|
gitDir,
|
|
597
|
-
commonDir: resolve
|
|
672
|
+
commonDir: resolve(gitDir, readFileSync(join(gitDir, "commondir"), "utf-8").trim())
|
|
598
673
|
};
|
|
599
674
|
} catch {
|
|
600
675
|
return {
|
|
@@ -623,21 +698,21 @@ function fileStamp(path) {
|
|
|
623
698
|
function gitFilesStamp(worktree, refs) {
|
|
624
699
|
const dirs = gitDirsOf(worktree.path);
|
|
625
700
|
if (!dirs) return {
|
|
626
|
-
stamp: fileStamp(join
|
|
701
|
+
stamp: fileStamp(join(worktree.path, ".git")),
|
|
627
702
|
missing: true
|
|
628
703
|
};
|
|
629
704
|
const { gitDir, commonDir } = dirs;
|
|
630
705
|
return {
|
|
631
706
|
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
|
|
707
|
+
join(gitDir, "index"),
|
|
708
|
+
join(gitDir, "HEAD"),
|
|
709
|
+
join(gitDir, "logs", "HEAD"),
|
|
710
|
+
join(commonDir, "packed-refs"),
|
|
711
|
+
join(commonDir, "FETCH_HEAD"),
|
|
712
|
+
join(commonDir, "refs", "remotes", "origin", "HEAD"),
|
|
713
|
+
...refs.target ? [join(commonDir, refs.target)] : [],
|
|
714
|
+
...worktree.branch ? [join(commonDir, "refs", "heads", worktree.branch)] : [],
|
|
715
|
+
...refs.upstream ? [join(commonDir, "refs", "remotes", refs.upstream), join(commonDir, "refs", "heads", refs.upstream)] : []
|
|
641
716
|
].map(fileStamp).join(" "),
|
|
642
717
|
missing: false
|
|
643
718
|
};
|
|
@@ -753,7 +828,7 @@ function plistBundleId(appPath) {
|
|
|
753
828
|
"raw",
|
|
754
829
|
"-o",
|
|
755
830
|
"-",
|
|
756
|
-
join
|
|
831
|
+
join(appPath, "Info.plist")
|
|
757
832
|
], { timeoutMs: 2e3 })?.trim() || null;
|
|
758
833
|
if (id) bundleIdsByApp.set(appPath, id);
|
|
759
834
|
return id;
|
|
@@ -777,196 +852,178 @@ function createAppProcessReader(tables, bundleIdOf = plistBundleId) {
|
|
|
777
852
|
};
|
|
778
853
|
}
|
|
779
854
|
//#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|$)/
|
|
855
|
+
//#region src/metro-bundle.ts
|
|
856
|
+
const IN_FLIGHT_MAX_MS = 6e5;
|
|
857
|
+
const RESTART_EVENTS = /* @__PURE__ */ new Set(["supervisor_started", "server_started"]);
|
|
858
|
+
const BUNDLE_EVENT = /^bundle_(?:response|prefetch)_(started|progress|finished|failed)$/;
|
|
859
|
+
function parseRecord(line) {
|
|
860
|
+
if (!line.includes("\"bundle_") && !line.includes("_started\"")) return null;
|
|
861
|
+
try {
|
|
862
|
+
const value = JSON.parse(line);
|
|
863
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
864
|
+
} catch {
|
|
865
|
+
return null;
|
|
805
866
|
}
|
|
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
867
|
}
|
|
823
|
-
function
|
|
824
|
-
return
|
|
868
|
+
function platformOf(value) {
|
|
869
|
+
return value === "ios" || value === "android" ? value : null;
|
|
825
870
|
}
|
|
826
871
|
/**
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
872
|
+
* The bundle state of a workspace's Metro from the tail of its `metro.ndjson`: the records Stim's middleware writes
|
|
873
|
+
* for each native bundle request, an app's (`bundle_response_*`) or Stim's own prefetch (`bundle_prefetch_*`),
|
|
874
|
+
* matched by `requestId`. A request counts as in flight only while
|
|
875
|
+
* Metro runs, when it started under the current dev server and less than 10 minutes ago, since a dev server that
|
|
876
|
+
* died mid-request never records its end. Null when the tail holds no bundle request.
|
|
832
877
|
*/
|
|
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);
|
|
878
|
+
function metroBundleState(lines, { running, now }) {
|
|
879
|
+
let open = /* @__PURE__ */ new Map();
|
|
880
|
+
let last;
|
|
881
|
+
let seen = false;
|
|
882
|
+
for (const line of lines) {
|
|
883
|
+
const record = parseRecord(line);
|
|
884
|
+
if (!record) continue;
|
|
885
|
+
const { event, requestId, ts } = record;
|
|
886
|
+
if (typeof event !== "string") continue;
|
|
887
|
+
if (RESTART_EVENTS.has(event)) {
|
|
888
|
+
open = /* @__PURE__ */ new Map();
|
|
877
889
|
continue;
|
|
878
890
|
}
|
|
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
|
|
891
|
+
const platform = platformOf(record.platform);
|
|
892
|
+
const stage = BUNDLE_EVENT.exec(event)?.[1];
|
|
893
|
+
if (!stage || typeof requestId !== "string" || typeof ts !== "number" || !platform) continue;
|
|
894
|
+
seen = true;
|
|
895
|
+
if (stage === "started") open.set(requestId, {
|
|
896
|
+
platform,
|
|
897
|
+
startedAt: ts
|
|
898
|
+
});
|
|
899
|
+
else if (stage === "progress") {
|
|
900
|
+
const request = open.get(requestId);
|
|
901
|
+
const percent = record.percent;
|
|
902
|
+
if (request && typeof percent === "number" && percent >= 0 && percent <= 100) request.percent = percent;
|
|
903
|
+
} else {
|
|
904
|
+
const request = open.get(requestId);
|
|
905
|
+
open.delete(requestId);
|
|
906
|
+
if (!request) continue;
|
|
907
|
+
last = {
|
|
908
|
+
platform,
|
|
909
|
+
status: stage === "finished" ? "ok" : "failed",
|
|
910
|
+
durationMs: Math.max(0, ts - request.startedAt),
|
|
911
|
+
finishedAt: new Date(ts).toISOString()
|
|
909
912
|
};
|
|
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
913
|
}
|
|
927
|
-
for (const pid of root.browserPids) if (present.has(pid)) claim(pid, `browser:${env.path}`, workspaceOwner("browser", env, "Chrome", null));
|
|
928
914
|
}
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
915
|
+
if (!seen) return null;
|
|
916
|
+
const active = running ? [...open.values()].filter((request) => now - request.startedAt < IN_FLIGHT_MAX_MS).toSorted((a, b) => b.startedAt - a.startedAt)[0] : void 0;
|
|
917
|
+
return {
|
|
918
|
+
bundling: Boolean(active),
|
|
919
|
+
...active ? {
|
|
920
|
+
platform: active.platform,
|
|
921
|
+
startedAt: new Date(active.startedAt).toISOString(),
|
|
922
|
+
...active.percent === void 0 ? {} : { percent: active.percent }
|
|
923
|
+
} : {},
|
|
924
|
+
...last ? { last } : {}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region src/physical-devices.ts
|
|
929
|
+
const EMULATOR_SERIAL = /^emulator-\d+$/;
|
|
930
|
+
/**
|
|
931
|
+
* The physical devices a workspace holds: each lease its state records whose file still carries the same token,
|
|
932
|
+
* names this workspace as holder, and has not expired. A lease on a simulator (a UDID in `simulatorUdids`) or an
|
|
933
|
+
* emulator (an `emulator-<port>` serial), which `device lock` also takes, is not a physical device. `read` is
|
|
934
|
+
* called once per device.
|
|
935
|
+
*/
|
|
936
|
+
function physicalDeviceStates(entries, held, { root, now, simulatorUdids, read }) {
|
|
937
|
+
const out = [];
|
|
938
|
+
for (const [key, record] of Object.entries(held)) {
|
|
939
|
+
const parsed = parseDeviceSlotKey(key);
|
|
940
|
+
const lease = entries.find((entry) => entry.lease?.token === record.token)?.lease;
|
|
941
|
+
if (!parsed || !lease || lease.platform !== parsed.platform || lease.id !== record.id) continue;
|
|
942
|
+
if (lease.holder !== root || leaseIsExpired(lease, now)) continue;
|
|
943
|
+
if (parsed.platform === "ios" ? simulatorUdids.has(lease.id.toUpperCase()) : EMULATOR_SERIAL.test(lease.id)) continue;
|
|
944
|
+
const reading = read(parsed.platform, lease.id);
|
|
945
|
+
out.push({
|
|
946
|
+
platform: parsed.platform,
|
|
947
|
+
slot: parsed.slot,
|
|
948
|
+
id: lease.id,
|
|
949
|
+
name: reading.name ?? lease.deviceName,
|
|
950
|
+
model: reading.model ?? (parsed.platform === "android" && lease.deviceName !== lease.id ? lease.deviceName : null),
|
|
951
|
+
owned: false,
|
|
952
|
+
physical: true,
|
|
953
|
+
connection: reading.connection,
|
|
954
|
+
lease: {
|
|
955
|
+
holder: lease.holder,
|
|
956
|
+
kind: record.kind,
|
|
957
|
+
grantedAt: lease.grantedAt,
|
|
958
|
+
expiresAt: lease.expiresAt
|
|
959
|
+
}
|
|
961
960
|
});
|
|
962
961
|
}
|
|
963
|
-
|
|
964
|
-
|
|
962
|
+
return out;
|
|
963
|
+
}
|
|
964
|
+
/** A phone's reading from devicectl's device list; null `devices` means devicectl could not be read. */
|
|
965
|
+
function iosPhysicalReading(devices, udid) {
|
|
966
|
+
if (!devices) return {
|
|
967
|
+
name: null,
|
|
968
|
+
model: null,
|
|
969
|
+
connection: "unknown"
|
|
970
|
+
};
|
|
971
|
+
const device = devices.find((entry) => entry.udid.toUpperCase() === udid.toUpperCase());
|
|
972
|
+
if (!device) return {
|
|
973
|
+
name: null,
|
|
974
|
+
model: null,
|
|
975
|
+
connection: "disconnected"
|
|
976
|
+
};
|
|
977
|
+
return {
|
|
978
|
+
name: device.name,
|
|
979
|
+
model: device.model ?? null,
|
|
980
|
+
connection: isReachable(device) ? "connected" : "disconnected"
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* A phone's reading from `adb devices`; null `devices` means adb could not be read. `android --device` and `device lock`
|
|
985
|
+
* record the phone's model as the lease's device name, so the model comes from there.
|
|
986
|
+
*/
|
|
987
|
+
function androidPhysicalReading(devices, serial) {
|
|
988
|
+
return {
|
|
989
|
+
name: null,
|
|
990
|
+
model: null,
|
|
991
|
+
connection: !devices ? "unknown" : devices.physical.some((entry) => entry.serial === serial) ? "connected" : "disconnected"
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function physicalDeviceLine(device, now) {
|
|
995
|
+
const slot = device.slot === "default" ? "" : ` [${device.slot}]`;
|
|
996
|
+
const name = device.name ?? device.id;
|
|
997
|
+
const model = device.model && device.model !== name ? `, ${device.model}` : "";
|
|
998
|
+
const remaining = formatElapsed(Date.parse(device.lease.expiresAt) - now);
|
|
999
|
+
return `${device.platform}${slot}: ${name} (physical${model}) ${device.connection} -- leased until ${clockTime(device.lease.expiresAt)} (${remaining} left)`;
|
|
965
1000
|
}
|
|
966
1001
|
//#endregion
|
|
967
1002
|
//#region src/commands/status.ts
|
|
968
1003
|
const WATCH_GIT_MAX_AGE_MS = 6e4;
|
|
969
|
-
|
|
1004
|
+
function iosStatusName(ios) {
|
|
1005
|
+
const host = ios.host;
|
|
1006
|
+
return host && !ios.udid ? `${host.device?.name ?? "iOS simulator"} (${host.device ? `iOS ${host.device.runtime.replace(/^iOS /, "")}` : "runtime pending"}) on ${host.machine}` : ios.name ?? ios.udid;
|
|
1007
|
+
}
|
|
1008
|
+
function iosStatusLines(record, slotLabel) {
|
|
1009
|
+
const out = [];
|
|
1010
|
+
const host = record?.udid ? record.host : void 0;
|
|
1011
|
+
const iosDevices = [record, ...host ? [{
|
|
1012
|
+
host,
|
|
1013
|
+
name: host.device?.name ?? null,
|
|
1014
|
+
udid: "",
|
|
1015
|
+
owned: false,
|
|
1016
|
+
state: host.state ?? "unverified"
|
|
1017
|
+
}] : []];
|
|
1018
|
+
for (const ios of iosDevices) {
|
|
1019
|
+
if (!ios) continue;
|
|
1020
|
+
const booted = ios.state === "Booted" ? chalk.green("booted") : chalk.dim(ios.state.toLowerCase());
|
|
1021
|
+
const owned = ios.owned ? chalk.dim(" (owned)") : "";
|
|
1022
|
+
out.push(` ios${slotLabel}: ${chalk.cyan(iosStatusName(ios))} ${booted}${owned}${activitySuffix(ios.activity)}${appSuffix(ios.app)}${idleShutdownSuffix(ios.idleShutdown)}`);
|
|
1023
|
+
if (!ios.udid && ios.host?.agent.driver === "agent-device") out.push(chalk.dim(` agent: ${ios.host.agent.command}`));
|
|
1024
|
+
}
|
|
1025
|
+
return out;
|
|
1026
|
+
}
|
|
970
1027
|
function formatGb(mb) {
|
|
971
1028
|
return `${(mb / 1024).toFixed(1)} GB`;
|
|
972
1029
|
}
|
|
@@ -977,12 +1034,14 @@ function statusCommand(program) {
|
|
|
977
1034
|
for (const line of renderStatus(await readStatus(0), json)) console.log(line);
|
|
978
1035
|
});
|
|
979
1036
|
}
|
|
980
|
-
function readStatus(gitMaxAgeMs, simctlListing = null) {
|
|
981
|
-
return withStateReadCache(() => readStatusFacts(gitMaxAgeMs, simctlListing));
|
|
1037
|
+
function readStatus(gitMaxAgeMs, simctlListing = null, archives) {
|
|
1038
|
+
return withStateReadCache(() => readStatusFacts(gitMaxAgeMs, simctlListing, archives));
|
|
982
1039
|
}
|
|
983
|
-
async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
1040
|
+
async function readStatusFacts(gitMaxAgeMs, simctlListing, cachedArchives) {
|
|
984
1041
|
const cfg = loadConfig$1();
|
|
985
1042
|
const projects = Object.entries(cfg?.projects || {});
|
|
1043
|
+
const iosReads = projects.map(([path]) => readHostedIosStatus(hostedIosPlacements(readWorkspaceState$1(path))));
|
|
1044
|
+
const macosReads = projects.map(([path]) => readHostedMacosStatus(readMacosRecord(path)));
|
|
986
1045
|
const cwdRoot = findServerWorkspace(process.cwd())?.root ?? null;
|
|
987
1046
|
const worktrees = linkedWorktrees([process.cwd(), ...projects.map(([path]) => path)]);
|
|
988
1047
|
const orphanWorktrees = unprovisionedWorktrees(worktrees, projects.map(([p]) => p));
|
|
@@ -1017,7 +1076,7 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1017
1076
|
}
|
|
1018
1077
|
const running = await processes;
|
|
1019
1078
|
await gitRead;
|
|
1020
|
-
const history = readStats().record?.history;
|
|
1079
|
+
const history = readStats$1().record?.history;
|
|
1021
1080
|
const states = [];
|
|
1022
1081
|
const labelOnlyRoots = [];
|
|
1023
1082
|
const launchesByState = [];
|
|
@@ -1029,11 +1088,15 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1029
1088
|
now: leaseNow
|
|
1030
1089
|
});
|
|
1031
1090
|
const roots = [];
|
|
1091
|
+
const readPhysicalDevice = physicalDeviceReader();
|
|
1092
|
+
const simulatorUdids = new Set([...Object.keys(simsByUdid), ...projects.flatMap(([, proj]) => projectDeviceSlots(proj).flatMap(({ platforms }) => platforms.ios?.deviceUdid ?? []))].map((udid) => udid.toUpperCase()));
|
|
1032
1093
|
for (const [i, [path, proj]] of projects.entries()) {
|
|
1033
1094
|
const { metro, supervisor } = running[i];
|
|
1034
1095
|
const saved = readWorkspaceState$1(path);
|
|
1035
1096
|
const builds = workspaceBuilds(path, saved, history);
|
|
1036
1097
|
const web = webFacts(readWebRecord(path));
|
|
1098
|
+
const macosFacts = await macosReads[i];
|
|
1099
|
+
const macos = macosAppState(macosFacts.record);
|
|
1037
1100
|
const activeBuild = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1038
1101
|
roots.push({
|
|
1039
1102
|
path,
|
|
@@ -1042,7 +1105,8 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1042
1105
|
platform: activeBuild.platform,
|
|
1043
1106
|
pid: activeBuild.claim.pid
|
|
1044
1107
|
} : null,
|
|
1045
|
-
browserPids: browserPids(web)
|
|
1108
|
+
browserPids: browserPids(web),
|
|
1109
|
+
macosPids: [macos?.supervisor, macos?.app].flatMap((owner) => owner && (0, process_identity_exports.inspectProcessIdentity)(owner) === "same" ? [owner.pid] : [])
|
|
1046
1110
|
});
|
|
1047
1111
|
const launches = readWorkspaceLaunches(path);
|
|
1048
1112
|
launchesByState.push(launches);
|
|
@@ -1060,38 +1124,97 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1060
1124
|
remote: remoteDeviceState(readRemoteSession(path), easLedger, path),
|
|
1061
1125
|
idleStop: readIdleStop(saved),
|
|
1062
1126
|
lastStop: metroLastStop(saved, supervisor),
|
|
1127
|
+
idleShutdowns: readDeviceIdleShutdowns(saved),
|
|
1063
1128
|
launches,
|
|
1064
1129
|
leasedIds: new Set(deviceLeaseStates(leaseFiles, {
|
|
1065
1130
|
root: path,
|
|
1066
1131
|
now: leaseNow
|
|
1067
1132
|
}).flatMap((lease) => lease.mine && !lease.expired && lease.id ? [lease.id] : [])),
|
|
1068
|
-
now: leaseNow
|
|
1133
|
+
now: leaseNow,
|
|
1134
|
+
workspaces: projects.map(([other]) => other)
|
|
1069
1135
|
}), web));
|
|
1070
1136
|
const state = states[states.length - 1];
|
|
1071
|
-
if (state)
|
|
1072
|
-
|
|
1137
|
+
if (state) {
|
|
1138
|
+
for (const [slot, record] of Object.entries(hostedIosRecords(saved))) if (!parseHostedIosPlacement(record)) state.warnings.push(unreadableHostedIos(slot));
|
|
1139
|
+
for (const [slot, facts] of Object.entries(await iosReads[i])) {
|
|
1140
|
+
const local = slot === "default" ? state.ios : state.slots?.find((entry) => entry.slot === slot)?.ios;
|
|
1141
|
+
const conflict = local?.udid && (local.state === "Booted" || local.state === "unknown");
|
|
1142
|
+
const ios = conflict ? {
|
|
1143
|
+
...local,
|
|
1144
|
+
host: facts.ios.host
|
|
1145
|
+
} : facts.ios;
|
|
1146
|
+
if (conflict) state.warnings.push(`Slot ${slot} records both a local and hosted iOS simulator (${iosStatusName(facts.ios)}); run stim stop to reconcile them.`);
|
|
1147
|
+
if (slot === "default") state.ios = ios;
|
|
1148
|
+
else {
|
|
1149
|
+
state.slots ??= [];
|
|
1150
|
+
const savedSlot = state.slots.find((entry) => entry.slot === slot);
|
|
1151
|
+
if (savedSlot) savedSlot.ios = ios;
|
|
1152
|
+
else state.slots.push({
|
|
1153
|
+
slot,
|
|
1154
|
+
ios,
|
|
1155
|
+
android: null
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (facts.warning) state.warnings.push(facts.warning);
|
|
1159
|
+
state.live ||= facts.ios.state === "ready";
|
|
1160
|
+
}
|
|
1161
|
+
const tunnel = readMetroTunnel(path);
|
|
1162
|
+
if (state.metro && tunnel?.kind === "managed" && tunnel.port === state.metro.port && pidExists(tunnel.pid)) state.metro.tunnel = {
|
|
1163
|
+
provider: tunnel.provider,
|
|
1164
|
+
url: tunnel.url
|
|
1165
|
+
};
|
|
1166
|
+
if (macos) {
|
|
1167
|
+
state.macos = macos;
|
|
1168
|
+
if (macosFacts.warning) state.warnings.push(macosFacts.warning);
|
|
1169
|
+
state.live ||= macos.state === "running" || macos.state === "orphaned" || macos.build.state === "running";
|
|
1170
|
+
}
|
|
1171
|
+
Object.assign(state, builds, workspacePhase(state.live, saved, { now: leaseNow }), {
|
|
1172
|
+
platforms: detectPlatforms(path, resolveSettings({
|
|
1173
|
+
projectPath: path,
|
|
1174
|
+
gitCommonDir: gitCommonDirOnDisk(path),
|
|
1175
|
+
repoRoot: state.worktree?.path ?? path
|
|
1176
|
+
})),
|
|
1177
|
+
recording: { enabled: workspaceRecordingEnabled(path, proj, cfg, process.env) }
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
const physicalDevices = physicalDeviceStates(leaseFiles, parseWorkspaceLeases(saved?.deviceLeases), {
|
|
1181
|
+
root: path,
|
|
1182
|
+
now: leaseNow,
|
|
1183
|
+
simulatorUdids,
|
|
1184
|
+
read: readPhysicalDevice
|
|
1185
|
+
});
|
|
1186
|
+
if (state && physicalDevices.length) state.physicalDevices = physicalDevices;
|
|
1187
|
+
labelOnlyRoots.push(Boolean(proj.worktreeRoot && !proj.bundleId && !state?.metro && !state?.ios && !state?.android && !state?.web && !state?.macos));
|
|
1073
1188
|
}
|
|
1189
|
+
applyStatusMeasures(states, worktrees);
|
|
1190
|
+
readLogDerivedFacts(states);
|
|
1074
1191
|
const tables = createDeviceProcessTables();
|
|
1075
1192
|
readDeviceProcesses(states, tables, {
|
|
1076
1193
|
projects,
|
|
1077
1194
|
launchesByState
|
|
1078
1195
|
});
|
|
1196
|
+
readMetroReverses(states, launchesByState);
|
|
1079
1197
|
const sims = Object.values(simsByUdid);
|
|
1080
1198
|
const simNames = Object.fromEntries(sims.map((sim) => [sim.udid.toUpperCase(), sim.name]));
|
|
1081
|
-
const machine = readMachineUsage({
|
|
1199
|
+
const machine = await readMachineUsage({
|
|
1082
1200
|
states,
|
|
1083
1201
|
roots,
|
|
1084
1202
|
simNames,
|
|
1085
1203
|
tables,
|
|
1086
1204
|
busy: sims.some((sim) => sim.state === "Booted")
|
|
1087
1205
|
});
|
|
1206
|
+
const archives = cachedArchives ?? readArchives();
|
|
1207
|
+
const maxCount = machineNumber("archive.maxCount", cfg, process.env);
|
|
1088
1208
|
return {
|
|
1209
|
+
archived: linkReplacedArchives(archives.slice(0, maxCount.value ?? 200), states.map((state) => state.path)),
|
|
1210
|
+
archivedUsage: archivedUsage(archives),
|
|
1089
1211
|
projects,
|
|
1090
1212
|
states,
|
|
1091
1213
|
labelOnlyRoots,
|
|
1092
1214
|
leases,
|
|
1093
1215
|
leaseNow,
|
|
1094
1216
|
orphanWorktrees,
|
|
1217
|
+
worktrees,
|
|
1095
1218
|
simsAvailable,
|
|
1096
1219
|
simctlError,
|
|
1097
1220
|
cwdRoot,
|
|
@@ -1101,6 +1224,48 @@ async function readStatusFacts(gitMaxAgeMs, simctlListing) {
|
|
|
1101
1224
|
simNames
|
|
1102
1225
|
};
|
|
1103
1226
|
}
|
|
1227
|
+
/** Sets the facts status derives from workspace logs, the running build's record and its detail file. */
|
|
1228
|
+
function readLogDerivedFacts(states) {
|
|
1229
|
+
const now = Date.now();
|
|
1230
|
+
for (const state of states) {
|
|
1231
|
+
if (state.metro) {
|
|
1232
|
+
const bundle = metroBundleState(tailLines(join$1(workspaceLogsDir$1(state.path), "metro.ndjson")) ?? [], {
|
|
1233
|
+
running: state.metro.running,
|
|
1234
|
+
now
|
|
1235
|
+
});
|
|
1236
|
+
if (bundle) state.metro.bundle = bundle;
|
|
1237
|
+
else delete state.metro.bundle;
|
|
1238
|
+
}
|
|
1239
|
+
if (state.build?.state === "running") {
|
|
1240
|
+
const record = parseActiveBuild(readWorkspaceState$1(state.path)?.[ACTIVE_BUILD_KEY]);
|
|
1241
|
+
if (!record) {
|
|
1242
|
+
state.build = null;
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
const report = buildReport(record, {
|
|
1246
|
+
state: activeBuildState(record.claim),
|
|
1247
|
+
history: void 0
|
|
1248
|
+
});
|
|
1249
|
+
const { outcome, expectedMs, expectedPhaseMs, basis, plannedPhases } = state.build;
|
|
1250
|
+
state.build = record.estimate || record.startedAt !== state.build.startedAt ? report : {
|
|
1251
|
+
...report,
|
|
1252
|
+
outcome,
|
|
1253
|
+
expectedMs,
|
|
1254
|
+
expectedPhaseMs: report.phase === state.build.phase ? expectedPhaseMs : null,
|
|
1255
|
+
basis,
|
|
1256
|
+
plannedPhases
|
|
1257
|
+
};
|
|
1258
|
+
const detail = record.phase === "compile" ? readBuildDetail(state.path, record.claim.claimId) : null;
|
|
1259
|
+
if (detail) state.build.detail = detail;
|
|
1260
|
+
else delete state.build.detail;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function phaseMarker({ phase, live, warmStep }) {
|
|
1265
|
+
if (phase === "warming") return chalk.cyan(` [warming: ${warmStep}]`);
|
|
1266
|
+
if (phase === "ready") return chalk.green(" [ready]");
|
|
1267
|
+
return live ? "" : chalk.dim(" [idle]");
|
|
1268
|
+
}
|
|
1104
1269
|
function browserPids(web) {
|
|
1105
1270
|
if (!web) return [];
|
|
1106
1271
|
const chrome = web.record.chromeProcess?.pid;
|
|
@@ -1108,37 +1273,48 @@ function browserPids(web) {
|
|
|
1108
1273
|
return web.status === "orphaned" && chrome ? [chrome] : [];
|
|
1109
1274
|
}
|
|
1110
1275
|
/**
|
|
1111
|
-
* Attributes the host process table
|
|
1112
|
-
* live or a build runs, so an idle
|
|
1276
|
+
* Attributes the host process table and the processes' footprints to their owners, and sets each environment's
|
|
1277
|
+
* `memoryMb` from them. Reads both only when a simulator is booted, a workspace is live or a build runs, so an idle
|
|
1278
|
+
* machine runs no `ps` and keeps the estimate.
|
|
1113
1279
|
*/
|
|
1114
|
-
function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1280
|
+
async function readMachineUsage({ states, roots, simNames, tables, busy }) {
|
|
1115
1281
|
const processes = busy || states.some((state) => state.live || state.build?.state === "running") ? tables.host() : null;
|
|
1116
|
-
|
|
1282
|
+
if (!processes) return null;
|
|
1283
|
+
const footprints = await readFootprints();
|
|
1284
|
+
const machine = attributeMachineUsage({
|
|
1117
1285
|
processes,
|
|
1286
|
+
footprints,
|
|
1118
1287
|
environments: states,
|
|
1119
1288
|
roots,
|
|
1120
1289
|
simNames
|
|
1121
|
-
})
|
|
1290
|
+
});
|
|
1291
|
+
applyMachineMemory(states, machine);
|
|
1292
|
+
return machine;
|
|
1122
1293
|
}
|
|
1123
1294
|
/**
|
|
1124
1295
|
* Rereads only the error counts, device activity and machine usage. With `machine`, and when the snapshot measured
|
|
1125
1296
|
* machine usage, it reads the host process table again; it reuses every other subprocess fact of the full read.
|
|
1126
1297
|
*/
|
|
1127
|
-
function refreshLightFacts(snapshot, machine) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1298
|
+
async function refreshLightFacts(snapshot, machine) {
|
|
1299
|
+
const fresh = createDeviceProcessTables();
|
|
1300
|
+
snapshot.tables = {
|
|
1301
|
+
host: machine && snapshot.machine ? fresh.host : snapshot.tables.host,
|
|
1302
|
+
android: snapshot.tables.android,
|
|
1303
|
+
tcpConnections: fresh.tcpConnections,
|
|
1304
|
+
clients: fresh.host
|
|
1131
1305
|
};
|
|
1132
1306
|
withStateReadCache(() => {
|
|
1133
1307
|
for (const state of snapshot.states) state.logs = logFacts(state.path);
|
|
1308
|
+
applyStatusMeasures(snapshot.states, snapshot.worktrees);
|
|
1309
|
+
readLogDerivedFacts(snapshot.states);
|
|
1134
1310
|
readDeviceProcesses(snapshot.states, snapshot.tables, null);
|
|
1135
1311
|
});
|
|
1136
|
-
if (machine && snapshot.machine) snapshot.machine = readMachineUsage({
|
|
1312
|
+
if (machine && snapshot.machine) snapshot.machine = await readMachineUsage({
|
|
1137
1313
|
...snapshot,
|
|
1138
1314
|
busy: true
|
|
1139
1315
|
});
|
|
1140
1316
|
}
|
|
1141
|
-
function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orphanWorktrees, simsAvailable, simctlError, cwdRoot, machine }, json) {
|
|
1317
|
+
function renderStatus({ archived, archivedUsage: usage, projects, states, labelOnlyRoots, leases, leaseNow, orphanWorktrees, simsAvailable, simctlError, cwdRoot, machine }, json) {
|
|
1142
1318
|
const out = [];
|
|
1143
1319
|
const totalMemoryMb = Math.round(totalmem() / 1048576);
|
|
1144
1320
|
const cap = capacity(states, totalMemoryMb);
|
|
@@ -1153,20 +1329,26 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1153
1329
|
})
|
|
1154
1330
|
};
|
|
1155
1331
|
});
|
|
1332
|
+
const maintenance = maintenanceStatus();
|
|
1156
1333
|
if (json) {
|
|
1157
1334
|
out.push(JSON.stringify({
|
|
1158
|
-
environments: states.map((state, i) => labelOnlyRoots[i] ? {
|
|
1335
|
+
environments: states.map((state, i) => withDerivedFacts(labelOnlyRoots[i] ? {
|
|
1159
1336
|
...state,
|
|
1160
1337
|
labelOnly: true
|
|
1161
|
-
} : state),
|
|
1338
|
+
} : state)),
|
|
1339
|
+
archived,
|
|
1340
|
+
archivedUsage: usage,
|
|
1162
1341
|
capacity: cap,
|
|
1163
1342
|
deviceLeases: leases,
|
|
1164
|
-
unprovisionedWorktrees: orphanWorktrees,
|
|
1343
|
+
unprovisionedWorktrees: orphanWorktrees.map(withGitChip),
|
|
1165
1344
|
simctlAvailable: simsAvailable,
|
|
1166
|
-
machine
|
|
1345
|
+
machine,
|
|
1346
|
+
maintenance
|
|
1167
1347
|
}));
|
|
1168
1348
|
return out;
|
|
1169
1349
|
}
|
|
1350
|
+
out.push(...maintenanceLines(maintenance).map((line) => chalk.dim(line)));
|
|
1351
|
+
if (usage.count) out.push(`Archived: ${plural(usage.count, "workspace")}, ${formatBytes(usage.bytes)}`);
|
|
1170
1352
|
if (projects.length === 0 && orphanWorktrees.length === 0) {
|
|
1171
1353
|
out.push(chalk.dim("No projects registered."));
|
|
1172
1354
|
for (const pool of pools) {
|
|
@@ -1182,12 +1364,16 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1182
1364
|
if (!state) continue;
|
|
1183
1365
|
const shortcut = projectShortcut(path, proj);
|
|
1184
1366
|
const marker = path === cwdRoot ? chalk.bold.cyan(`* ${shortcut}`) : shortcut;
|
|
1185
|
-
const idle = state
|
|
1367
|
+
const idle = phaseMarker(state);
|
|
1186
1368
|
out.push(`\n${marker}${idle} ${chalk.dim(`(${path})`)}`);
|
|
1187
1369
|
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
1370
|
if (state.metro) {
|
|
1189
1371
|
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
1372
|
out.push(` metro: port ${state.metro.port} ${label}`);
|
|
1373
|
+
if (state.metro.tunnel) {
|
|
1374
|
+
const { provider, url } = state.metro.tunnel;
|
|
1375
|
+
out.push(` tunnel: ${provider}${provider === "tailscale" ? " (tailnet-only)" : ""} ${url}`);
|
|
1376
|
+
}
|
|
1191
1377
|
}
|
|
1192
1378
|
if (state.supervisor) {
|
|
1193
1379
|
const health = state.supervisor.healthy ? chalk.green("healthy") : chalk.yellow("not answering");
|
|
@@ -1200,6 +1386,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1200
1386
|
}
|
|
1201
1387
|
out.push(...lastBuildsLines(state.lastBuilds));
|
|
1202
1388
|
if (state.worktree?.git) out.push(chalk.dim(` git: ${gitSummaryText(state.worktree.git)}`));
|
|
1389
|
+
for (const agent of state.agents ?? []) out.push(chalk.dim(` agent: ${agentLine(agent)}`));
|
|
1203
1390
|
if (state.logs) {
|
|
1204
1391
|
const n = state.logs.errorsSinceMarker;
|
|
1205
1392
|
const errs = n > 0 ? chalk.yellow(` (${n} error${n === 1 ? "" : "s"} since the last marker)`) : "";
|
|
@@ -1211,23 +1398,31 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1211
1398
|
android: state.android
|
|
1212
1399
|
}, ...state.slots ?? []]) {
|
|
1213
1400
|
const slotLabel = deviceState.slot === "default" ? "" : ` [${deviceState.slot}]`;
|
|
1214
|
-
|
|
1215
|
-
const booted = deviceState.ios.state === "Booted" ? chalk.green("booted") : chalk.dim(deviceState.ios.state.toLowerCase());
|
|
1216
|
-
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)}`);
|
|
1218
|
-
}
|
|
1401
|
+
out.push(...iosStatusLines(deviceState.ios, slotLabel));
|
|
1219
1402
|
if (deviceState.android) {
|
|
1220
1403
|
const kind = deviceState.android.physical ? chalk.dim("(physical)") : chalk.dim("(emulator)");
|
|
1221
1404
|
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)}`);
|
|
1405
|
+
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
1406
|
}
|
|
1224
1407
|
}
|
|
1408
|
+
if (state.macos) {
|
|
1409
|
+
const host = state.macos.host;
|
|
1410
|
+
const where = host ? ` on ${host.machine} as ${host.bundleId}${chalk.dim(` (session ${host.session})`)}` : "";
|
|
1411
|
+
const build = state.macos.build;
|
|
1412
|
+
const placement = build.buildMachine === void 0 ? "" : `; build machine ${build.buildMachine}, built on ${build.builtOn ?? "none"}`;
|
|
1413
|
+
out.push(` macOS ${state.macos.product}: ${state.macos.state}${where}; build ${build.state}${placement}`);
|
|
1414
|
+
if (host?.agent.driver === "agent-device") out.push(chalk.dim(` agent: ${host.agent.command}`));
|
|
1415
|
+
}
|
|
1225
1416
|
if (state.web) {
|
|
1226
1417
|
const browser = state.web.running ? chalk.green(`running (pid ${state.web.pid}, DevTools ${state.web.cdpEndpoint})`) : chalk.dim("not running");
|
|
1227
|
-
|
|
1418
|
+
const page = state.web.page;
|
|
1419
|
+
const failed = page?.state === "failed" ? ` -- ${chalk.yellow(`page failed: ${page.error}`)}` : "";
|
|
1420
|
+
out.push(` web: ${chalk.cyan(page?.url ?? state.web.url)} ${browser}${state.web.headless ? "" : chalk.dim(" (headed)")}${activitySuffix(state.web.activity)}${failed}`);
|
|
1228
1421
|
}
|
|
1422
|
+
for (const device of state.physicalDevices ?? []) out.push(` ${physicalDeviceLine(device, leaseNow)}`);
|
|
1229
1423
|
for (const remote of state.remoteDevices ?? []) out.push(` ${remoteDeviceLine(remote)}`);
|
|
1230
1424
|
for (const w of state.warnings) out.push(chalk.yellow(` ! ${w}`));
|
|
1425
|
+
for (const note of state.issues.filter((issue) => issue.severity === "info")) out.push(chalk.dim(` - ${note.message}`));
|
|
1231
1426
|
}
|
|
1232
1427
|
for (const pool of pools) {
|
|
1233
1428
|
if (pool.error) out.push(chalk.yellow(`\n${pool.error} ${POOL_SETTING_REMEDY}`));
|
|
@@ -1242,7 +1437,7 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1242
1437
|
out.push(chalk.dim(`\nWorktrees with no environment (${orphanWorktrees.length}):`));
|
|
1243
1438
|
for (const w of orphanWorktrees) out.push(chalk.dim(` ${orphanWorktreeLine(w)}`));
|
|
1244
1439
|
}
|
|
1245
|
-
out.push(chalk.dim(`\n${cap.liveCount} live environment(s), roughly ${formatGb(cap.committedMb)} of ${formatGb(cap.totalMemoryMb)} committed.`));
|
|
1440
|
+
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
1441
|
const volumes = readVolumes(cwdRoot || process.cwd());
|
|
1247
1442
|
const line = diskLine(volumes);
|
|
1248
1443
|
if (line) {
|
|
@@ -1257,16 +1452,31 @@ function renderStatus({ projects, states, labelOnlyRoots, leases, leaseNow, orph
|
|
|
1257
1452
|
}
|
|
1258
1453
|
async function watchStatus(json) {
|
|
1259
1454
|
let last = null;
|
|
1455
|
+
let archiveStamp = null;
|
|
1456
|
+
let archives;
|
|
1260
1457
|
let snapshot = null;
|
|
1261
1458
|
let sources = null;
|
|
1459
|
+
const measurer = createStatusMeasurer({ updated: () => scheduler.trigger("light") });
|
|
1262
1460
|
const scheduler = createRefreshScheduler({
|
|
1263
1461
|
debounceMs: 250,
|
|
1264
1462
|
lightIntervalMs: WATCH_LIGHT_INTERVAL_MS,
|
|
1265
1463
|
run: async (kind) => {
|
|
1464
|
+
triggerMaintenance("status-watch");
|
|
1266
1465
|
let text;
|
|
1267
1466
|
try {
|
|
1268
|
-
if (kind === "light" && snapshot) refreshLightFacts(snapshot, json);
|
|
1269
|
-
else
|
|
1467
|
+
if (kind === "light" && snapshot) await refreshLightFacts(snapshot, json);
|
|
1468
|
+
else {
|
|
1469
|
+
let stamp = null;
|
|
1470
|
+
try {
|
|
1471
|
+
stamp = statSync$1(archiveRoot()).mtimeMs;
|
|
1472
|
+
} catch {}
|
|
1473
|
+
if (archives === void 0 || stamp !== archiveStamp) {
|
|
1474
|
+
archives = readArchives();
|
|
1475
|
+
archiveStamp = stamp;
|
|
1476
|
+
}
|
|
1477
|
+
snapshot = await readStatus(WATCH_GIT_MAX_AGE_MS, sources?.simulatorListing(), archives);
|
|
1478
|
+
}
|
|
1479
|
+
measurer.schedule(snapshot.states, snapshot.worktrees);
|
|
1270
1480
|
text = renderStatus(snapshot, json).join("\n");
|
|
1271
1481
|
} catch (error) {
|
|
1272
1482
|
console.error(chalk.red(String(error?.message || error)));
|
|
@@ -1281,24 +1491,17 @@ async function watchStatus(json) {
|
|
|
1281
1491
|
});
|
|
1282
1492
|
const fallback = setInterval(() => scheduler.trigger("full"), WATCH_FALLBACK_MS);
|
|
1283
1493
|
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
1494
|
const finish = () => {
|
|
1292
1495
|
clearInterval(fallback);
|
|
1293
1496
|
clearInterval(machine);
|
|
1294
|
-
|
|
1497
|
+
stopReader();
|
|
1295
1498
|
scheduler.stop();
|
|
1296
1499
|
sources?.stop();
|
|
1297
1500
|
process.exit(0);
|
|
1298
1501
|
};
|
|
1502
|
+
const stopReader = watchStdoutReader(finish);
|
|
1299
1503
|
process.on("SIGINT", finish);
|
|
1300
1504
|
process.on("SIGTERM", finish);
|
|
1301
|
-
process.stdout.on("error", finish);
|
|
1302
1505
|
sources = watchStatusSources({
|
|
1303
1506
|
home: getConfigDir(),
|
|
1304
1507
|
onChange: (kind) => scheduler.trigger(kind)
|
|
@@ -1314,6 +1517,10 @@ async function readGitInto(worktrees, orphans, maxAgeMs) {
|
|
|
1314
1517
|
});
|
|
1315
1518
|
for (const worktree of worktrees) worktree.git = byPath.get(worktree.path) ?? null;
|
|
1316
1519
|
}
|
|
1520
|
+
function agentLine({ tool, title, sessionId, lastActiveAt }) {
|
|
1521
|
+
const at = lastActiveAt ? `, active ${lastActiveAt}` : "";
|
|
1522
|
+
return `${tool === "codex" ? "Codex" : "Claude Code"} ${title ? `"${title}"` : sessionId}${at}`;
|
|
1523
|
+
}
|
|
1317
1524
|
function orphanWorktreeLine(w) {
|
|
1318
1525
|
return `${w.path}${w.branch ? ` [${w.branch}]` : ""}${w.git ? ` -- ${gitSummaryText(w.git)}` : ""}`;
|
|
1319
1526
|
}
|
|
@@ -1324,7 +1531,11 @@ function lastBuildsLines(reports) {
|
|
|
1324
1531
|
function lastBuildText(report) {
|
|
1325
1532
|
const source = report.cacheHit ? `${report.cacheHit} cache` : report.status === "ok" ? "compiled" : "no cache hit";
|
|
1326
1533
|
const took = report.durationMs === null ? "" : ` in ${formatDuration(report.durationMs)}`;
|
|
1327
|
-
|
|
1534
|
+
const placement = report.buildMachine === void 0 ? "" : `; build machine ${report.buildMachine}, built on ${report.builtOn ?? (report.cacheHit ? "none (cache)" : "none")}`;
|
|
1535
|
+
return `${report.platform} ${report.status === "ok" ? source : `failed (${report.errorCode ?? "error"}), ${source}`}${took}${placement}`;
|
|
1536
|
+
}
|
|
1537
|
+
function idleShutdownSuffix(record) {
|
|
1538
|
+
return record ? chalk.dim(` -- shut down after ${record.idleMinutes}m idle at ${record.at} (devices.idleShutdownMinutes)`) : "";
|
|
1328
1539
|
}
|
|
1329
1540
|
function activitySuffix(activity) {
|
|
1330
1541
|
const label = activityLabel(activity, Date.now());
|
|
@@ -1378,6 +1589,28 @@ function readDeviceProcesses(states, tables, apps) {
|
|
|
1378
1589
|
});
|
|
1379
1590
|
}
|
|
1380
1591
|
}
|
|
1592
|
+
const web = state.web;
|
|
1593
|
+
const port = web?.cdpEndpoint ? Number(new URL(web.cdpEndpoint).port) : null;
|
|
1594
|
+
if (web?.running && port) {
|
|
1595
|
+
web.page = readWebPage(state.path);
|
|
1596
|
+
const ownPids = [web.pid, web.supervisorPid].filter((pid) => pid !== null);
|
|
1597
|
+
web.activity = statusActivity(readWebActivity({
|
|
1598
|
+
port,
|
|
1599
|
+
ownPids,
|
|
1600
|
+
workspace: state.path
|
|
1601
|
+
}, tables));
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
function readMetroReverses(states, launchesByState) {
|
|
1606
|
+
for (const [i, state] of states.entries()) for (const target of metroReverseTargets(state, launchesByState[i])) {
|
|
1607
|
+
const listing = getExecutor().runFileQuiet("adb", [
|
|
1608
|
+
"-s",
|
|
1609
|
+
target.serial,
|
|
1610
|
+
"reverse",
|
|
1611
|
+
"--list"
|
|
1612
|
+
], { timeoutMs: 5e3 });
|
|
1613
|
+
if (listing !== null && !hasMetroReverse(listing, target.metroPort)) addReverseMissingIssue(state, target);
|
|
1381
1614
|
}
|
|
1382
1615
|
}
|
|
1383
1616
|
function appSuffix(app) {
|
|
@@ -1388,7 +1621,7 @@ function appSuffix(app) {
|
|
|
1388
1621
|
}
|
|
1389
1622
|
function linkedWorktrees(paths) {
|
|
1390
1623
|
return [...new Set(paths.flatMap((path) => gitCommonDirOnDisk(path) ?? []))].flatMap((common) => {
|
|
1391
|
-
const repository = basename(common) === ".git" ? dirname(common) : common;
|
|
1624
|
+
const repository = basename$1(common) === ".git" ? dirname$1(common) : common;
|
|
1392
1625
|
return linkedWorktreesOnDisk(common).map((entry) => Object.assign(entry, { repository }));
|
|
1393
1626
|
});
|
|
1394
1627
|
}
|
|
@@ -1404,7 +1637,7 @@ function workspaceBuilds(path, saved, history) {
|
|
|
1404
1637
|
function workspaceBuild(path, saved, history) {
|
|
1405
1638
|
const record = parseActiveBuild(saved?.[ACTIVE_BUILD_KEY]);
|
|
1406
1639
|
if (!record) return null;
|
|
1407
|
-
const projectKey = statsProjectKey({
|
|
1640
|
+
const projectKey = statsProjectKey$1({
|
|
1408
1641
|
root: path,
|
|
1409
1642
|
commonDir: gitCommonDir(path),
|
|
1410
1643
|
repoRoot: repoRoot(path)
|
|
@@ -1414,6 +1647,32 @@ function workspaceBuild(path, saved, history) {
|
|
|
1414
1647
|
history: history?.[projectKey]
|
|
1415
1648
|
});
|
|
1416
1649
|
}
|
|
1650
|
+
const PHYSICAL_DEVICE_LIST_TIMEOUT_MS = 5e3;
|
|
1651
|
+
const PHYSICAL_DEVICE_LIST_MAX_AGE_MS = 3e4;
|
|
1652
|
+
const physicalDeviceLists = {};
|
|
1653
|
+
function freshList(entry, read) {
|
|
1654
|
+
const now = Date.now();
|
|
1655
|
+
return entry && now - entry.at < PHYSICAL_DEVICE_LIST_MAX_AGE_MS ? entry : {
|
|
1656
|
+
at: now,
|
|
1657
|
+
devices: read()
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function physicalDeviceReader() {
|
|
1661
|
+
return (platform, id) => {
|
|
1662
|
+
if (platform === "ios") {
|
|
1663
|
+
physicalDeviceLists.ios = freshList(physicalDeviceLists.ios, () => readIosDevices({ timeoutMs: PHYSICAL_DEVICE_LIST_TIMEOUT_MS }));
|
|
1664
|
+
return iosPhysicalReading(physicalDeviceLists.ios.devices, id);
|
|
1665
|
+
}
|
|
1666
|
+
physicalDeviceLists.android = freshList(physicalDeviceLists.android, () => {
|
|
1667
|
+
try {
|
|
1668
|
+
return listAdbDevices({ timeoutMs: PHYSICAL_DEVICE_LIST_TIMEOUT_MS });
|
|
1669
|
+
} catch {
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
return androidPhysicalReading(physicalDeviceLists.android.devices, id);
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1417
1676
|
function androidRuntimeReader() {
|
|
1418
1677
|
const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
|
|
1419
1678
|
return (avdName) => readAndroidRuntime(() => resolve(avdName));
|
|
@@ -1494,7 +1753,7 @@ async function supervisorFacts(path, proj, metroResolution, lookup) {
|
|
|
1494
1753
|
};
|
|
1495
1754
|
}
|
|
1496
1755
|
function logFacts(path) {
|
|
1497
|
-
const dir = workspaceLogsDir(path);
|
|
1756
|
+
const dir = workspaceLogsDir$1(path);
|
|
1498
1757
|
if (!existsSync$1(dir)) return null;
|
|
1499
1758
|
try {
|
|
1500
1759
|
return {
|