stim 1.21.0 → 1.22.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 +60 -7
- package/dist/{acquire-artifact-CD4lbiWE.mjs → acquire-artifact-Co2k4f7O.mjs} +21 -3
- package/dist/{act-DHAw2A2S.mjs → act-DsuqEkiM.mjs} +298 -164
- package/dist/{activity-BzVfHSrY.mjs → activity-CMLmzp7o.mjs} +33 -12
- package/dist/{agent-device-usage-output-zh1vhbLp.mjs → agent-device-usage-output-DUFaTMdd.mjs} +7 -7
- package/dist/{android-BLgbNqox.mjs → android-ByRMUoiJ.mjs} +66 -52
- package/dist/{android-DUYkNF4u.mjs → android-C7sVmyI9.mjs} +160 -59
- package/dist/{android-CV_GCELZ.mjs → android-DGkNqa_c.mjs} +5 -5
- package/dist/{android-BEJ8Jpl7.mjs → android-DR5lZ2Az.mjs} +2 -2
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{android-cas-Ct5b1xCK.mjs → android-cas-ngferVq2.mjs} +3 -3
- package/dist/{client-B7QVOlCr.d.mts → android-project-DVWyCbWU.d.mts} +145 -7
- package/dist/api.d.mts +132 -118
- package/dist/api.mjs +239 -42
- package/dist/{artifact-B8T3S5gi.mjs → artifact-8EY8SAPw.mjs} +40 -35
- package/dist/{artifact-DAuZ-FNU.mjs → artifact-DJPy518Y.mjs} +69 -40
- package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
- package/dist/{browser-web-QQzVjz3O.mjs → browser-web-Bcv99PBM.mjs} +187 -59
- package/dist/{budget-Bs6SxxzO.mjs → budget-Cvwn54qV.mjs} +830 -699
- package/dist/{build-CoPzQRNw.mjs → build-CabC3G23.mjs} +34 -27
- package/dist/{build-cache-DmyR9Zzc.mjs → build-cache-Dv7RN200.mjs} +4 -4
- package/dist/{build-plan-CaFskB7_.mjs → build-plan-DUC8Ka9y.mjs} +74 -14
- package/dist/{cache-manifest-DkkqguoK.mjs → cache-manifest-DSeQjt-Q.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-BJRZ9pG3.mjs → ccache-Btwc36-I.mjs} +6 -6
- package/dist/{cdp-CWmIUMbw.mjs → cdp-3fwICjzi.mjs} +1 -1
- package/dist/{chrome-B6tfCta1.mjs → chrome-Bbw689-6.mjs} +1 -1
- package/dist/{cli-0DYf8QhQ.mjs → cli-CHZa9Lvk.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-UXD70ZbM.mjs → client-OyRoEuSr.mjs} +218 -47
- package/dist/collector-run.d.mts +1 -1
- package/dist/collector-run.mjs +6 -6
- package/dist/{command-output-B1UrjZHL.mjs → command-output-gwpm28fu.mjs} +1 -1
- package/dist/{config-CYcBF478.mjs → config-DlrsduwG.mjs} +17 -11
- package/dist/{created-devices-CqeBlrEn.mjs → created-devices-x9rN6bhs.mjs} +13 -3
- package/dist/{dependency-state-BbKQW83h.mjs → dependency-state-1I9KZT1Y.mjs} +2 -2
- package/dist/{detached-entry-DFRyG1_o.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
- package/dist/{dev-client-UOeESKm6.mjs → dev-client-BSDR5h3F.mjs} +2 -2
- package/dist/{device-ExJq8Ei7.mjs → device-B-L3Fbxy.mjs} +71 -14
- package/dist/{device-DCxH7_By.mjs → device-DklWZLqb.mjs} +16 -16
- package/dist/{device-capacity-CMlqEcvw.mjs → device-capacity-DK5_EwRS.mjs} +9 -10
- package/dist/device-host-worker.mjs +25 -32
- package/dist/{device-ios-DRUeKaFV.mjs → device-ios-DoB6KFCO.mjs} +39 -16
- package/dist/{device-lease-run-C8raGFoP.mjs → device-lease-run-xJPQ_QQG.mjs} +5 -5
- package/dist/{device-pool-DMRpVX9a.mjs → device-pool-BMkdsefq.mjs} +3 -3
- package/dist/{doctor-DTvaDOda.mjs → doctor-Bo7BXLux.mjs} +24 -37
- package/dist/{doctor-Dhqebm0q.mjs → doctor-D5_EzwfA.mjs} +19 -16
- package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-BLHCYxzi.mjs → doctor-thjWAV0z.mjs} +1 -1
- package/dist/{doctor-watchman-Cv1F10Kj.mjs → doctor-watchman-N2wDotwQ.mjs} +5 -5
- package/dist/{eas-build-BomfRnFT.mjs → eas-build-CwknaQYj.mjs} +4 -4
- package/dist/{eas-fallback-DJwZpy0c.mjs → eas-fallback-CSgAulr5.mjs} +27 -18
- package/dist/{eas-session-ledger-op6A54yi.mjs → eas-session-ledger-DlY_serQ.mjs} +8 -7
- package/dist/{error-diagnostics-Bf0rZvG9.mjs → error-diagnostics-DBWO2rxE.mjs} +10 -10
- package/dist/{exec-_UbtCQcG.mjs → exec-DEoAhcze.mjs} +155 -47
- package/dist/{exec-H20xSg2T.d.mts → exec-DnYc4HGH.d.mts} +17 -4
- package/dist/{gc-_-dCXdyL.mjs → gc-CXxN-4SE.mjs} +587 -313
- package/dist/gc-DnNDQyaP.mjs +3 -0
- package/dist/{guide-Yqwt2bd6.mjs → guide-Br0LEfKy.mjs} +239 -43
- package/dist/{hosted-android-Bw-honsd.mjs → hosted-android-B-9f-jkd.mjs} +6 -6
- package/dist/{hosted-client-DB_gBPfY.mjs → hosted-client-DZAPrcro.mjs} +4 -4
- package/dist/{hosted-ios-CUvOf4Vr.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
- package/dist/{hosted-logs-DL_P5AdN.mjs → hosted-logs-FIRx9X2f.mjs} +5 -5
- package/dist/{hosted-macos-QI141yKk.mjs → hosted-macos-_awYSpfv.mjs} +21 -12
- package/dist/{hosted-native-Ksyjgzq-.mjs → hosted-native-BdzE668-.mjs} +27 -14
- package/dist/{idle-BVeRGroT.d.mts → idle-C4cZ757K.d.mts} +3 -3
- package/dist/{idle-shutdown-DaKmZi0z.mjs → idle-shutdown-DDg3SdvY.mjs} +7 -7
- package/dist/ios-CDvlmUs7.d.mts +24 -0
- package/dist/{ios-CQ68hG9s.mjs → ios-Ds1clbIt.mjs} +8 -8
- package/dist/{ios-device-CwFMehUS.mjs → ios-device--h_-8ENa.mjs} +3 -3
- package/dist/{ios-device-CXGP1bsD.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
- package/dist/{ios-CpByZy12.mjs → ios-hcBpnEGk.mjs} +81 -34
- package/dist/{ios-ubFsBzwq.mjs → ios-pm4nL6By.mjs} +59 -50
- package/dist/{ios-project-WuwwBdvW.mjs → ios-project-CZhKODF3.mjs} +5 -5
- package/dist/{ios-signing-DTTIChGs.mjs → ios-signing-BtAffm0A.mjs} +2 -2
- package/dist/{ios-state-DvFX-K_l.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
- package/dist/{ios-qNJt4dFm.mjs → ios-xtlRHaeB.mjs} +1 -1
- package/dist/{js-swap-Cc1dttps.mjs → js-swap-Bwhh4S7V.mjs} +7 -29
- package/dist/{launch-Zq2mOSDy.mjs → launch-BjxemNpK.mjs} +40 -39
- package/dist/{launch-CD1yVc7U.mjs → launch-CCzXlc8r.mjs} +16 -16
- package/dist/{logs-BS00Anxf.mjs → logs-BfwwadsZ.mjs} +151 -24
- package/dist/{logs-CMm05HxN.mjs → logs-jTS_lHSo.mjs} +10 -17
- package/dist/{macos-B65--pPd.mjs → macos-DVlSSyUg.mjs} +1 -1
- package/dist/{macos-C-0qJ6zq.mjs → macos-NfV2D7dO.mjs} +143 -46
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +8 -8
- package/dist/{metro-DEXpIhHC.mjs → metro-DiXtA3Ty.mjs} +59 -37
- package/dist/{metro-gateway-BMJNlj3E.mjs → metro-gateway-CKZqKqx7.mjs} +120 -22
- package/dist/{named-ports-BWTzt6mu.mjs → named-ports-DkEzOfzO.mjs} +40 -33
- package/dist/{native-android-JACyjTZs.mjs → native-android-CqgnOgsT.mjs} +43 -242
- package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
- package/dist/{native-gradle-inputs-Bep9JcSF.mjs → native-gradle-inputs-Dnh84Zbd.mjs} +3 -129
- package/dist/{native-run-DY_RLx3C.mjs → native-run-NwUuxbkg.mjs} +4 -5
- package/dist/{native-runtime-Dg1Q7tfF.mjs → native-runtime-B7KIafpM.mjs} +8 -7
- package/dist/native-source-Y0yLhFyQ.mjs +133 -0
- package/dist/{native-xcode-inputs-CB2iQeWq.mjs → native-xcode-inputs-D_VOfFL7.mjs} +2 -3
- package/dist/{native-xcode-ios-CXX39F7o.mjs → native-xcode-ios-BYDOHWLN.mjs} +27 -32
- package/dist/offload-worker.d.mts +65 -2
- package/dist/offload-worker.mjs +305 -73
- package/dist/operation-exec-BW0em61G.mjs +171 -0
- package/dist/{ownership-DEmM5vhh.mjs → ownership-C5fo5Edb.mjs} +3 -3
- package/dist/{page-CPaHeYqj.mjs → page-DBL2PiUU.mjs} +8 -4
- package/dist/{placement-log-BqGbTa61.mjs → placement-log-BnGgJRRZ.mjs} +1 -1
- package/dist/{plan-placement-Bwj-IjY1.mjs → plan-placement-DIsMtN-d.mjs} +12 -8
- package/dist/{ports-D-MmCTdv.mjs → ports-Cy1RkmuQ.mjs} +3 -3
- package/dist/{prebuild-uD_ipCxF.mjs → prebuild-D4JoEAEs.mjs} +49 -14
- package/dist/{project-BZNjmW_8.mjs → project-wM_XFTvE.mjs} +16 -16
- package/dist/{pull-request-Lfetn8Kr.mjs → pull-request-W1IuLXDt.mjs} +102 -20
- package/dist/{pull-request-zjCSAryH.d.mts → pull-request-eODyIqZF.d.mts} +6 -2
- package/dist/pull-requests.d.mts +2 -2
- package/dist/pull-requests.mjs +2 -2
- package/dist/{react-native-android-DuzH0cn3.mjs → react-native-android-6OZ7MSM7.mjs} +71 -48
- package/dist/{react-native-build-rhk4smwQ.mjs → react-native-build-Dvd8hC21.mjs} +8 -8
- package/dist/{react-native-doctor-CQpuIMso.mjs → react-native-doctor-DsoSF1Y9.mjs} +7 -7
- package/dist/{react-native-ios-Bhq1rJeV.mjs → react-native-ios-4u1Rn-GR.mjs} +40 -26
- package/dist/{recordings-DYteQSu4.mjs → recordings-CQmfzAh7.mjs} +1 -1
- package/dist/registration-BcRgF6Ox.mjs +51 -0
- package/dist/{reload-CmTRFJaX.mjs → reload-BzkYabB-.mjs} +16 -15
- package/dist/{reload-DzjSl5P_.mjs → reload-D7AVlb8N.mjs} +1 -1
- package/dist/{remote-ByYkXdGD.mjs → remote-DjGDmlTZ.mjs} +18 -15
- package/dist/{result-DNiS-tTP.mjs → result-D2Ykz_iD.mjs} +24 -23
- package/dist/{run-D23HmJMs.mjs → run-DidHghD1.mjs} +3 -2
- package/dist/{selection-CSl9i5qU.mjs → selection-DSMbWEX2.mjs} +6 -5
- package/dist/{server-bare-CxTrvDg8.mjs → server-bare-CafzuyFB.mjs} +2 -2
- package/dist/{server-command-CtzloH3v.mjs → server-command-C6QjeupS.mjs} +5 -5
- package/dist/{server-expo-DT_F_uVI.mjs → server-expo-CrjB29pq.mjs} +7 -5
- package/dist/server-expo-D58c9L0M.mjs +2 -0
- package/dist/{settings-CsJDEXgK.mjs → settings-C7O8A6ZI.mjs} +223 -243
- package/dist/{settings-T1gtc3bf.mjs → settings-DpR-1Wnn.mjs} +1 -1
- package/dist/{settings-write-HyVZ8eQ8.mjs → settings-write-CzD60rOD.mjs} +7 -6
- package/dist/settings.schema.json +13 -0
- package/dist/{simslim-Cgk17SXV.mjs → simslim-BNckWpXi.mjs} +2 -2
- package/dist/{slot-launch-DF8VfT64.mjs → slot-launch-CbfmNgM6.mjs} +6 -6
- package/dist/{stage-BIS0ydsV.mjs → stage-Bps5oius.mjs} +21 -4
- package/dist/{start-RChynRDp.mjs → start-Br-oxqMD.mjs} +1 -1
- package/dist/{start-7lpH7hmN.mjs → start-EojN4WFz.mjs} +622 -393
- package/dist/{state-C4ZtUisW.mjs → state--smjFSTL.mjs} +22 -7
- package/dist/{state-XMziexv2.mjs → state-08I3hw3_.mjs} +2 -2
- package/dist/{stats-53T7OTb9.mjs → stats-D3GerlFW.mjs} +5 -5
- package/dist/{stats-report-BFfMpWWF.mjs → stats-report-BzaPrRm6.mjs} +1 -1
- package/dist/{status-DqEUSSxs.mjs → status-BRROykC8.mjs} +2 -2
- package/dist/{status-CSi6tlRq.mjs → status-EvrK72L2.mjs} +1 -1
- package/dist/{status-BL7jVyAB.mjs → status-WBDajew2.mjs} +43 -29
- package/dist/{status-watch-GDCfqpTE.mjs → status-watch-Bg_u-pwR.mjs} +48 -16
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-C12we7lz.mjs → stim-desktop-D5OGpYZM.mjs} +2 -2
- package/dist/{stop-Bk_1qiV6.mjs → stop-DTZ9uTw3.mjs} +2 -2
- package/dist/{stop-D7h6jAXQ.mjs → stop-Dky10AEJ.mjs} +31 -22
- package/dist/{stop-CPzlM1L-.mjs → stop-Mv6W9FAk.mjs} +15 -16
- package/dist/supervisor-run.d.mts +3 -3
- package/dist/supervisor-run.mjs +28 -36
- package/dist/{support-Dlu37g6H.mjs → support-CNioewsd.mjs} +10 -10
- package/dist/{support-67vraa1Q.mjs → support-yUiOjnVY.mjs} +8 -8
- package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
- package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
- package/dist/toolchain-BY8X8HXx.mjs +2 -0
- package/dist/{toolchain-Hp9sCpK4.mjs → toolchain-DpJMHq0n.mjs} +9 -6
- package/dist/{trigger-BAahxUPq.mjs → trigger-Cbyc41d_.mjs} +3 -3
- package/dist/trigger-yb_nFzPF.mjs +2 -0
- package/dist/{warm-progress-BHX5r0Ei.mjs → warm-progress-CA3TgT3-.mjs} +29 -16
- package/dist/web-CZ07gd77.mjs +2 -0
- package/dist/{web-CJmIj6AI.mjs → web-DKx6yoFA.mjs} +11 -10
- package/dist/web-run.mjs +11 -9
- package/dist/windows-report.mjs +1 -1
- package/dist/{workspace-state-Cjuigeeu.mjs → workspace-state-U08dEzhG.mjs} +3 -3
- package/dist/{workspaces-BccMFmGQ.mjs → workspaces-CpANmBlq.mjs} +594 -266
- package/dist/{worktree-DQ6Zi2Mv.mjs → worktree-CHecZ4Oq.mjs} +850 -551
- package/dist/worktree-ZqZVjD9K.mjs +2 -0
- package/dist/{xcode-Zlx3qjEs.mjs → xcode-BXn9-xYx.mjs} +5 -5
- package/package.json +7 -7
- package/shim/android-audio/Main.java +24 -0
- package/shim/android-audio/README.md +17 -0
- package/shim/android-audio.jar +0 -0
- package/dist/gc-C-WwL0yi.mjs +0 -3
- package/dist/git-C3iTsJaZ.mjs +0 -73
- package/dist/server-expo-C-90EhcG.mjs +0 -2
- package/dist/settings-DkdmlelE.d.mts +0 -15
- package/dist/swiftpm-macos-p8WZbxKU.mjs +0 -130
- package/dist/toolchain-DhKwp9iI.mjs +0 -2
- package/dist/trigger-Bf3uDcpv.mjs +0 -2
- package/dist/web-DknQETPs.mjs +0 -2
- package/dist/workspace-process-lock-1_1OoVW5.mjs +0 -57
- package/dist/worktree-CK2MpJW5.mjs +0 -2
|
@@ -1,42 +1,47 @@
|
|
|
1
|
-
import { f as
|
|
2
|
-
import { d as workspaceDir
|
|
3
|
-
import {
|
|
4
|
-
import { h as shortUdid, o as formatLongDuration, u as plural } from "./command-output-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
1
|
+
import { c as declareSpawnsOn, f as stopDeclaringSpawnsOn, g as process_identity_exports, h as withDrainingCancellation, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
|
|
2
|
+
import { d as workspaceDir } from "./paths-DQtTgsBf.mjs";
|
|
3
|
+
import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, O as withConfigLockAsync$1, P as formatBytes, R as volumeRootFor, S as removeProject, _ as loadConfig$1, d as getConfigDir, j as acquireAvdClaim } from "./config-DlrsduwG.mjs";
|
|
4
|
+
import { h as shortUdid, o as formatLongDuration, u as plural } from "./command-output-gwpm28fu.mjs";
|
|
5
|
+
import { f as throwIfCancelled, t as runOperationFile } from "./operation-exec-BW0em61G.mjs";
|
|
6
|
+
import { u as workspaceLastUsed } from "./workspace-state-U08dEzhG.mjs";
|
|
7
|
+
import { Et as listWorktreesAsync, It as resolveSourceCheckoutAsync } from "./settings-C7O8A6ZI.mjs";
|
|
8
|
+
import { i as findProjectRoot$1, v as detectIsExpo } from "./project-wM_XFTvE.mjs";
|
|
9
|
+
import { n as easMachineStateRoot$1, o as removeEasSessionClaimAsync, r as readEasSessionLedger$1 } from "./eas-session-ledger-DlY_serQ.mjs";
|
|
10
|
+
import { $ as includesRecordings, At as unverifiedDeviceCommand, C as collectIdleDevices, D as idleDeviceLines, Dt as findOrphanedDevices, Et as eraseParkedDevices, Ft as withRemoteSessionGcLocks, In as runProcessOperation, 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-Cvwn54qV.mjs";
|
|
10
11
|
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
11
|
-
import { B as
|
|
12
|
-
import { N as listBuildLocks, k as removeExpiredLease } from "./activity-
|
|
13
|
-
import { n as
|
|
14
|
-
import { F as
|
|
15
|
-
import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-
|
|
16
|
-
import { r as readCollectors } from "./ownership-
|
|
17
|
-
import {
|
|
18
|
-
import { s as writeCacheFile } from "./warm-progress-
|
|
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-
|
|
20
|
-
import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-
|
|
21
|
-
import { existsSync, lstatSync, readFileSync, readdirSync,
|
|
12
|
+
import { B as createGcResults, L as listBuildSlots, a as collectWorkspaceOutputs, c as listWorkspaceDirs, f as withIdleWorkspace, i as collectOrphanedWorkspaces, l as rootPresence, m as workspaceInUseAsync, n as WORKSPACE_OUTPUT_DIRS, o as deleteOrphanedWorkspaces, r as clearWorkspaceOutputs, s as isInsideWorkspaces, t as REBUILD_COST, u as sizeText, z as GC_OUTPUT } from "./workspaces-CpANmBlq.mjs";
|
|
13
|
+
import { N as listBuildLocks, k as removeExpiredLease } from "./activity-CMLmzp7o.mjs";
|
|
14
|
+
import { n as forgetCreatedDeviceAsync, r as readCreatedDevices$1 } from "./created-devices-x9rN6bhs.mjs";
|
|
15
|
+
import { F as listAvdsAsync, L as listOrphanedAvdDirectories, V as ownedAvdDirectory, _t as parkedMaxSetting, dt as POOL_SETTING_REMEDY, h as avdNameAbsent } from "./android-C7sVmyI9.mjs";
|
|
16
|
+
import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-hcBpnEGk.mjs";
|
|
17
|
+
import { r as readCollectors } from "./ownership-C5fo5Edb.mjs";
|
|
18
|
+
import { a as matchWorktreeEntry, b as deleteSelectedArchives, v as describeDereferenced, x as sweepArchiveStaging, y as reclaimProject } from "./worktree-CHecZ4Oq.mjs";
|
|
19
|
+
import { s as writeCacheFile } from "./warm-progress-CA3TgT3-.mjs";
|
|
20
|
+
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-DsuqEkiM.mjs";
|
|
21
|
+
import { i as getSwiftpmCacheUsage, n as swiftpmCacheLines, r as getAgentDeviceUsage, t as agentDeviceLines } from "./agent-device-usage-output-DUFaTMdd.mjs";
|
|
22
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
22
23
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
23
|
-
import { LOG_ROTATE_BYTES, rotatedLogPath } from "@stim-cli/core";
|
|
24
|
-
import { assertHostedDeviceLedger, deviceHostArea, deviceHostRoot, diskUsageCacheFile, hostedDeviceId, isJsonObject,
|
|
24
|
+
import { LOG_ROTATE_BYTES, ensurePrivateDir, rotatedLogPath, withDirLock, withDirLockTransaction } from "@stim-cli/core";
|
|
25
|
+
import { assertHostedDeviceLedger, configLockPath, deviceHostArea, deviceHostRoot, diskUsageCacheFile, hostedDeviceId, isJsonObject, listRecordedDevicesAsync, listStatusCacheEntries, maintenanceRunClaims, parseHostedPlatformDevice, readArchivesAsync, readDiskUsage, readHostedDevice, readHostedDeviceLedger, readHostedSessions, recordedBytes } from "@stim-cli/core/state";
|
|
25
26
|
import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
26
27
|
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { setImmediate } from "node:timers/promises";
|
|
27
29
|
import chalk from "chalk";
|
|
28
|
-
import {
|
|
30
|
+
import { existsSync as existsSync$1 } from "fs";
|
|
29
31
|
import { InvalidArgumentError } from "commander";
|
|
32
|
+
import { open, readdir, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
30
33
|
//#region src/commands/gc/archives.ts
|
|
31
|
-
function stagingOrNone() {
|
|
34
|
+
async function stagingOrNone(signal) {
|
|
32
35
|
try {
|
|
33
|
-
return sweepArchiveStaging(false, 5e3);
|
|
36
|
+
return await sweepArchiveStaging(false, 5e3, signal);
|
|
34
37
|
} catch {
|
|
38
|
+
signal?.throwIfAborted();
|
|
35
39
|
return [];
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
|
-
function collectArchives(scope, olderThan, now) {
|
|
39
|
-
|
|
42
|
+
async function collectArchives(scope, olderThan, now, signal) {
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
const records = await readArchivesAsync(signal);
|
|
40
45
|
const { kind, id } = parseArchiveSelector(scope) ?? {
|
|
41
46
|
kind: null,
|
|
42
47
|
id: null
|
|
@@ -55,7 +60,7 @@ function collectArchives(scope, olderThan, now) {
|
|
|
55
60
|
expires: record.expires,
|
|
56
61
|
willDelete: (id === null && olderThan !== null ? now - Date.parse(record.removedAt) >= olderThan * 864e5 : true) && (!kind || record.bytes[kind] > 0)
|
|
57
62
|
})),
|
|
58
|
-
staging: stagingOrNone(),
|
|
63
|
+
staging: await stagingOrNone(signal),
|
|
59
64
|
unknownId: id !== null && !match ? id : null,
|
|
60
65
|
knownIds: records.map((record) => record.id),
|
|
61
66
|
kind
|
|
@@ -69,29 +74,35 @@ function archiveLines(selection) {
|
|
|
69
74
|
...selection.staging.map((entry) => ` ${entry.path}: ${entry.kept ? `kept: ${entry.reason}` : "abandoned staging"}${entry.removeCommand ? `; ${entry.removeCommand}` : ""}`)
|
|
70
75
|
];
|
|
71
76
|
}
|
|
72
|
-
function deleteArchives(selection) {
|
|
77
|
+
async function deleteArchives(selection, output = GC_OUTPUT, signal) {
|
|
78
|
+
signal?.throwIfAborted();
|
|
73
79
|
if (!selection) return 0;
|
|
74
80
|
let failures = 0;
|
|
75
|
-
for (const record of selection.records.filter((entry) => entry.willDelete))
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
for (const record of selection.records.filter((entry) => entry.willDelete)) {
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
try {
|
|
84
|
+
await deleteSelectedArchives([record.id], selection.kind, signal);
|
|
85
|
+
output.record("archive", "done", record.id, {
|
|
86
|
+
id: record.id,
|
|
87
|
+
bytes: record.bytes
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
signal?.throwIfAborted();
|
|
91
|
+
failures++;
|
|
92
|
+
output.record("archive", "failed", record.id, {
|
|
93
|
+
id: record.id,
|
|
94
|
+
detail: error.message
|
|
95
|
+
});
|
|
96
|
+
output.error(`Could not delete ${record.id}: ${error.message}`);
|
|
97
|
+
}
|
|
88
98
|
}
|
|
89
99
|
try {
|
|
90
|
-
const remaining = new Set(sweepArchiveStaging(true, 5e3).map((entry) => entry.path));
|
|
91
|
-
for (const entry of selection.staging) if (!entry.kept && !remaining.has(entry.path))
|
|
100
|
+
const remaining = new Set((await sweepArchiveStaging(true, 5e3, signal)).map((entry) => entry.path));
|
|
101
|
+
for (const entry of selection.staging) if (!entry.kept && !remaining.has(entry.path)) output.record("archive", "done", "archive staging", { id: entry.path });
|
|
92
102
|
} catch (error) {
|
|
103
|
+
signal?.throwIfAborted();
|
|
93
104
|
failures++;
|
|
94
|
-
|
|
105
|
+
output.error(`Could not clear archive staging: ${error.message}`);
|
|
95
106
|
}
|
|
96
107
|
return failures;
|
|
97
108
|
}
|
|
@@ -127,17 +138,24 @@ function findStaleEasLedgerEntries(mountedVolumes, deps = {}) {
|
|
|
127
138
|
workspaceRoot
|
|
128
139
|
}));
|
|
129
140
|
}
|
|
130
|
-
function forgetStaleEasLedgerEntries(entries, mountedVolumes, deps = {}) {
|
|
141
|
+
async function forgetStaleEasLedgerEntries(entries, mountedVolumes, deps = {}, output = GC_OUTPUT, signal) {
|
|
142
|
+
signal?.throwIfAborted();
|
|
131
143
|
let failures = 0;
|
|
132
144
|
const root = deps.easLedgerRoot ?? easMachineStateRoot$1();
|
|
133
|
-
const remove = deps.removeEasSessionClaim ??
|
|
134
|
-
for (const entry of entries)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
const remove = deps.removeEasSessionClaim ?? removeEasSessionClaimAsync;
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
await setImmediate();
|
|
148
|
+
signal?.throwIfAborted();
|
|
149
|
+
try {
|
|
150
|
+
if (await remove(entry.id, root, (claim) => isStaleEasLedgerEntry(claim, mountedVolumes), signal)) output.log(chalk.green(`Forgot the EAS ledger entry ${entry.id} (invalid id, ${entry.workspaceRoot} is gone)`));
|
|
151
|
+
else output.log(chalk.dim(`Kept the EAS ledger entry ${entry.id}: it changed since the report.`));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
signal?.throwIfAborted();
|
|
154
|
+
failures++;
|
|
155
|
+
output.log(chalk.red(`Could not forget the EAS ledger entry ${entry.id}: ${describeError(error)}`));
|
|
156
|
+
}
|
|
140
157
|
}
|
|
158
|
+
signal?.throwIfAborted();
|
|
141
159
|
return failures;
|
|
142
160
|
}
|
|
143
161
|
//#endregion
|
|
@@ -271,23 +289,26 @@ function statusCacheLines(stale) {
|
|
|
271
289
|
lines.push(" --delete removes them; `stim status` measures a folder again if it comes back.");
|
|
272
290
|
return lines;
|
|
273
291
|
}
|
|
274
|
-
function deleteStaleStatusCaches(stale) {
|
|
292
|
+
async function deleteStaleStatusCaches(stale, output = GC_OUTPUT, signal) {
|
|
293
|
+
signal?.throwIfAborted();
|
|
275
294
|
const mountedVolumes = listMountedVolumes();
|
|
276
295
|
let failures = 0;
|
|
277
296
|
for (const kind of KINDS) {
|
|
278
297
|
let removed = 0;
|
|
279
298
|
let bytes = 0;
|
|
280
299
|
for (const entry of stale.filter((candidate) => candidate.kind === kind)) {
|
|
300
|
+
signal?.throwIfAborted();
|
|
281
301
|
if (judge(entry.path, mountedVolumes) !== "gone") continue;
|
|
282
302
|
try {
|
|
283
|
-
|
|
303
|
+
await rm(entry.file, { force: true });
|
|
284
304
|
removed++;
|
|
285
305
|
bytes += entry.bytes;
|
|
286
306
|
} catch (error) {
|
|
307
|
+
signal?.throwIfAborted();
|
|
287
308
|
failures++;
|
|
288
309
|
const detail = error?.message || String(error);
|
|
289
|
-
|
|
290
|
-
|
|
310
|
+
output.log(chalk.red(`Could not remove ${entry.file}: ${detail}`));
|
|
311
|
+
output.record("statusCache", "failed", `${kind} cache entry`, {
|
|
291
312
|
id: entry.file,
|
|
292
313
|
detail
|
|
293
314
|
});
|
|
@@ -295,9 +316,10 @@ function deleteStaleStatusCaches(stale) {
|
|
|
295
316
|
}
|
|
296
317
|
if (!removed) continue;
|
|
297
318
|
const label = `${removed} stale ${kind} cache entr${removed === 1 ? "y" : "ies"}`;
|
|
298
|
-
|
|
299
|
-
|
|
319
|
+
output.log(chalk.green(`Removed ${label}`));
|
|
320
|
+
output.record("statusCache", "done", label, { bytes });
|
|
300
321
|
}
|
|
322
|
+
signal?.throwIfAborted();
|
|
301
323
|
return failures;
|
|
302
324
|
}
|
|
303
325
|
//#endregion
|
|
@@ -338,22 +360,25 @@ function finishedDeviceLines(devices = []) {
|
|
|
338
360
|
lines.push(" --delete keeps a workspace that is in use, leased, driven, locked or pinned.");
|
|
339
361
|
return lines;
|
|
340
362
|
}
|
|
341
|
-
async function deleteFinishedDevices({ finishedDevices: devices, worktreeSweep }) {
|
|
363
|
+
async function deleteFinishedDevices({ finishedDevices: devices, worktreeSweep }, output = GC_OUTPUT, operation) {
|
|
342
364
|
const graceMs = worktreeSweep?.graceMs ?? 0;
|
|
343
365
|
let failures = 0;
|
|
344
366
|
for (const project of new Set(devices.map((device) => device.project))) {
|
|
367
|
+
operation?.signal.throwIfAborted();
|
|
345
368
|
const outcome = await deleteIdleWorkspaceDevices(canonicalPath(project), {
|
|
346
369
|
windowMs: graceMs,
|
|
347
|
-
protectedRoot: triggeringRoot(),
|
|
348
|
-
purpose: "stim gc --delete"
|
|
370
|
+
protectedRoot: triggeringRoot(operation),
|
|
371
|
+
purpose: "stim gc --delete",
|
|
372
|
+
operation,
|
|
373
|
+
output
|
|
349
374
|
});
|
|
350
375
|
if (outcome.status === "kept") {
|
|
351
|
-
|
|
352
|
-
|
|
376
|
+
output.log(chalk.yellow(`Kept the owned devices of ${project}: ${outcome.detail}`));
|
|
377
|
+
output.record("device", "kept", project, { detail: outcome.detail });
|
|
353
378
|
} else if (outcome.status === "failed") {
|
|
354
379
|
failures++;
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
output.log(chalk.red(`Could not delete the owned devices of ${project}: ${outcome.detail}`));
|
|
381
|
+
output.record("device", "failed", project, { detail: outcome.detail });
|
|
357
382
|
}
|
|
358
383
|
}
|
|
359
384
|
return failures;
|
|
@@ -421,23 +446,37 @@ function planRecordings(entries, { whole, olderThan, now }) {
|
|
|
421
446
|
* workspace and dead project sweeps remove whole, are listed with them; entries whose project root is on an
|
|
422
447
|
* unmounted volume are left out.
|
|
423
448
|
*/
|
|
424
|
-
function collectRecordings(plan, gone = []) {
|
|
449
|
+
async function collectRecordings(plan, gone = [], signal) {
|
|
450
|
+
signal?.throwIfAborted();
|
|
425
451
|
const mountedVolumes = listMountedVolumes();
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
452
|
+
const entries = [];
|
|
453
|
+
for (const entry of listWorkspaceDirs()) {
|
|
454
|
+
signal?.throwIfAborted();
|
|
455
|
+
if (entry.projectRoot !== null && !isOnMountedVolume(entry.projectRoot, mountedVolumes)) continue;
|
|
456
|
+
const recordings = join$1(entry.dir, "recordings");
|
|
457
|
+
if (!existsSync$1(recordings)) continue;
|
|
458
|
+
entries.push({
|
|
459
|
+
...entry,
|
|
460
|
+
recordings,
|
|
461
|
+
gone: gone.includes(entry.dir),
|
|
462
|
+
devices: await listRecordedDevicesAsync(recordings, false, signal)
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
signal?.throwIfAborted();
|
|
466
|
+
return planRecordings(entries, plan);
|
|
430
467
|
}
|
|
431
468
|
/** Deletes what `planRecordings` chose, planning again from the files on disk. */
|
|
432
|
-
function deleteRecordings(report, plan) {
|
|
469
|
+
async function deleteRecordings(report, plan, output = GC_OUTPUT, signal) {
|
|
470
|
+
signal?.throwIfAborted();
|
|
433
471
|
let failures = 0;
|
|
434
472
|
for (const entry of report) {
|
|
473
|
+
signal?.throwIfAborted();
|
|
435
474
|
const label = entry.projectRoot ?? entry.dir;
|
|
436
475
|
if (entry.withWorkspace) continue;
|
|
437
476
|
if (!entry.willDelete) {
|
|
438
477
|
if (entry.keptCode === "unresolved" || entry.keptCode === "recently-recorded") {
|
|
439
|
-
|
|
440
|
-
|
|
478
|
+
output.log(chalk.dim(`Kept the device recordings of ${label}: ${entry.keptReason}`));
|
|
479
|
+
output.record("recording", "kept", label, {
|
|
441
480
|
bytes: entry.bytes,
|
|
442
481
|
detail: entry.keptReason
|
|
443
482
|
});
|
|
@@ -447,27 +486,34 @@ function deleteRecordings(report, plan) {
|
|
|
447
486
|
try {
|
|
448
487
|
let deleted = 0;
|
|
449
488
|
if (plan.whole) {
|
|
450
|
-
deleted = recordedBytes(
|
|
451
|
-
|
|
489
|
+
deleted = recordedBytes(await listRecordedDevicesAsync(entry.dir, false, signal));
|
|
490
|
+
signal?.throwIfAborted();
|
|
491
|
+
await rm(entry.dir, {
|
|
452
492
|
recursive: true,
|
|
453
493
|
force: true
|
|
454
494
|
});
|
|
455
|
-
} else if (plan.olderThan !== null)
|
|
456
|
-
|
|
457
|
-
|
|
495
|
+
} else if (plan.olderThan !== null) {
|
|
496
|
+
const devices = await listRecordedDevicesAsync(entry.dir, false, signal);
|
|
497
|
+
for (const segment of oldSegments(devices, plan.olderThan, plan.now)) {
|
|
498
|
+
signal?.throwIfAborted();
|
|
499
|
+
await rm(segment.file, { force: true });
|
|
500
|
+
deleted += segment.bytes;
|
|
501
|
+
}
|
|
458
502
|
}
|
|
459
|
-
|
|
460
|
-
|
|
503
|
+
output.log(chalk.green(`Deleted ${formatBytes(deleted)} of device recordings of ${label}`));
|
|
504
|
+
output.record("recording", "done", label, { bytes: deleted });
|
|
461
505
|
} catch (error) {
|
|
506
|
+
signal?.throwIfAborted();
|
|
462
507
|
failures++;
|
|
463
508
|
const detail = error?.message || String(error);
|
|
464
|
-
|
|
465
|
-
|
|
509
|
+
output.log(chalk.red(`Could not delete the device recordings of ${label}: ${detail}`));
|
|
510
|
+
output.record("recording", "failed", label, {
|
|
466
511
|
bytes: entry.bytes,
|
|
467
512
|
detail
|
|
468
513
|
});
|
|
469
514
|
}
|
|
470
515
|
}
|
|
516
|
+
signal?.throwIfAborted();
|
|
471
517
|
return failures;
|
|
472
518
|
}
|
|
473
519
|
function recordingLines(recordings) {
|
|
@@ -1126,8 +1172,12 @@ function inside(root, path) {
|
|
|
1126
1172
|
const rel = relative(root, path);
|
|
1127
1173
|
return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
|
|
1128
1174
|
}
|
|
1129
|
-
async function buildStateDirs(path, keys) {
|
|
1130
|
-
const entries = await listWorktreesAsync(path
|
|
1175
|
+
async function buildStateDirs(path, keys, operation) {
|
|
1176
|
+
const entries = await listWorktreesAsync(path, {
|
|
1177
|
+
env: operation?.env,
|
|
1178
|
+
signal: operation?.signal,
|
|
1179
|
+
declareFor: operation
|
|
1180
|
+
});
|
|
1131
1181
|
const own = keys.filter((key) => matchWorktreeEntry(entries, key)?.path === path);
|
|
1132
1182
|
const root = canonicalPath(path);
|
|
1133
1183
|
const worktree = {
|
|
@@ -1156,14 +1206,18 @@ function cachedSize(dir, now) {
|
|
|
1156
1206
|
return null;
|
|
1157
1207
|
}
|
|
1158
1208
|
}
|
|
1159
|
-
async function duSize(dir) {
|
|
1209
|
+
async function duSize(dir, operation) {
|
|
1160
1210
|
let out;
|
|
1161
1211
|
try {
|
|
1162
|
-
out = await getExecutor()
|
|
1212
|
+
out = await runOperationFile(operation, getExecutor(), "du", ["-sk", dir], {
|
|
1163
1213
|
timeoutMs: BUILD_STATE_TIMEOUT_MS,
|
|
1164
|
-
killSignal: "SIGKILL"
|
|
1214
|
+
killSignal: "SIGKILL",
|
|
1215
|
+
cwd: operation?.cwd,
|
|
1216
|
+
signal: operation?.signal
|
|
1165
1217
|
});
|
|
1166
1218
|
} catch (error) {
|
|
1219
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1220
|
+
operation?.signal.throwIfAborted();
|
|
1167
1221
|
const stdout = error instanceof Error && "stdout" in error ? error.stdout : null;
|
|
1168
1222
|
out = typeof stdout === "string" ? stdout : "";
|
|
1169
1223
|
}
|
|
@@ -1180,16 +1234,19 @@ async function eachLimited(items, limit, run) {
|
|
|
1180
1234
|
/**
|
|
1181
1235
|
* Sizes the regenerable directories maintenance clears and a project-local `.pnpm-store` in each worktree of the
|
|
1182
1236
|
* 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,
|
|
1237
|
+
* directory since. Stale directories are measured with one `du` each, sequentially with an operation and at most
|
|
1238
|
+
* 4 at once otherwise, with 10 seconds each, so a
|
|
1184
1239
|
* slow tree delays only itself; a directory du could not measure in time is reported unmeasured (null). Also lists
|
|
1185
1240
|
* the directories directly under a repository's `.worktrees/` that are no git worktree at all.
|
|
1186
1241
|
*/
|
|
1187
1242
|
async function measureWorktreeSweep(sweep, operation) {
|
|
1243
|
+
operation?.signal.throwIfAborted();
|
|
1188
1244
|
const now = Date.now();
|
|
1189
1245
|
const pending = [];
|
|
1190
1246
|
for (const candidate of sweep.worktrees) {
|
|
1247
|
+
operation?.signal.throwIfAborted();
|
|
1191
1248
|
if (candidate.skipCode === "not-a-worktree") continue;
|
|
1192
|
-
const dirs = await buildStateDirs(candidate.path, candidate.keys);
|
|
1249
|
+
const dirs = await buildStateDirs(candidate.path, candidate.keys, operation);
|
|
1193
1250
|
pending.push({
|
|
1194
1251
|
candidate,
|
|
1195
1252
|
root: canonicalPath(candidate.path),
|
|
@@ -1203,9 +1260,10 @@ async function measureWorktreeSweep(sweep, operation) {
|
|
|
1203
1260
|
}] : []));
|
|
1204
1261
|
const worktrees = new Set(due.map(({ sizes }) => sizes)).size;
|
|
1205
1262
|
if (due.length) phase("worktrees", `measuring the build state of ${plural(worktrees, "worktree")}`, operation);
|
|
1206
|
-
await eachLimited(due, BUILD_STATE_CONCURRENCY, async ({ dir, sizes }) => {
|
|
1263
|
+
await eachLimited(due, operation ? 1 : BUILD_STATE_CONCURRENCY, async ({ dir, sizes }) => {
|
|
1264
|
+
operation?.signal.throwIfAborted();
|
|
1207
1265
|
const measuredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1208
|
-
const bytes = await duSize(dir);
|
|
1266
|
+
const bytes = await duSize(dir, operation);
|
|
1209
1267
|
if (bytes === null) return;
|
|
1210
1268
|
sizes.set(dir, bytes);
|
|
1211
1269
|
operation?.signal.throwIfAborted();
|
|
@@ -1225,7 +1283,7 @@ async function measureWorktreeSweep(sweep, operation) {
|
|
|
1225
1283
|
dirs: entries.toSorted((a, b) => (b.bytes ?? 0) - (a.bytes ?? 0))
|
|
1226
1284
|
};
|
|
1227
1285
|
}
|
|
1228
|
-
sweep.unlisted = await unlistedWorktreeDirs();
|
|
1286
|
+
sweep.unlisted = await unlistedWorktreeDirs(operation);
|
|
1229
1287
|
return sweep;
|
|
1230
1288
|
}
|
|
1231
1289
|
/** Whether `path` is a checkout git still knows: its own `.git` directory, or a `.git` file whose gitdir exists. */
|
|
@@ -1241,15 +1299,22 @@ function hasLiveGitDir(path) {
|
|
|
1241
1299
|
const gitdir = /^gitdir:\s*(.+)$/m.exec(text)?.[1]?.trim();
|
|
1242
1300
|
return Boolean(gitdir) && existsSync(resolve(dirname(dotGit), gitdir ?? ""));
|
|
1243
1301
|
}
|
|
1244
|
-
async function unlistedWorktreeDirs() {
|
|
1302
|
+
async function unlistedWorktreeDirs(operation) {
|
|
1245
1303
|
const repositories = /* @__PURE__ */ new Map();
|
|
1246
|
-
|
|
1247
|
-
|
|
1304
|
+
const options = {
|
|
1305
|
+
env: operation?.env,
|
|
1306
|
+
signal: operation?.signal,
|
|
1307
|
+
declareFor: operation
|
|
1308
|
+
};
|
|
1309
|
+
for (const root of await candidateRootsAsync(operation)) {
|
|
1310
|
+
operation?.signal.throwIfAborted();
|
|
1311
|
+
const { entries, ...source } = await resolveSourceCheckoutAsync(root, options);
|
|
1248
1312
|
if ("refusal" in source || repositories.has(source.path)) continue;
|
|
1249
1313
|
repositories.set(source.path, entries.map((entry) => canonicalPath(entry.path)));
|
|
1250
1314
|
}
|
|
1251
1315
|
const found = [];
|
|
1252
1316
|
for (const [repository, listed] of repositories) {
|
|
1317
|
+
operation?.signal.throwIfAborted();
|
|
1253
1318
|
const parent = join(repository, WORKTREES_DIR);
|
|
1254
1319
|
let names;
|
|
1255
1320
|
try {
|
|
@@ -1258,6 +1323,7 @@ async function unlistedWorktreeDirs() {
|
|
|
1258
1323
|
continue;
|
|
1259
1324
|
}
|
|
1260
1325
|
for (const name of names.toSorted()) {
|
|
1326
|
+
operation?.signal.throwIfAborted();
|
|
1261
1327
|
const path = join(parent, name);
|
|
1262
1328
|
if (!isDirectory(path) || hasLiveGitDir(path)) continue;
|
|
1263
1329
|
const real = canonicalPath(path);
|
|
@@ -1265,7 +1331,11 @@ async function unlistedWorktreeDirs() {
|
|
|
1265
1331
|
found.push({
|
|
1266
1332
|
path,
|
|
1267
1333
|
repository,
|
|
1268
|
-
bytes: await measuredDirectorySizeAsync(path, {
|
|
1334
|
+
bytes: await measuredDirectorySizeAsync(path, {
|
|
1335
|
+
timeoutMs: SIZE_TIMEOUT_MS,
|
|
1336
|
+
operation,
|
|
1337
|
+
signal: operation?.signal
|
|
1338
|
+
})
|
|
1269
1339
|
});
|
|
1270
1340
|
}
|
|
1271
1341
|
}
|
|
@@ -1302,15 +1372,23 @@ function isStaleAvdName(name, config) {
|
|
|
1302
1372
|
function reservedForSetup(name, config) {
|
|
1303
1373
|
return Object.values(config?.projects ?? {}).some((project) => projectDeviceSlots(project).some(({ platforms }) => platforms.android?.setupIncomplete && platforms.android.avdName === name));
|
|
1304
1374
|
}
|
|
1305
|
-
function forgetStaleAvdName(name) {
|
|
1375
|
+
async function forgetStaleAvdName(name, signal) {
|
|
1306
1376
|
let claim = null;
|
|
1307
1377
|
try {
|
|
1308
1378
|
claim = acquireAvdClaim(name);
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1379
|
+
while (true) try {
|
|
1380
|
+
return await forgetCreatedDeviceAsync("android", name, signal, (forget) => withDirLock(configLockPath(), () => {
|
|
1381
|
+
if (!isStaleAvdName(name, loadConfig$1())) return false;
|
|
1382
|
+
forget();
|
|
1383
|
+
return true;
|
|
1384
|
+
}, {
|
|
1385
|
+
waitMs: 0,
|
|
1386
|
+
ensureParent: () => ensurePrivateDir(getConfigDir())
|
|
1387
|
+
}));
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
if (!(error instanceof Error && "code" in error && error.code === "STIM_LOCK_TIMEOUT" && "lockPath" in error && error.lockPath === configLockPath())) throw error;
|
|
1390
|
+
await withConfigLockAsync$1(() => {}, signal);
|
|
1391
|
+
}
|
|
1314
1392
|
} finally {
|
|
1315
1393
|
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
1316
1394
|
}
|
|
@@ -1337,23 +1415,28 @@ function isDanglingLink(path) {
|
|
|
1337
1415
|
return false;
|
|
1338
1416
|
}
|
|
1339
1417
|
}
|
|
1340
|
-
function forgetStaleLedgerEntries(entries) {
|
|
1418
|
+
async function forgetStaleLedgerEntries(entries, output = GC_OUTPUT, signal) {
|
|
1419
|
+
signal?.throwIfAborted();
|
|
1341
1420
|
let failures = 0;
|
|
1342
1421
|
for (const entry of entries) {
|
|
1422
|
+
await setImmediate();
|
|
1423
|
+
signal?.throwIfAborted();
|
|
1343
1424
|
if (entry.kind === "web" && !isStaleProfile(entry.id, existsSync)) continue;
|
|
1344
1425
|
try {
|
|
1345
1426
|
if (entry.kind === "android") {
|
|
1346
|
-
if (!forgetStaleAvdName(entry.id)) {
|
|
1347
|
-
|
|
1427
|
+
if (!await forgetStaleAvdName(entry.id, signal)) {
|
|
1428
|
+
output.log(chalk.dim(`Kept the ledger entry for android ${entry.id}: the AVD or its setup reappeared`));
|
|
1348
1429
|
continue;
|
|
1349
1430
|
}
|
|
1350
|
-
} else
|
|
1351
|
-
|
|
1431
|
+
} else await forgetCreatedDeviceAsync(entry.kind, entry.id, signal);
|
|
1432
|
+
output.log(chalk.green(`Forgot the ledger entry for ${entry.kind} ${entry.id} (not on this machine)`));
|
|
1352
1433
|
} catch (error) {
|
|
1434
|
+
signal?.throwIfAborted();
|
|
1353
1435
|
failures++;
|
|
1354
|
-
|
|
1436
|
+
output.log(chalk.red(`Could not forget the ledger entry for ${entry.kind} ${entry.id}: ${error?.message || error}`));
|
|
1355
1437
|
}
|
|
1356
1438
|
}
|
|
1439
|
+
signal?.throwIfAborted();
|
|
1357
1440
|
return failures;
|
|
1358
1441
|
}
|
|
1359
1442
|
//#endregion
|
|
@@ -1364,11 +1447,14 @@ const CAPPED_LOGS = [
|
|
|
1364
1447
|
"device.ndjson"
|
|
1365
1448
|
];
|
|
1366
1449
|
const TRIM_ABOVE_BYTES = 2 * LOG_ROTATE_BYTES;
|
|
1367
|
-
function fileSize(path) {
|
|
1450
|
+
async function fileSize(path, signal) {
|
|
1451
|
+
signal?.throwIfAborted();
|
|
1368
1452
|
try {
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1453
|
+
const metadata = await stat(path);
|
|
1454
|
+
signal?.throwIfAborted();
|
|
1455
|
+
return metadata.isFile() ? metadata.size : 0;
|
|
1371
1456
|
} catch {
|
|
1457
|
+
signal?.throwIfAborted();
|
|
1372
1458
|
return 0;
|
|
1373
1459
|
}
|
|
1374
1460
|
}
|
|
@@ -1387,29 +1473,36 @@ function cappedFiles(logsDir) {
|
|
|
1387
1473
|
function trimmable(size) {
|
|
1388
1474
|
return size > TRIM_ABOVE_BYTES ? size - LOG_ROTATE_BYTES : 0;
|
|
1389
1475
|
}
|
|
1390
|
-
function excess(logsDir) {
|
|
1391
|
-
|
|
1476
|
+
async function excess(logsDir, signal) {
|
|
1477
|
+
let bytes = 0;
|
|
1478
|
+
for (const { file } of cappedFiles(logsDir)) bytes += trimmable(await fileSize(file, signal));
|
|
1479
|
+
return bytes;
|
|
1392
1480
|
}
|
|
1393
1481
|
function collectorReason(root) {
|
|
1394
1482
|
const platforms = Object.keys(readCollectors(root));
|
|
1395
1483
|
return platforms.length ? `a device log collector is recorded for ${platforms.join(", ")}` : null;
|
|
1396
1484
|
}
|
|
1397
|
-
function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
1485
|
+
async function collectWorkspaceLogs({ exclude = [], operation } = {}) {
|
|
1486
|
+
const signal = operation?.signal;
|
|
1487
|
+
signal?.throwIfAborted();
|
|
1398
1488
|
const mountedVolumes = listMountedVolumes();
|
|
1399
1489
|
const entries = [];
|
|
1400
1490
|
for (const { dir, projectRoot, problem } of listWorkspaceDirs()) {
|
|
1491
|
+
signal?.throwIfAborted();
|
|
1401
1492
|
if (exclude.includes(dir)) continue;
|
|
1402
1493
|
if (projectRoot !== null && !isOnMountedVolume(projectRoot, mountedVolumes)) continue;
|
|
1403
1494
|
const logsDir = join$1(dir, "logs");
|
|
1404
1495
|
let names;
|
|
1405
1496
|
try {
|
|
1406
|
-
names =
|
|
1497
|
+
names = await readdir(logsDir);
|
|
1407
1498
|
} catch {
|
|
1499
|
+
signal?.throwIfAborted();
|
|
1408
1500
|
continue;
|
|
1409
1501
|
}
|
|
1410
|
-
|
|
1502
|
+
let bytes = 0;
|
|
1503
|
+
for (const name of names) bytes += await fileSize(join$1(logsDir, name), signal);
|
|
1411
1504
|
if (bytes === 0) continue;
|
|
1412
|
-
const trimBytes = excess(logsDir);
|
|
1505
|
+
const trimBytes = await excess(logsDir, signal);
|
|
1413
1506
|
let keptCode = null;
|
|
1414
1507
|
let keptReason = null;
|
|
1415
1508
|
if (trimBytes > 0) {
|
|
@@ -1417,7 +1510,7 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1417
1510
|
keptCode = "unresolved";
|
|
1418
1511
|
keptReason = `workspace directory not resolved: ${problem ?? "unknown project root"}`;
|
|
1419
1512
|
} else {
|
|
1420
|
-
const inUse =
|
|
1513
|
+
const inUse = await workspaceInUseAsync(projectRoot, {}, signal, operation);
|
|
1421
1514
|
const collector = collectorReason(projectRoot);
|
|
1422
1515
|
if (inUse.length) {
|
|
1423
1516
|
keptCode = "in-use";
|
|
@@ -1438,70 +1531,89 @@ function collectWorkspaceLogs({ exclude = [] } = {}) {
|
|
|
1438
1531
|
keptReason
|
|
1439
1532
|
});
|
|
1440
1533
|
}
|
|
1534
|
+
signal?.throwIfAborted();
|
|
1441
1535
|
return entries;
|
|
1442
1536
|
}
|
|
1443
|
-
function trimToNewest({ file, log }, maxBytes) {
|
|
1444
|
-
if (!trimmable(fileSize(file))) return 0;
|
|
1445
|
-
return
|
|
1446
|
-
|
|
1537
|
+
async function trimToNewest({ file, log }, maxBytes, signal) {
|
|
1538
|
+
if (!trimmable(await fileSize(file, signal))) return 0;
|
|
1539
|
+
return withDirLockTransaction(`${log}.lock`, async () => {
|
|
1540
|
+
signal?.throwIfAborted();
|
|
1541
|
+
const { size, atime, mtime } = await stat(file);
|
|
1542
|
+
signal?.throwIfAborted();
|
|
1447
1543
|
if (!trimmable(size)) return 0;
|
|
1448
1544
|
const tail = Buffer.alloc(maxBytes);
|
|
1449
|
-
const fd =
|
|
1545
|
+
const fd = await open(file, "r");
|
|
1450
1546
|
try {
|
|
1451
|
-
|
|
1547
|
+
signal?.throwIfAborted();
|
|
1548
|
+
await fd.read(tail, 0, maxBytes, size - maxBytes);
|
|
1452
1549
|
} finally {
|
|
1453
|
-
|
|
1550
|
+
await fd.close();
|
|
1454
1551
|
}
|
|
1552
|
+
signal?.throwIfAborted();
|
|
1455
1553
|
const newline = tail.indexOf(10);
|
|
1456
1554
|
const kept = newline === -1 ? Buffer.alloc(0) : tail.subarray(newline + 1);
|
|
1457
1555
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
1458
1556
|
try {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1557
|
+
await writeFile(tmp, kept, { signal });
|
|
1558
|
+
await utimes(tmp, atime, mtime);
|
|
1559
|
+
signal?.throwIfAborted();
|
|
1560
|
+
await rename(tmp, file);
|
|
1561
|
+
} finally {
|
|
1562
|
+
await rm(tmp, { force: true });
|
|
1465
1563
|
}
|
|
1466
1564
|
return size - kept.length;
|
|
1467
|
-
});
|
|
1565
|
+
}, { signal });
|
|
1468
1566
|
}
|
|
1469
|
-
async function trimWorkspaceLogs(entries) {
|
|
1567
|
+
async function trimWorkspaceLogs(entries, output = GC_OUTPUT, operation) {
|
|
1568
|
+
const signal = operation?.signal;
|
|
1569
|
+
signal?.throwIfAborted();
|
|
1470
1570
|
let failures = 0;
|
|
1471
1571
|
let trimmed = 0;
|
|
1472
1572
|
for (const entry of entries) {
|
|
1573
|
+
signal?.throwIfAborted();
|
|
1473
1574
|
const root = entry.projectRoot;
|
|
1474
1575
|
if (!entry.willTrim || root === null) {
|
|
1475
|
-
if (entry.keptReason)
|
|
1576
|
+
if (entry.keptReason) output.log(chalk.dim(`Kept the logs of ${root ?? entry.dir}: ${entry.keptReason}`));
|
|
1476
1577
|
continue;
|
|
1477
1578
|
}
|
|
1478
1579
|
let run;
|
|
1479
1580
|
try {
|
|
1480
|
-
run = await withIdleWorkspace(root, () => {
|
|
1581
|
+
run = await withIdleWorkspace(root, async () => {
|
|
1481
1582
|
const collector = collectorReason(root);
|
|
1482
1583
|
if (collector) return {
|
|
1483
1584
|
kept: collector,
|
|
1484
1585
|
bytes: 0
|
|
1485
1586
|
};
|
|
1587
|
+
let bytes = 0;
|
|
1588
|
+
for (const capped of cappedFiles(join$1(entry.dir, "logs"))) {
|
|
1589
|
+
signal?.throwIfAborted();
|
|
1590
|
+
bytes += await trimToNewest(capped, LOG_ROTATE_BYTES, signal);
|
|
1591
|
+
}
|
|
1486
1592
|
return {
|
|
1487
1593
|
kept: null,
|
|
1488
|
-
bytes
|
|
1594
|
+
bytes
|
|
1489
1595
|
};
|
|
1490
|
-
}, {
|
|
1596
|
+
}, {
|
|
1597
|
+
purpose: "gc",
|
|
1598
|
+
operation
|
|
1599
|
+
});
|
|
1491
1600
|
} catch (error) {
|
|
1601
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1602
|
+
signal?.throwIfAborted();
|
|
1492
1603
|
failures++;
|
|
1493
|
-
|
|
1604
|
+
output.log(chalk.red(`Could not trim the logs of ${root}: ${error?.message || String(error)}`));
|
|
1494
1605
|
continue;
|
|
1495
1606
|
}
|
|
1496
1607
|
if (!run.ran || run.value.kept) {
|
|
1497
1608
|
const kept = run.ran ? run.value.kept : `in use: ${run.reasons.join("; ")}`;
|
|
1498
|
-
|
|
1609
|
+
output.log(chalk.yellow(`Kept the logs of ${root}: ${kept}`));
|
|
1499
1610
|
continue;
|
|
1500
1611
|
}
|
|
1501
1612
|
trimmed += run.value.bytes;
|
|
1502
|
-
|
|
1613
|
+
output.log(chalk.green(`Trimmed the logs of ${root} (${formatBytes(run.value.bytes)})`));
|
|
1503
1614
|
}
|
|
1504
|
-
|
|
1615
|
+
signal?.throwIfAborted();
|
|
1616
|
+
if (trimmed) output.log(chalk.dim(`Trimmed ${formatBytes(trimmed)} of workspace logs.`));
|
|
1505
1617
|
return failures;
|
|
1506
1618
|
}
|
|
1507
1619
|
//#endregion
|
|
@@ -1553,15 +1665,17 @@ function collectParkedHostedDevices({ olderThanDays = null, now = Date.now() } =
|
|
|
1553
1665
|
notices
|
|
1554
1666
|
};
|
|
1555
1667
|
}
|
|
1556
|
-
async function stopHostedDevice(record, claim) {
|
|
1668
|
+
async function stopHostedDevice(record, claim, operation) {
|
|
1669
|
+
operation?.signal.throwIfAborted();
|
|
1557
1670
|
const home = join(deviceHostArea(record.id), "home");
|
|
1558
1671
|
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
1672
|
+
if (operation) declareSpawnsOn(operation, claim);
|
|
1559
1673
|
let child;
|
|
1560
1674
|
try {
|
|
1561
1675
|
child = getExecutor().spawn(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), "device-host-worker.mjs")], {
|
|
1562
1676
|
cwd: home,
|
|
1563
1677
|
env: {
|
|
1564
|
-
...process.env,
|
|
1678
|
+
...operation?.env ?? process.env,
|
|
1565
1679
|
STIM_HOME: home
|
|
1566
1680
|
},
|
|
1567
1681
|
detached: true,
|
|
@@ -1578,6 +1692,8 @@ async function stopHostedDevice(record, claim) {
|
|
|
1578
1692
|
value: null,
|
|
1579
1693
|
notice: describeError(error)
|
|
1580
1694
|
};
|
|
1695
|
+
} finally {
|
|
1696
|
+
if (operation) stopDeclaringSpawnsOn(operation, claim);
|
|
1581
1697
|
}
|
|
1582
1698
|
let identity = null;
|
|
1583
1699
|
let closed = false;
|
|
@@ -1598,6 +1714,7 @@ async function stopHostedDevice(record, claim) {
|
|
|
1598
1714
|
clearTimeout(killTimer);
|
|
1599
1715
|
clearTimeout(finishTimer);
|
|
1600
1716
|
clearTimeout(groupTimer);
|
|
1717
|
+
operation?.signal.removeEventListener("abort", cancel);
|
|
1601
1718
|
const settled = closed && (!child.pid || !(0, ownership_claim_exports.processGroupAlive)(child.pid));
|
|
1602
1719
|
if (settled) (0, ownership_claim_exports.clearClaimChild)(claim);
|
|
1603
1720
|
let value = null;
|
|
@@ -1622,12 +1739,12 @@ async function stopHostedDevice(record, claim) {
|
|
|
1622
1739
|
if (child.pid && identity && ["same", "gone"].includes((0, process_identity_exports.inspectProcessIdentity)(identity))) try {
|
|
1623
1740
|
process.kill(-child.pid, name);
|
|
1624
1741
|
} catch {}
|
|
1625
|
-
else if (!identity) child.
|
|
1742
|
+
else if (!identity) child.stdin?.destroy();
|
|
1626
1743
|
};
|
|
1627
1744
|
const cancel = () => {
|
|
1628
1745
|
if (finished || cancelling) return;
|
|
1629
1746
|
cancelling = true;
|
|
1630
|
-
notice ??= "Hosted worker exceeded its deadline.";
|
|
1747
|
+
notice ??= operation?.signal.aborted ? "Hosted cleanup cancelled." : "Hosted worker exceeded its deadline.";
|
|
1631
1748
|
signal("SIGTERM");
|
|
1632
1749
|
killTimer = setTimeout(() => signal("SIGKILL"), 5e3);
|
|
1633
1750
|
finishTimer = setTimeout(finish, 1e4);
|
|
@@ -1641,6 +1758,7 @@ async function stopHostedDevice(record, claim) {
|
|
|
1641
1758
|
}
|
|
1642
1759
|
};
|
|
1643
1760
|
timer = setTimeout(cancel, 9e4);
|
|
1761
|
+
operation?.signal.addEventListener("abort", cancel, { once: true });
|
|
1644
1762
|
child.stdout?.on("data", (chunk) => {
|
|
1645
1763
|
if (Buffer.byteLength(output) + chunk.length > 16384) {
|
|
1646
1764
|
notice = "Hosted worker output exceeded its bound.";
|
|
@@ -1673,6 +1791,10 @@ async function stopHostedDevice(record, claim) {
|
|
|
1673
1791
|
};
|
|
1674
1792
|
try {
|
|
1675
1793
|
(0, ownership_claim_exports.setClaimChild)(claim, identity);
|
|
1794
|
+
if (operation?.signal.aborted) {
|
|
1795
|
+
cancel();
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1676
1798
|
child.stdin?.end(JSON.stringify({
|
|
1677
1799
|
mode: "stop",
|
|
1678
1800
|
platform: record.platform,
|
|
@@ -1689,14 +1811,15 @@ async function stopHostedDevice(record, claim) {
|
|
|
1689
1811
|
}
|
|
1690
1812
|
});
|
|
1691
1813
|
}
|
|
1692
|
-
async function deleteParkedHostedDevices(devices, { listAvds: list
|
|
1814
|
+
async function deleteParkedHostedDevices(devices, { listAvds: list, operation } = {}, output = GC_OUTPUT) {
|
|
1693
1815
|
let failures = 0;
|
|
1694
1816
|
for (const device of devices) {
|
|
1817
|
+
operation?.signal.throwIfAborted();
|
|
1695
1818
|
if (device.listed === false) continue;
|
|
1696
1819
|
const label = `${device.platform} ${device.name ?? device.session} (hosted session ${device.session})`;
|
|
1697
1820
|
const kept = (detail) => {
|
|
1698
|
-
|
|
1699
|
-
|
|
1821
|
+
output.log(`Kept ${label}: ${detail}`);
|
|
1822
|
+
output.record("parkedHostedDevice", "kept", label, {
|
|
1700
1823
|
id: device.id,
|
|
1701
1824
|
detail
|
|
1702
1825
|
});
|
|
@@ -1738,22 +1861,42 @@ async function deleteParkedHostedDevices(devices, { listAvds: list = listAvds }
|
|
|
1738
1861
|
kept(describeError(error));
|
|
1739
1862
|
continue;
|
|
1740
1863
|
}
|
|
1741
|
-
if (device.platform === "android"
|
|
1742
|
-
|
|
1743
|
-
|
|
1864
|
+
if (device.platform === "android") {
|
|
1865
|
+
let avds;
|
|
1866
|
+
if (operation) declareSpawnsOn(operation, claim);
|
|
1867
|
+
try {
|
|
1868
|
+
avds = await (list ? list() : listAvdsAsync({
|
|
1869
|
+
operation,
|
|
1870
|
+
signal: operation?.signal
|
|
1871
|
+
}));
|
|
1872
|
+
} finally {
|
|
1873
|
+
if (operation) stopDeclaringSpawnsOn(operation, claim);
|
|
1874
|
+
}
|
|
1875
|
+
if (!avds.includes(device.id)) {
|
|
1876
|
+
kept("AVD not visible from this shell's Android environment; run gc with the server's ANDROID_AVD_HOME/HOME");
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1744
1879
|
}
|
|
1880
|
+
operation?.signal.throwIfAborted();
|
|
1745
1881
|
settled = false;
|
|
1746
|
-
const outcome = await stopHostedDevice(current, claim);
|
|
1882
|
+
const outcome = await stopHostedDevice(current, claim, operation);
|
|
1747
1883
|
settled = outcome.settled;
|
|
1884
|
+
if (!settled) throw new WorkspaceProcessCleanupError([claim], outcome.notice ?? "Hosted cleanup could not be verified.", "Verify the hosted worker and its descendants have stopped.");
|
|
1885
|
+
operation?.signal.throwIfAborted();
|
|
1748
1886
|
const result = isJsonObject(outcome.value) ? outcome.value : null;
|
|
1749
1887
|
const stopped = result && parseHostedPlatformDevice(result.device, device.platform);
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1752
|
-
|
|
1888
|
+
if (outcome.notice || result?.state !== "stopped" || !stopped || hostedDeviceId(stopped) !== device.id) throw new Error(outcome.notice ?? (typeof result?.notice === "string" ? result.notice : "Hosted deletion could not be verified."));
|
|
1889
|
+
output.log(`Deleted ${label}`);
|
|
1890
|
+
output.record("parkedHostedDevice", "done", label, { id: device.id });
|
|
1753
1891
|
} catch (error) {
|
|
1892
|
+
if (claim && (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error)) {
|
|
1893
|
+
settled = false;
|
|
1894
|
+
throw Object.assign(new WorkspaceProcessCleanupError([.../* @__PURE__ */ new Set([claim, ...error instanceof WorkspaceProcessCleanupError ? error.claims : []])], error.message, "Verify the hosted device operation and its descendants have stopped."), { reportCleanup: true });
|
|
1895
|
+
}
|
|
1896
|
+
operation?.signal.throwIfAborted();
|
|
1754
1897
|
const detail = describeError(error);
|
|
1755
|
-
|
|
1756
|
-
|
|
1898
|
+
output.error(`Failed to delete ${label}: ${detail}`);
|
|
1899
|
+
output.record("parkedHostedDevice", "failed", label, {
|
|
1757
1900
|
id: device.id,
|
|
1758
1901
|
detail
|
|
1759
1902
|
});
|
|
@@ -1772,13 +1915,17 @@ function readGcReport(options = {}, deps = {}) {
|
|
|
1772
1915
|
cache: options.cache,
|
|
1773
1916
|
worktrees: options.worktrees,
|
|
1774
1917
|
json: true
|
|
1775
|
-
}, deps,
|
|
1918
|
+
}, deps, {
|
|
1919
|
+
output: GC_OUTPUT,
|
|
1920
|
+
takeResults: () => []
|
|
1921
|
+
}, true);
|
|
1776
1922
|
}
|
|
1777
|
-
function removeInvalidProjectEntries(invalidProjects) {
|
|
1923
|
+
function removeInvalidProjectEntries(invalidProjects, output, signal) {
|
|
1778
1924
|
for (const path of invalidProjects) {
|
|
1925
|
+
signal?.throwIfAborted();
|
|
1779
1926
|
removeProject(path);
|
|
1780
|
-
|
|
1781
|
-
|
|
1927
|
+
output.log(chalk.green(`Removed the invalid registry entry ${path}`));
|
|
1928
|
+
output.record("project", "done", path);
|
|
1782
1929
|
}
|
|
1783
1930
|
}
|
|
1784
1931
|
function collectGcMemory(scope, env, operation) {
|
|
@@ -1814,11 +1961,14 @@ function classifyProjectEntries(cfg, mountedVolumes) {
|
|
|
1814
1961
|
};
|
|
1815
1962
|
}
|
|
1816
1963
|
/** The workspace directories an unscoped run removes whole: those of dead projects and orphaned directories. */
|
|
1817
|
-
function removedWholeWorkspaceDirs() {
|
|
1964
|
+
async function removedWholeWorkspaceDirs(operation) {
|
|
1818
1965
|
const mountedVolumes = listMountedVolumes();
|
|
1819
1966
|
const cfg = loadConfig$1();
|
|
1820
1967
|
const { deadProjects } = classifyProjectEntries(cfg, mountedVolumes);
|
|
1821
|
-
const { orphaned } = collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, {
|
|
1968
|
+
const { orphaned } = await collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, {
|
|
1969
|
+
measure: false,
|
|
1970
|
+
operation
|
|
1971
|
+
});
|
|
1822
1972
|
return [...orphaned.map((entry) => entry.dir), ...deadProjects.map(workspaceDir)];
|
|
1823
1973
|
}
|
|
1824
1974
|
async function collectGcReport({ olderThan = null, cache = null, worktrees = false, refresh = true, now = Date.now(), lastTouched = workspaceLastUsed } = {}, deps = {}) {
|
|
@@ -1829,10 +1979,10 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1829
1979
|
const withWorkspaces = includesWorkspaceOutputs(scope);
|
|
1830
1980
|
const selected = selectCaches(discoverCaches(env, operation?.cwd), scope).filter((c) => !withWorkspaces || !isInsideWorkspaces(c.dir));
|
|
1831
1981
|
if (selected.length) phase("caches", `measuring ${plural(selected.length, "shared cache")}`, operation);
|
|
1832
|
-
const sized = await sizeCaches(selected);
|
|
1982
|
+
const sized = await sizeCaches(selected, operation);
|
|
1833
1983
|
const caches = scope === null ? previewScopedEmptying(sized, env) : planCacheEmptying(sized, all, env);
|
|
1834
1984
|
if (scope) return {
|
|
1835
|
-
archives: includesArchives(scope) ? collectArchives(scope, olderThan, now) : void 0,
|
|
1985
|
+
archives: includesArchives(scope) ? await collectArchives(scope, olderThan, now, operation?.signal) : void 0,
|
|
1836
1986
|
skipped: [],
|
|
1837
1987
|
deadProjects: [],
|
|
1838
1988
|
invalidProjects: [],
|
|
@@ -1881,14 +2031,16 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1881
2031
|
workspaceOutputs: withWorkspaces ? await collectWorkspaceOutputs({
|
|
1882
2032
|
olderThan,
|
|
1883
2033
|
now,
|
|
1884
|
-
exclude: isWorkspacesScope(scope) ? removedWholeWorkspaceDirs() : []
|
|
2034
|
+
exclude: isWorkspacesScope(scope) ? await removedWholeWorkspaceDirs(operation) : [],
|
|
2035
|
+
signal: operation?.signal,
|
|
2036
|
+
operation
|
|
1885
2037
|
}) : null,
|
|
1886
2038
|
workspaceLogs: [],
|
|
1887
|
-
recordings: includesRecordings(scope) ? collectRecordings({
|
|
2039
|
+
recordings: includesRecordings(scope) ? await collectRecordings({
|
|
1888
2040
|
whole: all,
|
|
1889
2041
|
olderThan,
|
|
1890
2042
|
now
|
|
1891
|
-
}) : [],
|
|
2043
|
+
}, [], operation?.signal) : [],
|
|
1892
2044
|
worktreeSweep: null,
|
|
1893
2045
|
memory: memoryCacheKind(scope) ? await collectGcMemory(memoryScope(scope), env, operation) : null,
|
|
1894
2046
|
agentDeviceUsage: null,
|
|
@@ -1914,7 +2066,8 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1914
2066
|
const tempLeftovers = await collectTempLeftovers({
|
|
1915
2067
|
now,
|
|
1916
2068
|
env,
|
|
1917
|
-
signal: operation?.signal
|
|
2069
|
+
signal: operation?.signal,
|
|
2070
|
+
operation
|
|
1918
2071
|
});
|
|
1919
2072
|
const mountedVolumes = listMountedVolumes();
|
|
1920
2073
|
const cfg = loadConfig$1();
|
|
@@ -1922,6 +2075,8 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1922
2075
|
if (!easSessionSweep) try {
|
|
1923
2076
|
easSessionSweep = await collectEasSessionSweep(cfg, deps);
|
|
1924
2077
|
} catch (error) {
|
|
2078
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2079
|
+
operation?.signal.throwIfAborted();
|
|
1925
2080
|
easSessionSweep = {
|
|
1926
2081
|
projectScope: null,
|
|
1927
2082
|
orphaned: [],
|
|
@@ -1938,7 +2093,7 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1938
2093
|
now
|
|
1939
2094
|
});
|
|
1940
2095
|
const { deadProjects, invalidProjects, skipped } = classifyProjectEntries(cfg, mountedVolumes);
|
|
1941
|
-
const workspaceDirs = await collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes);
|
|
2096
|
+
const workspaceDirs = await collectOrphanedWorkspaces(Object.keys(cfg?.projects ?? {}), mountedVolumes, { operation });
|
|
1942
2097
|
skipped.push(...workspaceDirs.skipped);
|
|
1943
2098
|
const statusCaches = collectStaleStatusCaches(mountedVolumes);
|
|
1944
2099
|
skipped.push(...statusCaches.skipped);
|
|
@@ -1958,7 +2113,11 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1958
2113
|
const avdSizes = /* @__PURE__ */ new Map();
|
|
1959
2114
|
const avdSize = (dir, options) => {
|
|
1960
2115
|
let bytes = avdSizes.get(dir);
|
|
1961
|
-
if (bytes === void 0) avdSizes.set(dir, bytes = Promise.resolve((deps.directorySize ?? directorySizeAsync)(dir,
|
|
2116
|
+
if (bytes === void 0) avdSizes.set(dir, bytes = Promise.resolve((deps.directorySize ?? directorySizeAsync)(dir, {
|
|
2117
|
+
...options,
|
|
2118
|
+
operation,
|
|
2119
|
+
signal: operation?.signal
|
|
2120
|
+
})));
|
|
1962
2121
|
return bytes;
|
|
1963
2122
|
};
|
|
1964
2123
|
const unsweepableReason = cfg === null ? "no Stim config found" : deviceSweepIsScoped(env) ? "STIM_HOME scopes this config, but simulators and AVDs are machine-global" : null;
|
|
@@ -1967,11 +2126,25 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1967
2126
|
let simNames = [];
|
|
1968
2127
|
let avdNames = [];
|
|
1969
2128
|
try {
|
|
1970
|
-
simNames = (await listAllIosSimsAsync({
|
|
1971
|
-
|
|
2129
|
+
simNames = (await listAllIosSimsAsync({
|
|
2130
|
+
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
2131
|
+
operation,
|
|
2132
|
+
signal: operation?.signal
|
|
2133
|
+
})).map((s) => s.name);
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2136
|
+
operation?.signal.throwIfAborted();
|
|
2137
|
+
}
|
|
1972
2138
|
try {
|
|
1973
|
-
avdNames = await listAvdsAsync({
|
|
1974
|
-
|
|
2139
|
+
avdNames = await listAvdsAsync({
|
|
2140
|
+
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
2141
|
+
operation,
|
|
2142
|
+
signal: operation?.signal
|
|
2143
|
+
});
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2146
|
+
operation?.signal.throwIfAborted();
|
|
2147
|
+
}
|
|
1975
2148
|
deviceSweepNotices.push(...describeUnverifiableDevices(simNames, avdNames, { reason: unsweepableReason }));
|
|
1976
2149
|
} else {
|
|
1977
2150
|
let sims = [];
|
|
@@ -1980,19 +2153,29 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
1980
2153
|
try {
|
|
1981
2154
|
const allSims = await listAllIosSimsAsync({
|
|
1982
2155
|
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
1983
|
-
includeUnavailable: true
|
|
2156
|
+
includeUnavailable: true,
|
|
2157
|
+
operation,
|
|
2158
|
+
signal: operation?.signal
|
|
1984
2159
|
});
|
|
1985
2160
|
staleLedgerEntries = [...staleLedgerEntries, ...findStaleLedgerEntries(ledger, allSims)];
|
|
1986
2161
|
sims = allSims.filter((sim) => sim.available);
|
|
1987
|
-
} catch {
|
|
2162
|
+
} catch (error) {
|
|
2163
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2164
|
+
operation?.signal.throwIfAborted();
|
|
1988
2165
|
simsChecked = false;
|
|
1989
2166
|
deviceSweepNotices.push(`ios device sweep skipped: simulator tooling did not answer within ${DEVICE_LIST_TIMEOUT_MS / 1e3}s`);
|
|
1990
2167
|
}
|
|
1991
2168
|
let avds = [];
|
|
1992
2169
|
let avdsChecked = true;
|
|
1993
2170
|
try {
|
|
1994
|
-
avds = await listAvdsAsync({
|
|
1995
|
-
|
|
2171
|
+
avds = await listAvdsAsync({
|
|
2172
|
+
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
2173
|
+
operation,
|
|
2174
|
+
signal: operation?.signal
|
|
2175
|
+
});
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2178
|
+
operation?.signal.throwIfAborted();
|
|
1996
2179
|
avdsChecked = false;
|
|
1997
2180
|
deviceSweepNotices.push(`android device sweep skipped: emulator tooling did not answer within ${DEVICE_LIST_TIMEOUT_MS / 1e3}s`);
|
|
1998
2181
|
}
|
|
@@ -2023,9 +2206,10 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2023
2206
|
return directories.length ? directories.map((orphanedDirectory) => Object.assign({}, device, { orphanedDirectory })) : [device];
|
|
2024
2207
|
}), {
|
|
2025
2208
|
avdDirectory: deps.avdDirectory ?? ownedAvdDirectory,
|
|
2026
|
-
size: avdSize
|
|
2209
|
+
size: avdSize,
|
|
2210
|
+
operation
|
|
2027
2211
|
});
|
|
2028
|
-
idleDevices = operation ? await collectIdleDevicesAsync(cfg, sims, deadProjects, now, env) : collectIdleDevices(cfg, sims, deadProjects, now);
|
|
2212
|
+
idleDevices = operation ? await collectIdleDevicesAsync(cfg, sims, deadProjects, now, env, operation.signal, operation) : collectIdleDevices(cfg, sims, deadProjects, now);
|
|
2029
2213
|
if (simsChecked || avdsChecked) {
|
|
2030
2214
|
const lists = {
|
|
2031
2215
|
sims,
|
|
@@ -2060,7 +2244,8 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2060
2244
|
deadProjects
|
|
2061
2245
|
}), {
|
|
2062
2246
|
avdDirectory: deps.avdDirectory ?? ownedAvdDirectory,
|
|
2063
|
-
size: avdSize
|
|
2247
|
+
size: avdSize,
|
|
2248
|
+
operation
|
|
2064
2249
|
});
|
|
2065
2250
|
}
|
|
2066
2251
|
const locks = listBuildLocks();
|
|
@@ -2112,14 +2297,19 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2112
2297
|
workspaceOutputs: await collectWorkspaceOutputs({
|
|
2113
2298
|
olderThan,
|
|
2114
2299
|
now,
|
|
2115
|
-
exclude: goneWorkspaceDirs
|
|
2300
|
+
exclude: goneWorkspaceDirs,
|
|
2301
|
+
signal: operation?.signal,
|
|
2302
|
+
operation
|
|
2303
|
+
}),
|
|
2304
|
+
workspaceLogs: await collectWorkspaceLogs({
|
|
2305
|
+
exclude: goneWorkspaceDirs,
|
|
2306
|
+
operation
|
|
2116
2307
|
}),
|
|
2117
|
-
|
|
2118
|
-
recordings: collectRecordings({
|
|
2308
|
+
recordings: await collectRecordings({
|
|
2119
2309
|
whole: false,
|
|
2120
2310
|
olderThan,
|
|
2121
2311
|
now
|
|
2122
|
-
}, goneWorkspaceDirs),
|
|
2312
|
+
}, goneWorkspaceDirs, operation?.signal),
|
|
2123
2313
|
worktreeSweep,
|
|
2124
2314
|
finishedDevices: listed ? collectFinishedDevices({
|
|
2125
2315
|
config: cfg,
|
|
@@ -2142,30 +2332,41 @@ async function collectGcReport({ olderThan = null, cache = null, worktrees = fal
|
|
|
2142
2332
|
};
|
|
2143
2333
|
}
|
|
2144
2334
|
async function runGc(opts = {}, deps = {}) {
|
|
2145
|
-
|
|
2146
|
-
await maintenanceGuardedSweep(opts, deps);
|
|
2147
|
-
return;
|
|
2148
|
-
}
|
|
2149
|
-
const log = console.log;
|
|
2150
|
-
console.log = console.error;
|
|
2151
|
-
let payload;
|
|
2335
|
+
let result;
|
|
2152
2336
|
try {
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2337
|
+
result = await withGcMaintenanceClaim(opts, (claim) => executeGc(opts, deps, (stream, line) => {
|
|
2338
|
+
if (stream === "stderr" || opts.json) console.error(line);
|
|
2339
|
+
else console.log(line);
|
|
2340
|
+
}, claim));
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
const failure = (0, ownership_claim_exports.claimFailure)(error, opts.delete ? "stim gc --delete" : "stim gc --idle <duration>") ?? (deps.operation?.signal.aborted ? {
|
|
2343
|
+
code: "STIM_CANCELLED",
|
|
2344
|
+
message: "GC was cancelled.",
|
|
2345
|
+
remedy: "Retry the same GC command."
|
|
2346
|
+
} : null);
|
|
2347
|
+
if (!failure) throw error;
|
|
2348
|
+
console.error(`${failure.code}: ${failure.message}`);
|
|
2349
|
+
console.error(failure.remedy);
|
|
2350
|
+
result = {
|
|
2351
|
+
payload: failure,
|
|
2352
|
+
exitCode: 1
|
|
2353
|
+
};
|
|
2156
2354
|
}
|
|
2157
|
-
|
|
2355
|
+
if (result.exitCode) process.exitCode = result.exitCode;
|
|
2356
|
+
if (opts.json) console.log(JSON.stringify(result.payload));
|
|
2158
2357
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2358
|
+
/** Runs GC under this home's exclusive maintenance claim, retaining it when owned cleanup is unresolved. */
|
|
2359
|
+
async function withGcMaintenanceClaim(opts, work) {
|
|
2360
|
+
if (!opts.delete && opts.idle === void 0) return work(void 0);
|
|
2161
2361
|
let claim;
|
|
2362
|
+
let retained = false;
|
|
2162
2363
|
try {
|
|
2163
2364
|
const root = maintenanceRunClaims();
|
|
2164
2365
|
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
2165
2366
|
root,
|
|
2166
2367
|
mode: "exclusive",
|
|
2167
2368
|
label: "maintenance",
|
|
2168
|
-
details: { trigger: "gc --delete" }
|
|
2369
|
+
details: { trigger: opts.delete ? "gc --delete" : "gc --idle" }
|
|
2169
2370
|
});
|
|
2170
2371
|
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
2171
2372
|
claim = attempt.acquired;
|
|
@@ -2178,20 +2379,45 @@ async function maintenanceGuardedSweep(opts, deps) {
|
|
|
2178
2379
|
reason: `held by pid ${holder?.owner.pid ?? "?"} since ${holder?.startedAt ?? "?"} (${String(holder?.details.trigger ?? "maintenance")})`
|
|
2179
2380
|
});
|
|
2180
2381
|
}
|
|
2181
|
-
return await
|
|
2182
|
-
} catch (
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2382
|
+
return await work(claim);
|
|
2383
|
+
} catch (cause) {
|
|
2384
|
+
if (claim && (cause instanceof WorkspaceProcessCleanupError || cause instanceof Error && "reportCleanup" in cause)) {
|
|
2385
|
+
retained = true;
|
|
2386
|
+
throw Object.assign(new WorkspaceProcessCleanupError([.../* @__PURE__ */ new Set([claim, ...cause instanceof WorkspaceProcessCleanupError ? cause.claims : []])], cause.message, "Verify GC and its owned children have stopped."), { reportCleanup: true });
|
|
2387
|
+
}
|
|
2388
|
+
throw cause;
|
|
2389
|
+
} finally {
|
|
2390
|
+
if (claim && !retained) (0, ownership_claim_exports.releaseClaim)(claim);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
/** Executes one already-admitted sweep with separate progress, results and exit status. */
|
|
2394
|
+
async function executeGc(opts, deps, write, claim) {
|
|
2395
|
+
const results = createGcResults();
|
|
2396
|
+
let failed = false;
|
|
2397
|
+
const output = {
|
|
2398
|
+
log: (line) => write("stdout", line),
|
|
2399
|
+
error: (line) => write("stderr", line),
|
|
2400
|
+
record: results.record,
|
|
2401
|
+
fail: () => {
|
|
2402
|
+
failed = true;
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
try {
|
|
2406
|
+
declareSpawnsOn(deps.operation, claim);
|
|
2407
|
+
return {
|
|
2408
|
+
payload: await sweep(opts, deps, {
|
|
2409
|
+
output,
|
|
2410
|
+
takeResults: results.take
|
|
2411
|
+
}),
|
|
2412
|
+
exitCode: failed ? 1 : 0
|
|
2413
|
+
};
|
|
2189
2414
|
} finally {
|
|
2190
|
-
|
|
2415
|
+
stopDeclaringSpawnsOn(deps.operation, claim);
|
|
2191
2416
|
}
|
|
2192
2417
|
}
|
|
2193
|
-
async function sweep(opts, deps, reportOnly = false) {
|
|
2194
|
-
const
|
|
2418
|
+
async function sweep(opts, deps, execution, reportOnly = false) {
|
|
2419
|
+
const { output } = execution;
|
|
2420
|
+
const core = (dependencies) => runGcCore(opts, dependencies, execution, reportOnly);
|
|
2195
2421
|
if (opts.cache && (opts.worktrees || opts.idle !== void 0)) {
|
|
2196
2422
|
const flag = opts.worktrees ? "--worktrees" : "--idle";
|
|
2197
2423
|
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.";
|
|
@@ -2201,10 +2427,10 @@ async function sweep(opts, deps, reportOnly = false) {
|
|
|
2201
2427
|
message,
|
|
2202
2428
|
remedy
|
|
2203
2429
|
};
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2430
|
+
output.error(chalk.red(message));
|
|
2431
|
+
output.error(chalk.dim(remedy));
|
|
2432
|
+
output.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
2433
|
+
output.fail();
|
|
2208
2434
|
return {
|
|
2209
2435
|
code: "STIM_BAD_ARG",
|
|
2210
2436
|
message,
|
|
@@ -2220,10 +2446,10 @@ async function sweep(opts, deps, reportOnly = false) {
|
|
|
2220
2446
|
message,
|
|
2221
2447
|
remedy
|
|
2222
2448
|
};
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2449
|
+
output.error(chalk.red(message));
|
|
2450
|
+
output.error(chalk.dim(remedy));
|
|
2451
|
+
output.error(chalk.red("failed: STIM_BAD_ARG"));
|
|
2452
|
+
output.fail();
|
|
2227
2453
|
return {
|
|
2228
2454
|
code: "STIM_BAD_ARG",
|
|
2229
2455
|
message,
|
|
@@ -2233,7 +2459,7 @@ async function sweep(opts, deps, reportOnly = false) {
|
|
|
2233
2459
|
const poolError = parkedMaxSetting("ios").error || parkedMaxSetting("android").error;
|
|
2234
2460
|
if (poolError) {
|
|
2235
2461
|
if (reportOnly) phase("note", `${poolError} ${POOL_SETTING_REMEDY}`, deps.operation);
|
|
2236
|
-
else
|
|
2462
|
+
else output.error(chalk.yellow(`${poolError} ${POOL_SETTING_REMEDY}`));
|
|
2237
2463
|
}
|
|
2238
2464
|
if (opts.cache) return core({
|
|
2239
2465
|
...deps,
|
|
@@ -2286,9 +2512,15 @@ async function sweep(opts, deps, reportOnly = false) {
|
|
|
2286
2512
|
}, {
|
|
2287
2513
|
waitMs: 0,
|
|
2288
2514
|
ownerPurpose: "EAS orphan sweep",
|
|
2289
|
-
machineRoot: deps.easLedgerRoot
|
|
2515
|
+
machineRoot: deps.easLedgerRoot,
|
|
2516
|
+
...deps.operation ? {
|
|
2517
|
+
signal: deps.operation.signal,
|
|
2518
|
+
declareSpawnsFor: deps.operation
|
|
2519
|
+
} : {}
|
|
2290
2520
|
});
|
|
2291
2521
|
} catch (error) {
|
|
2522
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
2523
|
+
deps.operation?.signal.throwIfAborted();
|
|
2292
2524
|
easSessionSweep = {
|
|
2293
2525
|
projectScope: projectRoot,
|
|
2294
2526
|
orphaned: [],
|
|
@@ -2301,44 +2533,48 @@ async function sweep(opts, deps, reportOnly = false) {
|
|
|
2301
2533
|
precollectedEasSessionSweep: easSessionSweep
|
|
2302
2534
|
});
|
|
2303
2535
|
}
|
|
2304
|
-
async function pruneDeadProjects(deadProjects) {
|
|
2536
|
+
async function pruneDeadProjects(deadProjects, output, operation) {
|
|
2305
2537
|
let deleteFailures = 0;
|
|
2306
2538
|
for (const path of deadProjects) {
|
|
2539
|
+
operation?.signal.throwIfAborted();
|
|
2307
2540
|
if (existsSync$1(path) || !isOnMountedVolume(path)) {
|
|
2308
|
-
|
|
2309
|
-
|
|
2541
|
+
output.log(chalk.yellow(`Kept ${path}: its absence can no longer be confirmed.`));
|
|
2542
|
+
output.record("project", "kept", path, { detail: "its absence can no longer be confirmed" });
|
|
2310
2543
|
continue;
|
|
2311
2544
|
}
|
|
2312
2545
|
const result = await reclaimProject(path, {
|
|
2313
2546
|
archive: { removedBy: "gc" },
|
|
2314
|
-
stopRequester: { by: "stim gc --delete" }
|
|
2547
|
+
stopRequester: { by: "stim gc --delete" },
|
|
2548
|
+
operation
|
|
2315
2549
|
}).catch((error) => {
|
|
2550
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2551
|
+
operation?.signal.throwIfAborted();
|
|
2316
2552
|
deleteFailures++;
|
|
2317
|
-
|
|
2318
|
-
|
|
2553
|
+
output.log(chalk.red(`Could not prune ${path}; its registry entry was kept: ${error.message}`));
|
|
2554
|
+
output.record("project", "failed", path, { detail: `its registry entry was kept: ${error.message}` });
|
|
2319
2555
|
return null;
|
|
2320
2556
|
});
|
|
2321
2557
|
if (!result) continue;
|
|
2322
2558
|
if (result.keptEntry) {
|
|
2323
|
-
|
|
2324
|
-
|
|
2559
|
+
output.log(chalk.yellow(`Could not fully prune ${path}; its registry entry was kept.`));
|
|
2560
|
+
output.record("project", "kept", path, { detail: "it could not be fully pruned; its registry entry was kept" });
|
|
2325
2561
|
} else {
|
|
2326
|
-
|
|
2327
|
-
|
|
2562
|
+
output.log(chalk.green(`Pruned ${path}`));
|
|
2563
|
+
output.record("project", "done", path);
|
|
2328
2564
|
}
|
|
2329
|
-
for (const dir of result.removedWorkspaceDirs)
|
|
2565
|
+
for (const dir of result.removedWorkspaceDirs) output.log(chalk.dim(` removed workspace output ${dir}`));
|
|
2330
2566
|
for (const dir of result.failedWorkspaceDirs) {
|
|
2331
|
-
|
|
2332
|
-
|
|
2567
|
+
output.log(chalk.red(` could not remove workspace output ${dir}`));
|
|
2568
|
+
output.record("workspaceDirectory", "failed", dir, { detail: `could not remove it while pruning ${path}` });
|
|
2333
2569
|
deleteFailures += 1;
|
|
2334
2570
|
}
|
|
2335
|
-
if (result.killedPid)
|
|
2336
|
-
if (result.stoppedSession)
|
|
2337
|
-
if (result.stoppedTunnel)
|
|
2338
|
-
for (const name of result.deletedDevices)
|
|
2571
|
+
if (result.killedPid) output.log(chalk.dim(` killed orphaned Metro pid ${result.killedPid}`));
|
|
2572
|
+
if (result.stoppedSession) output.log(chalk.dim(` stopped remote session ${result.stoppedSession}`));
|
|
2573
|
+
if (result.stoppedTunnel) output.log(chalk.dim(` stopped ${result.stoppedTunnel} tunnel`));
|
|
2574
|
+
for (const name of result.deletedDevices) output.record("device", "done", name);
|
|
2339
2575
|
for (const s of result.skippedDevices) {
|
|
2340
|
-
|
|
2341
|
-
|
|
2576
|
+
output.log(chalk.yellow(` ${s.name}: ${s.reason}`));
|
|
2577
|
+
output.record("device", result.failedDevices.includes(s) ? "failed" : "kept", s.name, {
|
|
2342
2578
|
id: s.udid ?? null,
|
|
2343
2579
|
detail: s.reason
|
|
2344
2580
|
});
|
|
@@ -2370,19 +2606,21 @@ function dryRunHint(cache, all, actionable, caches) {
|
|
|
2370
2606
|
function scopedWorkPending(cache, report) {
|
|
2371
2607
|
return memoryWorkPending(cache, report.memory) || gradleVersionsPending(report.gradleVersions);
|
|
2372
2608
|
}
|
|
2373
|
-
function pruneStatusCaches(report) {
|
|
2609
|
+
async function pruneStatusCaches(report, output, signal) {
|
|
2374
2610
|
if (report.cacheScope !== null) return 0;
|
|
2375
|
-
return deleteStaleStatusCaches(collectStaleStatusCaches(listMountedVolumes()).stale);
|
|
2611
|
+
return deleteStaleStatusCaches(collectStaleStatusCaches(listMountedVolumes()).stale, output, signal);
|
|
2376
2612
|
}
|
|
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
|
+
async function reclaimParkedDevices(report, deps, output) {
|
|
2614
|
+
return report.cacheScope ? await eraseParkedDevices(report.parkedSims, report.parkedAvds, output, deps.operation) : await deleteParkedSims(report.parkedSims, deps, output) + await deleteParkedAvds(report.parkedAvds, output, deps.operation);
|
|
2379
2615
|
}
|
|
2380
2616
|
function reportActionable(report) {
|
|
2381
2617
|
const { deadProjects, invalidProjects, orphanedDevices, staleDevices, staleDeviceRecords, buildLocks, buildSlots, deviceLeases, easSessionSweep, caches, olderThan, all, cacheScope: cache } = report;
|
|
2382
2618
|
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
2619
|
}
|
|
2384
|
-
async function runGcCore(opts, deps, reportOnly = false) {
|
|
2385
|
-
|
|
2620
|
+
async function runGcCore(opts, deps, execution, reportOnly = false) {
|
|
2621
|
+
const { output, takeResults } = execution;
|
|
2622
|
+
const signal = deps.operation?.signal;
|
|
2623
|
+
throwIfCancelled(signal, "GC");
|
|
2386
2624
|
const olderThan = typeof opts.olderThan === "number" ? opts.olderThan : null;
|
|
2387
2625
|
const cache = typeof opts.cache === "string" && opts.cache.trim() ? opts.cache : null;
|
|
2388
2626
|
const refresh = opts.delete === true;
|
|
@@ -2392,13 +2630,14 @@ async function runGcCore(opts, deps, reportOnly = false) {
|
|
|
2392
2630
|
worktrees: Boolean(opts.worktrees),
|
|
2393
2631
|
refresh
|
|
2394
2632
|
}, deps);
|
|
2633
|
+
throwIfCancelled(signal, "GC");
|
|
2395
2634
|
if (cache && report.caches.length === 0 && report.workspaceOutputs === null && !selectsBeyondCaches(cache)) {
|
|
2396
2635
|
const names = [...new Set(discoverCaches(deps.operation?.env ?? process.env, deps.operation?.cwd).map((c) => c.name))];
|
|
2397
2636
|
const message = `No shared cache carries "${cache}" in its name or directory.`;
|
|
2398
2637
|
if (!reportOnly) {
|
|
2399
|
-
|
|
2400
|
-
if (names.length)
|
|
2401
|
-
if (opts.json)
|
|
2638
|
+
output.log(chalk.yellow(message));
|
|
2639
|
+
if (names.length) output.log(chalk.dim(`Caches on this machine: ${names.join(", ")}`));
|
|
2640
|
+
if (opts.json) output.fail();
|
|
2402
2641
|
}
|
|
2403
2642
|
return {
|
|
2404
2643
|
code: "STIM_BAD_ARG",
|
|
@@ -2410,21 +2649,26 @@ async function runGcCore(opts, deps, reportOnly = false) {
|
|
|
2410
2649
|
const refusal = archiveRefusal(report.archives);
|
|
2411
2650
|
if (refusal !== null) {
|
|
2412
2651
|
if (!reportOnly) {
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2652
|
+
output.fail();
|
|
2653
|
+
output.error(chalk.red(refusal.message));
|
|
2654
|
+
output.error(chalk.dim(refusal.remedy));
|
|
2655
|
+
output.error(chalk.red(`failed: ${refusal.code}`));
|
|
2417
2656
|
}
|
|
2418
2657
|
return refusal;
|
|
2419
2658
|
}
|
|
2420
2659
|
const all = report.all;
|
|
2421
|
-
if (!reportOnly) for (const line of formatGcReport(report))
|
|
2660
|
+
if (!reportOnly) for (const line of formatGcReport(report)) output.log(line);
|
|
2422
2661
|
const { deadProjects, invalidProjects, orphanedDevices, staleDevices, staleDeviceRecords, buildLocks, buildSlots, deviceLeases, easSessionSweep, caches } = report;
|
|
2423
2662
|
const actionable = reportActionable(report);
|
|
2424
2663
|
const idle = opts.idle ?? null;
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2664
|
+
throwIfCancelled(signal, "GC");
|
|
2665
|
+
const idleFailures = idle === null ? 0 : (await shutDownIdleDevices(report.idleDevices, idle, async () => deps.operation ? collectIdleDevicesAsync(loadConfig$1(), await listAllIosSimsAsync({
|
|
2666
|
+
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
2667
|
+
operation: deps.operation,
|
|
2668
|
+
signal
|
|
2669
|
+
}), report.deadProjects, Date.now(), deps.operation.env, signal, deps.operation) : collectIdleDevices(loadConfig$1(), listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }), report.deadProjects), output, signal, deps.operation)).failures;
|
|
2670
|
+
if (idleFailures) output.fail();
|
|
2671
|
+
const inventory = opts.json && !opts.delete && idle === null && report.cacheScope === null ? await collectInventory(loadConfig$1(), deadProjects, deps.operation?.env ?? process.env, deps.operation) : null;
|
|
2428
2672
|
const payload = (failures) => ({
|
|
2429
2673
|
mode: opts.delete ? "delete" : "dry-run",
|
|
2430
2674
|
idle,
|
|
@@ -2435,48 +2679,70 @@ async function runGcCore(opts, deps, reportOnly = false) {
|
|
|
2435
2679
|
failures,
|
|
2436
2680
|
sections: gcReportSections(report),
|
|
2437
2681
|
...report.maintenance ? { maintenance: report.maintenance } : {},
|
|
2438
|
-
results: reportOnly ? [] :
|
|
2682
|
+
results: reportOnly ? [] : takeResults(),
|
|
2439
2683
|
inventory
|
|
2440
2684
|
});
|
|
2441
2685
|
if (!opts.delete) {
|
|
2442
2686
|
const hint = dryRunHint(cache, all, actionable, caches.length > 0);
|
|
2443
|
-
if (hint && !reportOnly)
|
|
2687
|
+
if (hint && !reportOnly) output.log(chalk.dim(`\n${hint}`));
|
|
2444
2688
|
return payload(idle === null ? null : idleFailures);
|
|
2445
2689
|
}
|
|
2446
2690
|
let deleteFailures = idleFailures;
|
|
2447
|
-
|
|
2448
|
-
deleteFailures +=
|
|
2449
|
-
|
|
2450
|
-
deleteFailures +=
|
|
2691
|
+
throwIfCancelled(signal, "GC");
|
|
2692
|
+
deleteFailures += await deleteArchives(report.archives, output, signal);
|
|
2693
|
+
throwIfCancelled(signal, "GC");
|
|
2694
|
+
deleteFailures += report.workspaceOutputs ? (await clearWorkspaceOutputs(report.workspaceOutputs, {
|
|
2695
|
+
olderThan,
|
|
2696
|
+
signal,
|
|
2697
|
+
operation: deps.operation
|
|
2698
|
+
}, output)).failures : 0;
|
|
2699
|
+
throwIfCancelled(signal, "GC");
|
|
2700
|
+
deleteFailures += await trimWorkspaceLogs(report.workspaceLogs, output, deps.operation);
|
|
2701
|
+
throwIfCancelled(signal, "GC");
|
|
2702
|
+
deleteFailures += await deleteRecordings(report.recordings, {
|
|
2451
2703
|
whole: all,
|
|
2452
2704
|
olderThan,
|
|
2453
2705
|
now: Date.now()
|
|
2454
|
-
});
|
|
2455
|
-
|
|
2456
|
-
deleteFailures += await
|
|
2457
|
-
|
|
2458
|
-
deleteFailures += await
|
|
2459
|
-
|
|
2460
|
-
deleteFailures += await
|
|
2461
|
-
|
|
2462
|
-
deleteFailures += await
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
deleteFailures +=
|
|
2706
|
+
}, output, signal);
|
|
2707
|
+
throwIfCancelled(signal, "GC");
|
|
2708
|
+
deleteFailures += await reclaimParkedDevices(report, deps, output);
|
|
2709
|
+
throwIfCancelled(signal, "GC");
|
|
2710
|
+
deleteFailures += await deleteParkedHostedDevices(report.parkedHostedDevices, { operation: deps.operation }, output);
|
|
2711
|
+
throwIfCancelled(signal, "GC");
|
|
2712
|
+
deleteFailures += await reclaimMemory(cache, report.memory, output, deps.operation);
|
|
2713
|
+
throwIfCancelled(signal, "GC");
|
|
2714
|
+
deleteFailures += await deleteGradleVersions(report.gradleVersions, output, deps.operation);
|
|
2715
|
+
throwIfCancelled(signal, "GC");
|
|
2716
|
+
removeInvalidProjectEntries(invalidProjects, output, signal);
|
|
2717
|
+
throwIfCancelled(signal, "GC");
|
|
2718
|
+
deleteFailures += await pruneDeadProjects(deadProjects, output, deps.operation);
|
|
2719
|
+
throwIfCancelled(signal, "GC");
|
|
2720
|
+
deleteFailures += await deleteOrphanedWorkspaces(report.orphanedWorkspaces, output, deps.operation);
|
|
2721
|
+
throwIfCancelled(signal, "GC");
|
|
2722
|
+
deleteFailures += await deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords, output, deps.operation);
|
|
2723
|
+
throwIfCancelled(signal, "GC");
|
|
2724
|
+
deleteFailures += await deleteFinishedDevices(report, output, deps.operation);
|
|
2725
|
+
throwIfCancelled(signal, "GC");
|
|
2726
|
+
deleteFailures += await forgetStaleLedgerEntries(report.staleLedgerEntries, output, signal);
|
|
2727
|
+
throwIfCancelled(signal, "GC");
|
|
2728
|
+
deleteFailures += await forgetStaleEasLedgerEntries(report.staleEasLedgerEntries, listMountedVolumes(), deps, output, signal);
|
|
2729
|
+
throwIfCancelled(signal, "GC");
|
|
2730
|
+
deleteFailures += await pruneStatusCaches(report, output, signal);
|
|
2467
2731
|
if (report.tempLeftovers) deleteFailures += (await deleteTempLeftovers({
|
|
2468
2732
|
env: deps.operation?.env ?? process.env,
|
|
2469
|
-
signal: deps.operation?.signal
|
|
2470
|
-
|
|
2733
|
+
signal: deps.operation?.signal,
|
|
2734
|
+
operation: deps.operation
|
|
2735
|
+
}, output)).failures;
|
|
2471
2736
|
for (const lock of buildLocks.stale) {
|
|
2737
|
+
throwIfCancelled(signal, "GC");
|
|
2472
2738
|
const cleared = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
2473
2739
|
root: lock.path,
|
|
2474
2740
|
label: `${lock.platform} build`
|
|
2475
2741
|
});
|
|
2476
2742
|
if (cleared.status === "held") continue;
|
|
2477
2743
|
if (cleared.status === "refused") {
|
|
2478
|
-
|
|
2479
|
-
|
|
2744
|
+
output.log(chalk.yellow(`Left the build lock at ${lock.path} alone: ${cleared.reason}`));
|
|
2745
|
+
output.record("buildLock", "kept", `${lock.platform} build lock`, {
|
|
2480
2746
|
id: lock.path,
|
|
2481
2747
|
detail: cleared.reason
|
|
2482
2748
|
});
|
|
@@ -2484,25 +2750,26 @@ async function runGcCore(opts, deps, reportOnly = false) {
|
|
|
2484
2750
|
}
|
|
2485
2751
|
if (cleared.status === "failed") {
|
|
2486
2752
|
deleteFailures++;
|
|
2487
|
-
|
|
2488
|
-
|
|
2753
|
+
output.log(chalk.red(`Failed to clear the build lock at ${lock.path}: ${cleared.reason}`));
|
|
2754
|
+
output.record("buildLock", "failed", `${lock.platform} build lock`, {
|
|
2489
2755
|
id: lock.path,
|
|
2490
2756
|
detail: cleared.reason
|
|
2491
2757
|
});
|
|
2492
2758
|
continue;
|
|
2493
2759
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2760
|
+
output.record("buildLock", "done", `${lock.platform} build lock`, { id: lock.path });
|
|
2761
|
+
output.log(chalk.green(`Cleared the ${lock.platform} build lock left by pid ${lock.pid ?? "?"} (${lock.projectRoot || "unrecorded workspace"})`));
|
|
2496
2762
|
}
|
|
2497
2763
|
for (const slot of buildSlots.stale) {
|
|
2764
|
+
throwIfCancelled(signal, "GC");
|
|
2498
2765
|
const cleared = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
2499
2766
|
root: slot.path,
|
|
2500
2767
|
label: "build slot"
|
|
2501
2768
|
});
|
|
2502
2769
|
if (cleared.status === "held") continue;
|
|
2503
2770
|
if (cleared.status === "refused") {
|
|
2504
|
-
|
|
2505
|
-
|
|
2771
|
+
output.log(chalk.yellow(`Left the build slot at ${slot.path} alone: ${cleared.reason}`));
|
|
2772
|
+
output.record("buildSlot", "kept", `build slot ${slot.index ?? "?"}`, {
|
|
2506
2773
|
id: slot.path,
|
|
2507
2774
|
detail: cleared.reason
|
|
2508
2775
|
});
|
|
@@ -2510,39 +2777,45 @@ async function runGcCore(opts, deps, reportOnly = false) {
|
|
|
2510
2777
|
}
|
|
2511
2778
|
if (cleared.status === "failed") {
|
|
2512
2779
|
deleteFailures++;
|
|
2513
|
-
|
|
2514
|
-
|
|
2780
|
+
output.log(chalk.red(`Failed to clear the build slot at ${slot.path}: ${cleared.reason}`));
|
|
2781
|
+
output.record("buildSlot", "failed", `build slot ${slot.index ?? "?"}`, {
|
|
2515
2782
|
id: slot.path,
|
|
2516
2783
|
detail: cleared.reason
|
|
2517
2784
|
});
|
|
2518
2785
|
continue;
|
|
2519
2786
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
}
|
|
2523
|
-
for (const entry of deviceLeases.expired)
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2787
|
+
output.record("buildSlot", "done", `build slot ${slot.index ?? "?"}`, { id: slot.path });
|
|
2788
|
+
output.log(chalk.green(`Cleared build slot ${slot.index ?? "?"} left by pid ${slot.pid ?? "?"} (${slot.projectRoot || "unrecorded workspace"})`));
|
|
2789
|
+
}
|
|
2790
|
+
for (const entry of deviceLeases.expired) {
|
|
2791
|
+
throwIfCancelled(signal, "GC");
|
|
2792
|
+
try {
|
|
2793
|
+
if (removeExpiredLease(entry)) {
|
|
2794
|
+
output.record("deviceLease", "done", `${entry.platform} lease on ${entry.id ?? entry.name}`, { id: entry.path });
|
|
2795
|
+
output.log(chalk.green(`Cleared the expired ${entry.platform} device lease on ${entry.id ?? entry.name} (held by ${entry.lease?.holder ?? "an unrecorded workspace"})`));
|
|
2796
|
+
} else output.log(chalk.dim(`${entry.name} is no longer an expired lease; left alone.`));
|
|
2797
|
+
} catch (err) {
|
|
2798
|
+
deleteFailures++;
|
|
2799
|
+
output.log(chalk.red(`Failed to clear the device lease at ${entry.path}: ${err?.message || err}`));
|
|
2800
|
+
output.record("deviceLease", "failed", `${entry.platform} lease on ${entry.id ?? entry.name}`, {
|
|
2801
|
+
id: entry.path,
|
|
2802
|
+
detail: err?.message || String(err)
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2535
2805
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2806
|
+
throwIfCancelled(signal, "GC");
|
|
2807
|
+
deleteFailures += await deleteEasSessions(easSessionSweep, deps, output);
|
|
2808
|
+
throwIfCancelled(signal, "GC");
|
|
2809
|
+
if (report.worktreeSweep) deleteFailures += await removeWorktrees(report.worktreeSweep, { operation: deps.operation }, output);
|
|
2538
2810
|
if (deleteFailures) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2811
|
+
output.log(chalk.red(`\n${deleteFailures} entr${deleteFailures === 1 ? "y" : "ies"} could not be deleted; see above.`));
|
|
2812
|
+
output.fail();
|
|
2541
2813
|
}
|
|
2542
|
-
|
|
2814
|
+
throwIfCancelled(signal, "GC");
|
|
2815
|
+
if (all) await emptyCaches(caches, output, signal, deps.operation);
|
|
2543
2816
|
else if (olderThan === null) {
|
|
2544
|
-
if (caches.length)
|
|
2545
|
-
} else trimCaches(caches, olderThan);
|
|
2817
|
+
if (caches.length) output.log(chalk.dim("Shared caches left alone: pass --cache all to empty them, or --older-than <days> to trim them."));
|
|
2818
|
+
} else await trimCaches(caches, olderThan, output, signal, deps.operation);
|
|
2546
2819
|
return payload(deleteFailures);
|
|
2547
2820
|
}
|
|
2548
2821
|
function gcCommand(program) {
|
|
@@ -2558,8 +2831,9 @@ function gcCommand(program) {
|
|
|
2558
2831
|
if (ms === null) throw new InvalidArgumentError("must be a whole number of minutes, hours, or days, e.g. --idle 2h");
|
|
2559
2832
|
return ms;
|
|
2560
2833
|
}).option("--json", "print the report as JSON on stdout; every other line goes to stderr").action(async (opts) => {
|
|
2561
|
-
await runGc(opts);
|
|
2834
|
+
const { interrupted } = await runProcessOperation(process.cwd(), (operation) => withDrainingCancellation(operation, operation.signal, () => runGc(opts, { operation })));
|
|
2835
|
+
if (interrupted !== null) process.exitCode = interrupted;
|
|
2562
2836
|
});
|
|
2563
2837
|
}
|
|
2564
2838
|
//#endregion
|
|
2565
|
-
export {
|
|
2839
|
+
export { runGc as a, readGcReport as i, executeGc as n, withGcMaintenanceClaim as o, gcCommand as r, formatGcReport as s, collectGcReport as t };
|