stim 1.25.0 → 1.27.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 +1 -1
- package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
- package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
- package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
- package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
- package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
- package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
- package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
- package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
- package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
- package/dist/api.d.mts +20 -7
- package/dist/api.mjs +42 -32
- package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
- package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
- package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
- package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
- package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
- package/dist/auto-placement-z-H5pJza.mjs +350 -0
- package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
- package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
- package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
- package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
- package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
- package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
- package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
- package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
- package/dist/cli.mjs +1 -1
- package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
- package/dist/collector-run.d.mts +10 -9
- package/dist/collector-run.mjs +42 -27
- package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
- package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
- package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
- package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
- package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
- package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
- package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
- package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
- package/dist/device-host-worker.mjs +19 -21
- package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
- package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
- package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
- package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
- package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
- package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
- package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
- package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
- package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
- package/dist/eas-fallback-9fqb46kW.mjs +87 -0
- package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
- package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
- package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
- package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
- package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
- package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
- package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
- package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
- package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
- package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
- package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
- package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
- package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
- package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
- package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
- package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
- package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
- package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
- package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
- package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
- package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
- package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
- package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
- package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
- package/dist/lightened-BCsdAGj_.mjs +171 -0
- package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
- package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
- package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
- package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +30 -13
- package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
- package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
- package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
- package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
- package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
- package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
- package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
- package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
- package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
- package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
- package/dist/offload-worker.d.mts +3 -1
- package/dist/offload-worker.mjs +34 -25
- package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
- package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
- package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
- package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
- package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
- package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
- package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
- package/dist/prebuild-BDpToDgR.mjs +1246 -0
- package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
- package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
- package/dist/pull-requests.mjs +1 -1
- package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
- package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
- package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
- package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
- package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
- package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
- package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
- package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
- package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
- package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
- package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
- package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
- package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
- package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
- package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
- package/dist/server-expo-Ba_Y21vB.mjs +2 -0
- package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
- package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
- package/dist/settings-payload-cm60Zhib.mjs +257 -0
- package/dist/settings-write-BdNRLlBn.mjs +185 -0
- package/dist/settings.schema.json +84 -39
- package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
- package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
- package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
- package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
- package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
- package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
- package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
- package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
- package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
- package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
- package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
- package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
- package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
- package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
- package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
- package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
- package/dist/supervisor-run.d.mts +1 -0
- package/dist/supervisor-run.mjs +17 -18
- package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
- package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
- package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
- package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
- package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
- package/dist/toolchain-JYAOquPg.mjs +2 -0
- package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
- package/dist/trigger-qp2ji5wE.mjs +2 -0
- package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
- package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
- package/dist/web-Je6abcpz.mjs +2 -0
- package/dist/web-run.mjs +9 -9
- package/dist/windows-report.mjs +1 -1
- package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
- package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
- package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
- package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
- package/package.json +8 -8
- package/dist/ccache-h1FKhZeg.mjs +0 -143
- package/dist/created-devices-D2Aca-xu.mjs +0 -52
- package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
- package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
- package/dist/native-run-D5OJsGwA.mjs +0 -276
- package/dist/paths-DQtTgsBf.mjs +0 -42
- package/dist/prebuild-JUaqOLpc.mjs +0 -501
- package/dist/server-expo-C16Zf2k7.mjs +0 -2
- package/dist/settings-write-n0zwfohx.mjs +0 -360
- package/dist/toolchain-DB1aeqxO.mjs +0 -2
- package/dist/toolchain-VzLJzZ7O.mjs +0 -908
- package/dist/trigger-BLsHgAGR.mjs +0 -2
- package/dist/web-DS4UDWvf.mjs +0 -2
- package/dist/xcode-CiIIDnuD.mjs +0 -910
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-
|
|
2
|
-
import {
|
|
3
|
-
import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-
|
|
4
|
-
import { o as formatLongDuration, u as plural } from "./command-output-
|
|
5
|
-
import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, r as childEnvironment } from "./operation-exec-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { a as unregister, n as readManifest } from "./cache-manifest-
|
|
11
|
-
import {
|
|
1
|
+
import { l as WorkspaceProcessCleanupError, n as isTimeoutError, t as getExecutor } from "./exec-aULwtNly.mjs";
|
|
2
|
+
import { j as workspaceStateFile$1, k as workspaceLogsDir$1, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-BjqdjnJR.mjs";
|
|
3
|
+
import { F as isOnMountedVolume, G as projectDeviceSlots, I as listMountedVolumes, L as measuredDirectorySizeAsync, N as directorySizeAsync, P as formatBytes, _ as loadConfig$1, p as getProject } from "./config-Cn94Jc0g.mjs";
|
|
4
|
+
import { o as formatLongDuration, u as plural } from "./command-output-Dj-WT4BP.mjs";
|
|
5
|
+
import { c as gitQuiet, l as gitQuietAsync, n as runOperationFileQuiet, r as childEnvironment } from "./operation-exec-Dp4FuIGR.mjs";
|
|
6
|
+
import { At as listWorktreesAsync, Bt as resolveSourceCheckoutAsync, Ct as hasPopulatedSubmodulesAsync, Ht as sourceCheckoutOf, O as maintenanceKeepSetting, Tt as hasUncommittedWorkAsync, Ut as unpushedCommitsAsync, X as resolveProjectSettings, Z as resolveProjectSettingsAsync, h as deviceIdleShutdownMinutesSetting, kt as listWorktrees, tn as removeTemporaryEntryAsync, yt as dirtyPathsAsync, zt as resolveFullRefAsync } from "./settings-BDVI8Ivc.mjs";
|
|
7
|
+
import { i as findProjectRoot$1 } from "./project-DYj-HVx6.mjs";
|
|
8
|
+
import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, P as listAvds, V as ownedAvdDirectory, yt as readParked } from "./android-DriNoAVy.mjs";
|
|
9
|
+
import { A as diskPressureLevel, G as planCacheEmptying, N as lowestFree, P as protectWindowMs, X as collectGradleVersions, Z as deleteGradleVersion, Zn as collectMemoryReport, _ as resolveBudget, at as DEVICE_LIST_TIMEOUT_MS, ct as collectParkedSims, d as enforceBudget, dr as pruneCache, dt as deleteProjectDevices, j as levelFloors, lt as deleteParkedAvds, mt as deviceSweepIsScoped, nr as memorySweepIsScoped, nt as waitForGradleExit, or as stopIdleGradleDaemons, p as machineNumber, sr as phase, st as collectParkedAvds, u as cacheLabel, ur as discoverCaches, ut as deleteParkedSims, v as trimmableCaches, vt as findStaleProjectDevices, y as stopOwnedMetro, z as emptyCaches } from "./ccache-BWTze1Ls.mjs";
|
|
10
|
+
import { a as unregister, n as readManifest } from "./cache-manifest-DEjqGGat.mjs";
|
|
11
|
+
import { Dt as readConcurrency, L as workspaceBuildInProgress, M as collectOwnedDeviceActivityAsync, St as GC_OUTPUT, Tt as eachLimited, _ as withIdleWorkspace, a as collectOrphanedWorkspaces, c as isInsideWorkspaces, h as ARTIFACT_DIRS, i as clearWorkspaceOutputs, j as collectOwnedDeviceActivity, l as listWorkspaceDirs, m as workspaceDirRemoved, o as collectWorkspaceOutputs, p as workspaceBuildReasons, s as deleteOrphanedWorkspaces, t as LIGHTEN_AFTER_MS, u as recentNativeUseReason, v as workspaceInUse, wt as takeGcResults, y as workspaceInUseAsync } from "./workspaces-DFbj07jp.mjs";
|
|
12
12
|
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-BQzvJlzw.mjs";
|
|
13
|
+
import { N as listBuildLocks, _ as workspaceDeviceLastUseAt, b as leaseIsExpired, g as workspaceActivity, x as listLeaseFiles } from "./activity-Cd-ysFes.mjs";
|
|
14
|
+
import { d as listAllIosSims, f as listAllIosSimsAsync } from "./ios-E26qSpkb.mjs";
|
|
16
15
|
import { s as metroStoreRoot } from "./errors-DHm_U5gv.mjs";
|
|
17
16
|
import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
17
|
+
import { D as stimCliEntries, E as serviceRunningFrom, N as worktreeArchiveLimits, O as stimCliUseOf, S as measurePressureAsync, T as uncommittedActivityOf, a as matchWorktreeEntry, b as measureDiskAsync, c as reclaimKeys, i as excludeWatchmanCookies, m as removeWorktreeTarget, o as nestedWorktrees, r as excludePodChurn, s as porcelainPath, v as cacheBlocked, x as measurePressure, y as measureDisk } from "./worktree-COAJ3-OD.mjs";
|
|
18
|
+
import { n as describePullRequest, r as endedPullRequest, s as pullRequestLookups } from "./pull-request-BzLmzAIQ.mjs";
|
|
19
|
+
import { a as lastFetchedAtAsync, c as mergeStateAsync, f as recordPullRequestLookup, i as fetchDefaultBranchAsync, o as localDefaultBranchAsync, r as FETCH_FRESH_MS } from "./warm-progress-C0RsbngN.mjs";
|
|
20
|
+
import { a as restoreProblem, r as recordLightened, t as finishLightened } from "./lightened-BCsdAGj_.mjs";
|
|
21
|
+
import { i as writeIdleShutdownEvent, n as dueIdleDevices, r as shutDownIdleDevices } from "./idle-shutdown-Cbvo5KEh.mjs";
|
|
21
22
|
import { readClaimSet } from "@stim-cli/core/ownership-claim";
|
|
22
|
-
import { existsSync, lstatSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
23
|
+
import { existsSync, lstatSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
23
24
|
import { tmpdir } from "node:os";
|
|
24
25
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
25
26
|
import { buildCacheRoot, configDir, metroCacheRoot } from "@stim-cli/core";
|
|
26
|
-
import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, loadConfig, readBuildHistory, readMaintenanceState, readPullRequestCache, settingDefinition, sharedCompilationCache } from "@stim-cli/core/state";
|
|
27
|
+
import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, lastNativeUseFrom, loadConfig, readBuildHistory, readMaintenanceState, readPullRequestCache, settingDefinition, sharedCompilationCache } from "@stim-cli/core/state";
|
|
27
28
|
import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
|
|
28
29
|
import chalk from "chalk";
|
|
29
30
|
import { existsSync as existsSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
|
|
@@ -34,6 +35,7 @@ const DIRTY_PATHS_SHOWN = 3;
|
|
|
34
35
|
const DAY_MS = 864e5;
|
|
35
36
|
const MINUTE_MS = 6e4;
|
|
36
37
|
const GRACE_SETTING = "gc.worktreeGraceMinutes";
|
|
38
|
+
const PROTECT_SETTING = "maintenance.protectRecentHours";
|
|
37
39
|
const NO_OWN_COMMITS_PRESSURE_FLOOR_MS = 5 * MINUTE_MS;
|
|
38
40
|
const PULL_REQUEST_NOT_CHECKED = "not checked since the last maintenance worktree check";
|
|
39
41
|
const WORKTREE_CHECK_SETTING = "maintenance.worktreeCheckMinutes";
|
|
@@ -78,6 +80,18 @@ function noOwnCommitsActivityBlocker(activity, idleMs, now) {
|
|
|
78
80
|
eligibleAt
|
|
79
81
|
};
|
|
80
82
|
}
|
|
83
|
+
function dirtyLines(porcelain) {
|
|
84
|
+
return excludePodChurn(excludeWatchmanCookies(porcelain).lines).lines;
|
|
85
|
+
}
|
|
86
|
+
function finished(facts) {
|
|
87
|
+
return Boolean(facts.merge?.merged || endedPullRequest(facts.pullRequest));
|
|
88
|
+
}
|
|
89
|
+
function openPullRequest(lookup) {
|
|
90
|
+
return Boolean(lookup && "pullRequest" in lookup && lookup.pullRequest?.state === "open");
|
|
91
|
+
}
|
|
92
|
+
function newerActivity(a, b) {
|
|
93
|
+
return a && b ? b.at > a.at ? b : a : a ?? b;
|
|
94
|
+
}
|
|
81
95
|
function hasNoOwnCommits(merge) {
|
|
82
96
|
return merge?.merged === false && merge.noOwnCommits === true;
|
|
83
97
|
}
|
|
@@ -116,11 +130,16 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
|
|
|
116
130
|
if (facts.nested?.length) return skip("nested-worktree", `contains the worktree ${facts.nested.join(", ")}`);
|
|
117
131
|
if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
|
|
118
132
|
if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
|
|
119
|
-
const dirty =
|
|
133
|
+
const dirty = dirtyLines(facts.porcelain);
|
|
120
134
|
if (dirty.length) {
|
|
121
135
|
const paths = dirty.slice(0, DIRTY_PATHS_SHOWN).flatMap((line) => porcelainPath(line) ?? []);
|
|
122
136
|
const more = dirty.length > DIRTY_PATHS_SHOWN ? ", ..." : "";
|
|
123
|
-
|
|
137
|
+
const kept = skip("dirty", `dirty: ${plural(dirty.length, "uncommitted or untracked file")} (${paths.join(", ")}${more})`);
|
|
138
|
+
if (!facts.archiveDirty) return kept;
|
|
139
|
+
return removalBlocker({
|
|
140
|
+
...facts,
|
|
141
|
+
porcelain: []
|
|
142
|
+
}, olderThan, now, noOwnCommitsIdleMs) === null && finished(facts) ? null : kept;
|
|
124
143
|
}
|
|
125
144
|
if (facts.unpushed === null) return skip("unpushed-unchecked", "unpushed commits could not be checked");
|
|
126
145
|
const merged = facts.merge?.merged ? facts.merge : null;
|
|
@@ -131,17 +150,9 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
|
|
|
131
150
|
if (merged || ended) return null;
|
|
132
151
|
const notMerged = facts.merge && !facts.merge.merged ? facts.merge : null;
|
|
133
152
|
const details = [notMerged?.detail, pullRequestNote(facts.pullRequest)].filter(Boolean);
|
|
134
|
-
|
|
135
|
-
if (olderThan === null)
|
|
136
|
-
if (noOwnCommits && noOwnCommitsIdleMs !== null) return noOwnCommitsActivityBlocker(facts.activity, noOwnCommitsIdleMs, now);
|
|
137
|
-
return skip(notMerged?.unknown ? "merge-unknown" : "not-merged", details.join("; ") || "not merged");
|
|
138
|
-
}
|
|
153
|
+
if (hasNoOwnCommits(notMerged) && !openPullRequest(facts.pullRequest) && noOwnCommitsIdleMs !== null) return noOwnCommitsActivityBlocker(facts.activity, noOwnCommitsIdleMs, now);
|
|
154
|
+
if (olderThan === null) return skip(notMerged?.unknown ? "merge-unknown" : "not-merged", details.join("; ") || "not merged");
|
|
139
155
|
const also = details.map((detail) => `; ${detail}`).join("");
|
|
140
|
-
if (facts.idleDays === null && noOwnCommits && facts.activity) {
|
|
141
|
-
const days = activityIdleDays(facts.activity, now);
|
|
142
|
-
if (days < olderThan) return skip("recently-used", `recently active: ${facts.activity.basis} ${days}d ago${also}`);
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
156
|
if (facts.idleDays === null) return skip("last-use-unknown", `recently used: its last use is unknown${also}`);
|
|
146
157
|
if (facts.idleDays < olderThan) return skip("recently-used", `recently used ${facts.idleDays}d ago${also}`);
|
|
147
158
|
return null;
|
|
@@ -153,11 +164,16 @@ function removalBlocker(facts, olderThan, now, noOwnCommitsIdleMs) {
|
|
|
153
164
|
*/
|
|
154
165
|
function worktreeRemovalReason(candidate, sweep) {
|
|
155
166
|
const { merge, activity } = candidate;
|
|
156
|
-
|
|
167
|
+
const archived = candidate.dirty ? `, ${plural(candidate.dirty, "uncommitted file")} archived first` : "";
|
|
168
|
+
if (merge?.merged) return `merged into ${merge.into}${archived}`;
|
|
157
169
|
const ended = endedPullRequest(candidate.pullRequest);
|
|
158
|
-
if (ended) return describePullRequest(ended)
|
|
170
|
+
if (ended) return `${describePullRequest(ended)}${archived}`;
|
|
159
171
|
if (merge && hasNoOwnCommits(merge)) {
|
|
160
|
-
if (sweep.noOwnCommitsIdleMs !== null && activity)
|
|
172
|
+
if (sweep.noOwnCommitsIdleMs !== null && activity) {
|
|
173
|
+
const age = Math.max(0, sweep.now - activity.at);
|
|
174
|
+
if (sweep.underPressure) return `${merge.detail}, inactive for ${formatLongDuration(age)} under disk pressure`;
|
|
175
|
+
return age >= DAY_MS ? `${merge.detail}, idle ${activityIdleDays(activity, sweep.now)}d` : `${merge.detail}, inactive for ${formatLongDuration(age)}`;
|
|
176
|
+
}
|
|
161
177
|
const days = candidate.idleDays ?? (activity ? activityIdleDays(activity, sweep.now) : null);
|
|
162
178
|
return days === null ? merge.detail : `${merge.detail}, idle ${days}d`;
|
|
163
179
|
}
|
|
@@ -343,6 +359,10 @@ function activityFromGitDir(gitDir, keys) {
|
|
|
343
359
|
const newest = newestMtime(paths);
|
|
344
360
|
return newest === "unreadable" ? null : newest;
|
|
345
361
|
}
|
|
362
|
+
function protectRecentMs(operation) {
|
|
363
|
+
const { value, error } = machineNumber(PROTECT_SETTING, loadConfig$1(), operation?.env ?? process.env);
|
|
364
|
+
return (error ? Number(settingDefinition(PROTECT_SETTING)?.default) : value ?? 0) * 60 * MINUTE_MS;
|
|
365
|
+
}
|
|
346
366
|
function graceMsSetting(operation) {
|
|
347
367
|
const { value, error } = machineNumber(GRACE_SETTING, loadConfig$1(), operation?.env ?? process.env);
|
|
348
368
|
if (error) {
|
|
@@ -448,7 +468,7 @@ async function checkMergeStates(pending, idle, grace, noOwnCommitsIdleMs, refres
|
|
|
448
468
|
operation?.signal.throwIfAborted();
|
|
449
469
|
const target = refresh ? await fetchDefaultBranchAsync(repo, grace.now, execution) : await localDefaultBranchAsync(repo, execution);
|
|
450
470
|
const fetch = refresh ? null : await staleFetch(repo, grace.now, freshMs, execution);
|
|
451
|
-
|
|
471
|
+
await eachLimited(entries, readConcurrency(operation), async ({ candidate, facts }) => {
|
|
452
472
|
operation?.signal.throwIfAborted();
|
|
453
473
|
const merge = "error" in target ? {
|
|
454
474
|
merged: false,
|
|
@@ -466,26 +486,29 @@ async function checkMergeStates(pending, idle, grace, noOwnCommitsIdleMs, refres
|
|
|
466
486
|
skipped: verdict ? note ? `${verdict.text}; ${note}` : verdict.text : null,
|
|
467
487
|
eligibleAt: verdict?.eligibleAt ?? null
|
|
468
488
|
});
|
|
469
|
-
}
|
|
489
|
+
});
|
|
470
490
|
}
|
|
471
491
|
}
|
|
472
492
|
/**
|
|
473
|
-
* Classifies each linked worktree of a registered project or its repository. A merged one is removable
|
|
474
|
-
*
|
|
475
|
-
*
|
|
493
|
+
* Classifies each linked worktree of a registered project or its repository. A merged one is removable, a dirty one
|
|
494
|
+
* after archiving its uncommitted work when archives are on; with `idle`, so is one unused for `olderThan` days. One
|
|
495
|
+
* with no commits of its own beyond the default branch is removable once its last activity is older than the grace
|
|
496
|
+
* period and `maintenance.protectRecentHours`, or, `underPressure`, than the grace period alone; never younger than 5
|
|
497
|
+
* minutes. Device use is read last, in one pass against one simulator list and one AVD resolver, so
|
|
476
498
|
* the resolver's time budget covers only device reads. Pull requests are asked once per repository, sequentially
|
|
477
499
|
* with an operation and concurrently otherwise. Merge state is checked only where it decides the verdict, after one fetch per repository.
|
|
478
500
|
* Without `refresh`, it runs no fetch and no `gh`: merge state comes from the existing remote-tracking ref and pull
|
|
479
501
|
* requests from the cache `status --watch` writes, and a kept verdict that fresher state could change says so.
|
|
480
502
|
*/
|
|
481
|
-
async function collectWorktreeSweep({ idle, olderThan, now, graceMs,
|
|
503
|
+
async function collectWorktreeSweep({ idle, olderThan, now, graceMs, underPressure = false, refresh = true, operation }) {
|
|
482
504
|
const days = idle ? olderThan ?? DEFAULT_WORKTREE_IDLE_DAYS : null;
|
|
483
505
|
const grace = {
|
|
484
506
|
ms: graceMs ?? graceMsSetting(operation),
|
|
485
507
|
now
|
|
486
508
|
};
|
|
487
509
|
const freshMs = refresh ? 0 : reportFreshMs(operation);
|
|
488
|
-
const noOwnCommitsIdleMs =
|
|
510
|
+
const noOwnCommitsIdleMs = Math.max(grace.ms, underPressure ? 0 : protectRecentMs(operation), NO_OWN_COMMITS_PRESSURE_FLOOR_MS);
|
|
511
|
+
const cli = stimCliEntries(operation?.env);
|
|
489
512
|
const groups = /* @__PURE__ */ new Map();
|
|
490
513
|
operation?.signal.throwIfAborted();
|
|
491
514
|
const read = {
|
|
@@ -496,9 +519,13 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
|
|
|
496
519
|
const outside = [];
|
|
497
520
|
const candidates = await candidateRootsAsync(operation);
|
|
498
521
|
if (candidates.length) phase("worktrees", `checking ${plural(candidates.length, "project")} for merged or idle worktrees`, operation);
|
|
499
|
-
|
|
522
|
+
const matched = [];
|
|
523
|
+
await eachLimited([...candidates.entries()], readConcurrency(operation), async ([index, root]) => {
|
|
500
524
|
operation?.signal.throwIfAborted();
|
|
501
|
-
|
|
525
|
+
matched[index] = matchWorktreeEntry(await listWorktreesAsync(root, read), root);
|
|
526
|
+
});
|
|
527
|
+
for (const [index, root] of candidates.entries()) {
|
|
528
|
+
const entry = matched[index];
|
|
502
529
|
if (!entry) {
|
|
503
530
|
outside.push({
|
|
504
531
|
path: root,
|
|
@@ -517,31 +544,38 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
|
|
|
517
544
|
groups.set(entry.path, [...groups.get(entry.path) ?? [], root]);
|
|
518
545
|
}
|
|
519
546
|
const inspected = [];
|
|
520
|
-
|
|
547
|
+
await eachLimited([...[...groups].entries()], readConcurrency(operation), async ([index, [path, roots]]) => {
|
|
521
548
|
operation?.signal.throwIfAborted();
|
|
522
549
|
const { entries, ...source } = await resolveSourceCheckoutAsync(path, read);
|
|
523
550
|
const entry = matchWorktreeEntry(entries, path);
|
|
524
551
|
const keys = [.../* @__PURE__ */ new Set([...roots, ...reclaimKeys(path)])];
|
|
525
552
|
const idleDays = idleDaysOf(keys, now);
|
|
526
553
|
const linked = !("refusal" in source) && entry !== null && source.path !== entry.path;
|
|
527
|
-
const
|
|
528
|
-
const
|
|
554
|
+
const porcelain = await porcelainOf(path, linked ? await unlessTimedOut(() => hasUncommittedWorkAsync(path, read), null) : null, operation);
|
|
555
|
+
const dirty = linked ? dirtyLines(porcelain).length : 0;
|
|
556
|
+
const archiveDirty = dirty > 0 && !underPressure && !("refusal" in worktreeArchiveLimits(path, operation?.env));
|
|
557
|
+
let activity = linked ? await worktreeActivityOfAsync(path, keys, operation) : null;
|
|
558
|
+
if (activity && archiveDirty) {
|
|
559
|
+
const edited = await uncommittedActivityOf(path, operation);
|
|
560
|
+
activity = edited === "unreadable" ? null : newerActivity(activity, edited);
|
|
561
|
+
}
|
|
529
562
|
const head = linked ? await resolveFullRefAsync(path, "HEAD", read) : null;
|
|
530
563
|
const facts = {
|
|
531
564
|
source: "refusal" in source ? source : linked ? "linked" : "source",
|
|
532
565
|
bare: Boolean(entry?.bare),
|
|
533
566
|
locked: Boolean(entry?.locked),
|
|
534
567
|
nested: entry ? nestedWorktrees(entries, entry.path) : [],
|
|
535
|
-
porcelain
|
|
568
|
+
porcelain,
|
|
536
569
|
unpushed: linked ? await unlessTimedOut(() => unpushedCommitsAsync(path, read), null) : null,
|
|
537
570
|
submodules: linked ? await unlessTimedOut(() => hasPopulatedSubmodulesAsync(path, read), null) : false,
|
|
538
|
-
inUse: [],
|
|
571
|
+
inUse: linked ? stimCliUseOf(path, canonicalPath, cli) : [],
|
|
539
572
|
idleDays,
|
|
540
573
|
merge: null,
|
|
541
574
|
pullRequest: null,
|
|
542
|
-
activity
|
|
575
|
+
activity,
|
|
576
|
+
archiveDirty
|
|
543
577
|
};
|
|
544
|
-
inspected
|
|
578
|
+
inspected[index] = {
|
|
545
579
|
candidate: {
|
|
546
580
|
path,
|
|
547
581
|
keys,
|
|
@@ -552,13 +586,14 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
|
|
|
552
586
|
skipCode: null,
|
|
553
587
|
skipped: null,
|
|
554
588
|
eligibleAt: null,
|
|
555
|
-
activity
|
|
589
|
+
activity,
|
|
590
|
+
...archiveDirty ? { dirty } : {}
|
|
556
591
|
},
|
|
557
592
|
facts,
|
|
558
593
|
repo: "refusal" in source ? null : source.path,
|
|
559
594
|
branch: head && entry?.branch ? entry.branch : null
|
|
560
|
-
}
|
|
561
|
-
}
|
|
595
|
+
};
|
|
596
|
+
});
|
|
562
597
|
const linked = inspected.filter(({ facts }) => facts.source === "linked");
|
|
563
598
|
for (const { candidate, facts } of linked) for (const key of candidate.keys) facts.inUse.push(...await workspaceInUseAsync(key, { managedLocks: true }, operation?.signal, operation));
|
|
564
599
|
const devices = await reportDeviceReads(linked.flatMap(({ candidate }) => candidate.keys), operation);
|
|
@@ -575,7 +610,7 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
|
|
|
575
610
|
skipped: verdict?.text ?? null,
|
|
576
611
|
eligibleAt: verdict?.eligibleAt ?? null
|
|
577
612
|
});
|
|
578
|
-
if (verdict && MERGE_DECIDES.has(verdict.code) && repo !== null) pending.push({
|
|
613
|
+
if (verdict && (MERGE_DECIDES.has(verdict.code) || verdict.code === "dirty" && facts.archiveDirty) && repo !== null) pending.push({
|
|
579
614
|
candidate,
|
|
580
615
|
facts,
|
|
581
616
|
repo
|
|
@@ -592,6 +627,7 @@ async function collectWorktreeSweep({ idle, olderThan, now, graceMs, removesNoOw
|
|
|
592
627
|
graceMs: grace.ms,
|
|
593
628
|
now,
|
|
594
629
|
noOwnCommitsIdleMs,
|
|
630
|
+
underPressure,
|
|
595
631
|
worktrees: listed
|
|
596
632
|
};
|
|
597
633
|
}
|
|
@@ -659,16 +695,8 @@ function cachedPullRequest(cached, branch, head, now, freshMs) {
|
|
|
659
695
|
} };
|
|
660
696
|
}
|
|
661
697
|
async function noOwnCommitsRecheck(sweep, path, keys, now, operation) {
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
return noOwnCommitsActivityBlocker(await worktreeActivityOfAsync(path, keys, operation), sweep.noOwnCommitsIdleMs, now)?.text ?? null;
|
|
665
|
-
}
|
|
666
|
-
const idleDays = idleDaysOf(keys, now);
|
|
667
|
-
if (idleDays !== null) return idleDays < sweep.idle.olderThan ? `used ${idleDays}d ago since gc checked it` : null;
|
|
668
|
-
const activity = await worktreeActivityOfAsync(path, keys, operation);
|
|
669
|
-
if (!activity) return "its last activity could not be read";
|
|
670
|
-
const days = activityIdleDays(activity, now);
|
|
671
|
-
return days < sweep.idle.olderThan ? `active ${days}d ago since gc checked it: ${activity.basis}` : null;
|
|
698
|
+
if (sweep.noOwnCommitsIdleMs === null) return "it has no commits of its own and is not idle";
|
|
699
|
+
return noOwnCommitsActivityBlocker(await worktreeActivityOfAsync(path, keys, operation), sweep.noOwnCommitsIdleMs, now)?.text ?? null;
|
|
672
700
|
}
|
|
673
701
|
async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, output = GC_OUTPUT) {
|
|
674
702
|
let failures = 0;
|
|
@@ -680,7 +708,11 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
|
|
|
680
708
|
const keys = [.../* @__PURE__ */ new Set([...candidate.keys, ...lockedKeys])];
|
|
681
709
|
const unlocked = keys.filter((key) => !lockedKeys.includes(key));
|
|
682
710
|
const devices = await reportDeviceReads(keys, operation);
|
|
683
|
-
const reasons = [
|
|
711
|
+
const reasons = [
|
|
712
|
+
...stimCliUseOf(candidate.path, canonicalPath, stimCliEntries(operation?.env)),
|
|
713
|
+
...await inUseOf(lockedKeys, { managedLocks: false }, devices, operation?.signal, operation),
|
|
714
|
+
...await inUseOf(unlocked, { managedLocks: true }, devices, operation?.signal, operation)
|
|
715
|
+
].map((r) => `in use: ${r}`);
|
|
684
716
|
const mergedHead = candidate.merge?.merged ? candidate.merge.head : endedPullRequest(candidate.pullRequest) ? candidate.head : null;
|
|
685
717
|
if (mergedHead) {
|
|
686
718
|
if (await resolveFullRefAsync(candidate.path, "HEAD", {
|
|
@@ -701,10 +733,12 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
|
|
|
701
733
|
if (idleDays === null || !sweep.idle || idleDays < sweep.idle.olderThan) reasons.push(`used ${idleDays === null ? "at an unknown time" : `${idleDays}d ago`} since gc checked it`);
|
|
702
734
|
}
|
|
703
735
|
if (sweep.graceMs > 0) {
|
|
736
|
+
const activity = await worktreeActivityOfAsync(candidate.path, keys, operation);
|
|
737
|
+
const edited = candidate.dirty && activity ? await uncommittedActivityOf(candidate.path, operation) : null;
|
|
704
738
|
const recent = graceBlocker({
|
|
705
739
|
merge: candidate.merge,
|
|
706
740
|
pullRequest: candidate.pullRequest,
|
|
707
|
-
activity:
|
|
741
|
+
activity: edited === "unreadable" ? null : newerActivity(activity, edited)
|
|
708
742
|
}, {
|
|
709
743
|
ms: sweep.graceMs,
|
|
710
744
|
now: Date.now()
|
|
@@ -716,6 +750,7 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
|
|
|
716
750
|
};
|
|
717
751
|
let removed = false;
|
|
718
752
|
let error = null;
|
|
753
|
+
const archived = { archive: null };
|
|
719
754
|
try {
|
|
720
755
|
const ended = endedPullRequest(candidate.pullRequest);
|
|
721
756
|
const mergedHead = candidate.merge?.merged && candidate.merge.coversUnpushed ? candidate.merge.head : ended?.state === "merged" ? candidate.head ?? void 0 : void 0;
|
|
@@ -726,7 +761,17 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
|
|
|
726
761
|
mergedHead,
|
|
727
762
|
expectedHead,
|
|
728
763
|
output,
|
|
729
|
-
operation
|
|
764
|
+
operation,
|
|
765
|
+
...candidate.dirty ? { archiveDirty: {
|
|
766
|
+
pullRequest: ended ? {
|
|
767
|
+
number: ended.number,
|
|
768
|
+
url: ended.url,
|
|
769
|
+
state: ended.state
|
|
770
|
+
} : null,
|
|
771
|
+
onArchived: (saved) => {
|
|
772
|
+
archived.archive = saved;
|
|
773
|
+
}
|
|
774
|
+
} } : {}
|
|
730
775
|
});
|
|
731
776
|
} catch (thrown) {
|
|
732
777
|
if (thrown instanceof WorkspaceProcessCleanupError || thrown instanceof Error && "reportCleanup" in thrown) throw thrown;
|
|
@@ -735,8 +780,16 @@ async function removeWorktrees(sweep, { now = Date.now(), operation } = {}, outp
|
|
|
735
780
|
output.error(chalk.red(`Could not remove ${candidate.path}: ${error}`));
|
|
736
781
|
}
|
|
737
782
|
if (removed) {
|
|
738
|
-
|
|
739
|
-
|
|
783
|
+
const saved = archived.archive;
|
|
784
|
+
const reason = saved ? `${worktreeRemovalReason({
|
|
785
|
+
...candidate,
|
|
786
|
+
dirty: 0
|
|
787
|
+
}, sweep)}; archived ${plural(saved.tracked.length + saved.untracked.length, "uncommitted file")} to ${saved.dir}; restore: ${saved.restore.join(" && ")}` : worktreeRemovalReason({
|
|
788
|
+
...candidate,
|
|
789
|
+
dirty: 0
|
|
790
|
+
}, sweep);
|
|
791
|
+
output.log(chalk.green(`Removed the worktree ${candidate.path} (${reason})`));
|
|
792
|
+
output.record("worktree", "done", candidate.path, { detail: reason });
|
|
740
793
|
} else if (kept.length) {
|
|
741
794
|
output.log(chalk.yellow(`Kept the worktree ${candidate.path}: ${kept.join("; ")}`));
|
|
742
795
|
output.record("worktree", "kept", candidate.path, { detail: kept.join("; ") });
|
|
@@ -1213,6 +1266,44 @@ function finishedWorkspaces(sweep, now) {
|
|
|
1213
1266
|
}));
|
|
1214
1267
|
});
|
|
1215
1268
|
}
|
|
1269
|
+
/** Each workspace's effective devices.idleShutdownMinutes, or 0 when it cannot be resolved. */
|
|
1270
|
+
function idleShutdownMinutes(root) {
|
|
1271
|
+
try {
|
|
1272
|
+
return deviceIdleShutdownMinutesSetting(resolveProjectSettings(root).settings);
|
|
1273
|
+
} catch {
|
|
1274
|
+
return 0;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Plans shutting down each booted owned simulator and emulator idle for its workspace's devices.idleShutdownMinutes,
|
|
1279
|
+
* whether or not a supervisor runs there. The devices are shut down, never deleted.
|
|
1280
|
+
*/
|
|
1281
|
+
function planIdleShutdown({ protectedRoot, now, env = process.env }) {
|
|
1282
|
+
const plan = {
|
|
1283
|
+
actions: [],
|
|
1284
|
+
blocked: [],
|
|
1285
|
+
skips: []
|
|
1286
|
+
};
|
|
1287
|
+
if (deviceSweepIsScoped(env)) return plan;
|
|
1288
|
+
const minutes = new Map(Object.entries(loadConfig$1()?.projects ?? {}).flatMap(([root, project]) => {
|
|
1289
|
+
const value = !projectDeviceSlots(project).some(({ platforms }) => platforms.ios?.owned === true || platforms.android?.owned === true) || canonicalPath(root) === protectedRoot ? 0 : idleShutdownMinutes(root);
|
|
1290
|
+
return value > 0 ? [[root, value]] : [];
|
|
1291
|
+
}));
|
|
1292
|
+
if (minutes.size === 0) return plan;
|
|
1293
|
+
for (const { device, idleForMs } of dueIdleDevices(void 0, Math.min(...minutes.values()) * 6e4, now)) {
|
|
1294
|
+
const own = minutes.get(device.project);
|
|
1295
|
+
if (own === void 0 || idleForMs < own * 6e4 || now - workspaceDeviceLastUseAt(device.project) < own * 6e4) continue;
|
|
1296
|
+
const root = canonicalPath(device.project);
|
|
1297
|
+
plan.actions.push({
|
|
1298
|
+
kind: "would-shutdown-device",
|
|
1299
|
+
target: device.id,
|
|
1300
|
+
workspace: root,
|
|
1301
|
+
bytes: 0,
|
|
1302
|
+
reason: `${device.kind === "ios" ? "simulator" : "emulator"} ${device.name} idle ${formatLongDuration(idleForMs)}, over devices.idleShutdownMinutes ${own}`
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return plan;
|
|
1306
|
+
}
|
|
1216
1307
|
//#endregion
|
|
1217
1308
|
//#region src/maintenance/sweep.ts
|
|
1218
1309
|
async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices = true, operation }) {
|
|
@@ -1220,13 +1311,13 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
|
|
|
1220
1311
|
const skips = [];
|
|
1221
1312
|
const blocked = [];
|
|
1222
1313
|
const days = settings.olderThanDays;
|
|
1223
|
-
const outputs = await collectWorkspaceOutputs({
|
|
1314
|
+
const outputs = days > 0 ? await collectWorkspaceOutputs({
|
|
1224
1315
|
olderThan: days,
|
|
1225
1316
|
now,
|
|
1226
1317
|
measure: false,
|
|
1227
1318
|
signal: operation?.signal,
|
|
1228
1319
|
operation
|
|
1229
|
-
});
|
|
1320
|
+
}) : { workspaces: [] };
|
|
1230
1321
|
for (const entry of outputs.workspaces) {
|
|
1231
1322
|
const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
|
|
1232
1323
|
if (root === null || !entry.willClear || root === protectedRoot) continue;
|
|
@@ -1249,7 +1340,7 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
|
|
|
1249
1340
|
check: "sweep"
|
|
1250
1341
|
});
|
|
1251
1342
|
}
|
|
1252
|
-
for (const cache of trimmableCaches(operation?.env, operation?.cwd).filter((candidate) => !isInsideWorkspaces(candidate.dir))) actions.push({
|
|
1343
|
+
for (const cache of days > 0 ? trimmableCaches(operation?.env, operation?.cwd).filter((candidate) => !isInsideWorkspaces(candidate.dir)) : []) actions.push({
|
|
1253
1344
|
kind: "would-trim-cache",
|
|
1254
1345
|
target: cacheLabel(cache),
|
|
1255
1346
|
dir: cache.dir,
|
|
@@ -1298,6 +1389,19 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
|
|
|
1298
1389
|
actions.push(...reaped.actions);
|
|
1299
1390
|
skips.push(...reaped.skips);
|
|
1300
1391
|
}
|
|
1392
|
+
if (devices && days > 0 && !deviceSweepIsScoped(operation?.env ?? process.env)) for (const kind of ["ios", "android"]) for (const record of readParked(kind)) {
|
|
1393
|
+
const parkedDays = Math.floor((now - Date.parse(record.parkedAt)) / 864e5);
|
|
1394
|
+
if (!(parkedDays >= days)) continue;
|
|
1395
|
+
const label = `parked ${kind === "ios" ? "simulator" : "emulator"} ${record.name}`;
|
|
1396
|
+
actions.push({
|
|
1397
|
+
kind: "would-delete-parked-device",
|
|
1398
|
+
target: kind === "ios" ? record.udid : record.name,
|
|
1399
|
+
bytes: sizes.find((size) => size.category === "owned-devices" && !size.workspace && size.name === label)?.bytes ?? 0,
|
|
1400
|
+
olderThanDays: days,
|
|
1401
|
+
reason: `${label} unused for ${parkedDays} days, older than ${days} days (age sweep)`,
|
|
1402
|
+
check: "sweep"
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1301
1405
|
const mounted = listMountedVolumes();
|
|
1302
1406
|
for (const cache of readManifest().caches) {
|
|
1303
1407
|
if (!isAbsolute(cache.dir) || existsSync(cache.dir) || !isOnMountedVolume(cache.dir, mounted)) continue;
|
|
@@ -1316,12 +1420,12 @@ async function planSweep({ settings, sizes, protectedRoot, pinned, now, devices
|
|
|
1316
1420
|
skips
|
|
1317
1421
|
};
|
|
1318
1422
|
}
|
|
1319
|
-
async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes, protectRecentMs = 0, refresh, operation }) {
|
|
1423
|
+
async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes, protectRecentMs = 0, refresh, idleDays = null, operation }) {
|
|
1320
1424
|
const sweep = await collectWorktreeSweep({
|
|
1321
|
-
idle:
|
|
1322
|
-
olderThan:
|
|
1425
|
+
idle: idleDays !== null,
|
|
1426
|
+
olderThan: idleDays,
|
|
1323
1427
|
now,
|
|
1324
|
-
|
|
1428
|
+
underPressure: level === 3,
|
|
1325
1429
|
operation,
|
|
1326
1430
|
...graceMs === void 0 ? {} : { graceMs },
|
|
1327
1431
|
...refresh === void 0 ? {} : { refresh }
|
|
@@ -1346,7 +1450,7 @@ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes
|
|
|
1346
1450
|
target: worktree.path,
|
|
1347
1451
|
bytes: 0,
|
|
1348
1452
|
reason: worktreeRemovalReason(worktree, sweep),
|
|
1349
|
-
check: "worktree",
|
|
1453
|
+
check: level === void 0 && idleDays !== null && !worktree.merge?.merged && !endedPullRequest(worktree.pullRequest) ? "sweep" : "worktree",
|
|
1350
1454
|
...level ? { level } : {}
|
|
1351
1455
|
});
|
|
1352
1456
|
}
|
|
@@ -1377,18 +1481,118 @@ async function planWorktrees({ protectedRoot, pinned, now, level, graceMs, sizes
|
|
|
1377
1481
|
};
|
|
1378
1482
|
}
|
|
1379
1483
|
//#endregion
|
|
1484
|
+
//#region src/workspace/running-from.ts
|
|
1485
|
+
const LIST_TIMEOUT_MS = 1e4;
|
|
1486
|
+
function parsePs(text) {
|
|
1487
|
+
return text.split("\n").flatMap((row) => {
|
|
1488
|
+
const match = /^\s*(\d+)\s+(.*)$/.exec(row);
|
|
1489
|
+
return match ? [{
|
|
1490
|
+
pid: Number(match[1]),
|
|
1491
|
+
line: match[2] ?? ""
|
|
1492
|
+
}] : [];
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
/** `lsof -Fpn` output: a `p<pid>` line, then the `n<path>` of its working directory. */
|
|
1496
|
+
function parseLsof(text) {
|
|
1497
|
+
const cwds = [];
|
|
1498
|
+
let pid = 0;
|
|
1499
|
+
for (const row of text.split("\n")) if (row.startsWith("p")) pid = Number(row.slice(1));
|
|
1500
|
+
else if (row.startsWith("n") && pid) cwds.push({
|
|
1501
|
+
pid,
|
|
1502
|
+
path: row.slice(1)
|
|
1503
|
+
});
|
|
1504
|
+
return cwds;
|
|
1505
|
+
}
|
|
1506
|
+
function procCwds() {
|
|
1507
|
+
try {
|
|
1508
|
+
return readdirSync("/proc").filter((name) => /^\d+$/.test(name)).flatMap((name) => {
|
|
1509
|
+
try {
|
|
1510
|
+
return [{
|
|
1511
|
+
pid: Number(name),
|
|
1512
|
+
path: readlinkSync(join("/proc", name, "cwd"))
|
|
1513
|
+
}];
|
|
1514
|
+
} catch {
|
|
1515
|
+
return [];
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
} catch {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
function listsSelf(list) {
|
|
1523
|
+
return list.some(({ pid }) => pid === process.pid);
|
|
1524
|
+
}
|
|
1525
|
+
function runningFrom(ps, cwds) {
|
|
1526
|
+
if (ps === null || cwds === null) return null;
|
|
1527
|
+
const commands = parsePs(ps);
|
|
1528
|
+
return listsSelf(commands) && listsSelf(cwds) ? {
|
|
1529
|
+
commands,
|
|
1530
|
+
cwds
|
|
1531
|
+
} : null;
|
|
1532
|
+
}
|
|
1533
|
+
const PS_ARGS = [
|
|
1534
|
+
"-axww",
|
|
1535
|
+
"-o",
|
|
1536
|
+
"pid=,args="
|
|
1537
|
+
];
|
|
1538
|
+
const LSOF_ARGS = [
|
|
1539
|
+
"-a",
|
|
1540
|
+
"-d",
|
|
1541
|
+
"cwd",
|
|
1542
|
+
"-Fpn",
|
|
1543
|
+
"-w"
|
|
1544
|
+
];
|
|
1545
|
+
function readRunningProcesses(platform = process.platform) {
|
|
1546
|
+
if (platform !== "darwin" && platform !== "linux") return null;
|
|
1547
|
+
const exec = getExecutor();
|
|
1548
|
+
const ps = exec.runFileQuiet("ps", PS_ARGS, { timeoutMs: LIST_TIMEOUT_MS });
|
|
1549
|
+
if (platform === "linux") return runningFrom(ps, procCwds());
|
|
1550
|
+
const lsof = exec.runFileQuiet("lsof", LSOF_ARGS, { timeoutMs: LIST_TIMEOUT_MS });
|
|
1551
|
+
return runningFrom(ps, lsof === null ? null : parseLsof(lsof));
|
|
1552
|
+
}
|
|
1553
|
+
async function readRunningProcessesAsync(signal, platform = process.platform) {
|
|
1554
|
+
if (platform !== "darwin" && platform !== "linux") return null;
|
|
1555
|
+
const exec = getExecutor();
|
|
1556
|
+
const ps = await exec.runFileQuietAsync("ps", PS_ARGS, {
|
|
1557
|
+
timeoutMs: LIST_TIMEOUT_MS,
|
|
1558
|
+
signal
|
|
1559
|
+
});
|
|
1560
|
+
if (platform === "linux") return runningFrom(ps, procCwds());
|
|
1561
|
+
const lsof = await exec.runFileQuietAsync("lsof", LSOF_ARGS, {
|
|
1562
|
+
timeoutMs: LIST_TIMEOUT_MS,
|
|
1563
|
+
signal
|
|
1564
|
+
});
|
|
1565
|
+
return runningFrom(ps, lsof === null ? null : parseLsof(lsof));
|
|
1566
|
+
}
|
|
1567
|
+
function stimOnPath() {
|
|
1568
|
+
const found = getExecutor().findExecutable("stim");
|
|
1569
|
+
if (!found) return null;
|
|
1570
|
+
try {
|
|
1571
|
+
return realpathSync(found);
|
|
1572
|
+
} catch {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Why something installed in or running from `folder` still needs it, or null: an installed stim-server service, the
|
|
1578
|
+
* `stim` on PATH, or a live process whose command line or working directory is inside it.
|
|
1579
|
+
*/
|
|
1580
|
+
function runningFromReason(folder, running) {
|
|
1581
|
+
const root = canonicalPath(folder);
|
|
1582
|
+
const inside = (path) => path === root || path.startsWith(`${root}${sep}`);
|
|
1583
|
+
const service = serviceRunningFrom(root, canonicalPath);
|
|
1584
|
+
if (service) return `the stim-server service ${service.label} runs from it`;
|
|
1585
|
+
const stim = stimOnPath();
|
|
1586
|
+
if (stim && inside(stim)) return `the stim on PATH runs from it (${stim})`;
|
|
1587
|
+
if (!running) return "the running processes cannot be listed to prove nothing runs from it";
|
|
1588
|
+
const command = running.commands.find(({ line }) => line.includes(`${root}${sep}`));
|
|
1589
|
+
if (command) return `process ${command.pid} runs from it`;
|
|
1590
|
+
const cwd = running.cwds.find(({ path }) => inside(canonicalPath(path)));
|
|
1591
|
+
if (cwd) return `process ${cwd.pid} works in it`;
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
//#endregion
|
|
1380
1595
|
//#region src/maintenance/artifacts.ts
|
|
1381
|
-
/** Directories that hold only what a build or an install regenerates, relative to an app or package root. */
|
|
1382
|
-
const ARTIFACT_DIRS = {
|
|
1383
|
-
build: [
|
|
1384
|
-
"ios/build",
|
|
1385
|
-
"android/app/build",
|
|
1386
|
-
"android/build",
|
|
1387
|
-
"android/.gradle",
|
|
1388
|
-
".build"
|
|
1389
|
-
],
|
|
1390
|
-
dependencies: ["node_modules", "ios/Pods"]
|
|
1391
|
-
};
|
|
1392
1596
|
const PACKAGE_PARENTS = ["apps", "packages"];
|
|
1393
1597
|
const SCAN_DEPTH = {
|
|
1394
1598
|
build: 3,
|
|
@@ -1402,6 +1606,14 @@ function isDirectory(path) {
|
|
|
1402
1606
|
return false;
|
|
1403
1607
|
}
|
|
1404
1608
|
}
|
|
1609
|
+
function pathExists(path) {
|
|
1610
|
+
try {
|
|
1611
|
+
lstatSync(path);
|
|
1612
|
+
return true;
|
|
1613
|
+
} catch {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1405
1617
|
function artifactWorktrees() {
|
|
1406
1618
|
const found = /* @__PURE__ */ new Map();
|
|
1407
1619
|
for (const root of candidateRoots()) {
|
|
@@ -1487,8 +1699,8 @@ async function newestWriteAsync(dir, maxDepth, signal) {
|
|
|
1487
1699
|
};
|
|
1488
1700
|
return await walk(dir, 0) ? newest : null;
|
|
1489
1701
|
}
|
|
1490
|
-
function keptReason(candidate, kind, { now, windowMs, pinned, locked }, reads) {
|
|
1491
|
-
const guarded = artifactGuard(candidate, kind, pinned, locked);
|
|
1702
|
+
function keptReason(candidate, kind, { now, windowMs, pinned, locked, running }, reads) {
|
|
1703
|
+
const guarded = artifactGuard(candidate, kind, pinned, locked, running);
|
|
1492
1704
|
if (guarded) return guarded;
|
|
1493
1705
|
const { worktree, path, keys } = candidate;
|
|
1494
1706
|
const tracked = reads ? reads.tracked : gitQuiet(worktree, [
|
|
@@ -1498,15 +1710,22 @@ function keptReason(candidate, kind, { now, windowMs, pinned, locked }, reads) {
|
|
|
1498
1710
|
]);
|
|
1499
1711
|
if (tracked === null) return "git cannot tell whether it holds tracked files";
|
|
1500
1712
|
if (tracked.trim()) return "it holds files git tracks";
|
|
1713
|
+
if (kind === "build") return buildNativeUseReason(keys, now);
|
|
1501
1714
|
const activity = reads ? reads.activity : worktreeActivityOf(worktree, keys);
|
|
1502
1715
|
const written = reads ? reads.written : newestWrite(path, SCAN_DEPTH[kind]);
|
|
1503
1716
|
if (!activity || written === null) return "its last activity cannot be read";
|
|
1504
1717
|
if (now - Math.max(activity.at, written) < windowMs) return `used within the last ${Math.round(windowMs / 6e4)} minutes`;
|
|
1505
1718
|
return null;
|
|
1506
1719
|
}
|
|
1507
|
-
function
|
|
1720
|
+
function buildNativeUseReason(keys, now) {
|
|
1721
|
+
const states = keys.map((key) => readWorkspaceState$1(key)).filter((state) => state !== null);
|
|
1722
|
+
if (!states.length) return "no Stim workspace records when it last ran ios, android or macos";
|
|
1723
|
+
return recentNativeUseReason(Math.max(...states.map((state) => lastNativeUseFrom(state)).filter(Number.isFinite)), now);
|
|
1724
|
+
}
|
|
1725
|
+
function artifactGuard(candidate, kind, pinned, locked, running) {
|
|
1508
1726
|
const { worktree, path, keys } = candidate;
|
|
1509
1727
|
if (keys.some(pinned)) return "pinned by maintenance.keep";
|
|
1728
|
+
if (!pathExists(path)) return "it is already gone";
|
|
1510
1729
|
let real;
|
|
1511
1730
|
try {
|
|
1512
1731
|
real = canonicalPath(path);
|
|
@@ -1514,16 +1733,15 @@ function artifactGuard(candidate, kind, pinned, locked) {
|
|
|
1514
1733
|
return "its path cannot be resolved";
|
|
1515
1734
|
}
|
|
1516
1735
|
if (!isDirectory(path) || real !== join(canonicalPath(worktree), relative(worktree, path))) return "it or a directory above it is a link, or it lies outside its worktree";
|
|
1517
|
-
const inUse = keys.flatMap((key) => workspaceInUse(key, {
|
|
1518
|
-
supervisor: kind === "dependencies",
|
|
1519
|
-
nativeRun: key !== locked
|
|
1520
|
-
}));
|
|
1736
|
+
const inUse = keys.flatMap((key) => kind === "build" ? workspaceBuildReasons(key, { nativeRun: key !== locked }) : workspaceInUse(key, { nativeRun: key !== locked }));
|
|
1521
1737
|
if (inUse.length) return `in use: ${inUse.join("; ")}`;
|
|
1738
|
+
if (kind === "dependencies") return runningFromReason(worktree, running === void 0 ? readRunningProcesses() : running);
|
|
1522
1739
|
return null;
|
|
1523
1740
|
}
|
|
1524
1741
|
async function collectArtifactsAsync(kind, options, signal, operation) {
|
|
1525
1742
|
const candidates = [];
|
|
1526
1743
|
const skips = [];
|
|
1744
|
+
const running = kind === "dependencies" ? await readRunningProcessesAsync(signal) : null;
|
|
1527
1745
|
for (const worktree of await artifactWorktreesAsync(signal, operation)) {
|
|
1528
1746
|
if (kind === "dependencies" && !worktree.linked) continue;
|
|
1529
1747
|
const root = canonicalPath(worktree.path);
|
|
@@ -1554,7 +1772,7 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
|
|
|
1554
1772
|
path,
|
|
1555
1773
|
keys: worktree.keys
|
|
1556
1774
|
};
|
|
1557
|
-
let reason = protectedHere ? "the command that started this pass runs inside its worktree" : artifactGuard(candidate, kind, options.pinned);
|
|
1775
|
+
let reason = protectedHere ? "the command that started this pass runs inside its worktree" : artifactGuard(candidate, kind, options.pinned, void 0, running);
|
|
1558
1776
|
if (!reason) {
|
|
1559
1777
|
const tracked = await gitQuietAsync(worktree.path, [
|
|
1560
1778
|
"ls-files",
|
|
@@ -1567,11 +1785,16 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
|
|
|
1567
1785
|
declareFor: operation,
|
|
1568
1786
|
killSignal: "SIGKILL"
|
|
1569
1787
|
});
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1788
|
+
const windowed = tracked !== null && !tracked.trim() && kind === "dependencies";
|
|
1789
|
+
const activity = windowed ? await worktreeActivityOfAsync(worktree.path, worktree.keys, operation) : null;
|
|
1790
|
+
const written = windowed ? await newestWriteAsync(path, SCAN_DEPTH[kind], signal) : null;
|
|
1791
|
+
reason = keptReason(candidate, kind, {
|
|
1792
|
+
...options,
|
|
1793
|
+
running
|
|
1794
|
+
}, {
|
|
1572
1795
|
tracked,
|
|
1573
|
-
activity
|
|
1574
|
-
written
|
|
1796
|
+
activity,
|
|
1797
|
+
written
|
|
1575
1798
|
});
|
|
1576
1799
|
}
|
|
1577
1800
|
if (reason) skips.push({
|
|
@@ -1590,6 +1813,7 @@ async function collectArtifactsAsync(kind, options, signal, operation) {
|
|
|
1590
1813
|
function collectArtifacts(kind, options) {
|
|
1591
1814
|
const candidates = [];
|
|
1592
1815
|
const skips = [];
|
|
1816
|
+
const running = kind === "dependencies" ? readRunningProcesses() : null;
|
|
1593
1817
|
for (const worktree of artifactWorktrees()) {
|
|
1594
1818
|
if (kind === "dependencies" && !worktree.linked) continue;
|
|
1595
1819
|
const root = canonicalPath(worktree.path);
|
|
@@ -1606,7 +1830,10 @@ function collectArtifacts(kind, options) {
|
|
|
1606
1830
|
path,
|
|
1607
1831
|
keys: worktree.keys
|
|
1608
1832
|
};
|
|
1609
|
-
const reason = protectedHere ? "the command that started this pass runs inside its worktree" : keptReason(candidate, kind,
|
|
1833
|
+
const reason = protectedHere ? "the command that started this pass runs inside its worktree" : keptReason(candidate, kind, {
|
|
1834
|
+
...options,
|
|
1835
|
+
running
|
|
1836
|
+
});
|
|
1610
1837
|
if (reason) skips.push({
|
|
1611
1838
|
target: path,
|
|
1612
1839
|
reason
|
|
@@ -1642,34 +1869,50 @@ async function clearArtifact(action, kind, options) {
|
|
|
1642
1869
|
path: action.target,
|
|
1643
1870
|
keys: worktree.keys
|
|
1644
1871
|
};
|
|
1872
|
+
const running = kind === "dependencies" ? await readRunningProcessesAsync() : null;
|
|
1645
1873
|
const early = keptReason(candidate, kind, {
|
|
1646
1874
|
...options,
|
|
1647
|
-
now: Date.now()
|
|
1875
|
+
now: Date.now(),
|
|
1876
|
+
running
|
|
1648
1877
|
});
|
|
1649
1878
|
if (early) return {
|
|
1650
1879
|
status: "kept",
|
|
1651
1880
|
bytes: 0,
|
|
1652
1881
|
detail: early
|
|
1653
1882
|
};
|
|
1883
|
+
const unrestorable = kind === "dependencies" ? await restoreProblem(candidate, options.restoreCache) : null;
|
|
1884
|
+
if (unrestorable && options.requireRestorable) return {
|
|
1885
|
+
status: "kept",
|
|
1886
|
+
bytes: 0,
|
|
1887
|
+
detail: unrestorable
|
|
1888
|
+
};
|
|
1654
1889
|
const before = lowestFree(measureDisk());
|
|
1655
1890
|
const [key] = candidate.keys;
|
|
1656
|
-
const remove = () => {
|
|
1891
|
+
const remove = async () => {
|
|
1657
1892
|
const reason = keptReason(candidate, kind, {
|
|
1658
1893
|
...options,
|
|
1659
1894
|
now: Date.now(),
|
|
1895
|
+
running: kind === "dependencies" ? await readRunningProcessesAsync() : null,
|
|
1660
1896
|
...key ? { locked: key } : {}
|
|
1661
1897
|
});
|
|
1662
1898
|
if (reason) return reason;
|
|
1899
|
+
const record = kind === "dependencies" && !unrestorable;
|
|
1900
|
+
if (record) recordLightened(candidate.keys, candidate.path);
|
|
1663
1901
|
rmSync(candidate.path, {
|
|
1664
1902
|
recursive: true,
|
|
1665
1903
|
force: true
|
|
1666
1904
|
});
|
|
1905
|
+
if (record) finishLightened(candidate.keys, candidate.path);
|
|
1667
1906
|
return null;
|
|
1668
1907
|
};
|
|
1669
1908
|
const kept = key ? await withIdleWorkspace(key, remove, {
|
|
1670
1909
|
purpose: "maintenance",
|
|
1671
|
-
supervisor: kind === "dependencies"
|
|
1672
|
-
|
|
1910
|
+
supervisor: kind === "dependencies",
|
|
1911
|
+
...kind === "build" ? {
|
|
1912
|
+
hosted: false,
|
|
1913
|
+
managedLocks: false
|
|
1914
|
+
} : {}
|
|
1915
|
+
}).then((run) => run.ran ? run.value : `in use: ${run.reasons.join("; ")}`) : await remove();
|
|
1673
1916
|
if (kept) return {
|
|
1674
1917
|
status: "kept",
|
|
1675
1918
|
bytes: 0,
|
|
@@ -2032,17 +2275,19 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
|
|
|
2032
2275
|
const actions = [];
|
|
2033
2276
|
const skips = [];
|
|
2034
2277
|
const reason = `disk pressure level 2, idle for ${settings.pressureProtectMinutes} minutes`;
|
|
2278
|
+
const lightenReason = `disk pressure level 2, no ios, android or macos run in the last ${LIGHTEN_AFTER_MS / 6e4} minutes`;
|
|
2035
2279
|
const outputs = (await collectWorkspaceOutputs({
|
|
2036
2280
|
olderThan: null,
|
|
2037
2281
|
now,
|
|
2038
2282
|
measure: false,
|
|
2039
2283
|
signal: operation?.signal,
|
|
2040
|
-
operation
|
|
2284
|
+
operation,
|
|
2285
|
+
lighten: true
|
|
2041
2286
|
})).workspaces.flatMap((entry) => {
|
|
2042
2287
|
const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
|
|
2043
2288
|
if (root === null || !entry.willClear || root === protectedRoot || pinned(root)) return [];
|
|
2044
2289
|
const used = workspaceLastUsed(root);
|
|
2045
|
-
return Number.isFinite(used)
|
|
2290
|
+
return Number.isFinite(used) ? [{
|
|
2046
2291
|
root,
|
|
2047
2292
|
used
|
|
2048
2293
|
}] : [];
|
|
@@ -2054,7 +2299,7 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
|
|
|
2054
2299
|
target: root,
|
|
2055
2300
|
workspace: root,
|
|
2056
2301
|
bytes: size?.bytes ?? 0,
|
|
2057
|
-
reason,
|
|
2302
|
+
reason: lightenReason,
|
|
2058
2303
|
level: 2
|
|
2059
2304
|
});
|
|
2060
2305
|
}
|
|
@@ -2090,7 +2335,7 @@ async function planLevel2({ settings, sizes, protectedRoot, pinned, now, operati
|
|
|
2090
2335
|
kind: "would-clear-build-dirs",
|
|
2091
2336
|
target: candidate.path,
|
|
2092
2337
|
bytes: 0,
|
|
2093
|
-
reason,
|
|
2338
|
+
reason: lightenReason,
|
|
2094
2339
|
level: 2
|
|
2095
2340
|
});
|
|
2096
2341
|
skips.push(...build.skips);
|
|
@@ -2256,7 +2501,7 @@ async function planPressureLevels(level, context) {
|
|
|
2256
2501
|
function triggeringRoot(operation) {
|
|
2257
2502
|
return canonicalPath(findProjectRoot$1(operation?.cwd ?? process.cwd()) ?? join(configDir(), "maintenance", "no-project"));
|
|
2258
2503
|
}
|
|
2259
|
-
async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, refresh = true, operation } = {}) {
|
|
2504
|
+
async function plannedMaintenance(pressure, recordedSizes, settings, { devices = true, sweep = false, worktrees = false, idle = false, refresh = true, operation } = {}) {
|
|
2260
2505
|
const env = operation?.env ?? process.env;
|
|
2261
2506
|
const resolved = resolveBudget({ env });
|
|
2262
2507
|
if (resolved.error) throw new Error(resolved.error);
|
|
@@ -2353,11 +2598,17 @@ async function plannedMaintenance(pressure, recordedSizes, settings, { devices =
|
|
|
2353
2598
|
sizes: currentSizes,
|
|
2354
2599
|
protectRecentMs: settings.protectRecentHours * 36e5,
|
|
2355
2600
|
refresh,
|
|
2601
|
+
idleDays: sweep && settings.removeIdleWorktrees && settings.olderThanDays > 0 ? settings.olderThanDays : null,
|
|
2356
2602
|
operation
|
|
2357
2603
|
});
|
|
2358
2604
|
merge(planned);
|
|
2359
2605
|
result.worktreeSweep = planned.sweep;
|
|
2360
2606
|
}
|
|
2607
|
+
if (idle && devices) merge(planIdleShutdown({
|
|
2608
|
+
protectedRoot: root,
|
|
2609
|
+
now: Date.now(),
|
|
2610
|
+
env
|
|
2611
|
+
}));
|
|
2361
2612
|
if (pressure) {
|
|
2362
2613
|
const pressured = await planPressureLevels(diskPressureLevel(lowestFree(pressure.disk), budget, settings), {
|
|
2363
2614
|
settings,
|
|
@@ -2405,6 +2656,7 @@ async function previewMaintenance({ devices = true, refresh = true, operation }
|
|
|
2405
2656
|
...await plannedMaintenance(pressure, state?.sizes ?? [], settings, {
|
|
2406
2657
|
devices,
|
|
2407
2658
|
sweep: true,
|
|
2659
|
+
idle: true,
|
|
2408
2660
|
refresh,
|
|
2409
2661
|
operation
|
|
2410
2662
|
}),
|
|
@@ -2543,14 +2795,16 @@ async function clearOutputs(action, context) {
|
|
|
2543
2795
|
if (!root) return kept("the action names no workspace");
|
|
2544
2796
|
if (root === context.protectedRoot) return kept("the command that started this pass runs in it");
|
|
2545
2797
|
if (maintenancePinned(root)) return kept("pinned by maintenance.keep");
|
|
2546
|
-
const
|
|
2798
|
+
const lighten = action.level === 2;
|
|
2799
|
+
const recent = lighten ? null : recentUse(root, context.settings, protectWindowMs(action, context.settings));
|
|
2547
2800
|
if (recent) return kept(recent);
|
|
2548
2801
|
const olderThan = action.olderThanDays ?? null;
|
|
2549
2802
|
const now = Date.now();
|
|
2550
2803
|
const report = collectWorkspaceOutputs({
|
|
2551
2804
|
olderThan,
|
|
2552
2805
|
now,
|
|
2553
|
-
measure: false
|
|
2806
|
+
measure: false,
|
|
2807
|
+
lighten
|
|
2554
2808
|
});
|
|
2555
2809
|
const entry = report.workspaces.find((candidate) => candidate.projectRoot && canonicalPath(candidate.projectRoot) === root);
|
|
2556
2810
|
if (!entry) return kept("it has no build outputs any more");
|
|
@@ -2559,7 +2813,8 @@ async function clearOutputs(action, context) {
|
|
|
2559
2813
|
workspaces: [entry]
|
|
2560
2814
|
}, {
|
|
2561
2815
|
olderThan,
|
|
2562
|
-
now
|
|
2816
|
+
now,
|
|
2817
|
+
lighten
|
|
2563
2818
|
}));
|
|
2564
2819
|
return outcomeOf(results, action.bytes);
|
|
2565
2820
|
}
|
|
@@ -2575,7 +2830,12 @@ async function removeWorktree(action, context) {
|
|
|
2575
2830
|
...sweep,
|
|
2576
2831
|
worktrees: [worktree]
|
|
2577
2832
|
}));
|
|
2578
|
-
|
|
2833
|
+
const outcome = outcomeOf(results, freedSince(before));
|
|
2834
|
+
const removal = results.find((result) => result.status === "done")?.detail;
|
|
2835
|
+
return outcome.status === "done" ? {
|
|
2836
|
+
...outcome,
|
|
2837
|
+
detail: removal ?? action.reason
|
|
2838
|
+
} : outcome;
|
|
2579
2839
|
}
|
|
2580
2840
|
async function removeOrphan(action) {
|
|
2581
2841
|
const orphan = (await collectOrphanedWorkspaces(Object.keys(loadConfig$1()?.projects ?? {}), listMountedVolumes())).orphaned.find((candidate) => candidate.dir === action.target);
|
|
@@ -2583,11 +2843,47 @@ async function removeOrphan(action) {
|
|
|
2583
2843
|
const { results } = await quietly(() => deleteOrphanedWorkspaces([orphan]));
|
|
2584
2844
|
return outcomeOf(results, orphan.bytes ?? 0);
|
|
2585
2845
|
}
|
|
2846
|
+
async function shutDownIdleDevice(action) {
|
|
2847
|
+
if (deviceSweepIsScoped()) return kept("STIM_HOME scopes this config, and devices are machine-wide");
|
|
2848
|
+
const root = action.workspace ?? action.target;
|
|
2849
|
+
const minutes = idleShutdownMinutes(root);
|
|
2850
|
+
if (minutes === 0) return kept("devices.idleShutdownMinutes is 0 for this workspace");
|
|
2851
|
+
const idleMs = minutes * 6e4;
|
|
2852
|
+
const due = dueIdleDevices(root, idleMs).find(({ device }) => device.id === action.target);
|
|
2853
|
+
if (!due) return kept(`it is no longer idle for ${minutes} minutes`);
|
|
2854
|
+
const failures = [];
|
|
2855
|
+
const skips = [];
|
|
2856
|
+
const log = (event) => {
|
|
2857
|
+
writeIdleShutdownEvent(root, event);
|
|
2858
|
+
if (event.event === "device_idle_shutdown_failed") (event.skipped ? skips : failures).push(event.msg);
|
|
2859
|
+
};
|
|
2860
|
+
const run = await withIdleWorkspace(root, () => shutDownIdleDevices(root, idleMs, log, Date.now(), { only: due.device }), {
|
|
2861
|
+
purpose: "automatic maintenance idle shutdown",
|
|
2862
|
+
supervisor: false,
|
|
2863
|
+
managedLocks: false
|
|
2864
|
+
});
|
|
2865
|
+
if (!run.ran) return kept(`in use: ${run.reasons.join("; ")}`);
|
|
2866
|
+
if (run.value) return {
|
|
2867
|
+
status: "done",
|
|
2868
|
+
bytes: 0,
|
|
2869
|
+
detail: action.target
|
|
2870
|
+
};
|
|
2871
|
+
if (failures.length) return {
|
|
2872
|
+
status: "failed",
|
|
2873
|
+
bytes: 0,
|
|
2874
|
+
detail: failures.join("; ")
|
|
2875
|
+
};
|
|
2876
|
+
return kept(skips.join("; ") || `it is no longer idle for ${minutes} minutes`);
|
|
2877
|
+
}
|
|
2586
2878
|
async function deleteParkedDevice(action) {
|
|
2587
2879
|
if (deviceSweepIsScoped()) return kept("STIM_HOME scopes this config, and devices are machine-wide");
|
|
2588
|
-
const
|
|
2589
|
-
|
|
2590
|
-
|
|
2880
|
+
const age = {
|
|
2881
|
+
olderThanDays: action.olderThanDays ?? null,
|
|
2882
|
+
now: Date.now()
|
|
2883
|
+
};
|
|
2884
|
+
const sims = (await collectParkedSims({}, age)).filter((sim) => sim.udid === action.target);
|
|
2885
|
+
const avds = sims.length ? [] : (await collectParkedAvds({}, age)).filter((avd) => avd.name === action.target);
|
|
2886
|
+
if (!sims.length && !avds.length) return kept(action.olderThanDays === void 0 ? "it is no longer parked" : `it is no longer parked, or was parked within ${action.olderThanDays} days`);
|
|
2591
2887
|
const before = lowestFree(measureDisk());
|
|
2592
2888
|
const { results } = await quietly(async () => await deleteParkedSims(sims) + await deleteParkedAvds(avds));
|
|
2593
2889
|
return outcomeOf(results, freedSince(before));
|
|
@@ -2799,6 +3095,7 @@ async function executeAction(action, context) {
|
|
|
2799
3095
|
case "would-unregister-cache": return unregisterCache(action);
|
|
2800
3096
|
case "would-delete-parked-device": return deleteParkedDevice(action);
|
|
2801
3097
|
case "would-delete-devices": return deleteDevices(action, context);
|
|
3098
|
+
case "would-shutdown-device": return action.check === "disk" ? kept("automatic maintenance does not stop devices or dev servers for disk") : shutDownIdleDevice(action);
|
|
2802
3099
|
case "would-stop-workspace": return action.level === 3 ? stopWorkspace(action, context) : kept("automatic maintenance stops dev servers only at disk pressure level 3");
|
|
2803
3100
|
case "would-clear-build-dirs":
|
|
2804
3101
|
case "would-delete-dependencies": return clearArtifacts(action, context);
|
|
@@ -2813,4 +3110,4 @@ function diskRecovered(budget) {
|
|
|
2813
3110
|
return measureDisk().every((disk) => disk.freeMb >= floor);
|
|
2814
3111
|
}
|
|
2815
3112
|
//#endregion
|
|
2816
|
-
export {
|
|
3113
|
+
export { worktreeRemovalReason as C, worktreePullRequestNote as S, ownedDeviceUsage as _, previewMaintenance as a, collectWorktreeSweep as b, deleteTempLeftovers as c, appRoots as d, artifactWorktrees as f, measureOwnedDevices as g, finishedWorkspaces as h, plannedMaintenance as i, tempLeftoverLines as l, collectArtifactsAsync as m, diskRecovered as n, triggeringRoot as o, clearArtifact as p, executeAction as r, collectTempLeftovers as s, deleteIdleWorkspaceDevices as t, tempLeftoverSection as u, maintenancePinned as v, removeWorktrees as x, candidateRootsAsync as y };
|