stim 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
- package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
- package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
- package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
- package/dist/android-cas-PnKfAdB2.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
- package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
- package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
- package/dist/build-progress-C2Jv1AtA.mjs +872 -0
- package/dist/build-slots-Df1ApSuH.mjs +127 -0
- package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
- package/dist/cli-0cX5s4NM.mjs +45 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-C2IS36Qf.mjs +1333 -0
- package/dist/collector-run.mjs +8 -7
- package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
- package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
- package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
- package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
- package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CjzLoNs0.mjs +80 -0
- package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
- package/dist/device-capacity-DMuKC7nx.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +769 -0
- package/dist/device-ios-DMQBUPus.mjs +381 -0
- package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
- package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
- package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
- package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
- package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
- package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
- package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
- package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
- package/dist/hosted-macos-DwowNU7W.mjs +447 -0
- package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
- package/dist/in-use-CndPMilN.mjs +110 -0
- package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
- package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
- package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
- package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
- package/dist/machine-usage-D_YYMzfR.mjs +283 -0
- package/dist/machines-CYtOCqcj.mjs +259 -0
- package/dist/macos-BwtbUtTN.mjs +468 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
- package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
- package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
- package/dist/native-run-DFDoIRbY.mjs +147 -0
- package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
- package/dist/offload-worker.d.mts +121 -0
- package/dist/offload-worker.mjs +601 -0
- package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
- package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
- package/dist/prebuild-BvxDlD6C.mjs +213 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-GEXp4iBT.mjs +37 -0
- package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
- package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
- package/dist/run-B-xqRrsg.mjs +119 -0
- package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
- package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
- package/dist/server-expo-KJeD109I.mjs +2 -0
- package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
- package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
- package/dist/settings.schema.json +357 -2
- package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
- package/dist/slot-launch-BY0BrOLh.mjs +65 -0
- package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-DncpJvWL.mjs +16 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CPO4wRg7.mjs +2 -0
- package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/state-BxCgeTQu.mjs +41 -0
- package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
- package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
- package/dist/stats-DBLLFmKd.mjs +66 -0
- package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
- package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
- package/dist/stop-Bvf1MflS.mjs +48 -0
- package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
- package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
- package/dist/supervisor-run.d.mts +44 -12
- package/dist/supervisor-run.mjs +276 -33
- package/dist/support-CDQbDkL5.mjs +315 -0
- package/dist/support-WLOzVKUv.mjs +176 -0
- package/dist/teardown-BYuQafiA.mjs +718 -0
- package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
- package/dist/warm-progress-DVHLN4uC.mjs +463 -0
- package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
- package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
- package/dist/web-run.mjs +326 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-CL2V2Ons.mjs +371 -0
- package/dist/worktree-BT_uULFx.mjs +2 -0
- package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
- package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
- package/package.json +10 -5
- package/shim/bundle-response.cjs +55 -10
- package/shim/bundle-response.d.cts +1 -0
- package/shim/expo-metro-config.cjs +10 -2
- package/dist/build-progress-l7d-X-ZP.mjs +0 -247
- package/dist/in-use-wYEaKQM9.mjs +0 -249
- package/dist/ownership-PUP4Dwai.mjs +0 -1138
- package/dist/server-expo-BeYwP_Xy.mjs +0 -2
- package/dist/slot-launch-BIWEW-wI.mjs +0 -19
- package/dist/start-Rw2GD3nV.mjs +0 -2
- package/dist/stats-CaSwmsSZ.mjs +0 -62
- package/dist/stats-D8rUTPHd.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/worktree-3cBa-bQt.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,28 +1,31 @@
|
|
|
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 {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
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 { C as workspaceLogsDir, E as withDirLock$1, T as workspaceStateFile, b as workspaceDir, s as workspaceLastUsed } from "./workspace-state-CL2V2Ons.mjs";
|
|
6
|
+
import { C as sourceCheckoutOf, b as resolveFullRef, d as hasUncommittedWork, l as hasPopulatedSubmodules, m as listWorktrees, o as dirtyPaths, w as unpushedCommits } from "./worktree-C0c5Udwa.mjs";
|
|
7
|
+
import { a as findProjectRoot$1, r as detectIsExpo } from "./project-CV3tDFHl.mjs";
|
|
8
|
+
import { h as shortUdid, o as formatLongDuration, u as plural } from "./command-output-CliHHn7a.mjs";
|
|
9
|
+
import { d as removeExpiredLease, h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-W5Pi1q38.mjs";
|
|
10
|
+
import { A as listAvds, H as parseAvdSystemImage, I as ownedAvdDirectory, M as listOrphanedAvdDirectories, V as parseAvdDeviceProfile, _ as avdStorageRoots, at as POOL_SETTING_REMEDY, d as androidToolPath, dt as parkedMaxSetting, ft as readParked, it as isStimOwnedSim, j as listInstalledSystemImages, m as avdNameAbsent, o as androidHome, p as avdIniPaths, rt as isStimOwnedAvd, z as ownedAvdSerialResolver } from "./android-Db2eEMaf.mjs";
|
|
11
|
+
import { n as readCreatedDevices$1, t as forgetCreatedDevice } from "./created-devices-BwVb3liH.mjs";
|
|
12
|
+
import { u as listAllIosSims } from "./ios-3CtCRhwr.mjs";
|
|
13
|
+
import { d as parseIdleDuration, f as shutDownIdleDevices, h as takeGcResults, l as idleDeviceLines, m as recordGcResult, s as collectIdleDevices } from "./watchman-Bx0wceoF.mjs";
|
|
14
|
+
import { i as canonicalPath, n as withIdleWorkspace, r as workspaceInUse } from "./in-use-CndPMilN.mjs";
|
|
15
|
+
import { n as listBuildSlots } from "./build-slots-Df1ApSuH.mjs";
|
|
16
|
+
import { r as readCollectors } from "./ownership-BkYxUtuj.mjs";
|
|
17
|
+
import { f as resolveEasCliBin } from "./remote-cache-CO2elm_b.mjs";
|
|
18
|
+
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-9ulPsdhY.mjs";
|
|
19
|
+
import { $ as discoverCaches, A as withAndroidAvdSizes, B as trimCaches, C as describeUnverifiableDevices, D as findStaleDeviceRecords, E as findOrphanedDevices, F as isWorkspacesScope, G as memoryCacheKind, I as planCacheEmptying, J as memorySweepIsScoped, K as memoryLines, L as previewScopedEmptying, M as includesParkedDevices, N as includesRecordings, O as findStaleProjectDevices, P as includesWorkspaceOutputs, Q as phase, R as selectCaches, S as describeParkedSims, T as eraseParkedDevices, U as collectMemoryReport, X as reclaimMemory, Y as memoryWorkPending, Z as scopedMemoryReport, _ as collectParkedAvds, a as REBUILD_COST, b as deleteParkedSims, c as collectOrphanedWorkspaces, d as isInsideWorkspaces, et as sizeCaches, f as listWorkspaceDirs, g as collectDeviceLeases, h as DEVICE_LIST_TIMEOUT_MS, i as machineNumber, j as emptyCaches, k as unverifiedDeviceCommand, l as collectWorkspaceOutputs, m as sizeText, o as WORKSPACE_OUTPUT_DIRS, p as rootPresence, q as memoryScope, s as clearWorkspaceOutputs, u as deleteOrphanedWorkspaces, v as collectParkedSims, w as deviceSweepIsScoped, x as deleteProjectDevices, y as deleteParkedAvds, z as selectsBeyondCaches } from "./budget-CMiqghig.mjs";
|
|
20
|
+
import { f as removeWorktreeTarget, g as reclaimProject, h as describeDereferenced, i as matchWorktreeEntry, n as excludePodChurn, o as reclaimKeys, r as excludeWatchmanCookies } from "./worktree-qXKzADSC.mjs";
|
|
21
|
+
import { n as mergeState, t as fetchDefaultBranch } from "./merge-state-BnxLNQiM.mjs";
|
|
22
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
23
|
+
import { basename, dirname, join } from "node:path";
|
|
21
24
|
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, writeFileSync as writeFileSync$1 } from "fs";
|
|
22
|
-
import { basename, dirname, isAbsolute, join } from "path";
|
|
25
|
+
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
23
26
|
import { LOG_ROTATE_BYTES, rotatedLogPath } from "@stim-cli/core";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
27
|
+
import { InvalidArgumentError } from "commander";
|
|
28
|
+
import { listRecordedDevices, listStatusCacheEntries, recordedBytes, settingDefinition } from "@stim-cli/core/state";
|
|
26
29
|
import chalk from "chalk";
|
|
27
30
|
//#region src/commands/gc/eas-sessions.ts
|
|
28
31
|
const EAS_OPERATION_TIMEOUT_MS = 3e4;
|
|
@@ -87,7 +90,7 @@ function readRecordedRemoteSessionIds(roots) {
|
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
92
|
function claimStateAbsence(claim) {
|
|
90
|
-
const stateDir = dirname(claim.stateFile);
|
|
93
|
+
const stateDir = dirname$1(claim.stateFile);
|
|
91
94
|
let entries;
|
|
92
95
|
try {
|
|
93
96
|
if (!statSync$1(stateDir).isDirectory()) throw new Error("path is not a directory");
|
|
@@ -98,7 +101,7 @@ function claimStateAbsence(claim) {
|
|
|
98
101
|
notice: `${stateDir} is not available for EAS ownership verification: ${describeError(error)}`
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
|
-
if (!entries.includes(basename(claim.stateFile))) return {
|
|
104
|
+
if (!entries.includes(basename$1(claim.stateFile))) return {
|
|
102
105
|
absent: true,
|
|
103
106
|
notice: null
|
|
104
107
|
};
|
|
@@ -135,7 +138,7 @@ function claimStateAbsence(claim) {
|
|
|
135
138
|
};
|
|
136
139
|
}
|
|
137
140
|
function collectEasSessionSweep(config, deps) {
|
|
138
|
-
const projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
141
|
+
const projectRoot = (deps.findProjectRoot ?? findProjectRoot$1)(process.cwd());
|
|
139
142
|
const empty = (notices = []) => ({
|
|
140
143
|
projectScope: projectRoot,
|
|
141
144
|
orphaned: [],
|
|
@@ -230,7 +233,7 @@ function unavailableLockRootNotice(roots) {
|
|
|
230
233
|
}
|
|
231
234
|
function malformedRemoteSessionLockNotice(roots) {
|
|
232
235
|
for (const root of roots) {
|
|
233
|
-
const lockPath = join(workspaceDir(root), "remote-session.lock");
|
|
236
|
+
const lockPath = join$1(workspaceDir(root), "remote-session.lock");
|
|
234
237
|
if (!existsSync$1(lockPath)) continue;
|
|
235
238
|
if ((0, ownership_claim_exports.readClaimSet)(lockPath).unresolved.length === 0) continue;
|
|
236
239
|
return `EAS session sweep skipped: the remote-session lock at ${lockPath} has a malformed owner record.`;
|
|
@@ -443,7 +446,7 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
443
446
|
//#endregion
|
|
444
447
|
//#region src/commands/gc/worktrees.ts
|
|
445
448
|
const DEFAULT_WORKTREE_IDLE_DAYS = 7;
|
|
446
|
-
const DAY_MS = 864e5;
|
|
449
|
+
const DAY_MS$1 = 864e5;
|
|
447
450
|
const MINUTE_MS = 6e4;
|
|
448
451
|
const GRACE_SETTING = "gc.worktreeGraceMinutes";
|
|
449
452
|
const MERGE_DECIDES = /* @__PURE__ */ new Set([
|
|
@@ -504,7 +507,7 @@ function removalBlocker(facts, olderThan) {
|
|
|
504
507
|
if (facts.locked) return skip("locked", "locked with git worktree lock");
|
|
505
508
|
if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
|
|
506
509
|
if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
|
|
507
|
-
const dirty = excludePodChurn(facts.porcelain).lines.length;
|
|
510
|
+
const dirty = excludePodChurn(excludeWatchmanCookies(facts.porcelain).lines).lines.length;
|
|
508
511
|
if (dirty) return skip("dirty", `dirty: ${plural(dirty, "uncommitted or untracked file")}`);
|
|
509
512
|
if (facts.unpushed === null) return skip("unpushed-unchecked", "unpushed commits could not be checked");
|
|
510
513
|
const merged = facts.merge?.merged ? facts.merge : null;
|
|
@@ -536,7 +539,7 @@ function lastUsedOf(keys) {
|
|
|
536
539
|
}
|
|
537
540
|
function idleDaysOf(keys, now) {
|
|
538
541
|
const last = lastUsedOf(keys);
|
|
539
|
-
return Number.isFinite(last) ? Math.max(0, Math.floor((now - last) / DAY_MS)) : null;
|
|
542
|
+
return Number.isFinite(last) ? Math.max(0, Math.floor((now - last) / DAY_MS$1)) : null;
|
|
540
543
|
}
|
|
541
544
|
function deviceReads() {
|
|
542
545
|
let sims;
|
|
@@ -608,7 +611,7 @@ function logFiles$1(key) {
|
|
|
608
611
|
const dir = workspaceLogsDir(key);
|
|
609
612
|
try {
|
|
610
613
|
return readdirSync$1(dir).map((name) => ({
|
|
611
|
-
path: join(dir, name),
|
|
614
|
+
path: join$1(dir, name),
|
|
612
615
|
basis: "a Stim log write"
|
|
613
616
|
}));
|
|
614
617
|
} catch (error) {
|
|
@@ -627,15 +630,15 @@ function worktreeActivityOf(path, keys) {
|
|
|
627
630
|
if (!gitDir) return null;
|
|
628
631
|
const paths = [
|
|
629
632
|
{
|
|
630
|
-
path: join(gitDir, "index"),
|
|
633
|
+
path: join$1(gitDir, "index"),
|
|
631
634
|
basis: "a git index write"
|
|
632
635
|
},
|
|
633
636
|
{
|
|
634
|
-
path: join(gitDir, "HEAD"),
|
|
637
|
+
path: join$1(gitDir, "HEAD"),
|
|
635
638
|
basis: "a git HEAD change"
|
|
636
639
|
},
|
|
637
640
|
{
|
|
638
|
-
path: join(gitDir, "logs", "HEAD"),
|
|
641
|
+
path: join$1(gitDir, "logs", "HEAD"),
|
|
639
642
|
basis: "a git reflog entry"
|
|
640
643
|
}
|
|
641
644
|
];
|
|
@@ -666,7 +669,7 @@ function porcelainOf(path, gitAnswered) {
|
|
|
666
669
|
return lines.length ? lines : null;
|
|
667
670
|
}
|
|
668
671
|
function candidateRoots() {
|
|
669
|
-
const registered = Object.keys(loadConfig$1()?.projects ?? {}).filter(isAbsolute);
|
|
672
|
+
const registered = Object.keys(loadConfig$1()?.projects ?? {}).filter(isAbsolute$1);
|
|
670
673
|
const recorded = listWorkspaceDirs().flatMap((entry) => entry.projectRoot ? [entry.projectRoot] : []);
|
|
671
674
|
return [.../* @__PURE__ */ new Set([...registered, ...recorded])].filter((root) => existsSync$1(root)).toSorted();
|
|
672
675
|
}
|
|
@@ -708,7 +711,9 @@ async function collectWorktreeSweep({ idle, olderThan, now }) {
|
|
|
708
711
|
};
|
|
709
712
|
const groups = /* @__PURE__ */ new Map();
|
|
710
713
|
const outside = [];
|
|
711
|
-
|
|
714
|
+
const candidates = candidateRoots();
|
|
715
|
+
if (candidates.length) phase("worktrees", `checking ${plural(candidates.length, "project")} for merged or idle worktrees`);
|
|
716
|
+
for (const root of candidates) {
|
|
712
717
|
const entry = matchWorktreeEntry(listWorktrees(root), root);
|
|
713
718
|
if (!entry) {
|
|
714
719
|
outside.push({
|
|
@@ -879,6 +884,206 @@ async function removeWorktrees(sweep, { now = Date.now() } = {}) {
|
|
|
879
884
|
return failures;
|
|
880
885
|
}
|
|
881
886
|
//#endregion
|
|
887
|
+
//#region src/commands/gc/status-caches.ts
|
|
888
|
+
const KINDS = ["disk-usage", "pull-request"];
|
|
889
|
+
const KEPT_REASON = {
|
|
890
|
+
unreadable: "the file records no readable path of its own",
|
|
891
|
+
unknown: "Stim cannot tell whether the folder it measures still exists",
|
|
892
|
+
unmounted: "the volume of the folder it measures is not mounted"
|
|
893
|
+
};
|
|
894
|
+
function witnessPath(path) {
|
|
895
|
+
return basename(path).startsWith("node_modules") ? dirname(path) : path;
|
|
896
|
+
}
|
|
897
|
+
function judge(path, mountedVolumes) {
|
|
898
|
+
if (path === null) return "unreadable";
|
|
899
|
+
const witness = witnessPath(path);
|
|
900
|
+
const present = rootPresence(witness);
|
|
901
|
+
if (present === null) return "unknown";
|
|
902
|
+
if (present) return "present";
|
|
903
|
+
return isOnMountedVolume(witness, mountedVolumes) ? "gone" : "unmounted";
|
|
904
|
+
}
|
|
905
|
+
function classifyStatusCaches(entries, mountedVolumes) {
|
|
906
|
+
const stale = [];
|
|
907
|
+
const kept = /* @__PURE__ */ new Map();
|
|
908
|
+
for (const { kind, file, path, bytes } of entries) {
|
|
909
|
+
const verdict = judge(path, mountedVolumes);
|
|
910
|
+
if (verdict === "present") continue;
|
|
911
|
+
if (verdict === "gone") {
|
|
912
|
+
stale.push({
|
|
913
|
+
kind,
|
|
914
|
+
file,
|
|
915
|
+
path,
|
|
916
|
+
bytes
|
|
917
|
+
});
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const dir = dirname(file);
|
|
921
|
+
const key = `${dir}\n${verdict}`;
|
|
922
|
+
const seen = kept.get(key);
|
|
923
|
+
if (seen) seen.count++;
|
|
924
|
+
else kept.set(key, {
|
|
925
|
+
dir,
|
|
926
|
+
verdict,
|
|
927
|
+
count: 1
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
stale,
|
|
932
|
+
skipped: [...kept.values()].map(({ dir, verdict, count }) => ({
|
|
933
|
+
dir,
|
|
934
|
+
reason: `${count} entr${count === 1 ? "y" : "ies"} kept: ${KEPT_REASON[verdict]}`
|
|
935
|
+
}))
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function collectStaleStatusCaches(mountedVolumes) {
|
|
939
|
+
return classifyStatusCaches(listStatusCacheEntries(), mountedVolumes);
|
|
940
|
+
}
|
|
941
|
+
function statusCacheLines(stale) {
|
|
942
|
+
if (!stale.length) return [];
|
|
943
|
+
const total = stale.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
944
|
+
const lines = [`Stale status cache entries (${stale.length}, ${formatBytes(total)}) - the folder or worktree they measure is gone:`];
|
|
945
|
+
for (const kind of KINDS) {
|
|
946
|
+
const count = stale.filter((entry) => entry.kind === kind).length;
|
|
947
|
+
if (count) lines.push(` ${kind}: ${count}`);
|
|
948
|
+
}
|
|
949
|
+
lines.push(" --delete removes them; `stim status` measures a folder again if it comes back.");
|
|
950
|
+
return lines;
|
|
951
|
+
}
|
|
952
|
+
function deleteStaleStatusCaches(stale) {
|
|
953
|
+
const mountedVolumes = listMountedVolumes();
|
|
954
|
+
let failures = 0;
|
|
955
|
+
for (const kind of KINDS) {
|
|
956
|
+
let removed = 0;
|
|
957
|
+
let bytes = 0;
|
|
958
|
+
for (const entry of stale.filter((candidate) => candidate.kind === kind)) {
|
|
959
|
+
if (judge(entry.path, mountedVolumes) !== "gone") continue;
|
|
960
|
+
try {
|
|
961
|
+
rmSync(entry.file, { force: true });
|
|
962
|
+
removed++;
|
|
963
|
+
bytes += entry.bytes;
|
|
964
|
+
} catch (error) {
|
|
965
|
+
failures++;
|
|
966
|
+
const detail = error?.message || String(error);
|
|
967
|
+
console.log(chalk.red(`Could not remove ${entry.file}: ${detail}`));
|
|
968
|
+
recordGcResult("statusCache", "failed", `${kind} cache entry`, {
|
|
969
|
+
id: entry.file,
|
|
970
|
+
detail
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (!removed) continue;
|
|
975
|
+
const label = `${removed} stale ${kind} cache entr${removed === 1 ? "y" : "ies"}`;
|
|
976
|
+
console.log(chalk.green(`Removed ${label}`));
|
|
977
|
+
recordGcResult("statusCache", "done", label, { bytes });
|
|
978
|
+
}
|
|
979
|
+
return failures;
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region src/commands/gc/recordings.ts
|
|
983
|
+
const DAY_MS = 864e5;
|
|
984
|
+
function oldSegments(devices, olderThan, now) {
|
|
985
|
+
const cutoff = now - olderThan * DAY_MS;
|
|
986
|
+
return devices.flatMap((device) => device.segments).filter((segment) => !segment.open && segment.end < cutoff);
|
|
987
|
+
}
|
|
988
|
+
function planRecordings(entries, { whole, olderThan, now }) {
|
|
989
|
+
return entries.map(({ recordings, projectRoot, problem, gone, devices }) => {
|
|
990
|
+
const bytes = recordedBytes(devices);
|
|
991
|
+
const entry = {
|
|
992
|
+
dir: recordings,
|
|
993
|
+
projectRoot,
|
|
994
|
+
bytes,
|
|
995
|
+
withWorkspace: false
|
|
996
|
+
};
|
|
997
|
+
const kept = (keptCode, keptReason) => ({
|
|
998
|
+
...entry,
|
|
999
|
+
deleteBytes: 0,
|
|
1000
|
+
willDelete: false,
|
|
1001
|
+
keptCode,
|
|
1002
|
+
keptReason
|
|
1003
|
+
});
|
|
1004
|
+
const remove = (deleteBytes, withWorkspace = false) => ({
|
|
1005
|
+
...entry,
|
|
1006
|
+
withWorkspace,
|
|
1007
|
+
deleteBytes,
|
|
1008
|
+
willDelete: true,
|
|
1009
|
+
keptCode: null,
|
|
1010
|
+
keptReason: null
|
|
1011
|
+
});
|
|
1012
|
+
if (projectRoot === null) return kept("unresolved", `workspace directory not resolved: ${problem ?? "unknown project root"}`);
|
|
1013
|
+
if (gone) return remove(bytes, true);
|
|
1014
|
+
if (whole) return remove(bytes);
|
|
1015
|
+
if (olderThan !== null) {
|
|
1016
|
+
const old = oldSegments(devices, olderThan, now);
|
|
1017
|
+
return old.length ? remove(old.reduce((sum, segment) => sum + segment.bytes, 0)) : kept("recently-recorded", `nothing recorded more than ${olderThan}d ago`);
|
|
1018
|
+
}
|
|
1019
|
+
return kept("retained", "the last 15 minutes of footage per device stay until worktree remove, --cache recordings, --older-than or recording.enabled false");
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* The device recordings of every workspace directory. Recordings in `gone` directories, which the orphaned
|
|
1024
|
+
* workspace and dead project sweeps remove whole, are listed with them; entries whose project root is on an
|
|
1025
|
+
* unmounted volume are left out.
|
|
1026
|
+
*/
|
|
1027
|
+
function collectRecordings(plan, gone = []) {
|
|
1028
|
+
const mountedVolumes = listMountedVolumes();
|
|
1029
|
+
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, {
|
|
1030
|
+
gone: gone.includes(entry.dir),
|
|
1031
|
+
devices: listRecordedDevices(entry.recordings)
|
|
1032
|
+
})), plan);
|
|
1033
|
+
}
|
|
1034
|
+
/** Deletes what `planRecordings` chose, planning again from the files on disk. */
|
|
1035
|
+
function deleteRecordings(report, plan) {
|
|
1036
|
+
let failures = 0;
|
|
1037
|
+
for (const entry of report) {
|
|
1038
|
+
const label = entry.projectRoot ?? entry.dir;
|
|
1039
|
+
if (entry.withWorkspace) continue;
|
|
1040
|
+
if (!entry.willDelete) {
|
|
1041
|
+
if (entry.keptCode === "unresolved" || entry.keptCode === "recently-recorded") {
|
|
1042
|
+
console.log(chalk.dim(`Kept the device recordings of ${label}: ${entry.keptReason}`));
|
|
1043
|
+
recordGcResult("recording", "kept", label, {
|
|
1044
|
+
bytes: entry.bytes,
|
|
1045
|
+
detail: entry.keptReason
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
let deleted = 0;
|
|
1052
|
+
if (plan.whole) {
|
|
1053
|
+
deleted = recordedBytes(listRecordedDevices(entry.dir));
|
|
1054
|
+
rmSync$1(entry.dir, {
|
|
1055
|
+
recursive: true,
|
|
1056
|
+
force: true
|
|
1057
|
+
});
|
|
1058
|
+
} else if (plan.olderThan !== null) for (const segment of oldSegments(listRecordedDevices(entry.dir), plan.olderThan, plan.now)) {
|
|
1059
|
+
rmSync$1(segment.file, { force: true });
|
|
1060
|
+
deleted += segment.bytes;
|
|
1061
|
+
}
|
|
1062
|
+
console.log(chalk.green(`Deleted ${formatBytes(deleted)} of device recordings of ${label}`));
|
|
1063
|
+
recordGcResult("recording", "done", label, { bytes: deleted });
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
failures++;
|
|
1066
|
+
const detail = error?.message || String(error);
|
|
1067
|
+
console.log(chalk.red(`Could not delete the device recordings of ${label}: ${detail}`));
|
|
1068
|
+
recordGcResult("recording", "failed", label, {
|
|
1069
|
+
bytes: entry.bytes,
|
|
1070
|
+
detail
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return failures;
|
|
1075
|
+
}
|
|
1076
|
+
function recordingLines(recordings) {
|
|
1077
|
+
if (!recordings.length) return [];
|
|
1078
|
+
const total = recordings.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
1079
|
+
const lines = [`Device recordings (${formatBytes(total)} in ${recordings.length}):`];
|
|
1080
|
+
for (const entry of recordings) {
|
|
1081
|
+
lines.push(` ${formatBytes(entry.bytes).padStart(10)} ${entry.projectRoot ?? entry.dir}`);
|
|
1082
|
+
lines.push(entry.withWorkspace ? " would be DELETED with its gone workspace directory" : entry.willDelete ? ` would DELETE ${formatBytes(entry.deleteBytes)}` : ` kept: ${entry.keptReason}`);
|
|
1083
|
+
}
|
|
1084
|
+
return lines;
|
|
1085
|
+
}
|
|
1086
|
+
//#endregion
|
|
882
1087
|
//#region src/commands/gc/report.ts
|
|
883
1088
|
function shortKey(key) {
|
|
884
1089
|
const text = String(key ?? "");
|
|
@@ -889,7 +1094,12 @@ function parkedAge(parkedAt, now) {
|
|
|
889
1094
|
if (!Number.isFinite(at)) return "parked at an unknown time";
|
|
890
1095
|
return `parked ${formatLongDuration(Math.max(0, now - at))} ago`;
|
|
891
1096
|
}
|
|
892
|
-
|
|
1097
|
+
const PARKED_DELETE_NOTE = " --delete attempts verified deletions and keeps failures.";
|
|
1098
|
+
const PARKED_ERASE_NOTE = " --delete erases each verified device (apps and data) and keeps it parked.";
|
|
1099
|
+
function parkedApp(app) {
|
|
1100
|
+
return app ? ` ${app}` : "";
|
|
1101
|
+
}
|
|
1102
|
+
function formatParkedSimReport(parkedSims, now, erase) {
|
|
893
1103
|
if (parkedSims.length === 0) return [];
|
|
894
1104
|
const known = parkedSims.filter((sim) => sim.bytes !== null);
|
|
895
1105
|
const total = known.reduce((sum, sim) => sum + (sim.bytes ?? 0), 0);
|
|
@@ -900,20 +1110,20 @@ function formatParkedSimReport(parkedSims, now) {
|
|
|
900
1110
|
const age = parkedAge(sim.parkedAt, now);
|
|
901
1111
|
const bytes = sim.bytes === null ? "" : ` ${formatBytes(sim.bytes)}`;
|
|
902
1112
|
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}`);
|
|
1113
|
+
lines.push(` ios ${sim.name} (${shortUdid(sim.udid)})${model ? ` ${model}` : ""} ${age}${parkedApp(sim.app)}${bytes}${gone}`);
|
|
904
1114
|
}
|
|
905
|
-
lines.push(
|
|
1115
|
+
lines.push(erase ? PARKED_ERASE_NOTE : PARKED_DELETE_NOTE);
|
|
906
1116
|
return lines;
|
|
907
1117
|
}
|
|
908
|
-
function formatParkedAvdReport(parkedAvds, now) {
|
|
1118
|
+
function formatParkedAvdReport(parkedAvds, now, erase) {
|
|
909
1119
|
if (!parkedAvds.length) return [];
|
|
910
1120
|
const lines = [];
|
|
911
1121
|
lines.push(`Parked emulators (${parkedAvds.length}):`);
|
|
912
1122
|
for (const avd of parkedAvds) {
|
|
913
1123
|
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}`);
|
|
1124
|
+
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
1125
|
}
|
|
916
|
-
lines.push(
|
|
1126
|
+
lines.push(erase ? PARKED_ERASE_NOTE : PARKED_DELETE_NOTE);
|
|
917
1127
|
return lines;
|
|
918
1128
|
}
|
|
919
1129
|
function projectEntryLines(header, paths) {
|
|
@@ -928,7 +1138,11 @@ function unresolvedLockLines(locks, slots) {
|
|
|
928
1138
|
" run that yourself once you know nothing is building with it"
|
|
929
1139
|
];
|
|
930
1140
|
}
|
|
931
|
-
function
|
|
1141
|
+
function scopeLine(cacheScope) {
|
|
1142
|
+
if (memoryCacheKind(cacheScope)) return `Cache scope: "${cacheScope}". Only these helper processes were inspected.`;
|
|
1143
|
+
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.`;
|
|
1144
|
+
}
|
|
1145
|
+
function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invalidProjects = [], orphanedWorkspaces = [], parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], staleStatusCaches = [], buildLocks = {
|
|
932
1146
|
stale: [],
|
|
933
1147
|
live: [],
|
|
934
1148
|
unresolved: []
|
|
@@ -944,13 +1158,13 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
944
1158
|
orphaned: [],
|
|
945
1159
|
notices: [],
|
|
946
1160
|
deletionSafe: true
|
|
947
|
-
}, caches = [], workspaceOutputs = null, workspaceLogs = [], worktreeSweep = null, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
1161
|
+
}, caches = [], workspaceOutputs = null, workspaceLogs = [], recordings = [], worktreeSweep = null, memory, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
948
1162
|
const lines = [];
|
|
949
1163
|
const staleLocks = buildLocks?.stale ?? [];
|
|
950
1164
|
const liveLocks = buildLocks?.live ?? [];
|
|
951
1165
|
const staleSlots = buildSlots?.stale ?? [];
|
|
952
1166
|
const expiredLeases = deviceLeases?.expired ?? [];
|
|
953
|
-
if (cacheScope) lines.push(
|
|
1167
|
+
if (cacheScope) lines.push(scopeLine(cacheScope));
|
|
954
1168
|
else if ([
|
|
955
1169
|
deadProjects,
|
|
956
1170
|
invalidProjects,
|
|
@@ -961,6 +1175,7 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
961
1175
|
staleDevices,
|
|
962
1176
|
staleDeviceRecords,
|
|
963
1177
|
staleLedgerEntries,
|
|
1178
|
+
staleStatusCaches,
|
|
964
1179
|
staleLocks,
|
|
965
1180
|
staleSlots,
|
|
966
1181
|
expiredLeases,
|
|
@@ -981,8 +1196,8 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
981
1196
|
lines.push(...namedPortLines(orphanedPorts));
|
|
982
1197
|
lines.push(...orphanedWorkspaceLines(orphanedWorkspaces));
|
|
983
1198
|
lines.push(...worktreeSweepLines(worktreeSweep));
|
|
984
|
-
lines.push(...formatParkedSimReport(parkedSims, now));
|
|
985
|
-
lines.push(...formatParkedAvdReport(parkedAvds, now));
|
|
1199
|
+
lines.push(...formatParkedSimReport(parkedSims, now, cacheScope !== null));
|
|
1200
|
+
lines.push(...formatParkedAvdReport(parkedAvds, now, cacheScope !== null));
|
|
986
1201
|
if (orphanedDevices.length) {
|
|
987
1202
|
lines.push(`Orphaned devices (${orphanedDevices.length}):`);
|
|
988
1203
|
for (const d of orphanedDevices) lines.push(` ${d.kind} ${d.name} (${d.id})${deviceSizeSuffix(d)}`);
|
|
@@ -1016,6 +1231,7 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
1016
1231
|
for (const entry of staleLedgerEntries) lines.push(` ${entry.kind} ${entry.id} is not on this machine`);
|
|
1017
1232
|
lines.push(" --delete forgets the LEDGER ENTRY only; there is no device left to touch.");
|
|
1018
1233
|
}
|
|
1234
|
+
lines.push(...statusCacheLines(staleStatusCaches));
|
|
1019
1235
|
if (easSessionSweep.orphaned.length) {
|
|
1020
1236
|
lines.push(`Orphaned EAS sessions (${easSessionSweep.orphaned.length}) - current EAS project only:`);
|
|
1021
1237
|
for (const session of easSessionSweep.orphaned) {
|
|
@@ -1061,7 +1277,9 @@ function formatGcReport({ skipped = [], deadProjects = [], orphanedPorts, invali
|
|
|
1061
1277
|
for (const entry of skipped) lines.push(` ${entry.dir}: ${entry.reason}`);
|
|
1062
1278
|
}
|
|
1063
1279
|
lines.push(...workspaceLogLines(workspaceLogs));
|
|
1280
|
+
lines.push(...recordingLines(recordings));
|
|
1064
1281
|
lines.push(...cacheLines(caches, workspaceOutputs));
|
|
1282
|
+
lines.push(...memoryLines(memory, memoryCacheKind(cacheScope), now));
|
|
1065
1283
|
return lines;
|
|
1066
1284
|
}
|
|
1067
1285
|
function workspaceLogLines(workspaceLogs) {
|
|
@@ -1117,7 +1335,7 @@ function workspaceOutputLines(outputs, bytes) {
|
|
|
1117
1335
|
const lines = [
|
|
1118
1336
|
` ${formatBytes(bytes).padStart(10)} Workspace build outputs (detected)`,
|
|
1119
1337
|
` ${outputs.root}`,
|
|
1120
|
-
` ${WORKSPACE_OUTPUT_DIRS.join(", ")} of each workspace; workspace.json, state.json, logs and device records stay`,
|
|
1338
|
+
` ${WORKSPACE_OUTPUT_DIRS.join(", ")}, macOS Swift scratch and staged app bundles of each workspace; workspace.json, state.json, logs and device records stay`,
|
|
1121
1339
|
` ${REBUILD_COST}`
|
|
1122
1340
|
];
|
|
1123
1341
|
for (const w of outputs.workspaces) {
|
|
@@ -1142,6 +1360,7 @@ function cacheLines(caches, workspaceOutputs) {
|
|
|
1142
1360
|
if (c.note) lines.push(` ${c.note}`);
|
|
1143
1361
|
if (c.willEmpty) lines.push(" would be EMPTIED");
|
|
1144
1362
|
else if (c.emptySkipped) lines.push(` would be left alone: ${c.emptySkipped}`);
|
|
1363
|
+
else if (c.scopedEmpty?.emptySkipped) lines.push(` --delete --cache would leave it alone: ${c.scopedEmpty.emptySkipped}`);
|
|
1145
1364
|
}
|
|
1146
1365
|
lines.push(` total: ${formatBytes(total)}`);
|
|
1147
1366
|
const doomed = caches.filter((c) => c.willEmpty);
|
|
@@ -1191,7 +1410,7 @@ function jsonPullRequest(lookup) {
|
|
|
1191
1410
|
containsHead: pr.containsHead
|
|
1192
1411
|
} : null;
|
|
1193
1412
|
}
|
|
1194
|
-
function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [], invalidProjects = [], orphanedWorkspaces = [], worktreeSweep = null, parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], buildLocks = {
|
|
1413
|
+
function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [], invalidProjects = [], orphanedWorkspaces = [], worktreeSweep = null, parkedSims = [], parkedAvds = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], staleStatusCaches = [], buildLocks = {
|
|
1195
1414
|
stale: [],
|
|
1196
1415
|
live: [],
|
|
1197
1416
|
unresolved: []
|
|
@@ -1207,7 +1426,7 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1207
1426
|
orphaned: [],
|
|
1208
1427
|
notices: [],
|
|
1209
1428
|
deletionSafe: true
|
|
1210
|
-
}, workspaceOutputs = null, workspaceLogs = [], caches = [] }) {
|
|
1429
|
+
}, workspaceOutputs = null, workspaceLogs = [], recordings = [], caches = [], memory = null }) {
|
|
1211
1430
|
return {
|
|
1212
1431
|
deadProjects: deadProjects.map((path) => ({ path })),
|
|
1213
1432
|
invalidProjects: invalidProjects.map((path) => ({ path })),
|
|
@@ -1232,20 +1451,22 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1232
1451
|
detail: w.skipped ?? worktreeRemovalReason(w),
|
|
1233
1452
|
eligibleAt: w.eligibleAt === null ? null : new Date(w.eligibleAt).toISOString()
|
|
1234
1453
|
})),
|
|
1235
|
-
parkedSimulators: parkedSims.map(({ udid, name, model, runtime, parkedAt, bytes, listed }) => ({
|
|
1454
|
+
parkedSimulators: parkedSims.map(({ udid, name, model, runtime, parkedAt, app, bytes, listed }) => ({
|
|
1236
1455
|
udid,
|
|
1237
1456
|
name,
|
|
1238
1457
|
model,
|
|
1239
1458
|
runtime,
|
|
1240
1459
|
parkedAt,
|
|
1460
|
+
app,
|
|
1241
1461
|
bytes,
|
|
1242
1462
|
listed
|
|
1243
1463
|
})),
|
|
1244
|
-
parkedEmulators: parkedAvds.map(({ name, systemImage, deviceProfile, parkedAt, bytes, listed }) => ({
|
|
1464
|
+
parkedEmulators: parkedAvds.map(({ name, systemImage, deviceProfile, parkedAt, app, bytes, listed }) => ({
|
|
1245
1465
|
name,
|
|
1246
1466
|
systemImage,
|
|
1247
1467
|
deviceProfile,
|
|
1248
1468
|
parkedAt,
|
|
1469
|
+
app,
|
|
1249
1470
|
bytes,
|
|
1250
1471
|
listed
|
|
1251
1472
|
})),
|
|
@@ -1281,6 +1502,12 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1281
1502
|
kind,
|
|
1282
1503
|
id
|
|
1283
1504
|
})),
|
|
1505
|
+
staleStatusCaches: staleStatusCaches.map(({ kind, path, file, bytes }) => ({
|
|
1506
|
+
kind,
|
|
1507
|
+
path,
|
|
1508
|
+
file,
|
|
1509
|
+
bytes
|
|
1510
|
+
})),
|
|
1284
1511
|
idleDevices: idleDevices.map(({ kind, id, name, project, slot, lastActivityAt, idleForMs, buildInProgress }) => ({
|
|
1285
1512
|
kind,
|
|
1286
1513
|
id,
|
|
@@ -1341,6 +1568,16 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1341
1568
|
reason: w.keptCode,
|
|
1342
1569
|
detail: w.keptReason
|
|
1343
1570
|
})),
|
|
1571
|
+
recordings: recordings.map((r) => ({
|
|
1572
|
+
dir: r.dir,
|
|
1573
|
+
projectRoot: r.projectRoot,
|
|
1574
|
+
bytes: r.bytes,
|
|
1575
|
+
deleteBytes: r.deleteBytes,
|
|
1576
|
+
willDelete: r.willDelete,
|
|
1577
|
+
withWorkspace: r.withWorkspace,
|
|
1578
|
+
reason: r.keptCode,
|
|
1579
|
+
detail: r.keptReason
|
|
1580
|
+
})),
|
|
1344
1581
|
workspaceBuildOutputs: (workspaceOutputs?.workspaces ?? []).map((w) => ({
|
|
1345
1582
|
dir: w.dir,
|
|
1346
1583
|
projectRoot: w.projectRoot,
|
|
@@ -1357,8 +1594,12 @@ function gcReportSections({ skipped = [], deadProjects = [], orphanedPorts = [],
|
|
|
1357
1594
|
bytes: c.bytes ?? null,
|
|
1358
1595
|
note: c.note || null,
|
|
1359
1596
|
willEmpty: Boolean(c.willEmpty),
|
|
1360
|
-
emptySkipped: c.emptySkipped ?? null
|
|
1361
|
-
|
|
1597
|
+
emptySkipped: c.emptySkipped ?? null,
|
|
1598
|
+
scopedEmpty: c.scopedEmpty ?? null
|
|
1599
|
+
})),
|
|
1600
|
+
memory: memory?.processes ?? [],
|
|
1601
|
+
watchmanRoots: memory?.watchmanRoots ?? [],
|
|
1602
|
+
memoryNotices: (memory?.notices ?? []).map((message) => ({ message }))
|
|
1362
1603
|
};
|
|
1363
1604
|
}
|
|
1364
1605
|
//#endregion
|
|
@@ -1417,8 +1658,8 @@ function staleBrowserProfiles(ledger, exists = existsSync) {
|
|
|
1417
1658
|
}));
|
|
1418
1659
|
}
|
|
1419
1660
|
function isStaleProfile(path, exists) {
|
|
1420
|
-
const workspace = dirname
|
|
1421
|
-
return !exists(path) && exists(dirname
|
|
1661
|
+
const workspace = dirname(dirname(path));
|
|
1662
|
+
return !exists(path) && exists(dirname(workspace)) && !isDanglingLink(workspace);
|
|
1422
1663
|
}
|
|
1423
1664
|
function isDanglingLink(path) {
|
|
1424
1665
|
try {
|
|
@@ -1464,7 +1705,7 @@ function fileSize(path) {
|
|
|
1464
1705
|
}
|
|
1465
1706
|
function cappedFiles(logsDir) {
|
|
1466
1707
|
return CAPPED_LOGS.flatMap((name) => {
|
|
1467
|
-
const log = join(logsDir, name);
|
|
1708
|
+
const log = join$1(logsDir, name);
|
|
1468
1709
|
return [{
|
|
1469
1710
|
file: log,
|
|
1470
1711
|
log
|
|
@@ -1490,14 +1731,14 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1490
1731
|
for (const { dir, projectRoot, problem } of listWorkspaceDirs()) {
|
|
1491
1732
|
if (exclude.includes(dir)) continue;
|
|
1492
1733
|
if (projectRoot !== null && !isOnMountedVolume(projectRoot, mountedVolumes)) continue;
|
|
1493
|
-
const logsDir = join(dir, "logs");
|
|
1734
|
+
const logsDir = join$1(dir, "logs");
|
|
1494
1735
|
let names;
|
|
1495
1736
|
try {
|
|
1496
1737
|
names = readdirSync$1(logsDir);
|
|
1497
1738
|
} catch {
|
|
1498
1739
|
continue;
|
|
1499
1740
|
}
|
|
1500
|
-
const bytes = names.reduce((sum, name) => sum + fileSize(join(logsDir, name)), 0);
|
|
1741
|
+
const bytes = names.reduce((sum, name) => sum + fileSize(join$1(logsDir, name)), 0);
|
|
1501
1742
|
if (bytes === 0) continue;
|
|
1502
1743
|
const trimBytes = excess(logsDir);
|
|
1503
1744
|
let keptCode = null;
|
|
@@ -1532,7 +1773,7 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1532
1773
|
}
|
|
1533
1774
|
function trimToNewest({ file, log }, maxBytes) {
|
|
1534
1775
|
if (!trimmable(fileSize(file))) return 0;
|
|
1535
|
-
return withDirLock(`${log}.lock`, () => {
|
|
1776
|
+
return withDirLock$1(`${log}.lock`, () => {
|
|
1536
1777
|
const { size, atime, mtime } = statSync$1(file);
|
|
1537
1778
|
if (!trimmable(size)) return 0;
|
|
1538
1779
|
const tail = Buffer.alloc(maxBytes);
|
|
@@ -1575,7 +1816,7 @@ async function trimWorkspaceLogs(entries) {
|
|
|
1575
1816
|
};
|
|
1576
1817
|
return {
|
|
1577
1818
|
kept: null,
|
|
1578
|
-
bytes: cappedFiles(join(entry.dir, "logs")).reduce((sum, capped) => sum + trimToNewest(capped, LOG_ROTATE_BYTES), 0)
|
|
1819
|
+
bytes: cappedFiles(join$1(entry.dir, "logs")).reduce((sum, capped) => sum + trimToNewest(capped, LOG_ROTATE_BYTES), 0)
|
|
1579
1820
|
};
|
|
1580
1821
|
}, { purpose: "gc" });
|
|
1581
1822
|
} catch (error) {
|
|
@@ -1775,7 +2016,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1775
2016
|
if (records.has(name)) continue;
|
|
1776
2017
|
let ini;
|
|
1777
2018
|
try {
|
|
1778
|
-
ini = readFileSync(join
|
|
2019
|
+
ini = readFileSync(join(root, entry), "utf8");
|
|
1779
2020
|
} catch (error) {
|
|
1780
2021
|
if (!isMissing(error)) notices.push(unreadableNotice(`AVD ${name}`, error));
|
|
1781
2022
|
continue;
|
|
@@ -1784,7 +2025,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1784
2025
|
let config = "";
|
|
1785
2026
|
let unreadable = null;
|
|
1786
2027
|
for (const candidate of avdIniPaths(root, ini)) try {
|
|
1787
|
-
config = readFileSync(join
|
|
2028
|
+
config = readFileSync(join(candidate, "config.ini"), "utf8");
|
|
1788
2029
|
directory = candidate;
|
|
1789
2030
|
break;
|
|
1790
2031
|
} catch (error) {
|
|
@@ -1796,7 +2037,7 @@ function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
|
1796
2037
|
directory,
|
|
1797
2038
|
systemImage: parseAvdSystemImage(config),
|
|
1798
2039
|
deviceProfile: parseAvdDeviceProfile(config),
|
|
1799
|
-
lastUsedAt: directory ? modifiedAt(join
|
|
2040
|
+
lastUsedAt: directory ? modifiedAt(join(directory, "hardware-qemu.ini")) : null
|
|
1800
2041
|
});
|
|
1801
2042
|
}
|
|
1802
2043
|
}
|
|
@@ -1837,10 +2078,10 @@ function collectInventory(config, deadProjects) {
|
|
|
1837
2078
|
notices.push(`simulator runtimes not listed: ${describeError(error)}`);
|
|
1838
2079
|
}
|
|
1839
2080
|
}
|
|
1840
|
-
const imagesRoot = join
|
|
2081
|
+
const imagesRoot = join(androidHome(), "system-images");
|
|
1841
2082
|
const images = listInstalledSystemImages((error) => notices.push(unreadableNotice("Android system images", error))).map((image) => ({
|
|
1842
2083
|
pkg: image.pkg,
|
|
1843
|
-
directory: join
|
|
2084
|
+
directory: join(imagesRoot, `android-${image.api}`, image.tag, image.arch)
|
|
1844
2085
|
}));
|
|
1845
2086
|
return {
|
|
1846
2087
|
...buildInventory({
|
|
@@ -1863,12 +2104,54 @@ function removeInvalidProjectEntries(invalidProjects) {
|
|
|
1863
2104
|
recordGcResult("project", "done", path);
|
|
1864
2105
|
}
|
|
1865
2106
|
}
|
|
2107
|
+
function collectGcMemory(scope) {
|
|
2108
|
+
return memorySweepIsScoped() ? Promise.resolve(scopedMemoryReport()) : collectMemoryReport(scope, true);
|
|
2109
|
+
}
|
|
2110
|
+
function classifyProjectEntries(cfg, mountedVolumes) {
|
|
2111
|
+
const deadProjects = [];
|
|
2112
|
+
const invalidProjects = [];
|
|
2113
|
+
const skipped = [];
|
|
2114
|
+
for (const [path, project] of Object.entries(cfg?.projects || {})) {
|
|
2115
|
+
if (!isAbsolute$1(path)) {
|
|
2116
|
+
const claimed = describeDereferenced(project);
|
|
2117
|
+
if (claimed.length) skipped.push({
|
|
2118
|
+
dir: path,
|
|
2119
|
+
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`
|
|
2120
|
+
});
|
|
2121
|
+
else invalidProjects.push(path);
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
if (existsSync$1(path)) continue;
|
|
2125
|
+
if (!isOnMountedVolume(path, mountedVolumes)) {
|
|
2126
|
+
const volume = volumeRootFor(path);
|
|
2127
|
+
skipped.push({
|
|
2128
|
+
dir: path,
|
|
2129
|
+
reason: `volume ${volume} is not mounted`
|
|
2130
|
+
});
|
|
2131
|
+
} else deadProjects.push(path);
|
|
2132
|
+
}
|
|
2133
|
+
return {
|
|
2134
|
+
deadProjects,
|
|
2135
|
+
invalidProjects,
|
|
2136
|
+
skipped
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
/** The workspace directories an unscoped run removes whole: those of dead projects and orphaned directories. */
|
|
2140
|
+
function removedWholeWorkspaceDirs() {
|
|
2141
|
+
const mountedVolumes = listMountedVolumes();
|
|
2142
|
+
const cfg = loadConfig$1();
|
|
2143
|
+
const { deadProjects } = classifyProjectEntries(cfg, mountedVolumes);
|
|
2144
|
+
const { orphaned } = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, { measure: false });
|
|
2145
|
+
return [...orphaned.map((entry) => entry.dir), ...deadProjects.map(workspaceDir)];
|
|
2146
|
+
}
|
|
1866
2147
|
async function collectGcReport({ olderThan = null, cache = null, worktrees = false, now = Date.now(), lastTouched = workspaceLastUsed } = {}, deps = {}) {
|
|
1867
2148
|
const scope = typeof cache === "string" && cache.trim() ? cache : null;
|
|
1868
2149
|
const all = scope !== null && olderThan === null;
|
|
1869
2150
|
const withWorkspaces = includesWorkspaceOutputs(scope);
|
|
1870
2151
|
const selected = selectCaches(discoverCaches(), scope).filter((c) => !withWorkspaces || !isInsideWorkspaces(c.dir));
|
|
1871
|
-
|
|
2152
|
+
if (selected.length) phase("caches", `measuring ${plural(selected.length, "shared cache")}`);
|
|
2153
|
+
const sized = sizeCaches(selected);
|
|
2154
|
+
const caches = scope === null ? previewScopedEmptying(sized) : planCacheEmptying(sized, all);
|
|
1872
2155
|
if (scope) return {
|
|
1873
2156
|
skipped: [],
|
|
1874
2157
|
deadProjects: [],
|
|
@@ -1879,6 +2162,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1879
2162
|
staleDevices: [],
|
|
1880
2163
|
staleDeviceRecords: [],
|
|
1881
2164
|
staleLedgerEntries: [],
|
|
2165
|
+
staleStatusCaches: [],
|
|
1882
2166
|
buildLocks: {
|
|
1883
2167
|
stale: [],
|
|
1884
2168
|
live: [],
|
|
@@ -1901,15 +2185,28 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1901
2185
|
notices: [],
|
|
1902
2186
|
deletionSafe: true
|
|
1903
2187
|
},
|
|
1904
|
-
parkedSims:
|
|
1905
|
-
|
|
2188
|
+
parkedSims: includesParkedDevices(scope) ? collectParkedSims(deps, {
|
|
2189
|
+
olderThanDays: olderThan,
|
|
2190
|
+
now
|
|
2191
|
+
}) : [],
|
|
2192
|
+
parkedAvds: includesParkedDevices(scope) ? collectParkedAvds(deps, {
|
|
2193
|
+
olderThanDays: olderThan,
|
|
2194
|
+
now
|
|
2195
|
+
}) : [],
|
|
1906
2196
|
caches,
|
|
1907
2197
|
workspaceOutputs: withWorkspaces ? collectWorkspaceOutputs({
|
|
1908
2198
|
olderThan,
|
|
1909
|
-
now
|
|
2199
|
+
now,
|
|
2200
|
+
exclude: isWorkspacesScope(scope) ? removedWholeWorkspaceDirs() : []
|
|
1910
2201
|
}) : null,
|
|
1911
2202
|
workspaceLogs: [],
|
|
2203
|
+
recordings: includesRecordings(scope) ? collectRecordings({
|
|
2204
|
+
whole: all,
|
|
2205
|
+
olderThan,
|
|
2206
|
+
now
|
|
2207
|
+
}) : [],
|
|
1912
2208
|
worktreeSweep: null,
|
|
2209
|
+
memory: memoryCacheKind(scope) ? await collectGcMemory(memoryScope(scope)) : null,
|
|
1913
2210
|
cacheScope: scope,
|
|
1914
2211
|
olderThan,
|
|
1915
2212
|
all
|
|
@@ -1935,30 +2232,11 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1935
2232
|
olderThanDays: olderThan,
|
|
1936
2233
|
now
|
|
1937
2234
|
});
|
|
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
|
-
}
|
|
2235
|
+
const { deadProjects, invalidProjects, skipped } = classifyProjectEntries(cfg, mountedVolumes);
|
|
1960
2236
|
const workspaceDirs = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes);
|
|
1961
2237
|
skipped.push(...workspaceDirs.skipped);
|
|
2238
|
+
const statusCaches = collectStaleStatusCaches(mountedVolumes);
|
|
2239
|
+
skipped.push(...statusCaches.skipped);
|
|
1962
2240
|
const deviceSweepNotices = [];
|
|
1963
2241
|
let orphanedDevices = [];
|
|
1964
2242
|
let unverifiedDevices = [];
|
|
@@ -1967,6 +2245,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1967
2245
|
let staleLedgerEntries = staleBrowserProfiles(readCreatedDevices$1());
|
|
1968
2246
|
let idleDevices = [];
|
|
1969
2247
|
const unsweepableReason = cfg === null ? "no Stim config found" : deviceSweepIsScoped() ? "STIM_HOME scopes this config, but simulators and AVDs are machine-global" : null;
|
|
2248
|
+
phase("devices", "listing simulators and emulators");
|
|
1970
2249
|
if (unsweepableReason) {
|
|
1971
2250
|
let simNames = [];
|
|
1972
2251
|
let avdNames = [];
|
|
@@ -2072,6 +2351,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2072
2351
|
staleDevices,
|
|
2073
2352
|
staleDeviceRecords,
|
|
2074
2353
|
staleLedgerEntries,
|
|
2354
|
+
staleStatusCaches: statusCaches.stale,
|
|
2075
2355
|
buildLocks: {
|
|
2076
2356
|
stale: locks.filter((l) => !l.alive && !l.unresolved),
|
|
2077
2357
|
live: locks.filter((l) => l.alive),
|
|
@@ -2093,11 +2373,20 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2093
2373
|
exclude: goneWorkspaceDirs
|
|
2094
2374
|
}),
|
|
2095
2375
|
workspaceLogs: collectWorkspaceLogs({ exclude: goneWorkspaceDirs }),
|
|
2376
|
+
recordings: collectRecordings({
|
|
2377
|
+
whole: false,
|
|
2378
|
+
olderThan,
|
|
2379
|
+
now
|
|
2380
|
+
}, goneWorkspaceDirs),
|
|
2096
2381
|
worktreeSweep: await collectWorktreeSweep({
|
|
2097
2382
|
idle: worktrees,
|
|
2098
2383
|
olderThan,
|
|
2099
2384
|
now
|
|
2100
2385
|
}),
|
|
2386
|
+
memory: await collectGcMemory({
|
|
2387
|
+
watchman: true,
|
|
2388
|
+
gradle: true
|
|
2389
|
+
}),
|
|
2101
2390
|
cacheScope: null,
|
|
2102
2391
|
olderThan,
|
|
2103
2392
|
all
|
|
@@ -2133,6 +2422,20 @@ async function sweep(opts, deps) {
|
|
|
2133
2422
|
remedy
|
|
2134
2423
|
};
|
|
2135
2424
|
}
|
|
2425
|
+
const memoryKind = memoryCacheKind(opts.cache);
|
|
2426
|
+
if (memoryKind && opts.olderThan !== void 0) {
|
|
2427
|
+
const message = `--older-than does not apply to --cache ${memoryKind}, which acts on running processes.`;
|
|
2428
|
+
const remedy = `Run \`stim gc --cache ${memoryKind}\` without --older-than.`;
|
|
2429
|
+
console.error(chalk.red(message));
|
|
2430
|
+
console.error(chalk.dim(remedy));
|
|
2431
|
+
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
2432
|
+
process.exitCode = 1;
|
|
2433
|
+
return {
|
|
2434
|
+
code: "STIM_BAD_ARG",
|
|
2435
|
+
message,
|
|
2436
|
+
remedy
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2136
2439
|
const poolError = parkedMaxSetting("ios").error || parkedMaxSetting("android").error;
|
|
2137
2440
|
if (poolError) console.error(chalk.yellow(`${poolError} ${POOL_SETTING_REMEDY}`));
|
|
2138
2441
|
if (opts.cache) return runGcCore(opts, {
|
|
@@ -2146,7 +2449,7 @@ async function sweep(opts, deps) {
|
|
|
2146
2449
|
});
|
|
2147
2450
|
let projectRoot = null;
|
|
2148
2451
|
try {
|
|
2149
|
-
projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
2452
|
+
projectRoot = (deps.findProjectRoot ?? findProjectRoot$1)(process.cwd());
|
|
2150
2453
|
if (!projectRoot) return runGcCore(opts, {
|
|
2151
2454
|
...deps,
|
|
2152
2455
|
precollectedEasSessionSweep: {
|
|
@@ -2209,7 +2512,7 @@ async function pruneDeadProjects(deadProjects) {
|
|
|
2209
2512
|
recordGcResult("project", "kept", path, { detail: "its absence can no longer be confirmed" });
|
|
2210
2513
|
continue;
|
|
2211
2514
|
}
|
|
2212
|
-
const result = await reclaimProject(path).catch((error) => {
|
|
2515
|
+
const result = await reclaimProject(path, { stopRequester: { by: "stim gc --delete" } }).catch((error) => {
|
|
2213
2516
|
deleteFailures++;
|
|
2214
2517
|
console.log(chalk.red(`Could not prune ${path}; its registry entry was kept: ${error.message}`));
|
|
2215
2518
|
recordGcResult("project", "failed", path, { detail: `its registry entry was kept: ${error.message}` });
|
|
@@ -2244,6 +2547,25 @@ async function pruneDeadProjects(deadProjects) {
|
|
|
2244
2547
|
}
|
|
2245
2548
|
return deleteFailures;
|
|
2246
2549
|
}
|
|
2550
|
+
function scopedDryRunAction(cache) {
|
|
2551
|
+
const memory = memoryCacheKind(cache);
|
|
2552
|
+
if (memory === "watchman") return "remove the stale roots and shut down the unused watchman";
|
|
2553
|
+
if (memory === "gradle-daemons") return "stop the idle daemons";
|
|
2554
|
+
return includesParkedDevices(cache) ? "erase the parked devices" : "empty the caches";
|
|
2555
|
+
}
|
|
2556
|
+
function dryRunHint(cache, all, actionable, caches) {
|
|
2557
|
+
if (all && (!memoryCacheKind(cache) || actionable)) return `Dry run. Re-run with --delete to ${scopedDryRunAction(cache)} above.`;
|
|
2558
|
+
if (actionable) return "Dry run. Re-run with --delete to reclaim.";
|
|
2559
|
+
if (caches) return "Pass --delete --cache all to empty the caches above, or --delete --older-than <days> to trim them.";
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
function pruneStatusCaches(report) {
|
|
2563
|
+
if (report.cacheScope !== null) return 0;
|
|
2564
|
+
return deleteStaleStatusCaches(collectStaleStatusCaches(listMountedVolumes()).stale);
|
|
2565
|
+
}
|
|
2566
|
+
function reclaimParkedDevices(report, deps) {
|
|
2567
|
+
return report.cacheScope ? eraseParkedDevices(report.parkedSims, report.parkedAvds) : deleteParkedSims(report.parkedSims, deps) + deleteParkedAvds(report.parkedAvds);
|
|
2568
|
+
}
|
|
2247
2569
|
async function runGcCore(opts, deps) {
|
|
2248
2570
|
takeGcResults();
|
|
2249
2571
|
const olderThan = typeof opts.olderThan === "number" ? opts.olderThan : null;
|
|
@@ -2253,7 +2575,7 @@ async function runGcCore(opts, deps) {
|
|
|
2253
2575
|
cache,
|
|
2254
2576
|
worktrees: Boolean(opts.worktrees)
|
|
2255
2577
|
}, deps);
|
|
2256
|
-
if (cache && report.caches.length === 0 && report.workspaceOutputs === null) {
|
|
2578
|
+
if (cache && report.caches.length === 0 && report.workspaceOutputs === null && !selectsBeyondCaches(cache)) {
|
|
2257
2579
|
const names = [...new Set(discoverCaches().map((c) => c.name))];
|
|
2258
2580
|
const message = `No shared cache carries "${cache}" in its name or directory.`;
|
|
2259
2581
|
console.log(chalk.yellow(message));
|
|
@@ -2268,7 +2590,7 @@ async function runGcCore(opts, deps) {
|
|
|
2268
2590
|
const all = report.all;
|
|
2269
2591
|
for (const line of formatGcReport(report)) console.log(line);
|
|
2270
2592
|
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;
|
|
2593
|
+
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) || 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
2594
|
const idle = opts.idle ?? null;
|
|
2273
2595
|
const idleFailures = idle === null ? 0 : shutDownIdleDevices(report.idleDevices, idle, () => collectIdleDevices(loadConfig$1(), listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }), report.deadProjects)).failures;
|
|
2274
2596
|
if (idleFailures) process.exitCode = 1;
|
|
@@ -2286,20 +2608,26 @@ async function runGcCore(opts, deps) {
|
|
|
2286
2608
|
inventory
|
|
2287
2609
|
});
|
|
2288
2610
|
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."));
|
|
2611
|
+
const hint = dryRunHint(cache, all, actionable, caches.length > 0);
|
|
2612
|
+
if (hint) console.log(chalk.dim(`\n${hint}`));
|
|
2292
2613
|
return payload(idle === null ? null : idleFailures);
|
|
2293
2614
|
}
|
|
2294
2615
|
let deleteFailures = idleFailures;
|
|
2295
2616
|
deleteFailures += report.workspaceOutputs ? (await clearWorkspaceOutputs(report.workspaceOutputs, { olderThan })).failures : 0;
|
|
2296
2617
|
deleteFailures += await trimWorkspaceLogs(report.workspaceLogs);
|
|
2297
|
-
deleteFailures +=
|
|
2618
|
+
deleteFailures += deleteRecordings(report.recordings, {
|
|
2619
|
+
whole: all,
|
|
2620
|
+
olderThan,
|
|
2621
|
+
now: Date.now()
|
|
2622
|
+
});
|
|
2623
|
+
deleteFailures += reclaimParkedDevices(report, deps);
|
|
2624
|
+
deleteFailures += await reclaimMemory(cache, report.memory);
|
|
2298
2625
|
removeInvalidProjectEntries(invalidProjects);
|
|
2299
2626
|
deleteFailures += await pruneDeadProjects(deadProjects);
|
|
2300
2627
|
deleteFailures += await deleteOrphanedWorkspaces(report.orphanedWorkspaces);
|
|
2301
2628
|
deleteFailures += deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords);
|
|
2302
2629
|
deleteFailures += forgetStaleLedgerEntries(report.staleLedgerEntries);
|
|
2630
|
+
deleteFailures += pruneStatusCaches(report);
|
|
2303
2631
|
for (const lock of buildLocks.stale) {
|
|
2304
2632
|
const cleared = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
2305
2633
|
root: lock.path,
|
|
@@ -2378,11 +2706,11 @@ async function runGcCore(opts, deps) {
|
|
|
2378
2706
|
return payload(deleteFailures);
|
|
2379
2707
|
}
|
|
2380
2708
|
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,
|
|
2709
|
+
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
2710
|
const n = parseInt(v, 10);
|
|
2383
2711
|
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
2712
|
return n;
|
|
2385
|
-
}).option("--cache <name>", "act on the shared caches whose name or directory contains <name>, every cache
|
|
2713
|
+
}).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; all, workspaces, recordings, parked, 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. 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
2714
|
if (!v.trim()) throw new InvalidArgumentError("must name a cache, e.g. --cache \"compilation cache\"");
|
|
2387
2715
|
return v;
|
|
2388
2716
|
}).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) => {
|