stim 1.20.2 → 1.21.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 +88 -8
- package/dist/acquire-artifact-CD4lbiWE.mjs +153 -0
- package/dist/act-DHAw2A2S.mjs +2667 -0
- package/dist/activity-BzVfHSrY.mjs +1376 -0
- package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-zh1vhbLp.mjs} +21 -65
- package/dist/{android-HqP1_vB_.mjs → android-BEJ8Jpl7.mjs} +2 -2
- package/dist/{android-DCKZrNvV.mjs → android-BLgbNqox.mjs} +128 -78
- package/dist/{android-Dlzxshgp.mjs → android-CV_GCELZ.mjs} +5 -5
- package/dist/{android-Mz_Inn3w.mjs → android-DUYkNF4u.mjs} +184 -102
- package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-Ct5b1xCK.mjs} +7 -6
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/android-project-BCAAmMwz.mjs +12 -0
- package/dist/api.d.mts +740 -8
- package/dist/api.mjs +585 -1
- package/dist/{artifact-HSwkIWEQ.mjs → artifact-B8T3S5gi.mjs} +223 -201
- package/dist/{artifact-D3czlUvT.mjs → artifact-DAuZ-FNU.mjs} +158 -148
- package/dist/{browser-web-CA-F1GQo.mjs → browser-web-QQzVjz3O.mjs} +26 -24
- package/dist/budget-Bs6SxxzO.mjs +9154 -0
- package/dist/{build-DYDbfkk5.mjs → build-CoPzQRNw.mjs} +144 -96
- package/dist/build-cache-DmyR9Zzc.mjs +602 -0
- package/dist/build-plan-CaFskB7_.mjs +188 -0
- package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DkkqguoK.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-zdKiYQ3E.mjs → ccache-BJRZ9pG3.mjs} +10 -9
- package/dist/{cdp-Dq_ypAlJ.mjs → cdp-CWmIUMbw.mjs} +18 -5
- package/dist/{chrome-BnzZMqsR.mjs → chrome-B6tfCta1.mjs} +7 -4
- package/dist/{cli-CLP3diBm.mjs → cli-0DYf8QhQ.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/client-B7QVOlCr.d.mts +253 -0
- package/dist/client-UXD70ZbM.mjs +1406 -0
- package/dist/collector-run.d.mts +4 -40
- package/dist/collector-run.mjs +9 -8
- package/dist/{command-output-BTy8hZ0F.mjs → command-output-B1UrjZHL.mjs} +1 -1
- package/dist/config-CYcBF478.mjs +949 -0
- package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-CqeBlrEn.mjs} +3 -3
- package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-BbKQW83h.mjs} +20 -5
- package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-DFRyG1_o.mjs} +1 -1
- package/dist/{dev-client-DAWVIGBu.mjs → dev-client-UOeESKm6.mjs} +2 -2
- package/dist/device-DCxH7_By.mjs +1068 -0
- package/dist/{device-BECOiufl.mjs → device-ExJq8Ei7.mjs} +35 -35
- package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-CMlqEcvw.mjs} +43 -13
- package/dist/device-host-worker.mjs +65 -61
- package/dist/{device-ios-COSi6oS1.mjs → device-ios-DRUeKaFV.mjs} +48 -45
- package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-C8raGFoP.mjs} +14 -26
- package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-DMRpVX9a.mjs} +5 -5
- package/dist/doctor-BLHCYxzi.mjs +2 -0
- package/dist/{doctor-kfMBnUw6.mjs → doctor-DTvaDOda.mjs} +281 -317
- package/dist/{doctor-CB8oZKCk.mjs → doctor-Dhqebm0q.mjs} +183 -91
- package/dist/doctor-fingerprint.mjs +19 -0
- package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-Cv1F10Kj.mjs} +40 -21
- package/dist/{eas-build-JyfqyHfC.mjs → eas-build-BomfRnFT.mjs} +53 -20
- package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-DJwZpy0c.mjs} +26 -208
- package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-op6A54yi.mjs} +229 -11
- package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-Bf0rZvG9.mjs} +172 -82
- package/dist/exec-H20xSg2T.d.mts +65 -0
- package/dist/exec-_UbtCQcG.mjs +750 -0
- package/dist/gc-C-WwL0yi.mjs +3 -0
- package/dist/{gc-qWOMdnER.mjs → gc-_-dCXdyL.mjs} +584 -796
- package/dist/git-C3iTsJaZ.mjs +73 -0
- package/dist/{guide-G9cAbVAc.mjs → guide-Yqwt2bd6.mjs} +941 -269
- package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-Bw-honsd.mjs} +8 -6
- package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DB_gBPfY.mjs} +34 -19
- package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CUvOf4Vr.mjs} +1 -1
- package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-DL_P5AdN.mjs} +77 -43
- package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-QI141yKk.mjs} +21 -17
- package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-Ksyjgzq-.mjs} +30 -21
- package/dist/idle-BVeRGroT.d.mts +24 -0
- package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DaKmZi0z.mjs} +11 -10
- package/dist/{ios-BwVCF7oA.mjs → ios-CQ68hG9s.mjs} +8 -8
- package/dist/{ios-RkW4pnQf.mjs → ios-CpByZy12.mjs} +140 -80
- package/dist/{ios-device-B5veSDDQ.mjs → ios-device-CXGP1bsD.mjs} +1 -1
- package/dist/{ios-device-D7pi2Sca.mjs → ios-device-CwFMehUS.mjs} +51 -26
- package/dist/{ios-project-K9hY_Jab.mjs → ios-project-WuwwBdvW.mjs} +13 -13
- package/dist/{ios-nOLJGVdy.mjs → ios-qNJt4dFm.mjs} +1 -1
- package/dist/ios-signing-DTTIChGs.mjs +489 -0
- package/dist/{ios-state-CBLe1xYw.mjs → ios-state-DvFX-K_l.mjs} +1 -1
- package/dist/{ios-Ce7bc7br.mjs → ios-ubFsBzwq.mjs} +129 -86
- package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Cc1dttps.mjs} +53 -26
- package/dist/{launch-F4sOqAhB.mjs → launch-CD1yVc7U.mjs} +91 -90
- package/dist/{launch-C3qJ6Agb.mjs → launch-Zq2mOSDy.mjs} +82 -541
- package/dist/logs-BS00Anxf.mjs +352 -0
- package/dist/logs-CMm05HxN.mjs +255 -0
- package/dist/{macos-D7uDvO-z.mjs → macos-B65--pPd.mjs} +1 -1
- package/dist/{macos-Ch5VJmcS.mjs → macos-C-0qJ6zq.mjs} +225 -212
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +122 -247
- package/dist/{metro-DnCEjCxs.mjs → metro-DEXpIhHC.mjs} +67 -12
- package/dist/{metro-gateway-CX9Xlkq2.mjs → metro-gateway-BMJNlj3E.mjs} +20 -12
- package/dist/{named-ports-4WopRPg-.mjs → named-ports-BWTzt6mu.mjs} +8 -63
- package/dist/native-android-JACyjTZs.mjs +844 -0
- package/dist/{native-gradle-inputs-BsLRrl7u.mjs → native-gradle-inputs-Bep9JcSF.mjs} +128 -140
- package/dist/native-inputs-TRTTTRxt.mjs +91 -0
- package/dist/native-run-DY_RLx3C.mjs +277 -0
- package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-Dg1Q7tfF.mjs} +9 -8
- package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-CB2iQeWq.mjs} +27 -18
- package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-CXX39F7o.mjs} +96 -47
- package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
- package/dist/offload-worker.d.mts +1 -46
- package/dist/offload-worker.mjs +197 -66
- package/dist/{ownership-DCtw_2IA.mjs → ownership-DEmM5vhh.mjs} +3 -3
- package/dist/{page-DRHy329N.mjs → page-CPaHeYqj.mjs} +7 -4
- package/dist/paths-BsJMoPP2.mjs +12 -0
- package/dist/paths-DQtTgsBf.mjs +42 -0
- package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BqGbTa61.mjs} +5 -5
- package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-Bwj-IjY1.mjs} +6 -6
- package/dist/{ports-DdSUicVo.mjs → ports-D-MmCTdv.mjs} +3 -3
- package/dist/prebuild-uD_ipCxF.mjs +449 -0
- package/dist/{project-_phNIJfs.mjs → project-BZNjmW_8.mjs} +65 -37
- package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-Lfetn8Kr.mjs} +2 -2
- package/dist/pull-request-zjCSAryH.d.mts +120 -0
- package/dist/pull-requests.d.mts +1 -119
- package/dist/pull-requests.mjs +1 -1
- package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-DuzH0cn3.mjs} +74 -48
- package/dist/{gradle-DSrXb6lG.mjs → react-native-build-rhk4smwQ.mjs} +292 -15
- package/dist/react-native-doctor-CQpuIMso.mjs +234 -0
- package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-Bhq1rJeV.mjs} +46 -28
- package/dist/{recordings-D0d5aQt0.mjs → recordings-DYteQSu4.mjs} +12 -9
- package/dist/{reload-C8JhxjSh.mjs → reload-CmTRFJaX.mjs} +93 -47
- package/dist/reload-DzjSl5P_.mjs +2 -0
- package/dist/{remote-BiR6aYjy.mjs → remote-ByYkXdGD.mjs} +15 -10
- package/dist/result-DNiS-tTP.mjs +434 -0
- package/dist/{run-BtPgpKks.mjs → run-D23HmJMs.mjs} +4 -5
- package/dist/selection-CSl9i5qU.mjs +619 -0
- package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CxTrvDg8.mjs} +3 -4
- package/dist/{server-command-CTtCkmcQ.mjs → server-command-CtzloH3v.mjs} +14 -7
- package/dist/server-expo-C-90EhcG.mjs +2 -0
- package/dist/{server-expo-DsguMGUF.mjs → server-expo-DT_F_uVI.mjs} +50 -9
- package/dist/{settings-B717odTD.mjs → settings-CsJDEXgK.mjs} +234 -24
- package/dist/{settings-vk3mJGSm.d.mts → settings-DkdmlelE.d.mts} +6 -4
- package/dist/settings-T1gtc3bf.mjs +114 -0
- package/dist/settings-write-HyVZ8eQ8.mjs +359 -0
- package/dist/settings.schema.json +163 -16
- package/dist/{simslim-CCr3rE-A.mjs → simslim-Cgk17SXV.mjs} +14 -12
- package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-DF8VfT64.mjs} +15 -20
- package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
- package/dist/stage-BIS0ydsV.mjs +183 -0
- package/dist/{start-BWqzODpb.mjs → start-7lpH7hmN.mjs} +63 -44
- package/dist/{start-TXeMmmO2.mjs → start-RChynRDp.mjs} +1 -1
- package/dist/{state-D1Icyjlu.mjs → state-C4ZtUisW.mjs} +40 -13
- package/dist/{state-CG_OFoaA.mjs → state-XMziexv2.mjs} +3 -2
- package/dist/{stats-BoY5sEPU.mjs → stats-53T7OTb9.mjs} +7 -13
- package/dist/stats-report-BFfMpWWF.mjs +16 -0
- package/dist/{status-WgFbkRzj.mjs → status-BL7jVyAB.mjs} +535 -447
- package/dist/status-CSi6tlRq.mjs +2 -0
- package/dist/{status-BTA7kKiQ.mjs → status-DqEUSSxs.mjs} +6 -4
- package/dist/{status-watch-C23ILtf9.mjs → status-watch-GDCfqpTE.mjs} +187 -22
- package/dist/status-worker.d.mts +1 -0
- package/dist/status-worker.mjs +19 -0
- package/dist/stim-desktop-C12we7lz.mjs +112 -0
- package/dist/{stop-DR6VoDIk.mjs → stop-Bk_1qiV6.mjs} +2 -2
- package/dist/{stop-BNmBMi1F.mjs → stop-CPzlM1L-.mjs} +138 -98
- package/dist/{stop-DPEhunlw.mjs → stop-D7h6jAXQ.mjs} +11 -10
- package/dist/supervisor-run.d.mts +8 -17
- package/dist/supervisor-run.mjs +63 -27
- package/dist/{support-DMTqj3Hm.mjs → support-67vraa1Q.mjs} +18 -11
- package/dist/{support-CwjbFs9H.mjs → support-Dlu37g6H.mjs} +12 -12
- package/dist/{swiftpm-macos-DRNVJWlb.mjs → swiftpm-macos-p8WZbxKU.mjs} +35 -24
- package/dist/toolchain-DhKwp9iI.mjs +2 -0
- package/dist/toolchain-Hp9sCpK4.mjs +873 -0
- package/dist/{trigger-CigAYjeL.mjs → trigger-BAahxUPq.mjs} +20 -5
- package/dist/trigger-Bf3uDcpv.mjs +2 -0
- package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-BHX5r0Ei.mjs} +250 -156
- package/dist/{web-BDBMxDpx.mjs → web-CJmIj6AI.mjs} +18 -13
- package/dist/web-DknQETPs.mjs +2 -0
- package/dist/web-run.mjs +11 -12
- package/dist/windows-report.d.mts +1 -0
- package/dist/windows-report.mjs +18 -0
- package/dist/windows-report.ps1 +43 -0
- package/dist/workspace-process-lock-1_1OoVW5.mjs +57 -0
- package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-Cjuigeeu.mjs} +62 -58
- package/dist/{workspaces-C36s2jb9.mjs → workspaces-BccMFmGQ.mjs} +1188 -1079
- package/dist/worktree-CK2MpJW5.mjs +2 -0
- package/dist/{worktree-CgvJYJ-Q.mjs → worktree-DQ6Zi2Mv.mjs} +304 -53
- package/dist/xcode-Zlx3qjEs.mjs +896 -0
- package/package.json +7 -5
- package/shim/android-optimizations.gradle +7 -5
- package/dist/activity-BSV745Kz.mjs +0 -629
- package/dist/api-qvOzWyfO.mjs +0 -154
- package/dist/api-run.mjs +0 -294
- package/dist/app-install-BIKtMfPq.mjs +0 -896
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
- package/dist/budget-f7516Zk0.mjs +0 -4106
- package/dist/build-facts-DcLv2HSd.d.mts +0 -111
- package/dist/cancellation-C_22VQTH.mjs +0 -24
- package/dist/client-qQAaXSUs.mjs +0 -1771
- package/dist/config-BYDxlMY3.mjs +0 -562
- package/dist/device-DuiPJQSn.mjs +0 -290
- package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
- package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
- package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
- package/dist/exec-DqKo9GKJ.mjs +0 -268
- package/dist/git-DDfFzkMm.mjs +0 -45
- package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
- package/dist/logs-BGrEUaEs.mjs +0 -383
- package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
- package/dist/native-android-D5qc3-_v.mjs +0 -226
- package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
- package/dist/plan-C4TjdPsR.mjs +0 -260
- package/dist/prebuild-DYbucPXy.mjs +0 -212
- package/dist/preview-78a9kR_-.mjs +0 -1011
- package/dist/react-native-build-CtCccJtb.mjs +0 -239
- package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
- package/dist/remote-cache-OyOyVIS3.mjs +0 -526
- package/dist/result-CaJg5Iat.mjs +0 -186
- package/dist/server-expo-Bigriskl.mjs +0 -2
- package/dist/settings-BORNaaCO.mjs +0 -402
- package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
- package/dist/stage-KNTQopqv.mjs +0 -157
- package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
- package/dist/tailnet-Dh-qiYzG.mjs +0 -265
- package/dist/toolchain-BFkAcstd.mjs +0 -2
- package/dist/toolchain-DxSsNssu.mjs +0 -1657
- package/dist/trigger-9_USgmxz.mjs +0 -2
- package/dist/web-Dq-i3wzJ.mjs +0 -2
- package/dist/worktree-DpLdCHga.mjs +0 -2
- package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
- package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { n as
|
|
9
|
-
import { C as
|
|
10
|
-
import {
|
|
11
|
-
import { f as
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { _ as
|
|
18
|
-
import {
|
|
1
|
+
import { f as ownership_claim_exports, p as process_identity_exports, t as getExecutor } from "./exec-_UbtCQcG.mjs";
|
|
2
|
+
import { d as workspaceDir, v as withDirLock$1 } from "./paths-DQtTgsBf.mjs";
|
|
3
|
+
import { D as withConfigLock$1, F as isOnMountedVolume, I as listMountedVolumes, K as projectDeviceSlots, N as directorySizeAsync, P as formatBytes, R as measuredDirectorySizeAsync, S as removeProject, _ as loadConfig$1, j as acquireAvdClaim, z as volumeRootFor } from "./config-CYcBF478.mjs";
|
|
4
|
+
import { h as shortUdid, o as formatLongDuration, u as plural } from "./command-output-B1UrjZHL.mjs";
|
|
5
|
+
import { u as workspaceLastUsed } from "./workspace-state-Cjuigeeu.mjs";
|
|
6
|
+
import { kt as listWorktreesAsync, zt as resolveSourceCheckoutAsync } from "./settings-CsJDEXgK.mjs";
|
|
7
|
+
import { i as findProjectRoot$1, v as detectIsExpo } from "./project-BZNjmW_8.mjs";
|
|
8
|
+
import { a as removeEasSessionClaim, n as easMachineStateRoot$1, r as readEasSessionLedger$1 } from "./eas-session-ledger-op6A54yi.mjs";
|
|
9
|
+
import { $ as includesRecordings, At as unverifiedDeviceCommand, C as collectIdleDevices, D as idleDeviceLines, Dt as findOrphanedDevices, Et as eraseParkedDevices, Ft as withRemoteSessionGcLocks, Kt as withEasProjectLock, Mt as collectEasSessionSweep, Nt as deleteEasSessions, O as parseIdleDuration, Ot as findStaleDeviceRecords, Pt as describeError, Q as includesParkedDevices, Sr as sizeCaches, St as deleteProjectDevices, Tt as deviceSweepIsScoped, X as emptyCaches, Z as includesArchives, _r as phase, _t as collectDeviceLeases, at as selectsBeyondCaches, br as discoverCaches, bt as deleteParkedAvds, dr as memoryScope, dt as gradleVersionsPending, et as includesWorkspaceOutputs, fr as memorySweepIsScoped, ft as includesGradleVersions, gt as DEVICE_LIST_TIMEOUT_MS, hr as scopedMemoryReport, ht as collectInventory, ir as parseArchiveSelector, it as selectCaches, jt as withAndroidAvdSizes, k as shutDownIdleDevices, kt as findStaleProjectDevices, lr as memoryCacheKind, lt as deleteGradleVersions, mr as reclaimMemory, mt as getMachineCacheReport, nt as planCacheEmptying, ot as trimCaches, pr as memoryWorkPending, rt as previewScopedEmptying, sr as collectMemoryReport, st as collectGradleVersions, tt as isWorkspacesScope, ur as memoryLines, ut as gradleVersionLines, vt as collectParkedAvds, w as collectIdleDevicesAsync, wt as describeUnverifiableDevices, xt as deleteParkedSims, yt as collectParkedSims } from "./budget-Bs6SxxzO.mjs";
|
|
10
|
+
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
11
|
+
import { B as takeGcResults, I as listBuildSlots, a as collectWorkspaceOutputs, c as listWorkspaceDirs, f as withIdleWorkspace, i as collectOrphanedWorkspaces, l as rootPresence, n as WORKSPACE_OUTPUT_DIRS, o as deleteOrphanedWorkspaces, p as workspaceInUse, r as clearWorkspaceOutputs, s as isInsideWorkspaces, t as REBUILD_COST, u as sizeText, z as recordGcResult } from "./workspaces-BccMFmGQ.mjs";
|
|
12
|
+
import { N as listBuildLocks, k as removeExpiredLease } from "./activity-BzVfHSrY.mjs";
|
|
13
|
+
import { n as readCreatedDevices$1, t as forgetCreatedDevice } from "./created-devices-CqeBlrEn.mjs";
|
|
14
|
+
import { F as listOrphanedAvdDirectories, M as listAvds, N as listAvdsAsync, ht as parkedMaxSetting, lt as POOL_SETTING_REMEDY, m as avdNameAbsent, z as ownedAvdDirectory } from "./android-DUYkNF4u.mjs";
|
|
15
|
+
import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-CpByZy12.mjs";
|
|
16
|
+
import { r as readCollectors } from "./ownership-DEmM5vhh.mjs";
|
|
17
|
+
import { _ as describeDereferenced, a as matchWorktreeEntry, b as sweepArchiveStaging, v as reclaimProject, y as deleteSelectedArchives } from "./worktree-DQ6Zi2Mv.mjs";
|
|
18
|
+
import { s as writeCacheFile } from "./warm-progress-BHX5r0Ei.mjs";
|
|
19
|
+
import { _ as collectWorktreeSweep, a as previewMaintenance, b as worktreeRemovalReason, c as deleteTempLeftovers, d as ARTIFACT_DIRS, f as appRoots, g as candidateRootsAsync, h as ownedDeviceUsage, l as tempLeftoverLines, o as triggeringRoot, p as finishedWorkspaces, s as collectTempLeftovers, t as deleteIdleWorkspaceDevices, u as tempLeftoverSection, v as removeWorktrees, y as worktreePullRequestNote } from "./act-DHAw2A2S.mjs";
|
|
20
|
+
import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-zh1vhbLp.mjs";
|
|
19
21
|
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
20
|
-
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
21
|
-
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
22
|
-
import { assertHostedDeviceLedger, deviceHostArea, deviceHostRoot, hostedDeviceId, isJsonObject, listRecordedDevices, listStatusCacheEntries, maintenanceRunClaims, parseHostedPlatformDevice, readArchives, readHostedDevice, readHostedDeviceLedger, readHostedSessions, recordedBytes } from "@stim-cli/core/state";
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
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 as utimesSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
22
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
25
23
|
import { LOG_ROTATE_BYTES, rotatedLogPath } from "@stim-cli/core";
|
|
26
|
-
import {
|
|
24
|
+
import { assertHostedDeviceLedger, deviceHostArea, deviceHostRoot, diskUsageCacheFile, hostedDeviceId, isJsonObject, listRecordedDevices, listStatusCacheEntries, maintenanceRunClaims, parseHostedPlatformDevice, readArchives, readDiskUsage, readHostedDevice, readHostedDeviceLedger, readHostedSessions, recordedBytes } from "@stim-cli/core/state";
|
|
25
|
+
import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
27
|
import chalk from "chalk";
|
|
28
|
+
import { closeSync as closeSync$1, existsSync as existsSync$1, openSync as openSync$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";
|
|
29
|
+
import { InvalidArgumentError } from "commander";
|
|
28
30
|
//#region src/commands/gc/archives.ts
|
|
29
31
|
function stagingOrNone() {
|
|
30
32
|
try {
|
|
@@ -95,439 +97,16 @@ function deleteArchives(selection) {
|
|
|
95
97
|
}
|
|
96
98
|
function archiveRefusal(selection) {
|
|
97
99
|
if (selection?.unknownId === void 0 || selection.unknownId === null) return null;
|
|
98
|
-
process.exitCode = 1;
|
|
99
|
-
const message = `Unknown archive "${selection.unknownId}".`;
|
|
100
|
-
const remedy = `Known archive ids: ${selection.knownIds.join(", ") || "(none)"}`;
|
|
101
|
-
console.error(chalk.red(message));
|
|
102
|
-
console.error(chalk.dim(remedy));
|
|
103
|
-
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
104
100
|
return {
|
|
105
101
|
code: "STIM_BAD_ARG",
|
|
106
|
-
message
|
|
107
|
-
remedy
|
|
102
|
+
message: `Unknown archive "${selection.unknownId}".`,
|
|
103
|
+
remedy: `Known archive ids: ${selection.knownIds.join(", ") || "(none)"}`
|
|
108
104
|
};
|
|
109
105
|
}
|
|
110
106
|
function archiveWorkPending(selection, otherWork) {
|
|
111
107
|
return otherWork || Boolean(selection?.records.some((record) => record.willDelete) || selection?.staging.some((entry) => !entry.kept));
|
|
112
108
|
}
|
|
113
109
|
//#endregion
|
|
114
|
-
//#region src/commands/gc/eas-sessions.ts
|
|
115
|
-
const EAS_OPERATION_TIMEOUT_MS = 3e4;
|
|
116
|
-
const EAS_COLLECTION_TIMEOUT_MS = 6e4;
|
|
117
|
-
const EAS_MAX_LIST_PAGES = 100;
|
|
118
|
-
const EAS_LOCK_WAIT_MS = 0;
|
|
119
|
-
const EAS_LOCK_SNAPSHOT_RETRIES = 3;
|
|
120
|
-
const PROXY_CREDENTIAL_ENV = ["AGENT_DEVICE_DAEMON_BASE_URL", "AGENT_DEVICE_DAEMON_AUTH_TOKEN"];
|
|
121
|
-
function describeError(error) {
|
|
122
|
-
const candidate = error;
|
|
123
|
-
if (typeof candidate?.stderr === "string" && candidate.stderr.trim()) return candidate.stderr.trim();
|
|
124
|
-
if (typeof candidate?.message === "string" && candidate.message.trim()) return candidate.message.trim();
|
|
125
|
-
return String(error);
|
|
126
|
-
}
|
|
127
|
-
function readRecordedRemoteSessionIds(roots) {
|
|
128
|
-
const ids = /* @__PURE__ */ new Set();
|
|
129
|
-
const notices = [];
|
|
130
|
-
let safe = true;
|
|
131
|
-
for (const root of new Set(roots)) {
|
|
132
|
-
try {
|
|
133
|
-
if (!statSync$1(root).isDirectory()) throw new Error("path is not a directory");
|
|
134
|
-
readdirSync$1(root);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
safe = false;
|
|
137
|
-
notices.push(`${root} is not available as a readable workspace directory: ${describeError(error)}`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const path = workspaceStateFile(root);
|
|
141
|
-
if (!existsSync$1(path)) continue;
|
|
142
|
-
let state;
|
|
143
|
-
try {
|
|
144
|
-
state = JSON.parse(readFileSync$1(path, "utf-8"));
|
|
145
|
-
} catch (error) {
|
|
146
|
-
safe = false;
|
|
147
|
-
notices.push(`${path} could not be read as valid JSON: ${describeError(error)}`);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
151
|
-
safe = false;
|
|
152
|
-
notices.push(`${path} does not contain a valid workspace state object.`);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const remote = state.remoteDevice;
|
|
156
|
-
if (remote === void 0) continue;
|
|
157
|
-
if (!remote || typeof remote !== "object" || Array.isArray(remote)) {
|
|
158
|
-
safe = false;
|
|
159
|
-
notices.push(`${path} has a malformed remote session record.`);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
const id = remote.sessionId;
|
|
163
|
-
if (typeof id !== "string" || id.length === 0) {
|
|
164
|
-
safe = false;
|
|
165
|
-
notices.push(`${path} has a remote session record with no valid session id.`);
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
ids.add(id);
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
ids: [...ids],
|
|
172
|
-
notices,
|
|
173
|
-
safe
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
function claimStateAbsence(claim) {
|
|
177
|
-
const stateDir = dirname$1(claim.stateFile);
|
|
178
|
-
let entries;
|
|
179
|
-
try {
|
|
180
|
-
if (!statSync$1(stateDir).isDirectory()) throw new Error("path is not a directory");
|
|
181
|
-
entries = readdirSync$1(stateDir);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
return {
|
|
184
|
-
absent: false,
|
|
185
|
-
notice: `${stateDir} is not available for EAS ownership verification: ${describeError(error)}`
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
if (!entries.includes(basename$1(claim.stateFile))) return {
|
|
189
|
-
absent: true,
|
|
190
|
-
notice: null
|
|
191
|
-
};
|
|
192
|
-
let state;
|
|
193
|
-
try {
|
|
194
|
-
state = JSON.parse(readFileSync$1(claim.stateFile, "utf-8"));
|
|
195
|
-
} catch (error) {
|
|
196
|
-
return {
|
|
197
|
-
absent: false,
|
|
198
|
-
notice: `${claim.stateFile} could not be read as valid JSON: ${describeError(error)}`
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
if (!state || typeof state !== "object" || Array.isArray(state)) return {
|
|
202
|
-
absent: false,
|
|
203
|
-
notice: `${claim.stateFile} does not contain a valid workspace state object.`
|
|
204
|
-
};
|
|
205
|
-
const remote = state.remoteDevice;
|
|
206
|
-
if (remote === void 0) return {
|
|
207
|
-
absent: true,
|
|
208
|
-
notice: null
|
|
209
|
-
};
|
|
210
|
-
if (!remote || typeof remote !== "object" || Array.isArray(remote)) return {
|
|
211
|
-
absent: false,
|
|
212
|
-
notice: `${claim.stateFile} has a malformed remote session record.`
|
|
213
|
-
};
|
|
214
|
-
const sessionId = remote.sessionId;
|
|
215
|
-
if (typeof sessionId !== "string" || !sessionId) return {
|
|
216
|
-
absent: false,
|
|
217
|
-
notice: `${claim.stateFile} has a remote session record with no valid session id.`
|
|
218
|
-
};
|
|
219
|
-
return {
|
|
220
|
-
absent: sessionId !== claim.sessionId,
|
|
221
|
-
notice: null
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function collectEasSessionSweep(config, deps) {
|
|
225
|
-
const projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
226
|
-
const empty = (notices = []) => ({
|
|
227
|
-
projectScope: projectRoot,
|
|
228
|
-
orphaned: [],
|
|
229
|
-
notices,
|
|
230
|
-
deletionSafe: notices.length === 0
|
|
231
|
-
});
|
|
232
|
-
if (!projectRoot || !(deps.detectIsExpo ?? detectIsExpo)(projectRoot)) return empty();
|
|
233
|
-
if (deps.easSweepBlockedNotice) return empty([deps.easSweepBlockedNotice]);
|
|
234
|
-
const eas = (deps.resolveEasCliBin ?? resolveEasCliBin)(projectRoot);
|
|
235
|
-
if (!eas) return empty(["EAS session sweep skipped: eas-cli is not available for the current Expo project."]);
|
|
236
|
-
const recorded = readRecordedRemoteSessionIds([...Object.keys(config?.projects ?? {}), projectRoot]);
|
|
237
|
-
const ledgerRoot = deps.easLedgerRoot ?? easMachineStateRoot$1();
|
|
238
|
-
const ledger = readEasSessionLedger$1(ledgerRoot);
|
|
239
|
-
const run = deps.runEasFile ?? ((file, args, options) => getExecutor().runFile(file, args, options));
|
|
240
|
-
const sessions = [];
|
|
241
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
242
|
-
const now = deps.easNow ?? Date.now;
|
|
243
|
-
const collectionTimeoutMs = deps.easCollectionTimeoutMs ?? EAS_COLLECTION_TIMEOUT_MS;
|
|
244
|
-
const maxPages = deps.easMaxPages ?? EAS_MAX_LIST_PAGES;
|
|
245
|
-
const deadline = now() + collectionTimeoutMs;
|
|
246
|
-
let pageCount = 0;
|
|
247
|
-
let after = null;
|
|
248
|
-
while (true) {
|
|
249
|
-
if (pageCount >= maxPages) return empty([...recorded.notices, `EAS session list exceeded the maximum page limit of ${maxPages} for ${projectRoot}.`]);
|
|
250
|
-
const remainingMs = deadline - now();
|
|
251
|
-
if (remainingMs <= 0) return empty([...recorded.notices, `EAS session list exceeded its total time limit for ${projectRoot}.`]);
|
|
252
|
-
let stdout;
|
|
253
|
-
try {
|
|
254
|
-
stdout = run(eas.file, listOwnedSessionsArgs(after), {
|
|
255
|
-
cwd: projectRoot,
|
|
256
|
-
timeoutMs: Math.min(EAS_OPERATION_TIMEOUT_MS, remainingMs),
|
|
257
|
-
omitEnv: PROXY_CREDENTIAL_ENV
|
|
258
|
-
});
|
|
259
|
-
} catch (error) {
|
|
260
|
-
return empty([...recorded.notices, `EAS session list failed for ${projectRoot}: ${describeError(error)}`]);
|
|
261
|
-
}
|
|
262
|
-
pageCount++;
|
|
263
|
-
if (now() > deadline) return empty([...recorded.notices, `EAS session list exceeded its total time limit for ${projectRoot}.`]);
|
|
264
|
-
const parsed = parseSessionListPage(stdout);
|
|
265
|
-
if (!parsed.ok) return empty([...recorded.notices, parsed.reason]);
|
|
266
|
-
sessions.push(...parsed.page.sessions);
|
|
267
|
-
if (!parsed.page.hasNextPage) break;
|
|
268
|
-
const cursor = parsed.page.endCursor?.trim();
|
|
269
|
-
if (!cursor) return empty([...recorded.notices, "EAS session list has a next page but returned no pagination cursor."]);
|
|
270
|
-
if (seenCursors.has(cursor)) return empty([...recorded.notices, `EAS session list repeated pagination cursor ${cursor}.`]);
|
|
271
|
-
seenCursors.add(cursor);
|
|
272
|
-
after = cursor;
|
|
273
|
-
}
|
|
274
|
-
const lockedRoots = deps.easLockedRoots;
|
|
275
|
-
if (lockedRoots) {
|
|
276
|
-
if (easLockRoots(projectRoot, loadConfig$1()).some((root) => !lockedRoots.includes(root))) return empty([...recorded.notices, "EAS session sweep skipped: registered workspace roots changed after remote-session locks were acquired."]);
|
|
277
|
-
}
|
|
278
|
-
const compared = findOrphanedOwnedSessions({
|
|
279
|
-
sessions,
|
|
280
|
-
recordedSessionIds: recorded.ids,
|
|
281
|
-
projectScope: projectRoot
|
|
282
|
-
});
|
|
283
|
-
const notices = [...recorded.notices, ...compared.notices];
|
|
284
|
-
if (ledger.notice) notices.push(ledger.notice);
|
|
285
|
-
const orphaned = [];
|
|
286
|
-
if (ledger.safe && recorded.safe) for (const session of compared.orphaned) {
|
|
287
|
-
const claim = ledger.claims.get(session.id);
|
|
288
|
-
if (!claim || claim.name !== session.name || claim.platform !== session.platform) {
|
|
289
|
-
notices.push(`EAS session ${session.id} (${session.name}) is unclaimed or its fixed ownership record does not match; it was not selected for deletion.`);
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
const state = claimStateAbsence(claim);
|
|
293
|
-
if (state.notice) {
|
|
294
|
-
notices.push(state.notice);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
if (state.absent) orphaned.push(session);
|
|
298
|
-
}
|
|
299
|
-
return {
|
|
300
|
-
projectScope: projectRoot,
|
|
301
|
-
orphaned,
|
|
302
|
-
notices,
|
|
303
|
-
deletionSafe: recorded.safe && ledger.safe
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
function easLockRoots(projectRoot, config) {
|
|
307
|
-
return [...new Set([...Object.keys(config?.projects ?? {}), projectRoot].map(canonicalPath))].toSorted();
|
|
308
|
-
}
|
|
309
|
-
function unavailableLockRootNotice(roots) {
|
|
310
|
-
for (const root of roots) try {
|
|
311
|
-
if (!statSync$1(root).isDirectory()) throw new Error("path is not a directory");
|
|
312
|
-
readdirSync$1(root);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
return `EAS session sweep skipped: ${root} is not available for remote-session lock inspection: ${describeError(error)}`;
|
|
315
|
-
}
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
function malformedRemoteSessionLockNotice(roots) {
|
|
319
|
-
for (const root of roots) {
|
|
320
|
-
const lockPath = join$1(workspaceDir(root), "remote-session.lock");
|
|
321
|
-
if (!existsSync$1(lockPath)) continue;
|
|
322
|
-
if ((0, ownership_claim_exports.readClaimSet)(lockPath).unresolved.length === 0) continue;
|
|
323
|
-
return `EAS session sweep skipped: the remote-session lock at ${lockPath} has a malformed owner record.`;
|
|
324
|
-
}
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
async function withRemoteSessionGcLocks(projectRoot, deps, fn) {
|
|
328
|
-
const withLock = deps.withRemoteSessionLock ?? withRemoteSessionLock;
|
|
329
|
-
const waitMs = deps.easLockWaitMs ?? EAS_LOCK_WAIT_MS;
|
|
330
|
-
const maxSnapshots = deps.easLockSnapshotRetries ?? EAS_LOCK_SNAPSHOT_RETRIES;
|
|
331
|
-
for (let attempt = 0; attempt < maxSnapshots; attempt++) {
|
|
332
|
-
const roots = easLockRoots(projectRoot, loadConfig$1());
|
|
333
|
-
const unavailable = unavailableLockRootNotice(roots);
|
|
334
|
-
if (unavailable) return fn({
|
|
335
|
-
...deps,
|
|
336
|
-
easSweepBlockedNotice: unavailable
|
|
337
|
-
});
|
|
338
|
-
const malformed = malformedRemoteSessionLockNotice(roots);
|
|
339
|
-
if (malformed) return fn({
|
|
340
|
-
...deps,
|
|
341
|
-
easSweepBlockedNotice: malformed
|
|
342
|
-
});
|
|
343
|
-
let expanded = false;
|
|
344
|
-
let coreStarted = false;
|
|
345
|
-
const acquire = (index) => index === roots.length ? (async () => {
|
|
346
|
-
if (easLockRoots(projectRoot, loadConfig$1()).some((root) => !roots.includes(root))) {
|
|
347
|
-
expanded = true;
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
coreStarted = true;
|
|
351
|
-
return fn({
|
|
352
|
-
...deps,
|
|
353
|
-
easSweepBlockedNotice: void 0,
|
|
354
|
-
easLockedRoots: roots
|
|
355
|
-
});
|
|
356
|
-
})() : withLock(roots[index], () => acquire(index + 1), { waitMs });
|
|
357
|
-
try {
|
|
358
|
-
const result = await acquire(0);
|
|
359
|
-
if (!expanded) return result;
|
|
360
|
-
} catch (error) {
|
|
361
|
-
if (coreStarted) throw error;
|
|
362
|
-
return fn({
|
|
363
|
-
...deps,
|
|
364
|
-
easSweepBlockedNotice: `EAS session sweep skipped: remote-session lock acquisition failed: ${describeError(error)}`
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return fn({
|
|
369
|
-
...deps,
|
|
370
|
-
easSweepBlockedNotice: `EAS session sweep skipped: registered workspace roots kept changing during remote-session lock acquisition.`
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
async function deleteEasSessions(easSessionSweep, deps) {
|
|
374
|
-
let deleteFailures = 0;
|
|
375
|
-
const refuseAll = (detail) => {
|
|
376
|
-
deleteFailures += easSessionSweep.orphaned.length;
|
|
377
|
-
for (const session of easSessionSweep.orphaned) recordGcResult("easSession", "failed", session.name, {
|
|
378
|
-
id: session.id,
|
|
379
|
-
detail
|
|
380
|
-
});
|
|
381
|
-
};
|
|
382
|
-
if (easSessionSweep.orphaned.length && easSessionSweep.deletionSafe && easSessionSweep.projectScope) {
|
|
383
|
-
const projectScope = easSessionSweep.projectScope;
|
|
384
|
-
const withProjectLock = deps.withEasProjectLock ?? withEasProjectLock;
|
|
385
|
-
let finalSweepStarted = false;
|
|
386
|
-
try {
|
|
387
|
-
await withProjectLock(projectScope, () => {
|
|
388
|
-
finalSweepStarted = true;
|
|
389
|
-
return withRemoteSessionGcLocks(projectScope, deps, async (lockedDeps) => {
|
|
390
|
-
if (lockedDeps.easSweepBlockedNotice) {
|
|
391
|
-
refuseAll(`EAS session deletion refused: ${lockedDeps.easSweepBlockedNotice}`);
|
|
392
|
-
console.log(chalk.red(`EAS session deletion refused: ${lockedDeps.easSweepBlockedNotice}`));
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const coordinatedDeps = lockedDeps;
|
|
396
|
-
const currentConfig = loadConfig$1();
|
|
397
|
-
const currentRoots = easLockRoots(projectScope, currentConfig);
|
|
398
|
-
const registryExpanded = coordinatedDeps.easLockedRoots && currentRoots.some((root) => !coordinatedDeps.easLockedRoots.includes(root));
|
|
399
|
-
const currentRecords = registryExpanded ? null : readRecordedRemoteSessionIds([...Object.keys(currentConfig?.projects ?? {}), projectScope]);
|
|
400
|
-
const ledgerRoot = coordinatedDeps.easLedgerRoot ?? easMachineStateRoot$1();
|
|
401
|
-
const ledger = readEasSessionLedger$1(ledgerRoot);
|
|
402
|
-
const removeClaim = coordinatedDeps.removeEasSessionClaim ?? removeEasSessionClaim;
|
|
403
|
-
const reconcileClaim = (sessionId) => {
|
|
404
|
-
let detail = "the claim store did not remove it";
|
|
405
|
-
try {
|
|
406
|
-
if (removeClaim(sessionId, ledgerRoot)) return;
|
|
407
|
-
} catch (error) {
|
|
408
|
-
detail = describeError(error);
|
|
409
|
-
}
|
|
410
|
-
deleteFailures++;
|
|
411
|
-
console.log(chalk.red(`EAS session ${sessionId} is resolved, but its ownership claim could not be removed: ${detail}. The claim was retained for reconciliation.`));
|
|
412
|
-
};
|
|
413
|
-
if (registryExpanded) {
|
|
414
|
-
refuseAll("EAS session deletion refused: registered workspace roots changed after classification.");
|
|
415
|
-
console.log(chalk.red("EAS session deletion refused: registered workspace roots changed after classification."));
|
|
416
|
-
} else if (!currentRecords?.safe || !ledger.safe) {
|
|
417
|
-
refuseAll("EAS session deletion refused: the session records could not be read safely.");
|
|
418
|
-
for (const notice of [...currentRecords?.notices ?? [], ...ledger.notice ? [ledger.notice] : []]) console.log(chalk.red(`EAS session deletion refused: ${notice}`));
|
|
419
|
-
} else {
|
|
420
|
-
const recorded = new Set(currentRecords.ids);
|
|
421
|
-
const eas = (coordinatedDeps.resolveEasCliBin ?? resolveEasCliBin)(projectScope);
|
|
422
|
-
const run = coordinatedDeps.runEasFile ?? ((file, args, options) => getExecutor().runFile(file, args, options));
|
|
423
|
-
for (const session of easSessionSweep.orphaned) {
|
|
424
|
-
if (recorded.has(session.id)) {
|
|
425
|
-
console.log(chalk.dim(`EAS session ${session.id} has a workspace record and was left running.`));
|
|
426
|
-
recordGcResult("easSession", "kept", session.name, {
|
|
427
|
-
id: session.id,
|
|
428
|
-
detail: "it has a workspace record and was left running"
|
|
429
|
-
});
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
const claim = ledger.claims.get(session.id);
|
|
433
|
-
const claimState = claim ? claimStateAbsence(claim) : null;
|
|
434
|
-
if (!claim || claim.name !== session.name || claim.platform !== session.platform || !claimState?.absent || claimState.notice) {
|
|
435
|
-
deleteFailures++;
|
|
436
|
-
console.log(chalk.red(`Could not verify fixed ownership for EAS session ${session.id}: ${claimState?.notice ?? "the claim is missing, mismatched, or still recorded in workspace state"}.`));
|
|
437
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
438
|
-
id: session.id,
|
|
439
|
-
detail: `Could not verify fixed ownership for EAS session ${session.id}: ${claimState?.notice ?? "the claim is missing, mismatched, or still recorded in workspace state"}.`
|
|
440
|
-
});
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (!eas) {
|
|
444
|
-
deleteFailures++;
|
|
445
|
-
console.log(chalk.red(`Could not verify EAS session ${session.id}: eas-cli is not available.`));
|
|
446
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
447
|
-
id: session.id,
|
|
448
|
-
detail: `Could not verify EAS session ${session.id}: eas-cli is not available.`
|
|
449
|
-
});
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
const options = {
|
|
453
|
-
cwd: session.projectScope,
|
|
454
|
-
timeoutMs: EAS_OPERATION_TIMEOUT_MS,
|
|
455
|
-
omitEnv: PROXY_CREDENTIAL_ENV
|
|
456
|
-
};
|
|
457
|
-
let live;
|
|
458
|
-
try {
|
|
459
|
-
live = run(eas.file, getSessionArgs(session.id), options);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
if (isDefinitiveMissingSessionError(error, session.id)) {
|
|
462
|
-
console.log(chalk.dim(`EAS session ${session.id} is already gone.`));
|
|
463
|
-
reconcileClaim(session.id);
|
|
464
|
-
} else {
|
|
465
|
-
deleteFailures++;
|
|
466
|
-
console.log(chalk.red(`Could not verify EAS session ${session.id}: ${describeError(error)}`));
|
|
467
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
468
|
-
id: session.id,
|
|
469
|
-
detail: `Could not verify EAS session ${session.id}: ${describeError(error)}`
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
const inspection = inspectSessionForTeardown(live, session.id);
|
|
475
|
-
if (inspection.action === "already-stopped") {
|
|
476
|
-
console.log(chalk.dim(`EAS session ${session.id} is already stopped (${inspection.status}).`));
|
|
477
|
-
reconcileClaim(session.id);
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
if (inspection.action === "refused") {
|
|
481
|
-
deleteFailures++;
|
|
482
|
-
console.log(chalk.red(`Could not stop EAS session ${session.id}: ${inspection.reason}`));
|
|
483
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
484
|
-
id: session.id,
|
|
485
|
-
detail: `Could not stop EAS session ${session.id}: ${inspection.reason}`
|
|
486
|
-
});
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
let stopped;
|
|
490
|
-
try {
|
|
491
|
-
stopped = run(eas.file, stopSessionArgs(session.id), options);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
deleteFailures++;
|
|
494
|
-
console.log(chalk.red(`Could not stop EAS session ${session.id}: ${describeError(error)}`));
|
|
495
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
496
|
-
id: session.id,
|
|
497
|
-
detail: `Could not stop EAS session ${session.id}: ${describeError(error)}`
|
|
498
|
-
});
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
const verified = verifyStoppedSession(stopped, session.id);
|
|
502
|
-
if (!verified.ok) {
|
|
503
|
-
deleteFailures++;
|
|
504
|
-
console.log(chalk.red(`Could not verify EAS session ${session.id} stopped: ${verified.reason}`));
|
|
505
|
-
recordGcResult("easSession", "failed", session.name, {
|
|
506
|
-
id: session.id,
|
|
507
|
-
detail: `Could not verify EAS session ${session.id} stopped: ${verified.reason}`
|
|
508
|
-
});
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
console.log(chalk.green(`Stopped EAS session ${session.id} (${session.name})`));
|
|
512
|
-
recordGcResult("easSession", "done", session.name, { id: session.id });
|
|
513
|
-
reconcileClaim(session.id);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
}, {
|
|
518
|
-
waitMs: 0,
|
|
519
|
-
ownerPurpose: "EAS orphan deletion",
|
|
520
|
-
machineRoot: deps.easLedgerRoot
|
|
521
|
-
});
|
|
522
|
-
} catch (error) {
|
|
523
|
-
const phase = finalSweepStarted ? "failed" : "lock acquisition failed";
|
|
524
|
-
refuseAll(`EAS session deletion ${phase}: ${describeError(error)}`);
|
|
525
|
-
console.log(chalk.red(`EAS session deletion ${phase}: ${describeError(error)}`));
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
return deleteFailures;
|
|
529
|
-
}
|
|
530
|
-
//#endregion
|
|
531
110
|
//#region src/commands/gc/eas-ledger.ts
|
|
532
111
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
533
112
|
/**
|
|
@@ -562,6 +141,71 @@ function forgetStaleEasLedgerEntries(entries, mountedVolumes, deps = {}) {
|
|
|
562
141
|
return failures;
|
|
563
142
|
}
|
|
564
143
|
//#endregion
|
|
144
|
+
//#region src/devices/machine-cache-usage-output.ts
|
|
145
|
+
const size = (bytes) => bytes === null ? "unknown" : formatBytes(bytes);
|
|
146
|
+
function total(values) {
|
|
147
|
+
const known = values.reduce((sum, value) => sum + (value ?? 0), 0);
|
|
148
|
+
return `${values.some((value) => value === null) ? "at least " : ""}${formatBytes(known)}`;
|
|
149
|
+
}
|
|
150
|
+
function machineCacheLines(report, now = Date.now()) {
|
|
151
|
+
if (!report) return [];
|
|
152
|
+
const { gradle, simRuntimes, deviceSupport, runnerBuilds } = report;
|
|
153
|
+
const body = [];
|
|
154
|
+
if (gradle.unusedVersions.length) {
|
|
155
|
+
body.push(` Gradle versions no registered project pins (${total(gradle.unusedVersions.map((entry) => entry.bytes))}):`);
|
|
156
|
+
for (const entry of gradle.unusedVersions) {
|
|
157
|
+
body.push(` ${entry.version}: ${size(entry.bytes)}`);
|
|
158
|
+
for (const dir of entry.dirs) body.push(` ${dir.dir}`);
|
|
159
|
+
if (entry.remedy) body.push(chalk.dim(` remedy: ${entry.remedy}`));
|
|
160
|
+
}
|
|
161
|
+
if (gradle.unreadableProjects.length) body.push(chalk.dim(` ${gradle.unreadableProjects.length} registered project${gradle.unreadableProjects.length === 1 ? " is" : "s are"} missing or unreadable, so a version listed here may still be in use: ${gradle.unreadableProjects.join(", ")}`));
|
|
162
|
+
}
|
|
163
|
+
if (gradle.modules) {
|
|
164
|
+
body.push(` Gradle dependency cache: ${size(gradle.modules.bytes)}`, ` ${gradle.modules.dir}`);
|
|
165
|
+
body.push(chalk.dim(` remedy: ${gradle.modules.remedy}`));
|
|
166
|
+
}
|
|
167
|
+
if (!simRuntimes.checked) body.push(chalk.dim(" Simulator runtimes: not checked, simctl did not answer"));
|
|
168
|
+
else if (simRuntimes.runtimes.length) {
|
|
169
|
+
body.push(` Simulator runtimes (${total(simRuntimes.runtimes.map((entry) => entry.bytes))}):`);
|
|
170
|
+
for (const runtime of simRuntimes.runtimes) {
|
|
171
|
+
const details = [
|
|
172
|
+
runtime.devices ? `${runtime.devices} simulator${runtime.devices === 1 ? "" : "s"}` : "no simulator uses it",
|
|
173
|
+
runtime.lastUsedAt ? `last used ${runtime.lastUsedAt.slice(0, 10)}` : null,
|
|
174
|
+
runtime.deletable ? null : "not deletable"
|
|
175
|
+
].filter(Boolean);
|
|
176
|
+
body.push(` ${runtime.runtimeIdentifier.replace(/^com\.apple\.CoreSimulator\.SimRuntime\./, "")}${runtime.build ? ` (${runtime.build})` : ""}: ${size(runtime.bytes)}, ${details.join(", ")}`);
|
|
177
|
+
if (runtime.remedy) body.push(chalk.dim(` remedy: ${runtime.remedy}`));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (deviceSupport.entries.length) {
|
|
181
|
+
body.push(` Xcode device support (${total(deviceSupport.entries.map((entry) => entry.bytes))}):`);
|
|
182
|
+
for (const entry of deviceSupport.entries) body.push(` ${entry.dir}: ${size(entry.bytes)}`);
|
|
183
|
+
body.push(chalk.dim(` remedy: ${deviceSupport.remedy}`));
|
|
184
|
+
}
|
|
185
|
+
if (runnerBuilds.groups.length) {
|
|
186
|
+
const currentVersions = runnerBuilds.agentDeviceVersion && runnerBuilds.xcodeBuildVersion ? `current: agent-device ${runnerBuilds.agentDeviceVersion}, Xcode ${runnerBuilds.xcodeBuildVersion}` : "current versions unknown";
|
|
187
|
+
body.push(` agent-device runner builds by version (${currentVersions}):`);
|
|
188
|
+
for (const group of runnerBuilds.groups) {
|
|
189
|
+
const details = [
|
|
190
|
+
`${group.entries} entr${group.entries === 1 ? "y" : "ies"}`,
|
|
191
|
+
size(group.bytes),
|
|
192
|
+
group.oldestUseAt ? `oldest use ${group.oldestUseAt.slice(0, 10)}` : null,
|
|
193
|
+
group.inUse ? `${group.inUse} in use` : null,
|
|
194
|
+
group.current ? "current" : group.current === false ? "older toolchain" : null
|
|
195
|
+
].filter(Boolean);
|
|
196
|
+
body.push(` agent-device ${group.packageVersion ?? "unknown"}, Xcode ${group.xcodeBuildVersion ?? "unknown"}: ${details.join(", ")}`);
|
|
197
|
+
}
|
|
198
|
+
body.push(chalk.dim(` remedy: ${runnerBuilds.remedy}`));
|
|
199
|
+
}
|
|
200
|
+
for (const notice of report.notices) body.push(chalk.dim(` ${notice}`));
|
|
201
|
+
if (!body.length) return [];
|
|
202
|
+
return [
|
|
203
|
+
`Other tools' machine-wide caches${report.complete ? "" : " (incomplete)"} - measured ${formatLongDuration(Math.max(0, now - Date.parse(report.measuredAt)))} ago`,
|
|
204
|
+
...body,
|
|
205
|
+
chalk.dim(" Stim reports these and never deletes them; use each tool's own cleanup.")
|
|
206
|
+
];
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
565
209
|
//#region src/commands/gc/status-caches.ts
|
|
566
210
|
const KINDS = ["disk-usage", "pull-request"];
|
|
567
211
|
const KEPT_REASON = {
|
|
@@ -657,6 +301,81 @@ function deleteStaleStatusCaches(stale) {
|
|
|
657
301
|
return failures;
|
|
658
302
|
}
|
|
659
303
|
//#endregion
|
|
304
|
+
//#region src/commands/gc/finished-devices.ts
|
|
305
|
+
/** The owned devices of workspaces whose linked worktree is finished but kept, such as a dirty one. */
|
|
306
|
+
function collectFinishedDevices({ config, sims, avds, sweep, now = Date.now() }) {
|
|
307
|
+
const keys = Object.keys(config?.projects ?? {});
|
|
308
|
+
const devices = [];
|
|
309
|
+
const seen = /* @__PURE__ */ new Set();
|
|
310
|
+
for (const { root, reason } of finishedWorkspaces(sweep, now)) {
|
|
311
|
+
const key = keys.find((candidate) => canonicalPath(candidate) === root);
|
|
312
|
+
const project = key ? config?.projects[key] : void 0;
|
|
313
|
+
if (!config || !key || !project || seen.has(key)) continue;
|
|
314
|
+
seen.add(key);
|
|
315
|
+
for (const { idleDays: _idleDays, ...device } of findStaleProjectDevices({
|
|
316
|
+
config: {
|
|
317
|
+
...config,
|
|
318
|
+
projects: { [key]: project }
|
|
319
|
+
},
|
|
320
|
+
sims,
|
|
321
|
+
avds,
|
|
322
|
+
olderThanDays: 0,
|
|
323
|
+
lastTouched: () => 0
|
|
324
|
+
})) devices.push({
|
|
325
|
+
...device,
|
|
326
|
+
reason
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return devices;
|
|
330
|
+
}
|
|
331
|
+
function finishedDeviceLines(devices = []) {
|
|
332
|
+
if (!devices.length) return [];
|
|
333
|
+
const lines = [`Owned devices of finished worktrees (${devices.length}) - the worktree is kept, its devices are not:`];
|
|
334
|
+
for (const device of devices) {
|
|
335
|
+
lines.push(` ${device.kind} ${device.name} (${device.id})${device.bytes ? ` ${formatBytes(device.bytes)}` : ""}`);
|
|
336
|
+
lines.push(` ${device.project}: ${device.reason}`);
|
|
337
|
+
}
|
|
338
|
+
lines.push(" --delete keeps a workspace that is in use, leased, driven, locked or pinned.");
|
|
339
|
+
return lines;
|
|
340
|
+
}
|
|
341
|
+
async function deleteFinishedDevices({ finishedDevices: devices, worktreeSweep }) {
|
|
342
|
+
const graceMs = worktreeSweep?.graceMs ?? 0;
|
|
343
|
+
let failures = 0;
|
|
344
|
+
for (const project of new Set(devices.map((device) => device.project))) {
|
|
345
|
+
const outcome = await deleteIdleWorkspaceDevices(canonicalPath(project), {
|
|
346
|
+
windowMs: graceMs,
|
|
347
|
+
protectedRoot: triggeringRoot(),
|
|
348
|
+
purpose: "stim gc --delete"
|
|
349
|
+
});
|
|
350
|
+
if (outcome.status === "kept") {
|
|
351
|
+
console.log(chalk.yellow(`Kept the owned devices of ${project}: ${outcome.detail}`));
|
|
352
|
+
recordGcResult("device", "kept", project, { detail: outcome.detail });
|
|
353
|
+
} else if (outcome.status === "failed") {
|
|
354
|
+
failures++;
|
|
355
|
+
console.log(chalk.red(`Could not delete the owned devices of ${project}: ${outcome.detail}`));
|
|
356
|
+
recordGcResult("device", "failed", project, { detail: outcome.detail });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return failures;
|
|
360
|
+
}
|
|
361
|
+
/** Owned simulator and emulator disk of registered workspaces, by platform. */
|
|
362
|
+
function ownedDeviceDisk(usage, unmeasured = {}) {
|
|
363
|
+
return ["ios", "android"].flatMap((kind) => {
|
|
364
|
+
const owned = usage.filter((device) => device.kind === kind && device.workspace);
|
|
365
|
+
const missing = unmeasured[kind] ?? 0;
|
|
366
|
+
return owned.length || missing ? [{
|
|
367
|
+
kind,
|
|
368
|
+
count: owned.length,
|
|
369
|
+
bytes: owned.reduce((sum, device) => sum + device.bytes, 0),
|
|
370
|
+
unmeasured: missing
|
|
371
|
+
}] : [];
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function ownedDeviceDiskLines(disk = []) {
|
|
375
|
+
if (!disk.length) return [];
|
|
376
|
+
return [`Owned device disk: ${disk.map(({ kind, count, bytes, unmeasured }) => `${count} ${kind === "ios" ? "simulators" : "emulators"} ${formatBytes(bytes)}${unmeasured ? ` (${unmeasured} not measured)` : ""}`).join(", ")}`];
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
660
379
|
//#region src/commands/gc/recordings.ts
|
|
661
380
|
const DAY_MS = 864e5;
|
|
662
381
|
function oldSegments(devices, olderThan, now) {
|
|
@@ -850,7 +569,7 @@ function staleEasLedgerLines(entries = []) {
|
|
|
850
569
|
" --delete forgets the LEDGER ENTRY only; no EAS session is contacted."
|
|
851
570
|
];
|
|
852
571
|
}
|
|
853
|
-
function formatGcReport({ maintenance, archives, skipped = [], deadProjects = [], orphanedPorts, invalidProjects = [], orphanedWorkspaces = [], parkedSims = [], parkedAvds = [], parkedHostedDevices, orphanedDevices = [], unverifiedDevices = [], staleDevices = [], staleDeviceRecords = [], staleLedgerEntries = [], staleEasLedgerEntries, staleStatusCaches = [], buildLocks = {
|
|
572
|
+
function formatGcReport({ maintenance, archives, skipped = [], deadProjects = [], orphanedPorts, invalidProjects = [], orphanedWorkspaces = [], parkedSims = [], parkedAvds = [], parkedHostedDevices, orphanedDevices = [], unverifiedDevices = [], staleDevices = [], finishedDevices, ownedDeviceDisk, staleDeviceRecords = [], staleLedgerEntries = [], staleEasLedgerEntries, staleStatusCaches = [], buildLocks = {
|
|
854
573
|
stale: [],
|
|
855
574
|
live: [],
|
|
856
575
|
unresolved: []
|
|
@@ -866,10 +585,11 @@ function formatGcReport({ maintenance, archives, skipped = [], deadProjects = []
|
|
|
866
585
|
orphaned: [],
|
|
867
586
|
notices: [],
|
|
868
587
|
deletionSafe: true
|
|
869
|
-
}, agentDeviceUsage = null, swiftpmCacheUsage, caches = [], workspaceOutputs = null, workspaceLogs = [], recordings = [], worktreeSweep = null, memory, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
588
|
+
}, agentDeviceUsage = null, swiftpmCacheUsage, machineCaches, tempLeftovers, gradleVersions, caches = [], workspaceOutputs = null, workspaceLogs = [], recordings = [], worktreeSweep = null, memory, cacheScope = null, olderThan = null }, { now = Date.now() } = {}) {
|
|
870
589
|
const lines = archiveLines(archives);
|
|
871
590
|
const parkedHostedLines = formatParkedHostedDeviceReport(parkedHostedDevices, now);
|
|
872
591
|
const easLedgerLines = staleEasLedgerLines(staleEasLedgerEntries);
|
|
592
|
+
const finishedLines = finishedDeviceLines(finishedDevices);
|
|
873
593
|
const staleLocks = buildLocks?.stale ?? [];
|
|
874
594
|
const liveLocks = buildLocks?.live ?? [];
|
|
875
595
|
const staleSlots = buildSlots?.stale ?? [];
|
|
@@ -884,6 +604,7 @@ function formatGcReport({ maintenance, archives, skipped = [], deadProjects = []
|
|
|
884
604
|
parkedHostedLines,
|
|
885
605
|
orphanedDevices,
|
|
886
606
|
staleDevices,
|
|
607
|
+
finishedLines,
|
|
887
608
|
staleDeviceRecords,
|
|
888
609
|
staleLedgerEntries,
|
|
889
610
|
easLedgerLines,
|
|
@@ -908,6 +629,7 @@ function formatGcReport({ maintenance, archives, skipped = [], deadProjects = []
|
|
|
908
629
|
lines.push(...namedPortLines(orphanedPorts));
|
|
909
630
|
lines.push(...orphanedWorkspaceLines(orphanedWorkspaces));
|
|
910
631
|
lines.push(...worktreeSweepLines(worktreeSweep));
|
|
632
|
+
lines.push(...unlistedWorktreeLines(worktreeSweep));
|
|
911
633
|
lines.push(...formatParkedSimReport(parkedSims, now, cacheScope !== null));
|
|
912
634
|
lines.push(...formatParkedAvdReport(parkedAvds, now, cacheScope !== null));
|
|
913
635
|
lines.push(...parkedHostedLines);
|
|
@@ -930,6 +652,7 @@ function formatGcReport({ maintenance, archives, skipped = [], deadProjects = []
|
|
|
930
652
|
lines.push(` ${d.project} (idle ${d.idleDays}d)`);
|
|
931
653
|
}
|
|
932
654
|
}
|
|
655
|
+
lines.push(...finishedLines, ...ownedDeviceDiskLines(ownedDeviceDisk));
|
|
933
656
|
lines.push(...idleDeviceLines(idleDevices));
|
|
934
657
|
if (staleDeviceRecords.length) {
|
|
935
658
|
lines.push(`Stale device records (${staleDeviceRecords.length}) - the device is gone, the project is not:`);
|
|
@@ -995,6 +718,9 @@ function formatGcReport({ maintenance, archives, skipped = [], deadProjects = []
|
|
|
995
718
|
lines.push(...cacheLines(caches, workspaceOutputs));
|
|
996
719
|
lines.push(...agentDeviceLines(agentDeviceUsage, true, now));
|
|
997
720
|
lines.push(...swiftpmCacheLines(swiftpmCacheUsage, now));
|
|
721
|
+
lines.push(...machineCacheLines(machineCaches, now));
|
|
722
|
+
lines.push(...tempLeftoverLines(tempLeftovers));
|
|
723
|
+
lines.push(...gradleVersionLines(gradleVersions));
|
|
998
724
|
lines.push(...memoryLines(memory, memoryCacheKind(cacheScope), now));
|
|
999
725
|
lines.push(...maintenanceReportLines(maintenance));
|
|
1000
726
|
return lines;
|
|
@@ -1072,18 +798,20 @@ function cacheLines(caches, workspaceOutputs) {
|
|
|
1072
798
|
if (outputs) lines.push(...workspaceOutputLines(outputs, outputBytes));
|
|
1073
799
|
for (const c of caches) {
|
|
1074
800
|
const tag = c.source ? ` (${c.source})` : "";
|
|
1075
|
-
lines.push(` ${
|
|
801
|
+
lines.push(` ${sizeText(c.bytes ?? null).padStart(10)} ${c.name}${tag}`);
|
|
1076
802
|
lines.push(` ${c.dir}`);
|
|
1077
803
|
if (c.note) lines.push(` ${c.note}`);
|
|
804
|
+
if (c.sizeError) lines.push(` ${c.sizeError}`);
|
|
1078
805
|
if (c.willEmpty) lines.push(" would be EMPTIED");
|
|
1079
806
|
else if (c.emptySkipped) lines.push(` would be left alone: ${c.emptySkipped}`);
|
|
1080
807
|
else if (c.scopedEmpty?.emptySkipped) lines.push(` --delete --cache would leave it alone: ${c.scopedEmpty.emptySkipped}`);
|
|
1081
808
|
}
|
|
1082
|
-
|
|
809
|
+
const incomplete = caches.some((c) => c.bytes === void 0) || outputs?.workspaces.some((w) => w.bytes === null);
|
|
810
|
+
lines.push(` total: ${incomplete ? "at least " : ""}${formatBytes(total)}`);
|
|
1083
811
|
const doomed = caches.filter((c) => c.willEmpty);
|
|
1084
812
|
if (doomed.length) {
|
|
1085
813
|
const doomedBytes = doomed.reduce((n, c) => n + (c.bytes ?? 0), 0);
|
|
1086
|
-
lines.push(` would empty ${doomed.length} of these (${formatBytes(doomedBytes)})`);
|
|
814
|
+
lines.push(` would empty ${doomed.length} of these (${doomed.some((c) => c.bytes === void 0) ? "at least " : ""}${formatBytes(doomedBytes)})`);
|
|
1087
815
|
}
|
|
1088
816
|
const clearing = outputs?.workspaces.filter((w) => w.willClear) ?? [];
|
|
1089
817
|
if (clearing.length) {
|
|
@@ -1093,6 +821,29 @@ function cacheLines(caches, workspaceOutputs) {
|
|
|
1093
821
|
}
|
|
1094
822
|
return lines;
|
|
1095
823
|
}
|
|
824
|
+
const BUILD_STATE_DIRS_SHOWN = 4;
|
|
825
|
+
function buildStateLine(state) {
|
|
826
|
+
if (!state?.dirs.length) return null;
|
|
827
|
+
const shown = state.dirs.slice(0, BUILD_STATE_DIRS_SHOWN).map((dir) => `${dir.path} ${sizeText(dir.bytes)}`);
|
|
828
|
+
const more = state.dirs.length > BUILD_STATE_DIRS_SHOWN ? ", ..." : "";
|
|
829
|
+
return ` in-tree build state ${sizeText(state.bytes)}: ${shown.join(", ")}${more}`;
|
|
830
|
+
}
|
|
831
|
+
function unlistedWorktreeLines(sweep) {
|
|
832
|
+
const unlisted = sweep?.unlisted ?? [];
|
|
833
|
+
if (!unlisted.length) return [];
|
|
834
|
+
const lines = [`Directories under .worktrees that git does not list as worktrees (${unlisted.length}) - reported, never removed:`];
|
|
835
|
+
for (const dir of unlisted) {
|
|
836
|
+
lines.push(` ${dir.path} (${sizeText(dir.bytes)})`);
|
|
837
|
+
lines.push(` ${unlistedWorktreeDetail(dir)}`);
|
|
838
|
+
}
|
|
839
|
+
return lines;
|
|
840
|
+
}
|
|
841
|
+
function unlistedWorktreeDetail(dir) {
|
|
842
|
+
return `git worktree list in ${dir.repository} does not show it, so gc cannot tell whether it holds work; look through it, then remove it with \`${unlistedWorktreeRemedy(dir)}\``;
|
|
843
|
+
}
|
|
844
|
+
function unlistedWorktreeRemedy(dir) {
|
|
845
|
+
return `rm -rf '${dir.path.replaceAll("'", "'\\''")}'`;
|
|
846
|
+
}
|
|
1096
847
|
function worktreeSweepLines(sweep) {
|
|
1097
848
|
if (!sweep || !sweep.idle && !sweep.worktrees.length) return [];
|
|
1098
849
|
const removable = sweep.worktrees.filter((w) => !w.skipped);
|
|
@@ -1103,8 +854,10 @@ function worktreeSweepLines(sweep) {
|
|
|
1103
854
|
for (const w of sweep.worktrees) {
|
|
1104
855
|
const age = w.idleDays === null ? "" : ` (idle ${w.idleDays}d)`;
|
|
1105
856
|
lines.push(` ${w.path}${age}`);
|
|
857
|
+
const buildState = buildStateLine(w.buildState);
|
|
858
|
+
if (buildState) lines.push(buildState);
|
|
1106
859
|
const note = worktreePullRequestNote(w);
|
|
1107
|
-
lines.push(w.skipped ? ` kept: ${w.skipped}${note && !w.skipped.includes(note) ? ` (${note})` : ""}` : ` would be REMOVED by \`stim worktree remove\`: ${worktreeRemovalReason(w)}`);
|
|
860
|
+
lines.push(w.skipped ? ` kept: ${w.skipped}${note && !w.skipped.includes(note) ? ` (${note})` : ""}` : ` would be REMOVED by \`stim worktree remove\`: ${worktreeRemovalReason(w, sweep)}`);
|
|
1108
861
|
}
|
|
1109
862
|
if (removable.length) lines.push(" --delete runs it without --force; use, idleness and HEAD are re-checked under its removal locks.");
|
|
1110
863
|
return lines;
|
|
@@ -1127,7 +880,22 @@ function jsonPullRequest(lookup) {
|
|
|
1127
880
|
containsHead: pr.containsHead
|
|
1128
881
|
} : null;
|
|
1129
882
|
}
|
|
1130
|
-
function
|
|
883
|
+
function linkedWorktreesJson(sweep) {
|
|
884
|
+
if (!sweep) return [];
|
|
885
|
+
return sweep.worktrees.map((w) => ({
|
|
886
|
+
path: w.path,
|
|
887
|
+
idleDays: w.idleDays,
|
|
888
|
+
mergedInto: w.merge?.merged ? w.merge.into : null,
|
|
889
|
+
pullRequest: jsonPullRequest(w.pullRequest),
|
|
890
|
+
pullRequestUnknown: w.pullRequest && "unavailable" in w.pullRequest ? w.pullRequest.unavailable : null,
|
|
891
|
+
willRemove: w.skipCode === null,
|
|
892
|
+
reason: w.skipCode,
|
|
893
|
+
detail: w.skipped ?? worktreeRemovalReason(w, sweep),
|
|
894
|
+
eligibleAt: w.eligibleAt === null ? null : new Date(w.eligibleAt).toISOString(),
|
|
895
|
+
buildState: w.buildState ?? null
|
|
896
|
+
}));
|
|
897
|
+
}
|
|
898
|
+
function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedPorts = [], invalidProjects = [], orphanedWorkspaces = [], worktreeSweep = null, parkedSims = [], parkedAvds = [], parkedHostedDevices = [], orphanedDevices = [], unverifiedDevices = [], staleDevices = [], finishedDevices = [], ownedDeviceDisk = [], staleDeviceRecords = [], staleLedgerEntries = [], staleEasLedgerEntries = [], staleStatusCaches = [], buildLocks = {
|
|
1131
899
|
stale: [],
|
|
1132
900
|
live: [],
|
|
1133
901
|
unresolved: []
|
|
@@ -1143,7 +911,7 @@ function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedP
|
|
|
1143
911
|
orphaned: [],
|
|
1144
912
|
notices: [],
|
|
1145
913
|
deletionSafe: true
|
|
1146
|
-
}, workspaceOutputs = null, workspaceLogs = [], recordings = [], agentDeviceUsage = null, swiftpmCacheUsage = null, caches = [], memory = null }) {
|
|
914
|
+
}, workspaceOutputs = null, workspaceLogs = [], recordings = [], agentDeviceUsage = null, swiftpmCacheUsage = null, machineCaches = null, tempLeftovers = null, gradleVersions = null, caches = [], memory = null }) {
|
|
1147
915
|
return {
|
|
1148
916
|
...archives ? {
|
|
1149
917
|
archived: archives.records,
|
|
@@ -1161,16 +929,12 @@ function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedP
|
|
|
1161
929
|
projectRoot,
|
|
1162
930
|
bytes: bytes ?? null
|
|
1163
931
|
})),
|
|
1164
|
-
linkedWorktrees: (worktreeSweep
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
willRemove: w.skipCode === null,
|
|
1171
|
-
reason: w.skipCode,
|
|
1172
|
-
detail: w.skipped ?? worktreeRemovalReason(w),
|
|
1173
|
-
eligibleAt: w.eligibleAt === null ? null : new Date(w.eligibleAt).toISOString()
|
|
932
|
+
linkedWorktrees: linkedWorktreesJson(worktreeSweep),
|
|
933
|
+
unlistedWorktreeDirectories: (worktreeSweep?.unlisted ?? []).map((dir) => ({
|
|
934
|
+
path: dir.path,
|
|
935
|
+
repository: dir.repository,
|
|
936
|
+
bytes: dir.bytes,
|
|
937
|
+
remedy: unlistedWorktreeRemedy(dir)
|
|
1174
938
|
})),
|
|
1175
939
|
parkedSimulators: parkedSims.map(({ udid, name, model, runtime, parkedAt, app, bytes, listed }) => ({
|
|
1176
940
|
udid,
|
|
@@ -1214,6 +978,16 @@ function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedP
|
|
|
1214
978
|
idleDays: d.idleDays,
|
|
1215
979
|
bytes: d.bytes ?? null
|
|
1216
980
|
})),
|
|
981
|
+
finishedDevices: finishedDevices.map((d) => ({
|
|
982
|
+
kind: d.kind,
|
|
983
|
+
id: d.id,
|
|
984
|
+
name: d.name,
|
|
985
|
+
project: d.project,
|
|
986
|
+
slot: d.slot ?? null,
|
|
987
|
+
bytes: d.bytes ?? null,
|
|
988
|
+
reason: d.reason
|
|
989
|
+
})),
|
|
990
|
+
ownedDeviceDisk,
|
|
1217
991
|
staleDeviceRecords: staleDeviceRecords.map((r) => ({
|
|
1218
992
|
kind: r.kind,
|
|
1219
993
|
id: r.id,
|
|
@@ -1325,12 +1099,179 @@ function gcReportSections({ archives, skipped = [], deadProjects = [], orphanedP
|
|
|
1325
1099
|
})),
|
|
1326
1100
|
agentDevice: agentDeviceUsage,
|
|
1327
1101
|
swiftpmCache: swiftpmCacheUsage,
|
|
1102
|
+
machineCaches,
|
|
1103
|
+
tempLeftovers: tempLeftoverSection(tempLeftovers),
|
|
1104
|
+
gradleVersions,
|
|
1328
1105
|
memory: memory?.processes ?? [],
|
|
1329
1106
|
watchmanRoots: memory?.watchmanRoots ?? [],
|
|
1330
1107
|
memoryNotices: (memory?.notices ?? []).map((message) => ({ message }))
|
|
1331
1108
|
};
|
|
1332
1109
|
}
|
|
1333
1110
|
//#endregion
|
|
1111
|
+
//#region src/commands/gc/worktree-sizes.ts
|
|
1112
|
+
const SIZE_TIMEOUT_MS = 3e4;
|
|
1113
|
+
const BUILD_STATE_MAX_AGE_MS = 36e5;
|
|
1114
|
+
const BUILD_STATE_TIMEOUT_MS = 1e4;
|
|
1115
|
+
const BUILD_STATE_CONCURRENCY = 4;
|
|
1116
|
+
const PNPM_STORE = ".pnpm-store";
|
|
1117
|
+
const WORKTREES_DIR = ".worktrees";
|
|
1118
|
+
function isDirectory(path) {
|
|
1119
|
+
try {
|
|
1120
|
+
return lstatSync(path).isDirectory();
|
|
1121
|
+
} catch {
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
function inside(root, path) {
|
|
1126
|
+
const rel = relative(root, path);
|
|
1127
|
+
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
|
|
1128
|
+
}
|
|
1129
|
+
async function buildStateDirs(path, keys) {
|
|
1130
|
+
const entries = await listWorktreesAsync(path);
|
|
1131
|
+
const own = keys.filter((key) => matchWorktreeEntry(entries, key)?.path === path);
|
|
1132
|
+
const root = canonicalPath(path);
|
|
1133
|
+
const worktree = {
|
|
1134
|
+
path,
|
|
1135
|
+
linked: true,
|
|
1136
|
+
keys: own
|
|
1137
|
+
};
|
|
1138
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
1139
|
+
const add = (dir) => {
|
|
1140
|
+
if (!isDirectory(dir)) return;
|
|
1141
|
+
const real = canonicalPath(dir);
|
|
1142
|
+
if (inside(root, real) && !dirs.has(real)) dirs.set(real, dir);
|
|
1143
|
+
};
|
|
1144
|
+
for (const kind of ["build", "dependencies"]) for (const base of appRoots(worktree, kind)) for (const dir of ARTIFACT_DIRS[kind]) add(join(base, dir));
|
|
1145
|
+
add(join(path, PNPM_STORE));
|
|
1146
|
+
return [...dirs.keys()];
|
|
1147
|
+
}
|
|
1148
|
+
function cachedSize(dir, now) {
|
|
1149
|
+
const cached = readDiskUsage(dir);
|
|
1150
|
+
if (!cached) return null;
|
|
1151
|
+
const at = Date.parse(cached.measuredAt);
|
|
1152
|
+
if (now - at >= BUILD_STATE_MAX_AGE_MS) return null;
|
|
1153
|
+
try {
|
|
1154
|
+
return statSync(dir).mtimeMs > at ? null : cached.bytes;
|
|
1155
|
+
} catch {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function duSize(dir) {
|
|
1160
|
+
let out;
|
|
1161
|
+
try {
|
|
1162
|
+
out = await getExecutor().runFileAsync("du", ["-sk", dir], {
|
|
1163
|
+
timeoutMs: BUILD_STATE_TIMEOUT_MS,
|
|
1164
|
+
killSignal: "SIGKILL"
|
|
1165
|
+
});
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
const stdout = error instanceof Error && "stdout" in error ? error.stdout : null;
|
|
1168
|
+
out = typeof stdout === "string" ? stdout : "";
|
|
1169
|
+
}
|
|
1170
|
+
const kb = /^(\d+)\t/.exec(out)?.[1];
|
|
1171
|
+
return kb ? Number(kb) * 1024 : null;
|
|
1172
|
+
}
|
|
1173
|
+
async function eachLimited(items, limit, run) {
|
|
1174
|
+
const queue = items[Symbol.iterator]();
|
|
1175
|
+
const worker = async () => {
|
|
1176
|
+
for (const item of queue) await run(item);
|
|
1177
|
+
};
|
|
1178
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Sizes the regenerable directories maintenance clears and a project-local `.pnpm-store` in each worktree of the
|
|
1182
|
+
* sweep, from the disk-usage cache while an entry is under an hour old and no entry was added to or removed from the
|
|
1183
|
+
* directory since. Stale directories are measured with one `du` each, at most 4 at once and 10 seconds each, so a
|
|
1184
|
+
* slow tree delays only itself; a directory du could not measure in time is reported unmeasured (null). Also lists
|
|
1185
|
+
* the directories directly under a repository's `.worktrees/` that are no git worktree at all.
|
|
1186
|
+
*/
|
|
1187
|
+
async function measureWorktreeSweep(sweep, operation) {
|
|
1188
|
+
const now = Date.now();
|
|
1189
|
+
const pending = [];
|
|
1190
|
+
for (const candidate of sweep.worktrees) {
|
|
1191
|
+
if (candidate.skipCode === "not-a-worktree") continue;
|
|
1192
|
+
const dirs = await buildStateDirs(candidate.path, candidate.keys);
|
|
1193
|
+
pending.push({
|
|
1194
|
+
candidate,
|
|
1195
|
+
root: canonicalPath(candidate.path),
|
|
1196
|
+
dirs,
|
|
1197
|
+
sizes: new Map(dirs.map((dir) => [dir, cachedSize(dir, now)]))
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
const due = pending.flatMap(({ sizes }) => [...sizes].flatMap(([dir, bytes]) => bytes === null ? [{
|
|
1201
|
+
dir,
|
|
1202
|
+
sizes
|
|
1203
|
+
}] : []));
|
|
1204
|
+
const worktrees = new Set(due.map(({ sizes }) => sizes)).size;
|
|
1205
|
+
if (due.length) phase("worktrees", `measuring the build state of ${plural(worktrees, "worktree")}`, operation);
|
|
1206
|
+
await eachLimited(due, BUILD_STATE_CONCURRENCY, async ({ dir, sizes }) => {
|
|
1207
|
+
const measuredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1208
|
+
const bytes = await duSize(dir);
|
|
1209
|
+
if (bytes === null) return;
|
|
1210
|
+
sizes.set(dir, bytes);
|
|
1211
|
+
operation?.signal.throwIfAborted();
|
|
1212
|
+
writeCacheFile(diskUsageCacheFile(dir), {
|
|
1213
|
+
path: dir,
|
|
1214
|
+
measuredAt,
|
|
1215
|
+
bytes
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1218
|
+
for (const { candidate, root, dirs, sizes } of pending) {
|
|
1219
|
+
const entries = dirs.map((dir) => ({
|
|
1220
|
+
path: relative(root, dir).split(sep).join("/"),
|
|
1221
|
+
bytes: sizes.get(dir) ?? null
|
|
1222
|
+
}));
|
|
1223
|
+
candidate.buildState = {
|
|
1224
|
+
bytes: entries.every((dir) => dir.bytes !== null) ? entries.reduce((sum, dir) => sum + (dir.bytes ?? 0), 0) : null,
|
|
1225
|
+
dirs: entries.toSorted((a, b) => (b.bytes ?? 0) - (a.bytes ?? 0))
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
sweep.unlisted = await unlistedWorktreeDirs();
|
|
1229
|
+
return sweep;
|
|
1230
|
+
}
|
|
1231
|
+
/** Whether `path` is a checkout git still knows: its own `.git` directory, or a `.git` file whose gitdir exists. */
|
|
1232
|
+
function hasLiveGitDir(path) {
|
|
1233
|
+
const dotGit = join(path, ".git");
|
|
1234
|
+
let text;
|
|
1235
|
+
try {
|
|
1236
|
+
if (lstatSync(dotGit).isDirectory()) return true;
|
|
1237
|
+
text = readFileSync(dotGit, "utf-8");
|
|
1238
|
+
} catch {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
const gitdir = /^gitdir:\s*(.+)$/m.exec(text)?.[1]?.trim();
|
|
1242
|
+
return Boolean(gitdir) && existsSync(resolve(dirname(dotGit), gitdir ?? ""));
|
|
1243
|
+
}
|
|
1244
|
+
async function unlistedWorktreeDirs() {
|
|
1245
|
+
const repositories = /* @__PURE__ */ new Map();
|
|
1246
|
+
for (const root of await candidateRootsAsync()) {
|
|
1247
|
+
const { entries, ...source } = await resolveSourceCheckoutAsync(root);
|
|
1248
|
+
if ("refusal" in source || repositories.has(source.path)) continue;
|
|
1249
|
+
repositories.set(source.path, entries.map((entry) => canonicalPath(entry.path)));
|
|
1250
|
+
}
|
|
1251
|
+
const found = [];
|
|
1252
|
+
for (const [repository, listed] of repositories) {
|
|
1253
|
+
const parent = join(repository, WORKTREES_DIR);
|
|
1254
|
+
let names;
|
|
1255
|
+
try {
|
|
1256
|
+
names = readdirSync(parent);
|
|
1257
|
+
} catch {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
for (const name of names.toSorted()) {
|
|
1261
|
+
const path = join(parent, name);
|
|
1262
|
+
if (!isDirectory(path) || hasLiveGitDir(path)) continue;
|
|
1263
|
+
const real = canonicalPath(path);
|
|
1264
|
+
if (listed.some((entry) => entry === real || entry.startsWith(`${real}${sep}`))) continue;
|
|
1265
|
+
found.push({
|
|
1266
|
+
path,
|
|
1267
|
+
repository,
|
|
1268
|
+
bytes: await measuredDirectorySizeAsync(path, { timeoutMs: SIZE_TIMEOUT_MS })
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return found;
|
|
1273
|
+
}
|
|
1274
|
+
//#endregion
|
|
1334
1275
|
//#region src/commands/gc/ledger.ts
|
|
1335
1276
|
/** `sims` must be a complete listing, unavailable simulators included, taken after `ledger` was read. */
|
|
1336
1277
|
function findStaleLedgerEntries(ledger, sims) {
|
|
@@ -1564,266 +1505,6 @@ async function trimWorkspaceLogs(entries) {
|
|
|
1564
1505
|
return failures;
|
|
1565
1506
|
}
|
|
1566
1507
|
//#endregion
|
|
1567
|
-
//#region src/commands/gc/inventory.ts
|
|
1568
|
-
const IOS_SIMULATOR_PLATFORM = "com.apple.platform.iphonesimulator";
|
|
1569
|
-
function text(value) {
|
|
1570
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1571
|
-
}
|
|
1572
|
-
function parseSimRuntimeList(jsonOutput) {
|
|
1573
|
-
const parsed = JSON.parse(jsonOutput);
|
|
1574
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Expected simctl runtime list to return a JSON object.");
|
|
1575
|
-
const runtimes = [];
|
|
1576
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
1577
|
-
if (!isJsonObject(value)) continue;
|
|
1578
|
-
const entry = value;
|
|
1579
|
-
if (entry.platformIdentifier !== IOS_SIMULATOR_PLATFORM) continue;
|
|
1580
|
-
runtimes.push({
|
|
1581
|
-
identifier: text(entry.identifier) ?? key,
|
|
1582
|
-
runtimeIdentifier: text(entry.runtimeIdentifier),
|
|
1583
|
-
version: text(entry.version),
|
|
1584
|
-
build: text(entry.build),
|
|
1585
|
-
sizeBytes: typeof entry.sizeBytes === "number" && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null,
|
|
1586
|
-
deletable: entry.deletable === true,
|
|
1587
|
-
lastUsedAt: text(entry.lastUsedAt)
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
return runtimes;
|
|
1591
|
-
}
|
|
1592
|
-
function unlistedRuntimes(jsonOutput, images) {
|
|
1593
|
-
const known = new Set(images.map((image) => image.runtimeIdentifier));
|
|
1594
|
-
const parsed = JSON.parse(jsonOutput);
|
|
1595
|
-
if (!Array.isArray(parsed.runtimes)) throw new Error("Expected simctl list runtimes to return a runtimes array.");
|
|
1596
|
-
return parsed.runtimes.flatMap((value) => {
|
|
1597
|
-
if (value === null || typeof value !== "object") return [];
|
|
1598
|
-
const entry = value;
|
|
1599
|
-
const identifier = text(entry.identifier);
|
|
1600
|
-
if (!identifier || !/\.iOS-/.test(identifier) || known.has(identifier)) return [];
|
|
1601
|
-
return [{
|
|
1602
|
-
identifier,
|
|
1603
|
-
runtimeIdentifier: identifier,
|
|
1604
|
-
version: text(entry.version),
|
|
1605
|
-
build: text(entry.buildversion),
|
|
1606
|
-
sizeBytes: null,
|
|
1607
|
-
deletable: false,
|
|
1608
|
-
lastUsedAt: null
|
|
1609
|
-
}];
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
function modelName(deviceTypeIdentifier) {
|
|
1613
|
-
return deviceTypeIdentifier.replace(/^com\.apple\.CoreSimulator\.SimDeviceType\./, "").replaceAll("-", " ");
|
|
1614
|
-
}
|
|
1615
|
-
function sdkmanagerCommand(pkg) {
|
|
1616
|
-
const tool = androidToolPath("sdkmanager");
|
|
1617
|
-
return `${tool === "sdkmanager" ? tool : JSON.stringify(tool)} --uninstall ${JSON.stringify(pkg)}`;
|
|
1618
|
-
}
|
|
1619
|
-
function buildInventory({ sims, avds, runtimes, systemImages, config, deadProjects = [] }) {
|
|
1620
|
-
const dead = new Set(deadProjects);
|
|
1621
|
-
const assigned = /* @__PURE__ */ new Map();
|
|
1622
|
-
for (const [project, record] of Object.entries(config?.projects ?? {})) {
|
|
1623
|
-
if (dead.has(project)) continue;
|
|
1624
|
-
let slots;
|
|
1625
|
-
try {
|
|
1626
|
-
slots = projectDeviceSlots(record);
|
|
1627
|
-
} catch {
|
|
1628
|
-
continue;
|
|
1629
|
-
}
|
|
1630
|
-
for (const { slot, platforms } of slots) {
|
|
1631
|
-
if (platforms.ios?.deviceUdid) assigned.set(`ios:${platforms.ios.deviceUdid}`, {
|
|
1632
|
-
project,
|
|
1633
|
-
slot
|
|
1634
|
-
});
|
|
1635
|
-
if (platforms.android?.avdName) assigned.set(`android:${platforms.android.avdName}`, {
|
|
1636
|
-
project,
|
|
1637
|
-
slot
|
|
1638
|
-
});
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
const parked = /* @__PURE__ */ new Set([...readParked("ios", { config }).map((sim) => `ios:${sim.udid}`), ...readParked("android", { config }).map((avd) => `android:${avd.name}`)]);
|
|
1642
|
-
const owner = (key, name, created) => {
|
|
1643
|
-
const workspace = assigned.get(key);
|
|
1644
|
-
if (workspace && created()) return {
|
|
1645
|
-
owner: "workspace",
|
|
1646
|
-
project: workspace.project,
|
|
1647
|
-
slot: workspace.slot
|
|
1648
|
-
};
|
|
1649
|
-
if (parked.has(key)) return {
|
|
1650
|
-
owner: "parked",
|
|
1651
|
-
project: null,
|
|
1652
|
-
slot: null
|
|
1653
|
-
};
|
|
1654
|
-
if (!name.startsWith("stim-")) return {
|
|
1655
|
-
owner: "user",
|
|
1656
|
-
project: null,
|
|
1657
|
-
slot: null
|
|
1658
|
-
};
|
|
1659
|
-
return {
|
|
1660
|
-
owner: created() ? "orphaned" : "otherStimHome",
|
|
1661
|
-
project: null,
|
|
1662
|
-
slot: null
|
|
1663
|
-
};
|
|
1664
|
-
};
|
|
1665
|
-
const devices = [...sims.map((sim) => ({
|
|
1666
|
-
kind: "ios",
|
|
1667
|
-
id: sim.udid,
|
|
1668
|
-
name: sim.name,
|
|
1669
|
-
model: modelName(sim.deviceTypeIdentifier),
|
|
1670
|
-
runtime: sim.runtime,
|
|
1671
|
-
state: sim.state,
|
|
1672
|
-
lastUsedAt: sim.lastUsedAt ?? null,
|
|
1673
|
-
bytes: sim.dataPathSize ?? null,
|
|
1674
|
-
directory: sim.dataPath ? sim.dataPath.replace(/\/data\/?$/, "") : null,
|
|
1675
|
-
...owner(`ios:${sim.udid}`, sim.name, () => isStimOwnedSim({
|
|
1676
|
-
udid: sim.udid,
|
|
1677
|
-
name: sim.name
|
|
1678
|
-
}))
|
|
1679
|
-
})), ...avds.map((avd) => ({
|
|
1680
|
-
kind: "android",
|
|
1681
|
-
id: avd.name,
|
|
1682
|
-
name: avd.name,
|
|
1683
|
-
model: avd.deviceProfile,
|
|
1684
|
-
runtime: avd.systemImage,
|
|
1685
|
-
state: null,
|
|
1686
|
-
lastUsedAt: avd.lastUsedAt,
|
|
1687
|
-
bytes: null,
|
|
1688
|
-
directory: avd.directory,
|
|
1689
|
-
...owner(`android:${avd.name}`, avd.name, () => isStimOwnedAvd(avd.name))
|
|
1690
|
-
}))];
|
|
1691
|
-
const simsByRuntime = /* @__PURE__ */ new Map();
|
|
1692
|
-
for (const sim of sims) simsByRuntime.set(sim.runtime, (simsByRuntime.get(sim.runtime) ?? 0) + 1);
|
|
1693
|
-
const avdsByImage = /* @__PURE__ */ new Map();
|
|
1694
|
-
for (const avd of avds) if (avd.systemImage) avdsByImage.set(avd.systemImage, (avdsByImage.get(avd.systemImage) ?? 0) + 1);
|
|
1695
|
-
return {
|
|
1696
|
-
devices,
|
|
1697
|
-
runtimes: runtimes.map((runtime) => ({
|
|
1698
|
-
identifier: runtime.identifier,
|
|
1699
|
-
runtimeIdentifier: runtime.runtimeIdentifier,
|
|
1700
|
-
version: runtime.version,
|
|
1701
|
-
build: runtime.build,
|
|
1702
|
-
bytes: runtime.sizeBytes,
|
|
1703
|
-
lastUsedAt: runtime.lastUsedAt,
|
|
1704
|
-
deviceCount: runtime.runtimeIdentifier ? simsByRuntime.get(runtime.runtimeIdentifier) ?? 0 : 0,
|
|
1705
|
-
command: runtime.deletable ? `xcrun simctl runtime delete ${runtime.identifier}` : null
|
|
1706
|
-
})),
|
|
1707
|
-
systemImages: systemImages.map((image) => ({
|
|
1708
|
-
package: image.pkg,
|
|
1709
|
-
directory: image.directory,
|
|
1710
|
-
avdCount: avdsByImage.get(image.pkg) ?? 0,
|
|
1711
|
-
command: sdkmanagerCommand(image.pkg)
|
|
1712
|
-
}))
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
function modifiedAt(path) {
|
|
1716
|
-
try {
|
|
1717
|
-
return statSync(path).mtime.toISOString();
|
|
1718
|
-
} catch {
|
|
1719
|
-
return null;
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
function isMissing(error) {
|
|
1723
|
-
const code = error.code;
|
|
1724
|
-
return code === "ENOENT" || code === "ENOTDIR";
|
|
1725
|
-
}
|
|
1726
|
-
function unreadableNotice(what, error) {
|
|
1727
|
-
const notice = `${what} not listed: ${describeError(error)}`;
|
|
1728
|
-
if (process.platform !== "darwin" || error.code !== "EPERM") return notice;
|
|
1729
|
-
return `${notice}. macOS privacy protection blocked the read; allow ${process.env.STIM_DESKTOP_APP ? "Stim Desktop" : "the app that runs stim"} under System Settings > Privacy & Security > Files and Folders (Removable Volumes for an external disk) or Full Disk Access`;
|
|
1730
|
-
}
|
|
1731
|
-
function listAvdRecords(notices, roots = avdStorageRoots()) {
|
|
1732
|
-
const records = /* @__PURE__ */ new Map();
|
|
1733
|
-
for (const root of roots) {
|
|
1734
|
-
let names;
|
|
1735
|
-
try {
|
|
1736
|
-
names = readdirSync(root);
|
|
1737
|
-
} catch (error) {
|
|
1738
|
-
if (!isMissing(error)) notices.push(unreadableNotice(`AVDs in ${root}`, error));
|
|
1739
|
-
continue;
|
|
1740
|
-
}
|
|
1741
|
-
for (const entry of names) {
|
|
1742
|
-
if (!entry.endsWith(".ini")) continue;
|
|
1743
|
-
const name = entry.slice(0, -4);
|
|
1744
|
-
if (records.has(name)) continue;
|
|
1745
|
-
let ini;
|
|
1746
|
-
try {
|
|
1747
|
-
ini = readFileSync(join(root, entry), "utf8");
|
|
1748
|
-
} catch (error) {
|
|
1749
|
-
if (!isMissing(error)) notices.push(unreadableNotice(`AVD ${name}`, error));
|
|
1750
|
-
continue;
|
|
1751
|
-
}
|
|
1752
|
-
let directory = null;
|
|
1753
|
-
let config = "";
|
|
1754
|
-
let unreadable = null;
|
|
1755
|
-
for (const candidate of avdIniPaths(root, ini)) try {
|
|
1756
|
-
config = readFileSync(join(candidate, "config.ini"), "utf8");
|
|
1757
|
-
directory = candidate;
|
|
1758
|
-
break;
|
|
1759
|
-
} catch (error) {
|
|
1760
|
-
if (!isMissing(error)) unreadable ??= error;
|
|
1761
|
-
}
|
|
1762
|
-
if (!directory && unreadable) notices.push(unreadableNotice(`details of AVD ${name}`, unreadable));
|
|
1763
|
-
records.set(name, {
|
|
1764
|
-
name,
|
|
1765
|
-
directory,
|
|
1766
|
-
systemImage: parseAvdSystemImage(config),
|
|
1767
|
-
deviceProfile: parseAvdDeviceProfile(config),
|
|
1768
|
-
lastUsedAt: directory ? modifiedAt(join(directory, "hardware-qemu.ini")) : null
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
return [...records.values()];
|
|
1773
|
-
}
|
|
1774
|
-
function simctl(args) {
|
|
1775
|
-
return getExecutor().runFile("xcrun", ["simctl", ...args], {
|
|
1776
|
-
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
1777
|
-
killSignal: "SIGKILL"
|
|
1778
|
-
});
|
|
1779
|
-
}
|
|
1780
|
-
function collectInventory(config, deadProjects) {
|
|
1781
|
-
const notices = [];
|
|
1782
|
-
let sims = [];
|
|
1783
|
-
let runtimes = [];
|
|
1784
|
-
if (process.platform === "darwin") {
|
|
1785
|
-
try {
|
|
1786
|
-
sims = listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS });
|
|
1787
|
-
} catch (error) {
|
|
1788
|
-
notices.push(`simulators not listed: ${describeError(error)}`);
|
|
1789
|
-
}
|
|
1790
|
-
try {
|
|
1791
|
-
runtimes = parseSimRuntimeList(simctl([
|
|
1792
|
-
"runtime",
|
|
1793
|
-
"list",
|
|
1794
|
-
"-j"
|
|
1795
|
-
]));
|
|
1796
|
-
} catch (error) {
|
|
1797
|
-
notices.push(`simulator runtime images not listed: ${describeError(error)}`);
|
|
1798
|
-
}
|
|
1799
|
-
try {
|
|
1800
|
-
runtimes.push(...unlistedRuntimes(simctl([
|
|
1801
|
-
"list",
|
|
1802
|
-
"runtimes",
|
|
1803
|
-
"-j"
|
|
1804
|
-
]), runtimes));
|
|
1805
|
-
} catch (error) {
|
|
1806
|
-
notices.push(`simulator runtimes not listed: ${describeError(error)}`);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
const imagesRoot = join(androidHome(), "system-images");
|
|
1810
|
-
const images = listInstalledSystemImages((error) => notices.push(unreadableNotice("Android system images", error))).map((image) => ({
|
|
1811
|
-
pkg: image.pkg,
|
|
1812
|
-
directory: join(imagesRoot, `android-${image.api}`, image.tag, image.arch)
|
|
1813
|
-
}));
|
|
1814
|
-
return {
|
|
1815
|
-
...buildInventory({
|
|
1816
|
-
sims,
|
|
1817
|
-
avds: listAvdRecords(notices),
|
|
1818
|
-
runtimes,
|
|
1819
|
-
systemImages: images,
|
|
1820
|
-
config,
|
|
1821
|
-
deadProjects
|
|
1822
|
-
}),
|
|
1823
|
-
notices
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
//#endregion
|
|
1827
1508
|
//#region src/commands/gc/hosted-devices.ts
|
|
1828
1509
|
function collectParkedHostedDevices({ olderThanDays = null, now = Date.now() } = {}) {
|
|
1829
1510
|
const notices = [];
|
|
@@ -2085,6 +1766,14 @@ async function deleteParkedHostedDevices(devices, { listAvds: list = listAvds }
|
|
|
2085
1766
|
}
|
|
2086
1767
|
//#endregion
|
|
2087
1768
|
//#region src/commands/gc.ts
|
|
1769
|
+
function readGcReport(options = {}, deps = {}) {
|
|
1770
|
+
return sweep({
|
|
1771
|
+
olderThan: options.olderThan,
|
|
1772
|
+
cache: options.cache,
|
|
1773
|
+
worktrees: options.worktrees,
|
|
1774
|
+
json: true
|
|
1775
|
+
}, deps, true);
|
|
1776
|
+
}
|
|
2088
1777
|
function removeInvalidProjectEntries(invalidProjects) {
|
|
2089
1778
|
for (const path of invalidProjects) {
|
|
2090
1779
|
removeProject(path);
|
|
@@ -2092,8 +1781,8 @@ function removeInvalidProjectEntries(invalidProjects) {
|
|
|
2092
1781
|
recordGcResult("project", "done", path);
|
|
2093
1782
|
}
|
|
2094
1783
|
}
|
|
2095
|
-
function collectGcMemory(scope) {
|
|
2096
|
-
return memorySweepIsScoped() ? Promise.resolve(scopedMemoryReport()) : collectMemoryReport(scope, true);
|
|
1784
|
+
function collectGcMemory(scope, env, operation) {
|
|
1785
|
+
return memorySweepIsScoped(env) ? Promise.resolve(scopedMemoryReport()) : collectMemoryReport(scope, true, operation);
|
|
2097
1786
|
}
|
|
2098
1787
|
function classifyProjectEntries(cfg, mountedVolumes) {
|
|
2099
1788
|
const deadProjects = [];
|
|
@@ -2132,14 +1821,16 @@ function removedWholeWorkspaceDirs() {
|
|
|
2132
1821
|
const { orphaned } = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, { measure: false });
|
|
2133
1822
|
return [...orphaned.map((entry) => entry.dir), ...deadProjects.map(workspaceDir)];
|
|
2134
1823
|
}
|
|
2135
|
-
async function collectGcReport({ olderThan = null, cache = null, worktrees = false, now = Date.now(), lastTouched = workspaceLastUsed } = {}, deps = {}) {
|
|
1824
|
+
async function collectGcReport({ olderThan = null, cache = null, worktrees = false, refresh = true, now = Date.now(), lastTouched = workspaceLastUsed } = {}, deps = {}) {
|
|
1825
|
+
const operation = deps.operation;
|
|
1826
|
+
const env = operation?.env ?? process.env;
|
|
2136
1827
|
const scope = typeof cache === "string" && cache.trim() ? cache : null;
|
|
2137
1828
|
const all = scope !== null && olderThan === null;
|
|
2138
1829
|
const withWorkspaces = includesWorkspaceOutputs(scope);
|
|
2139
|
-
const selected = selectCaches(discoverCaches(), scope).filter((c) => !withWorkspaces || !isInsideWorkspaces(c.dir));
|
|
2140
|
-
if (selected.length) phase("caches", `measuring ${plural(selected.length, "shared cache")}
|
|
2141
|
-
const sized = sizeCaches(selected);
|
|
2142
|
-
const caches = scope === null ? previewScopedEmptying(sized) : planCacheEmptying(sized, all);
|
|
1830
|
+
const selected = selectCaches(discoverCaches(env, operation?.cwd), scope).filter((c) => !withWorkspaces || !isInsideWorkspaces(c.dir));
|
|
1831
|
+
if (selected.length) phase("caches", `measuring ${plural(selected.length, "shared cache")}`, operation);
|
|
1832
|
+
const sized = await sizeCaches(selected);
|
|
1833
|
+
const caches = scope === null ? previewScopedEmptying(sized, env) : planCacheEmptying(sized, all, env);
|
|
2143
1834
|
if (scope) return {
|
|
2144
1835
|
archives: includesArchives(scope) ? collectArchives(scope, olderThan, now) : void 0,
|
|
2145
1836
|
skipped: [],
|
|
@@ -2149,6 +1840,8 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2149
1840
|
orphanedDevices: [],
|
|
2150
1841
|
unverifiedDevices: [],
|
|
2151
1842
|
staleDevices: [],
|
|
1843
|
+
finishedDevices: [],
|
|
1844
|
+
ownedDeviceDisk: [],
|
|
2152
1845
|
staleDeviceRecords: [],
|
|
2153
1846
|
staleLedgerEntries: [],
|
|
2154
1847
|
staleEasLedgerEntries: [],
|
|
@@ -2175,17 +1868,17 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2175
1868
|
notices: [],
|
|
2176
1869
|
deletionSafe: true
|
|
2177
1870
|
},
|
|
2178
|
-
parkedSims: includesParkedDevices(scope) ? collectParkedSims(deps, {
|
|
1871
|
+
parkedSims: includesParkedDevices(scope) ? await collectParkedSims(deps, {
|
|
2179
1872
|
olderThanDays: olderThan,
|
|
2180
1873
|
now
|
|
2181
1874
|
}) : [],
|
|
2182
|
-
parkedAvds: includesParkedDevices(scope) ? collectParkedAvds(deps, {
|
|
1875
|
+
parkedAvds: includesParkedDevices(scope) ? await collectParkedAvds(deps, {
|
|
2183
1876
|
olderThanDays: olderThan,
|
|
2184
1877
|
now
|
|
2185
1878
|
}) : [],
|
|
2186
1879
|
parkedHostedDevices: [],
|
|
2187
1880
|
caches,
|
|
2188
|
-
workspaceOutputs: withWorkspaces ? collectWorkspaceOutputs({
|
|
1881
|
+
workspaceOutputs: withWorkspaces ? await collectWorkspaceOutputs({
|
|
2189
1882
|
olderThan,
|
|
2190
1883
|
now,
|
|
2191
1884
|
exclude: isWorkspacesScope(scope) ? removedWholeWorkspaceDirs() : []
|
|
@@ -2197,22 +1890,37 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2197
1890
|
now
|
|
2198
1891
|
}) : [],
|
|
2199
1892
|
worktreeSweep: null,
|
|
2200
|
-
memory: memoryCacheKind(scope) ? await collectGcMemory(memoryScope(scope)) : null,
|
|
1893
|
+
memory: memoryCacheKind(scope) ? await collectGcMemory(memoryScope(scope), env, operation) : null,
|
|
2201
1894
|
agentDeviceUsage: null,
|
|
2202
1895
|
swiftpmCacheUsage: null,
|
|
1896
|
+
machineCaches: null,
|
|
1897
|
+
tempLeftovers: null,
|
|
1898
|
+
gradleVersions: includesGradleVersions(scope) ? await collectGradleVersions({
|
|
1899
|
+
olderThanDays: olderThan,
|
|
1900
|
+
now,
|
|
1901
|
+
operation
|
|
1902
|
+
}) : null,
|
|
2203
1903
|
cacheScope: scope,
|
|
2204
1904
|
olderThan,
|
|
2205
1905
|
all
|
|
2206
1906
|
};
|
|
2207
|
-
phase("agent-device", "measuring report-only state");
|
|
2208
|
-
const agentDeviceUsage = await getAgentDeviceUsage();
|
|
2209
|
-
phase("SwiftPM cache", "measuring report-only state");
|
|
2210
|
-
const swiftpmCacheUsage = await getSwiftpmCacheUsage();
|
|
1907
|
+
phase("agent-device", "measuring report-only state", operation);
|
|
1908
|
+
const agentDeviceUsage = await getAgentDeviceUsage({ operation });
|
|
1909
|
+
phase("SwiftPM cache", "measuring report-only state", operation);
|
|
1910
|
+
const swiftpmCacheUsage = await getSwiftpmCacheUsage({ operation });
|
|
1911
|
+
phase("machine caches", "measuring report-only state", operation);
|
|
1912
|
+
const machineCaches = await getMachineCacheReport(agentDeviceUsage, { operation });
|
|
1913
|
+
if (!deviceSweepIsScoped(env)) phase("TMPDIR", "checking recognized leftovers", operation);
|
|
1914
|
+
const tempLeftovers = await collectTempLeftovers({
|
|
1915
|
+
now,
|
|
1916
|
+
env,
|
|
1917
|
+
signal: operation?.signal
|
|
1918
|
+
});
|
|
2211
1919
|
const mountedVolumes = listMountedVolumes();
|
|
2212
1920
|
const cfg = loadConfig$1();
|
|
2213
1921
|
let easSessionSweep = deps.precollectedEasSessionSweep;
|
|
2214
1922
|
if (!easSessionSweep) try {
|
|
2215
|
-
easSessionSweep = collectEasSessionSweep(cfg, deps);
|
|
1923
|
+
easSessionSweep = await collectEasSessionSweep(cfg, deps);
|
|
2216
1924
|
} catch (error) {
|
|
2217
1925
|
easSessionSweep = {
|
|
2218
1926
|
projectScope: null,
|
|
@@ -2221,16 +1929,16 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2221
1929
|
deletionSafe: false
|
|
2222
1930
|
};
|
|
2223
1931
|
}
|
|
2224
|
-
const parkedSims = collectParkedSims(deps, {
|
|
1932
|
+
const parkedSims = await collectParkedSims(deps, {
|
|
2225
1933
|
olderThanDays: olderThan,
|
|
2226
1934
|
now
|
|
2227
1935
|
});
|
|
2228
|
-
const parkedAvds = collectParkedAvds(deps, {
|
|
1936
|
+
const parkedAvds = await collectParkedAvds(deps, {
|
|
2229
1937
|
olderThanDays: olderThan,
|
|
2230
1938
|
now
|
|
2231
1939
|
});
|
|
2232
1940
|
const { deadProjects, invalidProjects, skipped } = classifyProjectEntries(cfg, mountedVolumes);
|
|
2233
|
-
const workspaceDirs = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes);
|
|
1941
|
+
const workspaceDirs = await collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes);
|
|
2234
1942
|
skipped.push(...workspaceDirs.skipped);
|
|
2235
1943
|
const statusCaches = collectStaleStatusCaches(mountedVolumes);
|
|
2236
1944
|
skipped.push(...statusCaches.skipped);
|
|
@@ -2245,16 +1953,24 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2245
1953
|
let staleDeviceRecords = [];
|
|
2246
1954
|
let staleLedgerEntries = staleBrowserProfiles(readCreatedDevices$1());
|
|
2247
1955
|
let idleDevices = [];
|
|
2248
|
-
|
|
2249
|
-
|
|
1956
|
+
let listed = null;
|
|
1957
|
+
let deviceDisk = [];
|
|
1958
|
+
const avdSizes = /* @__PURE__ */ new Map();
|
|
1959
|
+
const avdSize = (dir, options) => {
|
|
1960
|
+
let bytes = avdSizes.get(dir);
|
|
1961
|
+
if (bytes === void 0) avdSizes.set(dir, bytes = Promise.resolve((deps.directorySize ?? directorySizeAsync)(dir, options)));
|
|
1962
|
+
return bytes;
|
|
1963
|
+
};
|
|
1964
|
+
const unsweepableReason = cfg === null ? "no Stim config found" : deviceSweepIsScoped(env) ? "STIM_HOME scopes this config, but simulators and AVDs are machine-global" : null;
|
|
1965
|
+
phase("devices", "listing simulators and emulators", operation);
|
|
2250
1966
|
if (unsweepableReason) {
|
|
2251
1967
|
let simNames = [];
|
|
2252
1968
|
let avdNames = [];
|
|
2253
1969
|
try {
|
|
2254
|
-
simNames =
|
|
1970
|
+
simNames = (await listAllIosSimsAsync({ timeoutMs: DEVICE_LIST_TIMEOUT_MS })).map((s) => s.name);
|
|
2255
1971
|
} catch {}
|
|
2256
1972
|
try {
|
|
2257
|
-
avdNames =
|
|
1973
|
+
avdNames = await listAvdsAsync({ timeoutMs: DEVICE_LIST_TIMEOUT_MS });
|
|
2258
1974
|
} catch {}
|
|
2259
1975
|
deviceSweepNotices.push(...describeUnverifiableDevices(simNames, avdNames, { reason: unsweepableReason }));
|
|
2260
1976
|
} else {
|
|
@@ -2262,7 +1978,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2262
1978
|
let simsChecked = true;
|
|
2263
1979
|
const ledger = readCreatedDevices$1();
|
|
2264
1980
|
try {
|
|
2265
|
-
const allSims =
|
|
1981
|
+
const allSims = await listAllIosSimsAsync({
|
|
2266
1982
|
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
2267
1983
|
includeUnavailable: true
|
|
2268
1984
|
});
|
|
@@ -2275,7 +1991,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2275
1991
|
let avds = [];
|
|
2276
1992
|
let avdsChecked = true;
|
|
2277
1993
|
try {
|
|
2278
|
-
avds =
|
|
1994
|
+
avds = await listAvdsAsync({ timeoutMs: DEVICE_LIST_TIMEOUT_MS });
|
|
2279
1995
|
} catch {
|
|
2280
1996
|
avdsChecked = false;
|
|
2281
1997
|
deviceSweepNotices.push(`android device sweep skipped: emulator tooling did not answer within ${DEVICE_LIST_TIMEOUT_MS / 1e3}s`);
|
|
@@ -2302,14 +2018,30 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2302
2018
|
const directories = device.kind === "android" && !registeredAvds.has(device.id) ? orphanedAvdDirectories.filter((entry) => entry.name === device.id) : [];
|
|
2303
2019
|
return directories.length ? directories.map(({ directory }) => Object.assign({}, device, { directory })) : [device];
|
|
2304
2020
|
});
|
|
2305
|
-
orphanedDevices = withAndroidAvdSizes(found.orphaned.flatMap((device) => {
|
|
2021
|
+
orphanedDevices = await withAndroidAvdSizes(found.orphaned.flatMap((device) => {
|
|
2306
2022
|
const directories = device.kind === "android" ? orphanedAvdDirectories.filter((entry) => entry.name === device.name) : [];
|
|
2307
2023
|
return directories.length ? directories.map((orphanedDirectory) => Object.assign({}, device, { orphanedDirectory })) : [device];
|
|
2308
2024
|
}), {
|
|
2309
2025
|
avdDirectory: deps.avdDirectory ?? ownedAvdDirectory,
|
|
2310
|
-
size:
|
|
2026
|
+
size: avdSize
|
|
2311
2027
|
});
|
|
2312
|
-
idleDevices = collectIdleDevices(cfg, sims, deadProjects, now);
|
|
2028
|
+
idleDevices = operation ? await collectIdleDevicesAsync(cfg, sims, deadProjects, now, env) : collectIdleDevices(cfg, sims, deadProjects, now);
|
|
2029
|
+
if (simsChecked || avdsChecked) {
|
|
2030
|
+
const lists = {
|
|
2031
|
+
sims,
|
|
2032
|
+
avds: [...registeredAvds]
|
|
2033
|
+
};
|
|
2034
|
+
listed = lists;
|
|
2035
|
+
let unmeasured = 0;
|
|
2036
|
+
deviceDisk = ownedDeviceDisk(await ownedDeviceUsage((_target, workspace) => {
|
|
2037
|
+
if (workspace) unmeasured++;
|
|
2038
|
+
}, {
|
|
2039
|
+
sims: () => lists.sims,
|
|
2040
|
+
avds: () => lists.avds,
|
|
2041
|
+
avdDirectory: deps.avdDirectory ?? ownedAvdDirectory,
|
|
2042
|
+
size: async (dir) => await avdSize(dir, { timeoutMs: 5e3 }) || null
|
|
2043
|
+
}), { android: unmeasured });
|
|
2044
|
+
}
|
|
2313
2045
|
staleDeviceRecords = findStaleDeviceRecords({
|
|
2314
2046
|
config: cfg,
|
|
2315
2047
|
sims,
|
|
@@ -2318,7 +2050,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2318
2050
|
simsChecked,
|
|
2319
2051
|
avdsChecked
|
|
2320
2052
|
});
|
|
2321
|
-
if (olderThan !== null) staleDevices = withAndroidAvdSizes(findStaleProjectDevices({
|
|
2053
|
+
if (olderThan !== null) staleDevices = await withAndroidAvdSizes(findStaleProjectDevices({
|
|
2322
2054
|
config: cfg,
|
|
2323
2055
|
sims,
|
|
2324
2056
|
avds,
|
|
@@ -2328,13 +2060,20 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2328
2060
|
deadProjects
|
|
2329
2061
|
}), {
|
|
2330
2062
|
avdDirectory: deps.avdDirectory ?? ownedAvdDirectory,
|
|
2331
|
-
size:
|
|
2063
|
+
size: avdSize
|
|
2332
2064
|
});
|
|
2333
2065
|
}
|
|
2334
2066
|
const locks = listBuildLocks();
|
|
2335
2067
|
const slots = listBuildSlots();
|
|
2336
2068
|
const goneWorkspaceDirs = [...workspaceDirs.orphaned.map((entry) => entry.dir), ...deadProjects.map(workspaceDir)];
|
|
2337
2069
|
const deviceLeases = collectDeviceLeases(now);
|
|
2070
|
+
const worktreeSweep = await measureWorktreeSweep(await collectWorktreeSweep({
|
|
2071
|
+
idle: worktrees,
|
|
2072
|
+
olderThan,
|
|
2073
|
+
now,
|
|
2074
|
+
refresh,
|
|
2075
|
+
operation
|
|
2076
|
+
}), operation);
|
|
2338
2077
|
return {
|
|
2339
2078
|
skipped,
|
|
2340
2079
|
deadProjects,
|
|
@@ -2370,7 +2109,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2370
2109
|
deviceSweepNotices,
|
|
2371
2110
|
easSessionSweep,
|
|
2372
2111
|
caches,
|
|
2373
|
-
workspaceOutputs: collectWorkspaceOutputs({
|
|
2112
|
+
workspaceOutputs: await collectWorkspaceOutputs({
|
|
2374
2113
|
olderThan,
|
|
2375
2114
|
now,
|
|
2376
2115
|
exclude: goneWorkspaceDirs
|
|
@@ -2381,17 +2120,22 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2381
2120
|
olderThan,
|
|
2382
2121
|
now
|
|
2383
2122
|
}, goneWorkspaceDirs),
|
|
2384
|
-
worktreeSweep
|
|
2385
|
-
|
|
2386
|
-
|
|
2123
|
+
worktreeSweep,
|
|
2124
|
+
finishedDevices: listed ? collectFinishedDevices({
|
|
2125
|
+
config: cfg,
|
|
2126
|
+
...listed,
|
|
2127
|
+
sweep: worktreeSweep,
|
|
2387
2128
|
now
|
|
2388
|
-
}),
|
|
2129
|
+
}) : [],
|
|
2130
|
+
ownedDeviceDisk: deviceDisk,
|
|
2389
2131
|
memory: await collectGcMemory({
|
|
2390
2132
|
watchman: true,
|
|
2391
2133
|
gradle: true
|
|
2392
|
-
}),
|
|
2134
|
+
}, env, operation),
|
|
2393
2135
|
agentDeviceUsage,
|
|
2394
2136
|
swiftpmCacheUsage,
|
|
2137
|
+
machineCaches,
|
|
2138
|
+
tempLeftovers,
|
|
2395
2139
|
cacheScope: null,
|
|
2396
2140
|
olderThan,
|
|
2397
2141
|
all
|
|
@@ -2446,11 +2190,17 @@ async function maintenanceGuardedSweep(opts, deps) {
|
|
|
2446
2190
|
if (claim) (0, ownership_claim_exports.releaseClaim)(claim);
|
|
2447
2191
|
}
|
|
2448
2192
|
}
|
|
2449
|
-
async function sweep(opts, deps) {
|
|
2193
|
+
async function sweep(opts, deps, reportOnly = false) {
|
|
2194
|
+
const core = (dependencies) => runGcCore(opts, dependencies, reportOnly);
|
|
2450
2195
|
if (opts.cache && (opts.worktrees || opts.idle !== void 0)) {
|
|
2451
2196
|
const flag = opts.worktrees ? "--worktrees" : "--idle";
|
|
2452
2197
|
const message = opts.worktrees ? "--cache acts only on the named caches, and --worktrees sweeps linked worktrees." : "--cache acts only on the named caches, and --idle shuts down idle owned devices.";
|
|
2453
2198
|
const remedy = `Run \`stim gc ${flag}${opts.worktrees ? "" : " <duration>"}\` and \`stim gc --cache <name>\` separately.`;
|
|
2199
|
+
if (reportOnly) return {
|
|
2200
|
+
code: "STIM_BAD_ARG",
|
|
2201
|
+
message,
|
|
2202
|
+
remedy
|
|
2203
|
+
};
|
|
2454
2204
|
console.error(chalk.red(message));
|
|
2455
2205
|
console.error(chalk.dim(remedy));
|
|
2456
2206
|
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
@@ -2465,6 +2215,11 @@ async function sweep(opts, deps) {
|
|
|
2465
2215
|
if (memoryKind && opts.olderThan !== void 0) {
|
|
2466
2216
|
const message = `--older-than does not apply to --cache ${memoryKind}, which acts on running processes.`;
|
|
2467
2217
|
const remedy = `Run \`stim gc --cache ${memoryKind}\` without --older-than.`;
|
|
2218
|
+
if (reportOnly) return {
|
|
2219
|
+
code: "STIM_BAD_ARG",
|
|
2220
|
+
message,
|
|
2221
|
+
remedy
|
|
2222
|
+
};
|
|
2468
2223
|
console.error(chalk.red(message));
|
|
2469
2224
|
console.error(chalk.dim(remedy));
|
|
2470
2225
|
console.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
@@ -2476,8 +2231,11 @@ async function sweep(opts, deps) {
|
|
|
2476
2231
|
};
|
|
2477
2232
|
}
|
|
2478
2233
|
const poolError = parkedMaxSetting("ios").error || parkedMaxSetting("android").error;
|
|
2479
|
-
if (poolError)
|
|
2480
|
-
|
|
2234
|
+
if (poolError) {
|
|
2235
|
+
if (reportOnly) phase("note", `${poolError} ${POOL_SETTING_REMEDY}`, deps.operation);
|
|
2236
|
+
else console.error(chalk.yellow(`${poolError} ${POOL_SETTING_REMEDY}`));
|
|
2237
|
+
}
|
|
2238
|
+
if (opts.cache) return core({
|
|
2481
2239
|
...deps,
|
|
2482
2240
|
precollectedEasSessionSweep: {
|
|
2483
2241
|
projectScope: null,
|
|
@@ -2488,8 +2246,8 @@ async function sweep(opts, deps) {
|
|
|
2488
2246
|
});
|
|
2489
2247
|
let projectRoot = null;
|
|
2490
2248
|
try {
|
|
2491
|
-
projectRoot = (deps.findProjectRoot ?? findProjectRoot)(process.cwd());
|
|
2492
|
-
if (!projectRoot) return
|
|
2249
|
+
projectRoot = (deps.findProjectRoot ?? findProjectRoot$1)(deps.operation?.cwd ?? process.cwd());
|
|
2250
|
+
if (!projectRoot) return core({
|
|
2493
2251
|
...deps,
|
|
2494
2252
|
precollectedEasSessionSweep: {
|
|
2495
2253
|
projectScope: null,
|
|
@@ -2498,7 +2256,7 @@ async function sweep(opts, deps) {
|
|
|
2498
2256
|
deletionSafe: false
|
|
2499
2257
|
}
|
|
2500
2258
|
});
|
|
2501
|
-
if (!(deps.detectIsExpo ?? detectIsExpo)(projectRoot)) return
|
|
2259
|
+
if (!(deps.detectIsExpo ?? detectIsExpo)(projectRoot)) return core({
|
|
2502
2260
|
...deps,
|
|
2503
2261
|
precollectedEasSessionSweep: {
|
|
2504
2262
|
projectScope: projectRoot,
|
|
@@ -2508,7 +2266,7 @@ async function sweep(opts, deps) {
|
|
|
2508
2266
|
}
|
|
2509
2267
|
});
|
|
2510
2268
|
} catch (error) {
|
|
2511
|
-
return
|
|
2269
|
+
return core({
|
|
2512
2270
|
...deps,
|
|
2513
2271
|
precollectedEasSessionSweep: {
|
|
2514
2272
|
projectScope: projectRoot,
|
|
@@ -2524,7 +2282,7 @@ async function sweep(opts, deps) {
|
|
|
2524
2282
|
try {
|
|
2525
2283
|
easSessionSweep = await withProjectLock(projectRoot, () => {
|
|
2526
2284
|
sweepStarted = true;
|
|
2527
|
-
return withRemoteSessionGcLocks(projectRoot, deps, (coordinatedDeps) =>
|
|
2285
|
+
return withRemoteSessionGcLocks(projectRoot, deps, (coordinatedDeps) => collectEasSessionSweep(loadConfig$1(), coordinatedDeps));
|
|
2528
2286
|
}, {
|
|
2529
2287
|
waitMs: 0,
|
|
2530
2288
|
ownerPurpose: "EAS orphan sweep",
|
|
@@ -2538,7 +2296,7 @@ async function sweep(opts, deps) {
|
|
|
2538
2296
|
deletionSafe: false
|
|
2539
2297
|
};
|
|
2540
2298
|
}
|
|
2541
|
-
return
|
|
2299
|
+
return core({
|
|
2542
2300
|
...deps,
|
|
2543
2301
|
precollectedEasSessionSweep: easSessionSweep
|
|
2544
2302
|
});
|
|
@@ -2589,13 +2347,18 @@ async function pruneDeadProjects(deadProjects) {
|
|
|
2589
2347
|
}
|
|
2590
2348
|
return deleteFailures;
|
|
2591
2349
|
}
|
|
2592
|
-
async function attachMaintenancePreview(report, deleting, cache) {
|
|
2593
|
-
if (!deleting) report.maintenance = await previewMaintenance({
|
|
2350
|
+
async function attachMaintenancePreview(report, deleting, cache, refresh, operation) {
|
|
2351
|
+
if (!deleting) report.maintenance = await previewMaintenance({
|
|
2352
|
+
devices: !cache,
|
|
2353
|
+
refresh,
|
|
2354
|
+
operation
|
|
2355
|
+
});
|
|
2594
2356
|
}
|
|
2595
2357
|
function scopedDryRunAction(cache) {
|
|
2596
2358
|
const memory = memoryCacheKind(cache);
|
|
2597
2359
|
if (memory === "watchman") return "remove the stale roots and shut down the unused watchman";
|
|
2598
2360
|
if (memory === "gradle-daemons") return "stop the idle daemons";
|
|
2361
|
+
if (includesGradleVersions(cache)) return "delete the Gradle versions marked deletable";
|
|
2599
2362
|
return includesParkedDevices(cache) ? "erase the parked devices" : "empty the caches";
|
|
2600
2363
|
}
|
|
2601
2364
|
function dryRunHint(cache, all, actionable, caches) {
|
|
@@ -2604,45 +2367,64 @@ function dryRunHint(cache, all, actionable, caches) {
|
|
|
2604
2367
|
if (caches) return "Pass --delete --cache all to empty the caches above, or --delete --older-than <days> to trim them.";
|
|
2605
2368
|
return null;
|
|
2606
2369
|
}
|
|
2370
|
+
function scopedWorkPending(cache, report) {
|
|
2371
|
+
return memoryWorkPending(cache, report.memory) || gradleVersionsPending(report.gradleVersions);
|
|
2372
|
+
}
|
|
2607
2373
|
function pruneStatusCaches(report) {
|
|
2608
2374
|
if (report.cacheScope !== null) return 0;
|
|
2609
2375
|
return deleteStaleStatusCaches(collectStaleStatusCaches(listMountedVolumes()).stale);
|
|
2610
2376
|
}
|
|
2611
|
-
function reclaimParkedDevices(report, deps) {
|
|
2612
|
-
return report.cacheScope ? eraseParkedDevices(report.parkedSims, report.parkedAvds) : deleteParkedSims(report.parkedSims, deps) + deleteParkedAvds(report.parkedAvds);
|
|
2377
|
+
async function reclaimParkedDevices(report, deps) {
|
|
2378
|
+
return report.cacheScope ? await eraseParkedDevices(report.parkedSims, report.parkedAvds) : await deleteParkedSims(report.parkedSims, deps) + await deleteParkedAvds(report.parkedAvds);
|
|
2613
2379
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2380
|
+
function reportActionable(report) {
|
|
2381
|
+
const { deadProjects, invalidProjects, orphanedDevices, staleDevices, staleDeviceRecords, buildLocks, buildSlots, deviceLeases, easSessionSweep, caches, olderThan, all, cacheScope: cache } = report;
|
|
2382
|
+
return 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.tempLeftovers?.entries.some((entry) => entry.willDelete)) || Boolean(report.worktreeSweep?.worktrees.some((entry) => !entry.skipped)) || report.parkedSims.length > 0 || report.parkedAvds.length + report.parkedHostedDevices.filter((device) => device.listed !== false).length > 0 || orphanedDevices.length > 0 || staleDevices.length + report.finishedDevices.length > 0 || staleDeviceRecords.length > 0 || report.staleLedgerEntries.length + report.staleEasLedgerEntries.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 || scopedWorkPending(cache, report));
|
|
2383
|
+
}
|
|
2384
|
+
async function runGcCore(opts, deps, reportOnly = false) {
|
|
2385
|
+
if (!reportOnly) takeGcResults();
|
|
2616
2386
|
const olderThan = typeof opts.olderThan === "number" ? opts.olderThan : null;
|
|
2617
2387
|
const cache = typeof opts.cache === "string" && opts.cache.trim() ? opts.cache : null;
|
|
2388
|
+
const refresh = opts.delete === true;
|
|
2618
2389
|
const report = await collectGcReport({
|
|
2619
2390
|
olderThan,
|
|
2620
2391
|
cache,
|
|
2621
|
-
worktrees: Boolean(opts.worktrees)
|
|
2392
|
+
worktrees: Boolean(opts.worktrees),
|
|
2393
|
+
refresh
|
|
2622
2394
|
}, deps);
|
|
2623
2395
|
if (cache && report.caches.length === 0 && report.workspaceOutputs === null && !selectsBeyondCaches(cache)) {
|
|
2624
|
-
const names = [...new Set(discoverCaches().map((c) => c.name))];
|
|
2396
|
+
const names = [...new Set(discoverCaches(deps.operation?.env ?? process.env, deps.operation?.cwd).map((c) => c.name))];
|
|
2625
2397
|
const message = `No shared cache carries "${cache}" in its name or directory.`;
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2398
|
+
if (!reportOnly) {
|
|
2399
|
+
console.log(chalk.yellow(message));
|
|
2400
|
+
if (names.length) console.log(chalk.dim(`Caches on this machine: ${names.join(", ")}`));
|
|
2401
|
+
if (opts.json) process.exitCode = 1;
|
|
2402
|
+
}
|
|
2629
2403
|
return {
|
|
2630
2404
|
code: "STIM_BAD_ARG",
|
|
2631
2405
|
message,
|
|
2632
2406
|
remedy: names.length ? `Pass --cache all or a name from: ${names.join(", ")}.` : "No shared cache was found on this machine."
|
|
2633
2407
|
};
|
|
2634
2408
|
}
|
|
2635
|
-
await attachMaintenancePreview(report, opts.delete === true, cache);
|
|
2409
|
+
await attachMaintenancePreview(report, opts.delete === true, cache, refresh, deps.operation);
|
|
2636
2410
|
const refusal = archiveRefusal(report.archives);
|
|
2637
|
-
if (refusal !== null)
|
|
2411
|
+
if (refusal !== null) {
|
|
2412
|
+
if (!reportOnly) {
|
|
2413
|
+
process.exitCode = 1;
|
|
2414
|
+
console.error(chalk.red(refusal.message));
|
|
2415
|
+
console.error(chalk.dim(refusal.remedy));
|
|
2416
|
+
console.error(chalk.red(`failed: ${refusal.code}`));
|
|
2417
|
+
}
|
|
2418
|
+
return refusal;
|
|
2419
|
+
}
|
|
2638
2420
|
const all = report.all;
|
|
2639
|
-
for (const line of formatGcReport(report)) console.log(line);
|
|
2421
|
+
if (!reportOnly) for (const line of formatGcReport(report)) console.log(line);
|
|
2640
2422
|
const { deadProjects, invalidProjects, orphanedDevices, staleDevices, staleDeviceRecords, buildLocks, buildSlots, deviceLeases, easSessionSweep, caches } = report;
|
|
2641
|
-
const actionable =
|
|
2423
|
+
const actionable = reportActionable(report);
|
|
2642
2424
|
const idle = opts.idle ?? null;
|
|
2643
|
-
const idleFailures = idle === null ? 0 : shutDownIdleDevices(report.idleDevices, idle, () => collectIdleDevices(loadConfig$1(), listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }), report.deadProjects)).failures;
|
|
2425
|
+
const idleFailures = idle === null ? 0 : (await shutDownIdleDevices(report.idleDevices, idle, () => collectIdleDevices(loadConfig$1(), listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }), report.deadProjects))).failures;
|
|
2644
2426
|
if (idleFailures) process.exitCode = 1;
|
|
2645
|
-
const inventory = opts.json && !opts.delete && idle === null && report.cacheScope === null ? collectInventory(loadConfig$1(), deadProjects) : null;
|
|
2427
|
+
const inventory = opts.json && !opts.delete && idle === null && report.cacheScope === null ? await collectInventory(loadConfig$1(), deadProjects, deps.operation?.env ?? process.env) : null;
|
|
2646
2428
|
const payload = (failures) => ({
|
|
2647
2429
|
mode: opts.delete ? "delete" : "dry-run",
|
|
2648
2430
|
idle,
|
|
@@ -2653,12 +2435,12 @@ async function runGcCore(opts, deps) {
|
|
|
2653
2435
|
failures,
|
|
2654
2436
|
sections: gcReportSections(report),
|
|
2655
2437
|
...report.maintenance ? { maintenance: report.maintenance } : {},
|
|
2656
|
-
results: takeGcResults(),
|
|
2438
|
+
results: reportOnly ? [] : takeGcResults(),
|
|
2657
2439
|
inventory
|
|
2658
2440
|
});
|
|
2659
2441
|
if (!opts.delete) {
|
|
2660
2442
|
const hint = dryRunHint(cache, all, actionable, caches.length > 0);
|
|
2661
|
-
if (hint) console.log(chalk.dim(`\n${hint}`));
|
|
2443
|
+
if (hint && !reportOnly) console.log(chalk.dim(`\n${hint}`));
|
|
2662
2444
|
return payload(idle === null ? null : idleFailures);
|
|
2663
2445
|
}
|
|
2664
2446
|
let deleteFailures = idleFailures;
|
|
@@ -2670,16 +2452,22 @@ async function runGcCore(opts, deps) {
|
|
|
2670
2452
|
olderThan,
|
|
2671
2453
|
now: Date.now()
|
|
2672
2454
|
});
|
|
2673
|
-
deleteFailures += reclaimParkedDevices(report, deps);
|
|
2455
|
+
deleteFailures += await reclaimParkedDevices(report, deps);
|
|
2674
2456
|
deleteFailures += await deleteParkedHostedDevices(report.parkedHostedDevices);
|
|
2675
2457
|
deleteFailures += await reclaimMemory(cache, report.memory);
|
|
2458
|
+
deleteFailures += await deleteGradleVersions(report.gradleVersions);
|
|
2676
2459
|
removeInvalidProjectEntries(invalidProjects);
|
|
2677
2460
|
deleteFailures += await pruneDeadProjects(deadProjects);
|
|
2678
2461
|
deleteFailures += await deleteOrphanedWorkspaces(report.orphanedWorkspaces);
|
|
2679
|
-
deleteFailures += deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords);
|
|
2462
|
+
deleteFailures += await deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords);
|
|
2463
|
+
deleteFailures += await deleteFinishedDevices(report);
|
|
2680
2464
|
deleteFailures += forgetStaleLedgerEntries(report.staleLedgerEntries);
|
|
2681
2465
|
deleteFailures += forgetStaleEasLedgerEntries(report.staleEasLedgerEntries, listMountedVolumes(), deps);
|
|
2682
2466
|
deleteFailures += pruneStatusCaches(report);
|
|
2467
|
+
if (report.tempLeftovers) deleteFailures += (await deleteTempLeftovers({
|
|
2468
|
+
env: deps.operation?.env ?? process.env,
|
|
2469
|
+
signal: deps.operation?.signal
|
|
2470
|
+
})).failures;
|
|
2683
2471
|
for (const lock of buildLocks.stale) {
|
|
2684
2472
|
const cleared = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
2685
2473
|
root: lock.path,
|
|
@@ -2758,11 +2546,11 @@ async function runGcCore(opts, deps) {
|
|
|
2758
2546
|
return payload(deleteFailures);
|
|
2759
2547
|
}
|
|
2760
2548
|
function gcCommand(program) {
|
|
2761
|
-
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,
|
|
2549
|
+
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, the memory of watchman and of the Gradle and Kotlin daemons, and recognized leftovers in TMPDIR. 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) => {
|
|
2762
2550
|
const n = parseInt(v, 10);
|
|
2763
2551
|
if (!Number.isFinite(n) || String(n) !== String(v).trim()) throw new InvalidArgumentError("must be a whole number of days, e.g. --older-than 30");
|
|
2764
2552
|
return n;
|
|
2765
|
-
}).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,
|
|
2553
|
+
}).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, the Gradle and Kotlin compile daemons with --cache gradle-daemons, or the Gradle version caches and wrapper distributions no registered project pins with --cache gradle-versions; archived, archived:<id>, archived-logs, archived-recordings and archived-agent select archived history explicitly, and the three kind selectors also take :<id> (archived-logs:<id>) to clear one kind of one archive; --cache all excludes archives. all, workspaces, recordings, parked, archived, archived:<id>, archived-logs, archived-recordings, archived-agent, their :<id> forms, watchman, gradle-daemons and gradle-versions are reserved names that never select a single cache, and --cache all never includes watchman, gradle-daemons or gradle-versions. 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, including the :<id> of a kind selector, ignore --older-than; archived and per-kind archive selectors without an id 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) => {
|
|
2766
2554
|
if (!v.trim()) throw new InvalidArgumentError("must name a cache, e.g. --cache \"compilation cache\"");
|
|
2767
2555
|
return v;
|
|
2768
2556
|
}).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) => {
|
|
@@ -2774,4 +2562,4 @@ function gcCommand(program) {
|
|
|
2774
2562
|
});
|
|
2775
2563
|
}
|
|
2776
2564
|
//#endregion
|
|
2777
|
-
export {
|
|
2565
|
+
export { formatGcReport as a, runGc as i, gcCommand as n, readGcReport as r, collectGcReport as t };
|