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,29 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { r as
|
|
14
|
-
import {
|
|
15
|
-
import { A as
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { n as
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
+
import { A as directorySize, D as withConfigLock$1, F as volumeRootFor, H as projectDeviceSlots, M as isOnMountedVolume, N as listMountedVolumes, S as removeProject, _ as loadConfig$1, j as formatBytes, k as acquireAvdClaim, p as getProject } from "./config-D0DbpZtM.mjs";
|
|
3
|
+
import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
4
|
+
import { n as describePullRequest, o as pullRequestLookups, r as endedPullRequest } from "./pull-request-pxnDCQzn.mjs";
|
|
5
|
+
import { h as shortUdid, o as formatLongDuration, u as plural } from "./command-output-DnJMdEY-.mjs";
|
|
6
|
+
import { _t as resolveFullRef, bt as sourceCheckoutOf, ct as hasUncommittedWork, dt as listWorktrees, ot as hasPopulatedSubmodules, rt as dirtyPaths, xt as unpushedCommits } from "./settings-oTSkkNrM.mjs";
|
|
7
|
+
import { o as findProjectRoot$1, r as detectIsExpo } from "./project-Du91Ilvc.mjs";
|
|
8
|
+
import { C as workspaceStateFile, s as workspaceLastUsed, v as workspaceDir, w as withDirLock$1, x as workspaceLogsDir$1 } from "./workspace-state-B8-aJ1_5.mjs";
|
|
9
|
+
import { A as isWorkspacesScope, B as memoryCacheKind, C as unverifiedDeviceCommand, D as includesParkedDevices, E as includesArchives, F as trimCaches, G as reclaimMemory, H as memoryScope, K as scopedMemoryReport, M as previewScopedEmptying, N as selectCaches, O as includesRecordings, P as selectsBeyondCaches, R as collectMemoryReport, S as findStaleProjectDevices, T as emptyCaches, U as memorySweepIsScoped, V as memoryLines, W as memoryWorkPending, X as discoverCaches, Z as sizeCaches, _ as describeUnverifiableDevices, a as machineNumber, b as findOrphanedDevices, d as collectParkedAvds, f as collectParkedSims, g as describeParkedSims, h as deleteProjectDevices, j as planCacheEmptying, k as includesWorkspaceOutputs, l as DEVICE_LIST_TIMEOUT_MS, m as deleteParkedSims, p as deleteParkedAvds, q as phase, u as collectDeviceLeases, v as deviceSweepIsScoped, w as withAndroidAvdSizes, x as findStaleDeviceRecords, y as eraseParkedDevices } from "./budget-DjrURfQn.mjs";
|
|
10
|
+
import { a as takeGcResults, i as recordGcResult, n as withIdleWorkspace, o as canonicalPath, r as workspaceInUse } from "./in-use-DvBw0Kxo.mjs";
|
|
11
|
+
import { d as removeExpiredLease, h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-D-hLqx1b.mjs";
|
|
12
|
+
import { n as listBuildSlots } from "./build-slots-rUk77xqh.mjs";
|
|
13
|
+
import { a as parseIdleDuration, o as shutDownIdleDevices, r as idleDeviceLines, t as collectIdleDevices } from "./idle-DYVymG92.mjs";
|
|
14
|
+
import { n as readCreatedDevices$1, t as forgetCreatedDevice } from "./created-devices-D5owjN4C.mjs";
|
|
15
|
+
import { A as listAvds, H as parseAvdSystemImage, I as ownedAvdDirectory, M as listOrphanedAvdDirectories, V as parseAvdDeviceProfile, _ as avdStorageRoots, at as isStimOwnedSim, d as androidToolPath, ft as parkedMaxSetting, it as isStimOwnedAvd, j as listInstalledSystemImages, m as avdNameAbsent, o as androidHome, ot as POOL_SETTING_REMEDY, p as avdIniPaths, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
|
|
16
|
+
import { u as listAllIosSims } from "./ios-BaInn3sW.mjs";
|
|
17
|
+
import { a as collectWorkspaceOutputs, c as listWorkspaceDirs, i as collectOrphanedWorkspaces, l as rootPresence, n as WORKSPACE_OUTPUT_DIRS, o as deleteOrphanedWorkspaces, r as clearWorkspaceOutputs, s as isInsideWorkspaces, t as REBUILD_COST, u as sizeText } from "./workspaces-BV4pdHFz.mjs";
|
|
18
|
+
import { n as previewMaintenance } from "./preview-DhVbvPAZ.mjs";
|
|
19
|
+
import { r as readCollectors } from "./ownership-DiA16AsN.mjs";
|
|
20
|
+
import { f as resolveEasCliBin } from "./remote-cache-C8-V13XN.mjs";
|
|
21
|
+
import { C as listOwnedSessionsArgs, D as verifyStoppedSession, E as stopSessionArgs, S as isDefinitiveMissingSessionError, T as parseSessionListPage, b as getSessionArgs, d as withRemoteSessionLock, f as withEasProjectLock, h as removeEasSessionClaim, m as readEasSessionLedger$1, p as easMachineStateRoot$1, x as inspectSessionForTeardown, y as findOrphanedOwnedSessions } from "./device-remote-D5WVWqOq.mjs";
|
|
22
|
+
import { o as fetchDefaultBranch, s as mergeState } from "./warm-progress-CpGv3Wb6.mjs";
|
|
23
|
+
import { _ as deleteSelectedArchives, f as removeWorktreeTarget, g as reclaimProject, h as describeDereferenced, i as matchWorktreeEntry, n as excludePodChurn, o as reclaimKeys, r as excludeWatchmanCookies, v as sweepArchiveStaging } from "./worktree-K-xj9Db0.mjs";
|
|
24
|
+
import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-Dpih1Tfa.mjs";
|
|
25
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
26
|
+
import { basename, dirname, join } from "node:path";
|
|
27
|
+
import { closeSync as closeSync$1, existsSync as existsSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readSync as readSync$1, readdirSync as readdirSync$1, renameSync as renameSync$1, rmSync as rmSync$1, statSync as statSync$1, utimesSync as utimesSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
28
|
+
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
23
29
|
import { LOG_ROTATE_BYTES, rotatedLogPath } from "@stim-cli/core";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
30
|
+
import { InvalidArgumentError } from "commander";
|
|
31
|
+
import { listRecordedDevices, listStatusCacheEntries, maintenanceRunClaims, readArchives, recordedBytes, settingDefinition } from "@stim-cli/core/state";
|
|
26
32
|
import chalk from "chalk";
|
|
33
|
+
//#region src/commands/gc/archives.ts
|
|
34
|
+
function stagingOrNone() {
|
|
35
|
+
try {
|
|
36
|
+
return sweepArchiveStaging(false, 5e3);
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function collectArchives(scope, olderThan, now) {
|
|
42
|
+
const wanted = scope.trim().toLowerCase();
|
|
43
|
+
const records = readArchives();
|
|
44
|
+
const id = wanted.startsWith("archived:") ? scope.trim().slice(9) : null;
|
|
45
|
+
const kind = wanted === "archived-logs" ? "logs" : wanted === "archived-recordings" ? "recordings" : wanted === "archived-agent" ? "agentActions" : null;
|
|
46
|
+
const match = id !== null ? records.find((record) => record.id.toLowerCase() === id.toLowerCase()) : null;
|
|
47
|
+
return {
|
|
48
|
+
records: records.filter((record) => id === null || record.id === match?.id).map((record) => ({
|
|
49
|
+
id: record.id,
|
|
50
|
+
kinds: kind ? [kind] : [
|
|
51
|
+
"logs",
|
|
52
|
+
"recordings",
|
|
53
|
+
"agentActions",
|
|
54
|
+
"record"
|
|
55
|
+
].filter((key) => record.bytes[key] > 0),
|
|
56
|
+
bytes: kind ? record.bytes[kind] : record.bytes.total,
|
|
57
|
+
expires: record.expires,
|
|
58
|
+
willDelete: (id === null && olderThan !== null ? now - Date.parse(record.removedAt) >= olderThan * 864e5 : true) && (!kind || record.bytes[kind] > 0)
|
|
59
|
+
})),
|
|
60
|
+
staging: stagingOrNone(),
|
|
61
|
+
unknownId: id !== null && !match ? id : null,
|
|
62
|
+
knownIds: records.map((record) => record.id),
|
|
63
|
+
kind
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function archiveLines(selection) {
|
|
67
|
+
if (!selection) return [];
|
|
68
|
+
return [
|
|
69
|
+
`Archived workspaces (${selection.records.length}):`,
|
|
70
|
+
...selection.records.map((record) => ` ${record.id}: ${record.kinds.join(", ")}, ${formatBytes(record.bytes)}, expires ${JSON.stringify(record.expires)}${record.willDelete ? "" : " (kept)"}`),
|
|
71
|
+
...selection.staging.map((entry) => ` ${entry.path}: ${entry.kept ? `kept: ${entry.reason}` : "abandoned staging"}${entry.removeCommand ? `; ${entry.removeCommand}` : ""}`)
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
function deleteArchives(selection) {
|
|
75
|
+
if (!selection) return 0;
|
|
76
|
+
let failures = 0;
|
|
77
|
+
for (const record of selection.records.filter((entry) => entry.willDelete)) try {
|
|
78
|
+
deleteSelectedArchives([record.id], selection.kind);
|
|
79
|
+
recordGcResult("archive", "done", record.id, {
|
|
80
|
+
id: record.id,
|
|
81
|
+
bytes: record.bytes
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
failures++;
|
|
85
|
+
recordGcResult("archive", "failed", record.id, {
|
|
86
|
+
id: record.id,
|
|
87
|
+
detail: error.message
|
|
88
|
+
});
|
|
89
|
+
console.error(`Could not delete ${record.id}: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const remaining = new Set(sweepArchiveStaging(true, 5e3).map((entry) => entry.path));
|
|
93
|
+
for (const entry of selection.staging) if (!entry.kept && !remaining.has(entry.path)) recordGcResult("archive", "done", "archive staging", { id: entry.path });
|
|
94
|
+
} catch (error) {
|
|
95
|
+
failures++;
|
|
96
|
+
console.error(`Could not clear archive staging: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
return failures;
|
|
99
|
+
}
|
|
100
|
+
function archiveRefusal(selection) {
|
|
101
|
+
if (selection?.unknownId === void 0 || selection.unknownId === null) return null;
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
const message = `Unknown archive "${selection.unknownId}".`;
|
|
104
|
+
const remedy = `Known archive ids: ${selection.knownIds.join(", ") || "(none)"}`;
|
|
105
|
+
console.error(chalk.red(message));
|
|
106
|
+
console.error(chalk.dim(remedy));
|
|
107
|
+
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
108
|
+
return {
|
|
109
|
+
code: "STIM_BAD_ARG",
|
|
110
|
+
message,
|
|
111
|
+
remedy
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function archiveWorkPending(selection, otherWork) {
|
|
115
|
+
return otherWork || Boolean(selection?.records.some((record) => record.willDelete) || selection?.staging.some((entry) => !entry.kept));
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
27
118
|
//#region src/commands/gc/eas-sessions.ts
|
|
28
119
|
const EAS_OPERATION_TIMEOUT_MS = 3e4;
|
|
29
120
|
const EAS_COLLECTION_TIMEOUT_MS = 6e4;
|
|
@@ -87,7 +178,7 @@ function readRecordedRemoteSessionIds(roots) {
|
|
|
87
178
|
};
|
|
88
179
|
}
|
|
89
180
|
function claimStateAbsence(claim) {
|
|
90
|
-
const stateDir = dirname(claim.stateFile);
|
|
181
|
+
const stateDir = dirname$1(claim.stateFile);
|
|
91
182
|
let entries;
|
|
92
183
|
try {
|
|
93
184
|
if (!statSync$1(stateDir).isDirectory()) throw new Error("path is not a directory");
|
|
@@ -98,7 +189,7 @@ function claimStateAbsence(claim) {
|
|
|
98
189
|
notice: `${stateDir} is not available for EAS ownership verification: ${describeError(error)}`
|
|
99
190
|
};
|
|
100
191
|
}
|
|
101
|
-
if (!entries.includes(basename(claim.stateFile))) return {
|
|
192
|
+
if (!entries.includes(basename$1(claim.stateFile))) return {
|
|
102
193
|
absent: true,
|
|
103
194
|
notice: null
|
|
104
195
|
};
|
|
@@ -135,7 +226,7 @@ function claimStateAbsence(claim) {
|
|
|
135
226
|
};
|
|
136
227
|
}
|
|
137
228
|
function collectEasSessionSweep(config, deps) {
|
|
138
|
-
const projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
229
|
+
const projectRoot = (deps.findProjectRoot ?? findProjectRoot$1)(process.cwd());
|
|
139
230
|
const empty = (notices = []) => ({
|
|
140
231
|
projectScope: projectRoot,
|
|
141
232
|
orphaned: [],
|
|
@@ -230,7 +321,7 @@ function unavailableLockRootNotice(roots) {
|
|
|
230
321
|
}
|
|
231
322
|
function malformedRemoteSessionLockNotice(roots) {
|
|
232
323
|
for (const root of roots) {
|
|
233
|
-
const lockPath = join(workspaceDir(root), "remote-session.lock");
|
|
324
|
+
const lockPath = join$1(workspaceDir(root), "remote-session.lock");
|
|
234
325
|
if (!existsSync$1(lockPath)) continue;
|
|
235
326
|
if ((0, ownership_claim_exports.readClaimSet)(lockPath).unresolved.length === 0) continue;
|
|
236
327
|
return `EAS session sweep skipped: the remote-session lock at ${lockPath} has a malformed owner record.`;
|
|
@@ -443,7 +534,7 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
443
534
|
//#endregion
|
|
444
535
|
//#region src/commands/gc/worktrees.ts
|
|
445
536
|
const DEFAULT_WORKTREE_IDLE_DAYS = 7;
|
|
446
|
-
const DAY_MS = 864e5;
|
|
537
|
+
const DAY_MS$1 = 864e5;
|
|
447
538
|
const MINUTE_MS = 6e4;
|
|
448
539
|
const GRACE_SETTING = "gc.worktreeGraceMinutes";
|
|
449
540
|
const MERGE_DECIDES = /* @__PURE__ */ new Set([
|
|
@@ -504,7 +595,7 @@ function removalBlocker(facts, olderThan) {
|
|
|
504
595
|
if (facts.locked) return skip("locked", "locked with git worktree lock");
|
|
505
596
|
if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
|
|
506
597
|
if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
|
|
507
|
-
const dirty = excludePodChurn(facts.porcelain).lines.length;
|
|
598
|
+
const dirty = excludePodChurn(excludeWatchmanCookies(facts.porcelain).lines).lines.length;
|
|
508
599
|
if (dirty) return skip("dirty", `dirty: ${plural(dirty, "uncommitted or untracked file")}`);
|
|
509
600
|
if (facts.unpushed === null) return skip("unpushed-unchecked", "unpushed commits could not be checked");
|
|
510
601
|
const merged = facts.merge?.merged ? facts.merge : null;
|
|
@@ -536,7 +627,7 @@ function lastUsedOf(keys) {
|
|
|
536
627
|
}
|
|
537
628
|
function idleDaysOf(keys, now) {
|
|
538
629
|
const last = lastUsedOf(keys);
|
|
539
|
-
return Number.isFinite(last) ? Math.max(0, Math.floor((now - last) / DAY_MS)) : null;
|
|
630
|
+
return Number.isFinite(last) ? Math.max(0, Math.floor((now - last) / DAY_MS$1)) : null;
|
|
540
631
|
}
|
|
541
632
|
function deviceReads() {
|
|
542
633
|
let sims;
|
|
@@ -605,10 +696,10 @@ function newestMtime(paths) {
|
|
|
605
696
|
return newest;
|
|
606
697
|
}
|
|
607
698
|
function logFiles$1(key) {
|
|
608
|
-
const dir = workspaceLogsDir(key);
|
|
699
|
+
const dir = workspaceLogsDir$1(key);
|
|
609
700
|
try {
|
|
610
701
|
return readdirSync$1(dir).map((name) => ({
|
|
611
|
-
path: join(dir, name),
|
|
702
|
+
path: join$1(dir, name),
|
|
612
703
|
basis: "a Stim log write"
|
|
613
704
|
}));
|
|
614
705
|
} catch (error) {
|
|
@@ -627,15 +718,15 @@ function worktreeActivityOf(path, keys) {
|
|
|
627
718
|
if (!gitDir) return null;
|
|
628
719
|
const paths = [
|
|
629
720
|
{
|
|
630
|
-
path: join(gitDir, "index"),
|
|
721
|
+
path: join$1(gitDir, "index"),
|
|
631
722
|
basis: "a git index write"
|
|
632
723
|
},
|
|
633
724
|
{
|
|
634
|
-
path: join(gitDir, "HEAD"),
|
|
725
|
+
path: join$1(gitDir, "HEAD"),
|
|
635
726
|
basis: "a git HEAD change"
|
|
636
727
|
},
|
|
637
728
|
{
|
|
638
|
-
path: join(gitDir, "logs", "HEAD"),
|
|
729
|
+
path: join$1(gitDir, "logs", "HEAD"),
|
|
639
730
|
basis: "a git reflog entry"
|
|
640
731
|
}
|
|
641
732
|
];
|
|
@@ -666,7 +757,7 @@ function porcelainOf(path, gitAnswered) {
|
|
|
666
757
|
return lines.length ? lines : null;
|
|
667
758
|
}
|
|
668
759
|
function candidateRoots() {
|
|
669
|
-
const registered = Object.keys(loadConfig$1()?.projects ?? {}).filter(isAbsolute);
|
|
760
|
+
const registered = Object.keys(loadConfig$1()?.projects ?? {}).filter(isAbsolute$1);
|
|
670
761
|
const recorded = listWorkspaceDirs().flatMap((entry) => entry.projectRoot ? [entry.projectRoot] : []);
|
|
671
762
|
return [.../* @__PURE__ */ new Set([...registered, ...recorded])].filter((root) => existsSync$1(root)).toSorted();
|
|
672
763
|
}
|
|
@@ -708,7 +799,9 @@ async function collectWorktreeSweep({ idle, olderThan, now }) {
|
|
|
708
799
|
};
|
|
709
800
|
const groups = /* @__PURE__ */ new Map();
|
|
710
801
|
const outside = [];
|
|
711
|
-
|
|
802
|
+
const candidates = candidateRoots();
|
|
803
|
+
if (candidates.length) phase("worktrees", `checking ${plural(candidates.length, "project")} for merged or idle worktrees`);
|
|
804
|
+
for (const root of candidates) {
|
|
712
805
|
const entry = matchWorktreeEntry(listWorktrees(root), root);
|
|
713
806
|
if (!entry) {
|
|
714
807
|
outside.push({
|
|
@@ -879,6 +972,206 @@ async function removeWorktrees(sweep, { now = Date.now() } = {}) {
|
|
|
879
972
|
return failures;
|
|
880
973
|
}
|
|
881
974
|
//#endregion
|
|
975
|
+
//#region src/commands/gc/status-caches.ts
|
|
976
|
+
const KINDS = ["disk-usage", "pull-request"];
|
|
977
|
+
const KEPT_REASON = {
|
|
978
|
+
unreadable: "the file records no readable path of its own",
|
|
979
|
+
unknown: "Stim cannot tell whether the folder it measures still exists",
|
|
980
|
+
unmounted: "the volume of the folder it measures is not mounted"
|
|
981
|
+
};
|
|
982
|
+
function witnessPath(path) {
|
|
983
|
+
return basename(path).startsWith("node_modules") ? dirname(path) : path;
|
|
984
|
+
}
|
|
985
|
+
function judge(path, mountedVolumes) {
|
|
986
|
+
if (path === null) return "unreadable";
|
|
987
|
+
const witness = witnessPath(path);
|
|
988
|
+
const present = rootPresence(witness);
|
|
989
|
+
if (present === null) return "unknown";
|
|
990
|
+
if (present) return "present";
|
|
991
|
+
return isOnMountedVolume(witness, mountedVolumes) ? "gone" : "unmounted";
|
|
992
|
+
}
|
|
993
|
+
function classifyStatusCaches(entries, mountedVolumes) {
|
|
994
|
+
const stale = [];
|
|
995
|
+
const kept = /* @__PURE__ */ new Map();
|
|
996
|
+
for (const { kind, file, path, bytes } of entries) {
|
|
997
|
+
const verdict = judge(path, mountedVolumes);
|
|
998
|
+
if (verdict === "present") continue;
|
|
999
|
+
if (verdict === "gone") {
|
|
1000
|
+
stale.push({
|
|
1001
|
+
kind,
|
|
1002
|
+
file,
|
|
1003
|
+
path,
|
|
1004
|
+
bytes
|
|
1005
|
+
});
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const dir = dirname(file);
|
|
1009
|
+
const key = `${dir}\n${verdict}`;
|
|
1010
|
+
const seen = kept.get(key);
|
|
1011
|
+
if (seen) seen.count++;
|
|
1012
|
+
else kept.set(key, {
|
|
1013
|
+
dir,
|
|
1014
|
+
verdict,
|
|
1015
|
+
count: 1
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return {
|
|
1019
|
+
stale,
|
|
1020
|
+
skipped: [...kept.values()].map(({ dir, verdict, count }) => ({
|
|
1021
|
+
dir,
|
|
1022
|
+
reason: `${count} entr${count === 1 ? "y" : "ies"} kept: ${KEPT_REASON[verdict]}`
|
|
1023
|
+
}))
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function collectStaleStatusCaches(mountedVolumes) {
|
|
1027
|
+
return classifyStatusCaches(listStatusCacheEntries(), mountedVolumes);
|
|
1028
|
+
}
|
|
1029
|
+
function statusCacheLines(stale) {
|
|
1030
|
+
if (!stale.length) return [];
|
|
1031
|
+
const total = stale.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
1032
|
+
const lines = [`Stale status cache entries (${stale.length}, ${formatBytes(total)}) - the folder or worktree they measure is gone:`];
|
|
1033
|
+
for (const kind of KINDS) {
|
|
1034
|
+
const count = stale.filter((entry) => entry.kind === kind).length;
|
|
1035
|
+
if (count) lines.push(` ${kind}: ${count}`);
|
|
1036
|
+
}
|
|
1037
|
+
lines.push(" --delete removes them; `stim status` measures a folder again if it comes back.");
|
|
1038
|
+
return lines;
|
|
1039
|
+
}
|
|
1040
|
+
function deleteStaleStatusCaches(stale) {
|
|
1041
|
+
const mountedVolumes = listMountedVolumes();
|
|
1042
|
+
let failures = 0;
|
|
1043
|
+
for (const kind of KINDS) {
|
|
1044
|
+
let removed = 0;
|
|
1045
|
+
let bytes = 0;
|
|
1046
|
+
for (const entry of stale.filter((candidate) => candidate.kind === kind)) {
|
|
1047
|
+
if (judge(entry.path, mountedVolumes) !== "gone") continue;
|
|
1048
|
+
try {
|
|
1049
|
+
rmSync(entry.file, { force: true });
|
|
1050
|
+
removed++;
|
|
1051
|
+
bytes += entry.bytes;
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
failures++;
|
|
1054
|
+
const detail = error?.message || String(error);
|
|
1055
|
+
console.log(chalk.red(`Could not remove ${entry.file}: ${detail}`));
|
|
1056
|
+
recordGcResult("statusCache", "failed", `${kind} cache entry`, {
|
|
1057
|
+
id: entry.file,
|
|
1058
|
+
detail
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (!removed) continue;
|
|
1063
|
+
const label = `${removed} stale ${kind} cache entr${removed === 1 ? "y" : "ies"}`;
|
|
1064
|
+
console.log(chalk.green(`Removed ${label}`));
|
|
1065
|
+
recordGcResult("statusCache", "done", label, { bytes });
|
|
1066
|
+
}
|
|
1067
|
+
return failures;
|
|
1068
|
+
}
|
|
1069
|
+
//#endregion
|
|
1070
|
+
//#region src/commands/gc/recordings.ts
|
|
1071
|
+
const DAY_MS = 864e5;
|
|
1072
|
+
function oldSegments(devices, olderThan, now) {
|
|
1073
|
+
const cutoff = now - olderThan * DAY_MS;
|
|
1074
|
+
return devices.flatMap((device) => device.segments).filter((segment) => !segment.open && segment.end < cutoff);
|
|
1075
|
+
}
|
|
1076
|
+
function planRecordings(entries, { whole, olderThan, now }) {
|
|
1077
|
+
return entries.map(({ recordings, projectRoot, problem, gone, devices }) => {
|
|
1078
|
+
const bytes = recordedBytes(devices);
|
|
1079
|
+
const entry = {
|
|
1080
|
+
dir: recordings,
|
|
1081
|
+
projectRoot,
|
|
1082
|
+
bytes,
|
|
1083
|
+
withWorkspace: false
|
|
1084
|
+
};
|
|
1085
|
+
const kept = (keptCode, keptReason) => ({
|
|
1086
|
+
...entry,
|
|
1087
|
+
deleteBytes: 0,
|
|
1088
|
+
willDelete: false,
|
|
1089
|
+
keptCode,
|
|
1090
|
+
keptReason
|
|
1091
|
+
});
|
|
1092
|
+
const remove = (deleteBytes, withWorkspace = false) => ({
|
|
1093
|
+
...entry,
|
|
1094
|
+
withWorkspace,
|
|
1095
|
+
deleteBytes,
|
|
1096
|
+
willDelete: true,
|
|
1097
|
+
keptCode: null,
|
|
1098
|
+
keptReason: null
|
|
1099
|
+
});
|
|
1100
|
+
if (projectRoot === null) return kept("unresolved", `workspace directory not resolved: ${problem ?? "unknown project root"}`);
|
|
1101
|
+
if (gone) return remove(bytes, true);
|
|
1102
|
+
if (whole) return remove(bytes);
|
|
1103
|
+
if (olderThan !== null) {
|
|
1104
|
+
const old = oldSegments(devices, olderThan, now);
|
|
1105
|
+
return old.length ? remove(old.reduce((sum, segment) => sum + segment.bytes, 0)) : kept("recently-recorded", `nothing recorded more than ${olderThan}d ago`);
|
|
1106
|
+
}
|
|
1107
|
+
return kept("retained", "the last 15 minutes of footage per device stay until worktree remove, --cache recordings, --older-than or recording.enabled false");
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* The device recordings of every workspace directory. Recordings in `gone` directories, which the orphaned
|
|
1112
|
+
* workspace and dead project sweeps remove whole, are listed with them; entries whose project root is on an
|
|
1113
|
+
* unmounted volume are left out.
|
|
1114
|
+
*/
|
|
1115
|
+
function collectRecordings(plan, gone = []) {
|
|
1116
|
+
const mountedVolumes = listMountedVolumes();
|
|
1117
|
+
return planRecordings(listWorkspaceDirs().filter((entry) => entry.projectRoot === null || isOnMountedVolume(entry.projectRoot, mountedVolumes)).map((entry) => Object.assign({}, entry, { recordings: join$1(entry.dir, "recordings") })).filter((entry) => existsSync$1(entry.recordings)).map((entry) => Object.assign({}, entry, {
|
|
1118
|
+
gone: gone.includes(entry.dir),
|
|
1119
|
+
devices: listRecordedDevices(entry.recordings)
|
|
1120
|
+
})), plan);
|
|
1121
|
+
}
|
|
1122
|
+
/** Deletes what `planRecordings` chose, planning again from the files on disk. */
|
|
1123
|
+
function deleteRecordings(report, plan) {
|
|
1124
|
+
let failures = 0;
|
|
1125
|
+
for (const entry of report) {
|
|
1126
|
+
const label = entry.projectRoot ?? entry.dir;
|
|
1127
|
+
if (entry.withWorkspace) continue;
|
|
1128
|
+
if (!entry.willDelete) {
|
|
1129
|
+
if (entry.keptCode === "unresolved" || entry.keptCode === "recently-recorded") {
|
|
1130
|
+
console.log(chalk.dim(`Kept the device recordings of ${label}: ${entry.keptReason}`));
|
|
1131
|
+
recordGcResult("recording", "kept", label, {
|
|
1132
|
+
bytes: entry.bytes,
|
|
1133
|
+
detail: entry.keptReason
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
let deleted = 0;
|
|
1140
|
+
if (plan.whole) {
|
|
1141
|
+
deleted = recordedBytes(listRecordedDevices(entry.dir));
|
|
1142
|
+
rmSync$1(entry.dir, {
|
|
1143
|
+
recursive: true,
|
|
1144
|
+
force: true
|
|
1145
|
+
});
|
|
1146
|
+
} else if (plan.olderThan !== null) for (const segment of oldSegments(listRecordedDevices(entry.dir), plan.olderThan, plan.now)) {
|
|
1147
|
+
rmSync$1(segment.file, { force: true });
|
|
1148
|
+
deleted += segment.bytes;
|
|
1149
|
+
}
|
|
1150
|
+
console.log(chalk.green(`Deleted ${formatBytes(deleted)} of device recordings of ${label}`));
|
|
1151
|
+
recordGcResult("recording", "done", label, { bytes: deleted });
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
failures++;
|
|
1154
|
+
const detail = error?.message || String(error);
|
|
1155
|
+
console.log(chalk.red(`Could not delete the device recordings of ${label}: ${detail}`));
|
|
1156
|
+
recordGcResult("recording", "failed", label, {
|
|
1157
|
+
bytes: entry.bytes,
|
|
1158
|
+
detail
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return failures;
|
|
1163
|
+
}
|
|
1164
|
+
function recordingLines(recordings) {
|
|
1165
|
+
if (!recordings.length) return [];
|
|
1166
|
+
const total = recordings.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
1167
|
+
const lines = [`Device recordings (${formatBytes(total)} in ${recordings.length}):`];
|
|
1168
|
+
for (const entry of recordings) {
|
|
1169
|
+
lines.push(` ${formatBytes(entry.bytes).padStart(10)} ${entry.projectRoot ?? entry.dir}`);
|
|
1170
|
+
lines.push(entry.withWorkspace ? " would be DELETED with its gone workspace directory" : entry.willDelete ? ` would DELETE ${formatBytes(entry.deleteBytes)}` : ` kept: ${entry.keptReason}`);
|
|
1171
|
+
}
|
|
1172
|
+
return lines;
|
|
1173
|
+
}
|
|
1174
|
+
//#endregion
|
|
882
1175
|
//#region src/commands/gc/report.ts
|
|
883
1176
|
function shortKey(key) {
|
|
884
1177
|
const text = String(key ?? "");
|
|
@@ -889,7 +1182,12 @@ function parkedAge(parkedAt, now) {
|
|
|
889
1182
|
if (!Number.isFinite(at)) return "parked at an unknown time";
|
|
890
1183
|
return `parked ${formatLongDuration(Math.max(0, now - at))} ago`;
|
|
891
1184
|
}
|
|
892
|
-
|
|
1185
|
+
const PARKED_DELETE_NOTE = " --delete attempts verified deletions and keeps failures.";
|
|
1186
|
+
const PARKED_ERASE_NOTE = " --delete erases each verified device (apps and data) and keeps it parked.";
|
|
1187
|
+
function parkedApp(app) {
|
|
1188
|
+
return app ? ` ${app}` : "";
|
|
1189
|
+
}
|
|
1190
|
+
function formatParkedSimReport(parkedSims, now, erase) {
|
|
893
1191
|
if (parkedSims.length === 0) return [];
|
|
894
1192
|
const known = parkedSims.filter((sim) => sim.bytes !== null);
|
|
895
1193
|
const total = known.reduce((sum, sim) => sum + (sim.bytes ?? 0), 0);
|
|
@@ -900,20 +1198,20 @@ function formatParkedSimReport(parkedSims, now) {
|
|
|
900
1198
|
const age = parkedAge(sim.parkedAt, now);
|
|
901
1199
|
const bytes = sim.bytes === null ? "" : ` ${formatBytes(sim.bytes)}`;
|
|
902
1200
|
const gone = sim.listed === false ? " - not on this machine" : sim.listed === null ? " - listing unavailable; kept" : "";
|
|
903
|
-
lines.push(` ios ${sim.name} (${shortUdid(sim.udid)})${model ? ` ${model}` : ""} ${age}${bytes}${gone}`);
|
|
1201
|
+
lines.push(` ios ${sim.name} (${shortUdid(sim.udid)})${model ? ` ${model}` : ""} ${age}${parkedApp(sim.app)}${bytes}${gone}`);
|
|
904
1202
|
}
|
|
905
|
-
lines.push(
|
|
1203
|
+
lines.push(erase ? PARKED_ERASE_NOTE : PARKED_DELETE_NOTE);
|
|
906
1204
|
return lines;
|
|
907
1205
|
}
|
|
908
|
-
function formatParkedAvdReport(parkedAvds, now) {
|
|
1206
|
+
function formatParkedAvdReport(parkedAvds, now, erase) {
|
|
909
1207
|
if (!parkedAvds.length) return [];
|
|
910
1208
|
const lines = [];
|
|
911
1209
|
lines.push(`Parked emulators (${parkedAvds.length}):`);
|
|
912
1210
|
for (const avd of parkedAvds) {
|
|
913
1211
|
const listed = avd.listed === false ? " - not on this machine" : avd.listed === null ? " - listing unavailable; kept" : "";
|
|
914
|
-
lines.push(` android ${avd.name} ${avd.systemImage}${avd.deviceProfile ? ` ${avd.deviceProfile}` : ""} ${parkedAge(avd.parkedAt, now)}${avd.bytes === null ? "" : ` ${formatBytes(avd.bytes)}`}${listed}`);
|
|
1212
|
+
lines.push(` android ${avd.name} ${avd.systemImage}${avd.deviceProfile ? ` ${avd.deviceProfile}` : ""} ${parkedAge(avd.parkedAt, now)}${parkedApp(avd.app)}${avd.bytes === null ? "" : ` ${formatBytes(avd.bytes)}`}${listed}`);
|
|
915
1213
|
}
|
|
916
|
-
lines.push(
|
|
1214
|
+
lines.push(erase ? PARKED_ERASE_NOTE : PARKED_DELETE_NOTE);
|
|
917
1215
|
return lines;
|
|
918
1216
|
}
|
|
919
1217
|
function projectEntryLines(header, paths) {
|
|
@@ -928,7 +1226,23 @@ function unresolvedLockLines(locks, slots) {
|
|
|
928
1226
|
" run that yourself once you know nothing is building with it"
|
|
929
1227
|
];
|
|
930
1228
|
}
|
|
931
|
-
function
|
|
1229
|
+
function scopeLine(cacheScope) {
|
|
1230
|
+
if (memoryCacheKind(cacheScope)) return `Cache scope: "${cacheScope}". Only these helper processes were inspected.`;
|
|
1231
|
+
return includesParkedDevices(cacheScope) ? `Cache scope: "${cacheScope}". Unparked devices, project entries and locks were not inspected.` : `Cache scope: "${cacheScope}". Devices, project entries and locks were not inspected.`;
|
|
1232
|
+
}
|
|
1233
|
+
function maintenanceReportLines(maintenance) {
|
|
1234
|
+
const lines = [];
|
|
1235
|
+
if (maintenance) {
|
|
1236
|
+
lines.push(`Automatic maintenance (${maintenance.mode === "report" ? "report only" : "off"}):`);
|
|
1237
|
+
if (maintenance.note) lines.push(` ${maintenance.note}`);
|
|
1238
|
+
for (const action of maintenance.actions) lines.push(` ${action.kind}: ${action.target} (${formatBytes(action.bytes)}): ${action.reason}`);
|
|
1239
|
+
for (const skip of maintenance.skips) lines.push(` kept ${skip.target}: ${skip.reason}`);
|
|
1240
|
+
for (const reason of maintenance.blocked) lines.push(` blocked: ${reason}`);
|
|
1241
|
+
if (!maintenance.actions.length && !maintenance.blocked.length) lines.push(" no actions planned");
|
|
1242
|
+
}
|
|
1243
|
+
return lines;
|
|
1244
|
+
}
|
|
1245
|
+
function formatGcReport({ maintenance, archives, skipped = [], deadProjects = [], orphanedPorts, invalidProjects = [], orphanedWorkspaces = [], parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], staleStatusCaches = [], buildLocks = {
|
|
932
1246
|
stale: [],
|
|
933
1247
|
live: [],
|
|
934
1248
|
unresolved: []
|
|
@@ -944,13 +1258,13 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
944
1258
|
orphaned: [],
|
|
945
1259
|
notices: [],
|
|
946
1260
|
deletionSafe: true
|
|
947
|
-
}, caches = [], workspaceOutputs = null, workspaceLogs = [], worktreeSweep = null, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
948
|
-
const lines =
|
|
1261
|
+
}, agentDeviceUsage = null, swiftpmCacheUsage, caches = [], workspaceOutputs = null, workspaceLogs = [], recordings = [], worktreeSweep = null, memory, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
1262
|
+
const lines = archiveLines(archives);
|
|
949
1263
|
const staleLocks = buildLocks?.stale ?? [];
|
|
950
1264
|
const liveLocks = buildLocks?.live ?? [];
|
|
951
1265
|
const staleSlots = buildSlots?.stale ?? [];
|
|
952
1266
|
const expiredLeases = deviceLeases?.expired ?? [];
|
|
953
|
-
if (cacheScope) lines.push(
|
|
1267
|
+
if (cacheScope) lines.push(scopeLine(cacheScope));
|
|
954
1268
|
else if ([
|
|
955
1269
|
deadProjects,
|
|
956
1270
|
invalidProjects,
|
|
@@ -961,6 +1275,7 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
961
1275
|
staleDevices,
|
|
962
1276
|
staleDeviceRecords,
|
|
963
1277
|
staleLedgerEntries,
|
|
1278
|
+
staleStatusCaches,
|
|
964
1279
|
staleLocks,
|
|
965
1280
|
staleSlots,
|
|
966
1281
|
expiredLeases,
|
|
@@ -981,8 +1296,8 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
981
1296
|
lines.push(...namedPortLines(orphanedPorts));
|
|
982
1297
|
lines.push(...orphanedWorkspaceLines(orphanedWorkspaces));
|
|
983
1298
|
lines.push(...worktreeSweepLines(worktreeSweep));
|
|
984
|
-
lines.push(...formatParkedSimReport(parkedSims, now));
|
|
985
|
-
lines.push(...formatParkedAvdReport(parkedAvds, now));
|
|
1299
|
+
lines.push(...formatParkedSimReport(parkedSims, now, cacheScope !== null));
|
|
1300
|
+
lines.push(...formatParkedAvdReport(parkedAvds, now, cacheScope !== null));
|
|
986
1301
|
if (orphanedDevices.length) {
|
|
987
1302
|
lines.push(`Orphaned devices (${orphanedDevices.length}):`);
|
|
988
1303
|
for (const d of orphanedDevices) lines.push(` ${d.kind} ${d.name} (${d.id})${deviceSizeSuffix(d)}`);
|
|
@@ -1016,6 +1331,7 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
1016
1331
|
for (const entry of staleLedgerEntries) lines.push(` ${entry.kind} ${entry.id} is not on this machine`);
|
|
1017
1332
|
lines.push(" --delete forgets the LEDGER ENTRY only; there is no device left to touch.");
|
|
1018
1333
|
}
|
|
1334
|
+
lines.push(...statusCacheLines(staleStatusCaches));
|
|
1019
1335
|
if (easSessionSweep.orphaned.length) {
|
|
1020
1336
|
lines.push(`Orphaned EAS sessions (${easSessionSweep.orphaned.length}) - current EAS project only:`);
|
|
1021
1337
|
for (const session of easSessionSweep.orphaned) {
|
|
@@ -1061,7 +1377,12 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
1061
1377
|
for (const entry of skipped) lines.push(` ${entry.dir}: ${entry.reason}`);
|
|
1062
1378
|
}
|
|
1063
1379
|
lines.push(...workspaceLogLines(workspaceLogs));
|
|
1380
|
+
lines.push(...recordingLines(recordings));
|
|
1064
1381
|
lines.push(...cacheLines(caches, workspaceOutputs));
|
|
1382
|
+
lines.push(...agentDeviceLines(agentDeviceUsage, true, now));
|
|
1383
|
+
lines.push(...swiftpmCacheLines(swiftpmCacheUsage, now));
|
|
1384
|
+
lines.push(...memoryLines(memory, memoryCacheKind(cacheScope), now));
|
|
1385
|
+
lines.push(...maintenanceReportLines(maintenance));
|
|
1065
1386
|
return lines;
|
|
1066
1387
|
}
|
|
1067
1388
|
function workspaceLogLines(workspaceLogs) {
|
|
@@ -1117,7 +1438,7 @@ function workspaceOutputLines(outputs, bytes) {
|
|
|
1117
1438
|
const lines = [
|
|
1118
1439
|
` ${formatBytes(bytes).padStart(10)} Workspace build outputs (detected)`,
|
|
1119
1440
|
` ${outputs.root}`,
|
|
1120
|
-
` ${WORKSPACE_OUTPUT_DIRS.join(", ")} of each workspace; workspace.json, state.json, logs and device records stay`,
|
|
1441
|
+
` ${WORKSPACE_OUTPUT_DIRS.join(", ")}, macOS Swift scratch and staged app bundles of each workspace; workspace.json, state.json, logs and device records stay`,
|
|
1121
1442
|
` ${REBUILD_COST}`
|
|
1122
1443
|
];
|
|
1123
1444
|
for (const w of outputs.workspaces) {
|
|
@@ -1142,6 +1463,7 @@ function cacheLines(caches, workspaceOutputs) {
|
|
|
1142
1463
|
if (c.note) lines.push(` ${c.note}`);
|
|
1143
1464
|
if (c.willEmpty) lines.push(" would be EMPTIED");
|
|
1144
1465
|
else if (c.emptySkipped) lines.push(` would be left alone: ${c.emptySkipped}`);
|
|
1466
|
+
else if (c.scopedEmpty?.emptySkipped) lines.push(` --delete --cache would leave it alone: ${c.scopedEmpty.emptySkipped}`);
|
|
1145
1467
|
}
|
|
1146
1468
|
lines.push(` total: ${formatBytes(total)}`);
|
|
1147
1469
|
const doomed = caches.filter((c) => c.willEmpty);
|
|
@@ -1191,7 +1513,7 @@ function jsonPullRequest(lookup) {
|
|
|
1191
1513
|
containsHead: pr.containsHead
|
|
1192
1514
|
} : null;
|
|
1193
1515
|
}
|
|
1194
|
-
function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [], invalidProjects = [], orphanedWorkspaces = [], worktreeSweep = null, parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], buildLocks = {
|
|
1516
|
+
function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedPorts = [], invalidProjects = [], orphanedWorkspaces = [], worktreeSweep = null, parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], staleStatusCaches = [], buildLocks = {
|
|
1195
1517
|
stale: [],
|
|
1196
1518
|
live: [],
|
|
1197
1519
|
unresolved: []
|
|
@@ -1207,8 +1529,12 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1207
1529
|
orphaned: [],
|
|
1208
1530
|
notices: [],
|
|
1209
1531
|
deletionSafe: true
|
|
1210
|
-
}, workspaceOutputs = null, workspaceLogs = [], caches = [] }) {
|
|
1532
|
+
}, workspaceOutputs = null, workspaceLogs = [], recordings = [], agentDeviceUsage = null, swiftpmCacheUsage = null, caches = [], memory = null }) {
|
|
1211
1533
|
return {
|
|
1534
|
+
...archives ? {
|
|
1535
|
+
archived: archives.records,
|
|
1536
|
+
archiveStaging: archives.staging
|
|
1537
|
+
} : {},
|
|
1212
1538
|
deadProjects: deadProjects.map((path) => ({ path })),
|
|
1213
1539
|
invalidProjects: invalidProjects.map((path) => ({ path })),
|
|
1214
1540
|
orphanedPorts: orphanedPorts.map(({ project, label, port }) => ({
|
|
@@ -1232,20 +1558,22 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1232
1558
|
detail: w.skipped ?? worktreeRemovalReason(w),
|
|
1233
1559
|
eligibleAt: w.eligibleAt === null ? null : new Date(w.eligibleAt).toISOString()
|
|
1234
1560
|
})),
|
|
1235
|
-
parkedSimulators: parkedSims.map(({ udid, name, model, runtime, parkedAt, bytes, listed }) => ({
|
|
1561
|
+
parkedSimulators: parkedSims.map(({ udid, name, model, runtime, parkedAt, app, bytes, listed }) => ({
|
|
1236
1562
|
udid,
|
|
1237
1563
|
name,
|
|
1238
1564
|
model,
|
|
1239
1565
|
runtime,
|
|
1240
1566
|
parkedAt,
|
|
1567
|
+
app,
|
|
1241
1568
|
bytes,
|
|
1242
1569
|
listed
|
|
1243
1570
|
})),
|
|
1244
|
-
parkedEmulators: parkedAvds.map(({ name, systemImage, deviceProfile, parkedAt, bytes, listed }) => ({
|
|
1571
|
+
parkedEmulators: parkedAvds.map(({ name, systemImage, deviceProfile, parkedAt, app, bytes, listed }) => ({
|
|
1245
1572
|
name,
|
|
1246
1573
|
systemImage,
|
|
1247
1574
|
deviceProfile,
|
|
1248
1575
|
parkedAt,
|
|
1576
|
+
app,
|
|
1249
1577
|
bytes,
|
|
1250
1578
|
listed
|
|
1251
1579
|
})),
|
|
@@ -1281,6 +1609,12 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1281
1609
|
kind,
|
|
1282
1610
|
id
|
|
1283
1611
|
})),
|
|
1612
|
+
staleStatusCaches: staleStatusCaches.map(({ kind, path, file, bytes }) => ({
|
|
1613
|
+
kind,
|
|
1614
|
+
path,
|
|
1615
|
+
file,
|
|
1616
|
+
bytes
|
|
1617
|
+
})),
|
|
1284
1618
|
idleDevices: idleDevices.map(({ kind, id, name, project, slot, lastActivityAt, idleForMs, buildInProgress }) => ({
|
|
1285
1619
|
kind,
|
|
1286
1620
|
id,
|
|
@@ -1341,6 +1675,16 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1341
1675
|
reason: w.keptCode,
|
|
1342
1676
|
detail: w.keptReason
|
|
1343
1677
|
})),
|
|
1678
|
+
recordings: recordings.map((r) => ({
|
|
1679
|
+
dir: r.dir,
|
|
1680
|
+
projectRoot: r.projectRoot,
|
|
1681
|
+
bytes: r.bytes,
|
|
1682
|
+
deleteBytes: r.deleteBytes,
|
|
1683
|
+
willDelete: r.willDelete,
|
|
1684
|
+
withWorkspace: r.withWorkspace,
|
|
1685
|
+
reason: r.keptCode,
|
|
1686
|
+
detail: r.keptReason
|
|
1687
|
+
})),
|
|
1344
1688
|
workspaceBuildOutputs: (workspaceOutputs?.workspaces ?? []).map((w) => ({
|
|
1345
1689
|
dir: w.dir,
|
|
1346
1690
|
projectRoot: w.projectRoot,
|
|
@@ -1357,8 +1701,14 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1357
1701
|
bytes: c.bytes ?? null,
|
|
1358
1702
|
note: c.note || null,
|
|
1359
1703
|
willEmpty: Boolean(c.willEmpty),
|
|
1360
|
-
emptySkipped: c.emptySkipped ?? null
|
|
1361
|
-
|
|
1704
|
+
emptySkipped: c.emptySkipped ?? null,
|
|
1705
|
+
scopedEmpty: c.scopedEmpty ?? null
|
|
1706
|
+
})),
|
|
1707
|
+
agentDevice: agentDeviceUsage,
|
|
1708
|
+
swiftpmCache: swiftpmCacheUsage,
|
|
1709
|
+
memory: memory?.processes ?? [],
|
|
1710
|
+
watchmanRoots: memory?.watchmanRoots ?? [],
|
|
1711
|
+
memoryNotices: (memory?.notices ?? []).map((message) => ({ message }))
|
|
1362
1712
|
};
|
|
1363
1713
|
}
|
|
1364
1714
|
//#endregion
|
|
@@ -1417,8 +1767,8 @@ function staleBrowserProfiles(ledger, exists = existsSync) {
|
|
|
1417
1767
|
}));
|
|
1418
1768
|
}
|
|
1419
1769
|
function isStaleProfile(path, exists) {
|
|
1420
|
-
const workspace = dirname
|
|
1421
|
-
return !exists(path) && exists(dirname
|
|
1770
|
+
const workspace = dirname(dirname(path));
|
|
1771
|
+
return !exists(path) && exists(dirname(workspace)) && !isDanglingLink(workspace);
|
|
1422
1772
|
}
|
|
1423
1773
|
function isDanglingLink(path) {
|
|
1424
1774
|
try {
|
|
@@ -1464,7 +1814,7 @@ function fileSize(path) {
|
|
|
1464
1814
|
}
|
|
1465
1815
|
function cappedFiles(logsDir) {
|
|
1466
1816
|
return CAPPED_LOGS.flatMap((name) => {
|
|
1467
|
-
const log = join(logsDir, name);
|
|
1817
|
+
const log = join$1(logsDir, name);
|
|
1468
1818
|
return [{
|
|
1469
1819
|
file: log,
|
|
1470
1820
|
log
|
|
@@ -1490,14 +1840,14 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1490
1840
|
for (const { dir, projectRoot, problem } of listWorkspaceDirs()) {
|
|
1491
1841
|
if (exclude.includes(dir)) continue;
|
|
1492
1842
|
if (projectRoot !== null && !isOnMountedVolume(projectRoot, mountedVolumes)) continue;
|
|
1493
|
-
const logsDir = join(dir, "logs");
|
|
1843
|
+
const logsDir = join$1(dir, "logs");
|
|
1494
1844
|
let names;
|
|
1495
1845
|
try {
|
|
1496
1846
|
names = readdirSync$1(logsDir);
|
|
1497
1847
|
} catch {
|
|
1498
1848
|
continue;
|
|
1499
1849
|
}
|
|
1500
|
-
const bytes = names.reduce((sum, name) => sum + fileSize(join(logsDir, name)), 0);
|
|
1850
|
+
const bytes = names.reduce((sum, name) => sum + fileSize(join$1(logsDir, name)), 0);
|
|
1501
1851
|
if (bytes === 0) continue;
|
|
1502
1852
|
const trimBytes = excess(logsDir);
|
|
1503
1853
|
let keptCode = null;
|
|
@@ -1532,7 +1882,7 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1532
1882
|
}
|
|
1533
1883
|
function trimToNewest({ file, log }, maxBytes) {
|
|
1534
1884
|
if (!trimmable(fileSize(file))) return 0;
|
|
1535
|
-
return withDirLock(`${log}.lock`, () => {
|
|
1885
|
+
return withDirLock$1(`${log}.lock`, () => {
|
|
1536
1886
|
const { size, atime, mtime } = statSync$1(file);
|
|
1537
1887
|
if (!trimmable(size)) return 0;
|
|
1538
1888
|
const tail = Buffer.alloc(maxBytes);
|
|
@@ -1547,7 +1897,7 @@ function trimToNewest({ file, log }, maxBytes) {
|
|
|
1547
1897
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
1548
1898
|
try {
|
|
1549
1899
|
writeFileSync$1(tmp, kept);
|
|
1550
|
-
utimesSync(tmp, atime, mtime);
|
|
1900
|
+
utimesSync$1(tmp, atime, mtime);
|
|
1551
1901
|
renameSync$1(tmp, file);
|
|
1552
1902
|
} catch (error) {
|
|
1553
1903
|
rmSync$1(tmp, { force: true });
|
|
@@ -1575,7 +1925,7 @@ async function trimWorkspaceLogs(entries) {
|
|
|
1575
1925
|
};
|
|
1576
1926
|
return {
|
|
1577
1927
|
kept: null,
|
|
1578
|
-
bytes: cappedFiles(join(entry.dir, "logs")).reduce((sum, capped) => sum + trimToNewest(capped, LOG_ROTATE_BYTES), 0)
|
|
1928
|
+
bytes: cappedFiles(join$1(entry.dir, "logs")).reduce((sum, capped) => sum + trimToNewest(capped, LOG_ROTATE_BYTES), 0)
|
|
1579
1929
|
};
|
|
1580
1930
|
}, { purpose: "gc" });
|
|
1581
1931
|
} catch (error) {
|
|
@@ -1775,7 +2125,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1775
2125
|
if (records.has(name)) continue;
|
|
1776
2126
|
let ini;
|
|
1777
2127
|
try {
|
|
1778
|
-
ini = readFileSync(join
|
|
2128
|
+
ini = readFileSync(join(root, entry), "utf8");
|
|
1779
2129
|
} catch (error) {
|
|
1780
2130
|
if (!isMissing(error)) notices.push(unreadableNotice(`AVD ${name}`, error));
|
|
1781
2131
|
continue;
|
|
@@ -1784,7 +2134,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1784
2134
|
let config = "";
|
|
1785
2135
|
let unreadable = null;
|
|
1786
2136
|
for (const candidate of avdIniPaths(root, ini)) try {
|
|
1787
|
-
config = readFileSync(join
|
|
2137
|
+
config = readFileSync(join(candidate, "config.ini"), "utf8");
|
|
1788
2138
|
directory = candidate;
|
|
1789
2139
|
break;
|
|
1790
2140
|
} catch (error) {
|
|
@@ -1796,7 +2146,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1796
2146
|
directory,
|
|
1797
2147
|
systemImage: parseAvdSystemImage(config),
|
|
1798
2148
|
deviceProfile: parseAvdDeviceProfile(config),
|
|
1799
|
-
lastUsedAt: directory ? modifiedAt(join
|
|
2149
|
+
lastUsedAt: directory ? modifiedAt(join(directory, "hardware-qemu.ini")) : null
|
|
1800
2150
|
});
|
|
1801
2151
|
}
|
|
1802
2152
|
}
|
|
@@ -1837,10 +2187,10 @@ function collectInventory(config, deadProjects) {
|
|
|
1837
2187
|
notices.push(`simulator runtimes not listed: ${describeError(error)}`);
|
|
1838
2188
|
}
|
|
1839
2189
|
}
|
|
1840
|
-
const imagesRoot = join
|
|
2190
|
+
const imagesRoot = join(androidHome(), "system-images");
|
|
1841
2191
|
const images = listInstalledSystemImages((error) => notices.push(unreadableNotice("Android system images", error))).map((image) => ({
|
|
1842
2192
|
pkg: image.pkg,
|
|
1843
|
-
directory: join
|
|
2193
|
+
directory: join(imagesRoot, `android-${image.api}`, image.tag, image.arch)
|
|
1844
2194
|
}));
|
|
1845
2195
|
return {
|
|
1846
2196
|
...buildInventory({
|
|
@@ -1863,13 +2213,56 @@ function removeInvalidProjectEntries(invalidProjects) {
|
|
|
1863
2213
|
recordGcResult("project", "done", path);
|
|
1864
2214
|
}
|
|
1865
2215
|
}
|
|
2216
|
+
function collectGcMemory(scope) {
|
|
2217
|
+
return memorySweepIsScoped() ? Promise.resolve(scopedMemoryReport()) : collectMemoryReport(scope, true);
|
|
2218
|
+
}
|
|
2219
|
+
function classifyProjectEntries(cfg, mountedVolumes) {
|
|
2220
|
+
const deadProjects = [];
|
|
2221
|
+
const invalidProjects = [];
|
|
2222
|
+
const skipped = [];
|
|
2223
|
+
for (const [path, project] of Object.entries(cfg?.projects || {})) {
|
|
2224
|
+
if (!isAbsolute$1(path)) {
|
|
2225
|
+
const claimed = describeDereferenced(project);
|
|
2226
|
+
if (claimed.length) skipped.push({
|
|
2227
|
+
dir: path,
|
|
2228
|
+
reason: `not an absolute path, so this record is invalid; kept because it still claims ${claimed.join(" and ")}; gc removes it once that claim is gone, or drop the entry from the config file by hand`
|
|
2229
|
+
});
|
|
2230
|
+
else invalidProjects.push(path);
|
|
2231
|
+
continue;
|
|
2232
|
+
}
|
|
2233
|
+
if (existsSync$1(path)) continue;
|
|
2234
|
+
if (!isOnMountedVolume(path, mountedVolumes)) {
|
|
2235
|
+
const volume = volumeRootFor(path);
|
|
2236
|
+
skipped.push({
|
|
2237
|
+
dir: path,
|
|
2238
|
+
reason: `volume ${volume} is not mounted`
|
|
2239
|
+
});
|
|
2240
|
+
} else deadProjects.push(path);
|
|
2241
|
+
}
|
|
2242
|
+
return {
|
|
2243
|
+
deadProjects,
|
|
2244
|
+
invalidProjects,
|
|
2245
|
+
skipped
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
/** The workspace directories an unscoped run removes whole: those of dead projects and orphaned directories. */
|
|
2249
|
+
function removedWholeWorkspaceDirs() {
|
|
2250
|
+
const mountedVolumes = listMountedVolumes();
|
|
2251
|
+
const cfg = loadConfig$1();
|
|
2252
|
+
const { deadProjects } = classifyProjectEntries(cfg, mountedVolumes);
|
|
2253
|
+
const { orphaned } = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, { measure: false });
|
|
2254
|
+
return [...orphaned.map((entry) => entry.dir), ...deadProjects.map(workspaceDir)];
|
|
2255
|
+
}
|
|
1866
2256
|
async function collectGcReport({ olderThan = null, cache = null, worktrees = false, now = Date.now(), lastTouched = workspaceLastUsed } = {}, deps = {}) {
|
|
1867
2257
|
const scope = typeof cache === "string" && cache.trim() ? cache : null;
|
|
1868
2258
|
const all = scope !== null && olderThan === null;
|
|
1869
2259
|
const withWorkspaces = includesWorkspaceOutputs(scope);
|
|
1870
2260
|
const selected = selectCaches(discoverCaches(), scope).filter((c) => !withWorkspaces || !isInsideWorkspaces(c.dir));
|
|
1871
|
-
|
|
2261
|
+
if (selected.length) phase("caches", `measuring ${plural(selected.length, "shared cache")}`);
|
|
2262
|
+
const sized = sizeCaches(selected);
|
|
2263
|
+
const caches = scope === null ? previewScopedEmptying(sized) : planCacheEmptying(sized, all);
|
|
1872
2264
|
if (scope) return {
|
|
2265
|
+
archives: includesArchives(scope) ? collectArchives(scope, olderThan, now) : void 0,
|
|
1873
2266
|
skipped: [],
|
|
1874
2267
|
deadProjects: [],
|
|
1875
2268
|
invalidProjects: [],
|
|
@@ -1879,6 +2272,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1879
2272
|
staleDevices: [],
|
|
1880
2273
|
staleDeviceRecords: [],
|
|
1881
2274
|
staleLedgerEntries: [],
|
|
2275
|
+
staleStatusCaches: [],
|
|
1882
2276
|
buildLocks: {
|
|
1883
2277
|
stale: [],
|
|
1884
2278
|
live: [],
|
|
@@ -1901,19 +2295,38 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1901
2295
|
notices: [],
|
|
1902
2296
|
deletionSafe: true
|
|
1903
2297
|
},
|
|
1904
|
-
parkedSims:
|
|
1905
|
-
|
|
2298
|
+
parkedSims: includesParkedDevices(scope) ? collectParkedSims(deps, {
|
|
2299
|
+
olderThanDays: olderThan,
|
|
2300
|
+
now
|
|
2301
|
+
}) : [],
|
|
2302
|
+
parkedAvds: includesParkedDevices(scope) ? collectParkedAvds(deps, {
|
|
2303
|
+
olderThanDays: olderThan,
|
|
2304
|
+
now
|
|
2305
|
+
}) : [],
|
|
1906
2306
|
caches,
|
|
1907
2307
|
workspaceOutputs: withWorkspaces ? collectWorkspaceOutputs({
|
|
1908
2308
|
olderThan,
|
|
1909
|
-
now
|
|
2309
|
+
now,
|
|
2310
|
+
exclude: isWorkspacesScope(scope) ? removedWholeWorkspaceDirs() : []
|
|
1910
2311
|
}) : null,
|
|
1911
2312
|
workspaceLogs: [],
|
|
2313
|
+
recordings: includesRecordings(scope) ? collectRecordings({
|
|
2314
|
+
whole: all,
|
|
2315
|
+
olderThan,
|
|
2316
|
+
now
|
|
2317
|
+
}) : [],
|
|
1912
2318
|
worktreeSweep: null,
|
|
2319
|
+
memory: memoryCacheKind(scope) ? await collectGcMemory(memoryScope(scope)) : null,
|
|
2320
|
+
agentDeviceUsage: null,
|
|
2321
|
+
swiftpmCacheUsage: null,
|
|
1913
2322
|
cacheScope: scope,
|
|
1914
2323
|
olderThan,
|
|
1915
2324
|
all
|
|
1916
2325
|
};
|
|
2326
|
+
phase("agent-device", "measuring report-only state");
|
|
2327
|
+
const agentDeviceUsage = await getAgentDeviceUsage();
|
|
2328
|
+
phase("SwiftPM cache", "measuring report-only state");
|
|
2329
|
+
const swiftpmCacheUsage = await getSwiftpmCacheUsage();
|
|
1917
2330
|
const mountedVolumes = listMountedVolumes();
|
|
1918
2331
|
const cfg = loadConfig$1();
|
|
1919
2332
|
let easSessionSweep = deps.precollectedEasSessionSweep;
|
|
@@ -1935,30 +2348,11 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1935
2348
|
olderThanDays: olderThan,
|
|
1936
2349
|
now
|
|
1937
2350
|
});
|
|
1938
|
-
const deadProjects =
|
|
1939
|
-
const invalidProjects = [];
|
|
1940
|
-
const skipped = [];
|
|
1941
|
-
for (const [path, project] of Object.entries(cfg?.projects || {})) {
|
|
1942
|
-
if (!isAbsolute(path)) {
|
|
1943
|
-
const claimed = describeDereferenced(project);
|
|
1944
|
-
if (claimed.length) skipped.push({
|
|
1945
|
-
dir: path,
|
|
1946
|
-
reason: `not an absolute path, so this record is invalid; kept because it still claims ${claimed.join(" and ")}; gc removes it once that claim is gone, or drop the entry from the config file by hand`
|
|
1947
|
-
});
|
|
1948
|
-
else invalidProjects.push(path);
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
if (existsSync$1(path)) continue;
|
|
1952
|
-
if (!isOnMountedVolume(path, mountedVolumes)) {
|
|
1953
|
-
const volume = volumeRootFor(path);
|
|
1954
|
-
skipped.push({
|
|
1955
|
-
dir: path,
|
|
1956
|
-
reason: `volume ${volume} is not mounted`
|
|
1957
|
-
});
|
|
1958
|
-
} else deadProjects.push(path);
|
|
1959
|
-
}
|
|
2351
|
+
const { deadProjects, invalidProjects, skipped } = classifyProjectEntries(cfg, mountedVolumes);
|
|
1960
2352
|
const workspaceDirs = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes);
|
|
1961
2353
|
skipped.push(...workspaceDirs.skipped);
|
|
2354
|
+
const statusCaches = collectStaleStatusCaches(mountedVolumes);
|
|
2355
|
+
skipped.push(...statusCaches.skipped);
|
|
1962
2356
|
const deviceSweepNotices = [];
|
|
1963
2357
|
let orphanedDevices = [];
|
|
1964
2358
|
let unverifiedDevices = [];
|
|
@@ -1967,6 +2361,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1967
2361
|
let staleLedgerEntries = staleBrowserProfiles(readCreatedDevices$1());
|
|
1968
2362
|
let idleDevices = [];
|
|
1969
2363
|
const unsweepableReason = cfg === null ? "no Stim config found" : deviceSweepIsScoped() ? "STIM_HOME scopes this config, but simulators and AVDs are machine-global" : null;
|
|
2364
|
+
phase("devices", "listing simulators and emulators");
|
|
1970
2365
|
if (unsweepableReason) {
|
|
1971
2366
|
let simNames = [];
|
|
1972
2367
|
let avdNames = [];
|
|
@@ -2072,6 +2467,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2072
2467
|
staleDevices,
|
|
2073
2468
|
staleDeviceRecords,
|
|
2074
2469
|
staleLedgerEntries,
|
|
2470
|
+
staleStatusCaches: statusCaches.stale,
|
|
2075
2471
|
buildLocks: {
|
|
2076
2472
|
stale: locks.filter((l) => !l.alive && !l.unresolved),
|
|
2077
2473
|
live: locks.filter((l) => l.alive),
|
|
@@ -2093,11 +2489,22 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2093
2489
|
exclude: goneWorkspaceDirs
|
|
2094
2490
|
}),
|
|
2095
2491
|
workspaceLogs: collectWorkspaceLogs({ exclude: goneWorkspaceDirs }),
|
|
2492
|
+
recordings: collectRecordings({
|
|
2493
|
+
whole: false,
|
|
2494
|
+
olderThan,
|
|
2495
|
+
now
|
|
2496
|
+
}, goneWorkspaceDirs),
|
|
2096
2497
|
worktreeSweep: await collectWorktreeSweep({
|
|
2097
2498
|
idle: worktrees,
|
|
2098
2499
|
olderThan,
|
|
2099
2500
|
now
|
|
2100
2501
|
}),
|
|
2502
|
+
memory: await collectGcMemory({
|
|
2503
|
+
watchman: true,
|
|
2504
|
+
gradle: true
|
|
2505
|
+
}),
|
|
2506
|
+
agentDeviceUsage,
|
|
2507
|
+
swiftpmCacheUsage,
|
|
2101
2508
|
cacheScope: null,
|
|
2102
2509
|
olderThan,
|
|
2103
2510
|
all
|
|
@@ -2105,19 +2512,53 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2105
2512
|
}
|
|
2106
2513
|
async function runGc(opts = {}, deps = {}) {
|
|
2107
2514
|
if (!opts.json) {
|
|
2108
|
-
await
|
|
2515
|
+
await maintenanceGuardedSweep(opts, deps);
|
|
2109
2516
|
return;
|
|
2110
2517
|
}
|
|
2111
2518
|
const log = console.log;
|
|
2112
2519
|
console.log = console.error;
|
|
2113
2520
|
let payload;
|
|
2114
2521
|
try {
|
|
2115
|
-
payload = await
|
|
2522
|
+
payload = await maintenanceGuardedSweep(opts, deps);
|
|
2116
2523
|
} finally {
|
|
2117
2524
|
console.log = log;
|
|
2118
2525
|
}
|
|
2119
2526
|
console.log(JSON.stringify(payload));
|
|
2120
2527
|
}
|
|
2528
|
+
async function maintenanceGuardedSweep(opts, deps) {
|
|
2529
|
+
if (!opts.delete) return sweep(opts, deps);
|
|
2530
|
+
let claim;
|
|
2531
|
+
try {
|
|
2532
|
+
const root = maintenanceRunClaims();
|
|
2533
|
+
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
2534
|
+
root,
|
|
2535
|
+
mode: "exclusive",
|
|
2536
|
+
label: "maintenance",
|
|
2537
|
+
details: { trigger: "gc --delete" }
|
|
2538
|
+
});
|
|
2539
|
+
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
2540
|
+
claim = attempt.acquired;
|
|
2541
|
+
if (!claim) {
|
|
2542
|
+
const holder = attempt.held ?? attempt.waitingFor?.[0];
|
|
2543
|
+
throw new ownership_claim_exports.ClaimRefusedError({
|
|
2544
|
+
root,
|
|
2545
|
+
claimPath: holder?.path ?? root,
|
|
2546
|
+
label: "maintenance",
|
|
2547
|
+
reason: `held by pid ${holder?.owner.pid ?? "?"} since ${holder?.startedAt ?? "?"} (${String(holder?.details.trigger ?? "maintenance")})`
|
|
2548
|
+
});
|
|
2549
|
+
}
|
|
2550
|
+
return await sweep(opts, deps);
|
|
2551
|
+
} catch (error) {
|
|
2552
|
+
const failure = (0, ownership_claim_exports.claimFailure)(error, "stim gc --delete");
|
|
2553
|
+
if (!failure) throw error;
|
|
2554
|
+
console.error(`${failure.code}: ${failure.message}`);
|
|
2555
|
+
console.error(failure.remedy);
|
|
2556
|
+
process.exitCode = 1;
|
|
2557
|
+
return failure;
|
|
2558
|
+
} finally {
|
|
2559
|
+
if (claim) (0, ownership_claim_exports.releaseClaim)(claim);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2121
2562
|
async function sweep(opts, deps) {
|
|
2122
2563
|
if (opts.cache && (opts.worktrees || opts.idle !== void 0)) {
|
|
2123
2564
|
const flag = opts.worktrees ? "--worktrees" : "--idle";
|
|
@@ -2133,6 +2574,20 @@ async function sweep(opts, deps) {
|
|
|
2133
2574
|
remedy
|
|
2134
2575
|
};
|
|
2135
2576
|
}
|
|
2577
|
+
const memoryKind = memoryCacheKind(opts.cache);
|
|
2578
|
+
if (memoryKind && opts.olderThan !== void 0) {
|
|
2579
|
+
const message = `--older-than does not apply to --cache ${memoryKind}, which acts on running processes.`;
|
|
2580
|
+
const remedy = `Run \`stim gc --cache ${memoryKind}\` without --older-than.`;
|
|
2581
|
+
console.error(chalk.red(message));
|
|
2582
|
+
console.error(chalk.dim(remedy));
|
|
2583
|
+
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
2584
|
+
process.exitCode = 1;
|
|
2585
|
+
return {
|
|
2586
|
+
code: "STIM_BAD_ARG",
|
|
2587
|
+
message,
|
|
2588
|
+
remedy
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2136
2591
|
const poolError = parkedMaxSetting("ios").error || parkedMaxSetting("android").error;
|
|
2137
2592
|
if (poolError) console.error(chalk.yellow(`${poolError} ${POOL_SETTING_REMEDY}`));
|
|
2138
2593
|
if (opts.cache) return runGcCore(opts, {
|
|
@@ -2146,7 +2601,7 @@ async function sweep(opts, deps) {
|
|
|
2146
2601
|
});
|
|
2147
2602
|
let projectRoot = null;
|
|
2148
2603
|
try {
|
|
2149
|
-
projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
2604
|
+
projectRoot = (deps.findProjectRoot ?? findProjectRoot$1)(process.cwd());
|
|
2150
2605
|
if (!projectRoot) return runGcCore(opts, {
|
|
2151
2606
|
...deps,
|
|
2152
2607
|
precollectedEasSessionSweep: {
|
|
@@ -2209,7 +2664,10 @@ async function pruneDeadProjects(deadProjects) {
|
|
|
2209
2664
|
recordGcResult("project", "kept", path, { detail: "its absence can no longer be confirmed" });
|
|
2210
2665
|
continue;
|
|
2211
2666
|
}
|
|
2212
|
-
const result = await reclaimProject(path, {
|
|
2667
|
+
const result = await reclaimProject(path, {
|
|
2668
|
+
archive: { removedBy: "gc" },
|
|
2669
|
+
stopRequester: { by: "stim gc --delete" }
|
|
2670
|
+
}).catch((error) => {
|
|
2213
2671
|
deleteFailures++;
|
|
2214
2672
|
console.log(chalk.red(`Could not prune ${path}; its registry entry was kept: ${error.message}`));
|
|
2215
2673
|
recordGcResult("project", "failed", path, { detail: `its registry entry was kept: ${error.message}` });
|
|
@@ -2244,6 +2702,28 @@ async function pruneDeadProjects(deadProjects) {
|
|
|
2244
2702
|
}
|
|
2245
2703
|
return deleteFailures;
|
|
2246
2704
|
}
|
|
2705
|
+
async function attachMaintenancePreview(report, deleting, cache) {
|
|
2706
|
+
if (!deleting) report.maintenance = await previewMaintenance({ devices: !cache });
|
|
2707
|
+
}
|
|
2708
|
+
function scopedDryRunAction(cache) {
|
|
2709
|
+
const memory = memoryCacheKind(cache);
|
|
2710
|
+
if (memory === "watchman") return "remove the stale roots and shut down the unused watchman";
|
|
2711
|
+
if (memory === "gradle-daemons") return "stop the idle daemons";
|
|
2712
|
+
return includesParkedDevices(cache) ? "erase the parked devices" : "empty the caches";
|
|
2713
|
+
}
|
|
2714
|
+
function dryRunHint(cache, all, actionable, caches) {
|
|
2715
|
+
if (all && (!memoryCacheKind(cache) || actionable)) return `Dry run. Re-run with --delete to ${scopedDryRunAction(cache)} above.`;
|
|
2716
|
+
if (actionable) return "Dry run. Re-run with --delete to reclaim.";
|
|
2717
|
+
if (caches) return "Pass --delete --cache all to empty the caches above, or --delete --older-than <days> to trim them.";
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
function pruneStatusCaches(report) {
|
|
2721
|
+
if (report.cacheScope !== null) return 0;
|
|
2722
|
+
return deleteStaleStatusCaches(collectStaleStatusCaches(listMountedVolumes()).stale);
|
|
2723
|
+
}
|
|
2724
|
+
function reclaimParkedDevices(report, deps) {
|
|
2725
|
+
return report.cacheScope ? eraseParkedDevices(report.parkedSims, report.parkedAvds) : deleteParkedSims(report.parkedSims, deps) + deleteParkedAvds(report.parkedAvds);
|
|
2726
|
+
}
|
|
2247
2727
|
async function runGcCore(opts, deps) {
|
|
2248
2728
|
takeGcResults();
|
|
2249
2729
|
const olderThan = typeof opts.olderThan === "number" ? opts.olderThan : null;
|
|
@@ -2253,7 +2733,7 @@ async function runGcCore(opts, deps) {
|
|
|
2253
2733
|
cache,
|
|
2254
2734
|
worktrees: Boolean(opts.worktrees)
|
|
2255
2735
|
}, deps);
|
|
2256
|
-
if (cache && report.caches.length === 0 && report.workspaceOutputs === null) {
|
|
2736
|
+
if (cache && report.caches.length === 0 && report.workspaceOutputs === null && !selectsBeyondCaches(cache)) {
|
|
2257
2737
|
const names = [...new Set(discoverCaches().map((c) => c.name))];
|
|
2258
2738
|
const message = `No shared cache carries "${cache}" in its name or directory.`;
|
|
2259
2739
|
console.log(chalk.yellow(message));
|
|
@@ -2265,10 +2745,13 @@ async function runGcCore(opts, deps) {
|
|
|
2265
2745
|
remedy: names.length ? `Pass --cache all or a name from: ${names.join(", ")}.` : "No shared cache was found on this machine."
|
|
2266
2746
|
};
|
|
2267
2747
|
}
|
|
2748
|
+
await attachMaintenancePreview(report, opts.delete === true, cache);
|
|
2749
|
+
const refusal = archiveRefusal(report.archives);
|
|
2750
|
+
if (refusal !== null) return refusal;
|
|
2268
2751
|
const all = report.all;
|
|
2269
2752
|
for (const line of formatGcReport(report)) console.log(line);
|
|
2270
2753
|
const { deadProjects, invalidProjects, orphanedDevices, staleDevices, staleDeviceRecords, buildLocks, buildSlots, deviceLeases, easSessionSweep, caches } = report;
|
|
2271
|
-
const actionable = deadProjects.length + invalidProjects.length > 0 || report.orphanedWorkspaces.length > 0 || Boolean(report.workspaceOutputs?.workspaces.some((entry) => entry.willClear)) || report.workspaceLogs.some((entry) => entry.willTrim) || Boolean(report.worktreeSweep?.worktrees.some((entry) => !entry.skipped)) || report.parkedSims.length > 0 || report.parkedAvds.length > 0 || orphanedDevices.length > 0 || staleDevices.length > 0 || staleDeviceRecords.length > 0 || report.staleLedgerEntries.length > 0 || buildLocks.stale.length > 0 || buildSlots.stale.length > 0 || deviceLeases.expired.length > 0 || easSessionSweep.orphaned.length > 0 || (olderThan !== null || all) && caches.length > 0;
|
|
2754
|
+
const actionable = archiveWorkPending(report.archives, deadProjects.length + invalidProjects.length > 0 || report.orphanedWorkspaces.length > 0 || Boolean(report.workspaceOutputs?.workspaces.some((entry) => entry.willClear)) || report.workspaceLogs.some((entry) => entry.willTrim) || report.recordings.some((entry) => entry.willDelete && !entry.withWorkspace) || Boolean(report.worktreeSweep?.worktrees.some((entry) => !entry.skipped)) || report.parkedSims.length > 0 || report.parkedAvds.length > 0 || orphanedDevices.length > 0 || staleDevices.length > 0 || staleDeviceRecords.length > 0 || report.staleLedgerEntries.length > 0 || report.staleStatusCaches.length > 0 || buildLocks.stale.length > 0 || buildSlots.stale.length > 0 || deviceLeases.expired.length > 0 || easSessionSweep.orphaned.length > 0 || (olderThan !== null || all) && caches.length > 0 || memoryWorkPending(cache, report.memory));
|
|
2272
2755
|
const idle = opts.idle ?? null;
|
|
2273
2756
|
const idleFailures = idle === null ? 0 : shutDownIdleDevices(report.idleDevices, idle, () => collectIdleDevices(loadConfig$1(), listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }), report.deadProjects)).failures;
|
|
2274
2757
|
if (idleFailures) process.exitCode = 1;
|
|
@@ -2282,24 +2765,32 @@ async function runGcCore(opts, deps) {
|
|
|
2282
2765
|
actionable,
|
|
2283
2766
|
failures,
|
|
2284
2767
|
sections: gcReportSections(report),
|
|
2768
|
+
...report.maintenance ? { maintenance: report.maintenance } : {},
|
|
2285
2769
|
results: takeGcResults(),
|
|
2286
2770
|
inventory
|
|
2287
2771
|
});
|
|
2288
2772
|
if (!opts.delete) {
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
else if (caches.length) console.log(chalk.dim("\nPass --delete --cache all to empty the caches above, or --delete --older-than <days> to trim them."));
|
|
2773
|
+
const hint = dryRunHint(cache, all, actionable, caches.length > 0);
|
|
2774
|
+
if (hint) console.log(chalk.dim(`\n${hint}`));
|
|
2292
2775
|
return payload(idle === null ? null : idleFailures);
|
|
2293
2776
|
}
|
|
2294
2777
|
let deleteFailures = idleFailures;
|
|
2778
|
+
deleteFailures += deleteArchives(report.archives);
|
|
2295
2779
|
deleteFailures += report.workspaceOutputs ? (await clearWorkspaceOutputs(report.workspaceOutputs, { olderThan })).failures : 0;
|
|
2296
2780
|
deleteFailures += await trimWorkspaceLogs(report.workspaceLogs);
|
|
2297
|
-
deleteFailures +=
|
|
2781
|
+
deleteFailures += deleteRecordings(report.recordings, {
|
|
2782
|
+
whole: all,
|
|
2783
|
+
olderThan,
|
|
2784
|
+
now: Date.now()
|
|
2785
|
+
});
|
|
2786
|
+
deleteFailures += reclaimParkedDevices(report, deps);
|
|
2787
|
+
deleteFailures += await reclaimMemory(cache, report.memory);
|
|
2298
2788
|
removeInvalidProjectEntries(invalidProjects);
|
|
2299
2789
|
deleteFailures += await pruneDeadProjects(deadProjects);
|
|
2300
2790
|
deleteFailures += await deleteOrphanedWorkspaces(report.orphanedWorkspaces);
|
|
2301
2791
|
deleteFailures += deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords);
|
|
2302
2792
|
deleteFailures += forgetStaleLedgerEntries(report.staleLedgerEntries);
|
|
2793
|
+
deleteFailures += pruneStatusCaches(report);
|
|
2303
2794
|
for (const lock of buildLocks.stale) {
|
|
2304
2795
|
const cleared = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
2305
2796
|
root: lock.path,
|
|
@@ -2378,11 +2869,11 @@ async function runGcCore(opts, deps) {
|
|
|
2378
2869
|
return payload(deleteFailures);
|
|
2379
2870
|
}
|
|
2380
2871
|
function gcCommand(program) {
|
|
2381
|
-
program.command("gc").description("Report what Stim has left behind: dead project entries, orphaned workspace directories, clean Stim-managed linked worktrees whose branch is merged or whose pull request was merged or closed, orphaned owned devices and EAS sessions, records of devices that no longer exist, build locks whose builder is gone, expired physical-device leases, the shared build caches,
|
|
2872
|
+
program.command("gc").description("Report what Stim has left behind: dead project entries, orphaned workspace directories, clean Stim-managed linked worktrees whose branch is merged or whose pull request was merged or closed, orphaned owned devices and EAS sessions, records of devices that no longer exist, build locks whose builder is gone, expired physical-device leases, the shared build caches, the build outputs and logs of each workspace, and the memory of watchman and of the Gradle and Kotlin daemons. Reports by default; pass --delete to act.").option("--worktrees", "also remove clean, idle Stim-managed linked worktrees, not only merged ones: with --delete run `stim worktree remove` (never --force) on each; idle means unused for --older-than days, 7 without it").option("--delete", "actually prune the reported entries, reap the reported devices, clear the build outputs of every workspace not in use, and trim its Metro, client and device logs to their newest 8 MiB").option("--older-than <days>", "also reap owned devices whose workspace has not been used this long, trim shared cache entries nothing has used in that time and device recordings made before it, and clear workspace build outputs only for workspaces idle this long and parked devices only once parked this long", (v) => {
|
|
2382
2873
|
const n = parseInt(v, 10);
|
|
2383
2874
|
if (!Number.isFinite(n) || String(n) !== String(v).trim()) throw new InvalidArgumentError("must be a whole number of days, e.g. --older-than 30");
|
|
2384
2875
|
return n;
|
|
2385
|
-
}).option("--cache <name>", "act on the shared caches whose name or directory contains <name>, every cache
|
|
2876
|
+
}).option("--cache <name>", "act on the shared caches whose name or directory contains <name>, every cache, the workspace build outputs and the device recordings with --cache all, only the workspace build outputs with --cache workspaces, only the device recordings with --cache recordings, only the parked simulators and emulators with --cache parked, the watchman daemon and its stale roots with --cache watchman, or the Gradle and Kotlin compile daemons with --cache gradle-daemons; archived, archived:<id>, archived-logs, archived-recordings and archived-agent select archived history explicitly; --cache all excludes archives. all, workspaces, recordings, parked, archived, archived:<id>, archived-logs, archived-recordings, archived-agent, watchman and gradle-daemons are reserved names that never select a single cache, and --cache all never includes watchman or gradle-daemons. With --delete they are emptied whole (a parked device is erased and stays parked, watchman loses its stale roots and shuts down only when no client uses it, and only daemons proven idle stop), which is the only way to clear an index-backed cache; add --older-than <days> to trim them by age instead. Archive ids ignore --older-than; archived and per-kind archive selectors filter by removal age. Only those caches are reported; other devices and project entries are not inspected. Caches outside the config dir are refused while STIM_HOME is set.", (v) => {
|
|
2386
2877
|
if (!v.trim()) throw new InvalidArgumentError("must name a cache, e.g. --cache \"compilation cache\"");
|
|
2387
2878
|
return v;
|
|
2388
2879
|
}).option("--idle <duration>", "shut down (never delete) owned simulators and emulators that have had no driver, claim, or activity for at least <duration>, such as 30m, 2h, or 1d; acts without --delete", (v) => {
|