stim 1.20.2 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +88 -8
  2. package/dist/acquire-artifact-CD4lbiWE.mjs +153 -0
  3. package/dist/act-DHAw2A2S.mjs +2667 -0
  4. package/dist/activity-BzVfHSrY.mjs +1376 -0
  5. package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-zh1vhbLp.mjs} +21 -65
  6. package/dist/{android-HqP1_vB_.mjs → android-BEJ8Jpl7.mjs} +2 -2
  7. package/dist/{android-DCKZrNvV.mjs → android-BLgbNqox.mjs} +128 -78
  8. package/dist/{android-Dlzxshgp.mjs → android-CV_GCELZ.mjs} +5 -5
  9. package/dist/{android-Mz_Inn3w.mjs → android-DUYkNF4u.mjs} +184 -102
  10. package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-Ct5b1xCK.mjs} +7 -6
  11. package/dist/android-cas-compiler.mjs +2 -2
  12. package/dist/android-project-BCAAmMwz.mjs +12 -0
  13. package/dist/api.d.mts +740 -8
  14. package/dist/api.mjs +585 -1
  15. package/dist/{artifact-HSwkIWEQ.mjs → artifact-B8T3S5gi.mjs} +223 -201
  16. package/dist/{artifact-D3czlUvT.mjs → artifact-DAuZ-FNU.mjs} +158 -148
  17. package/dist/{browser-web-CA-F1GQo.mjs → browser-web-QQzVjz3O.mjs} +26 -24
  18. package/dist/budget-Bs6SxxzO.mjs +9154 -0
  19. package/dist/{build-DYDbfkk5.mjs → build-CoPzQRNw.mjs} +144 -96
  20. package/dist/build-cache-DmyR9Zzc.mjs +602 -0
  21. package/dist/build-plan-CaFskB7_.mjs +188 -0
  22. package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DkkqguoK.mjs} +2 -2
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/{ccache-zdKiYQ3E.mjs → ccache-BJRZ9pG3.mjs} +10 -9
  25. package/dist/{cdp-Dq_ypAlJ.mjs → cdp-CWmIUMbw.mjs} +18 -5
  26. package/dist/{chrome-BnzZMqsR.mjs → chrome-B6tfCta1.mjs} +7 -4
  27. package/dist/{cli-CLP3diBm.mjs → cli-0DYf8QhQ.mjs} +20 -20
  28. package/dist/cli.mjs +1 -1
  29. package/dist/client-B7QVOlCr.d.mts +253 -0
  30. package/dist/client-UXD70ZbM.mjs +1406 -0
  31. package/dist/collector-run.d.mts +4 -40
  32. package/dist/collector-run.mjs +9 -8
  33. package/dist/{command-output-BTy8hZ0F.mjs → command-output-B1UrjZHL.mjs} +1 -1
  34. package/dist/config-CYcBF478.mjs +949 -0
  35. package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-CqeBlrEn.mjs} +3 -3
  36. package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-BbKQW83h.mjs} +20 -5
  37. package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-DFRyG1_o.mjs} +1 -1
  38. package/dist/{dev-client-DAWVIGBu.mjs → dev-client-UOeESKm6.mjs} +2 -2
  39. package/dist/device-DCxH7_By.mjs +1068 -0
  40. package/dist/{device-BECOiufl.mjs → device-ExJq8Ei7.mjs} +35 -35
  41. package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-CMlqEcvw.mjs} +43 -13
  42. package/dist/device-host-worker.mjs +65 -61
  43. package/dist/{device-ios-COSi6oS1.mjs → device-ios-DRUeKaFV.mjs} +48 -45
  44. package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-C8raGFoP.mjs} +14 -26
  45. package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-DMRpVX9a.mjs} +5 -5
  46. package/dist/doctor-BLHCYxzi.mjs +2 -0
  47. package/dist/{doctor-kfMBnUw6.mjs → doctor-DTvaDOda.mjs} +281 -317
  48. package/dist/{doctor-CB8oZKCk.mjs → doctor-Dhqebm0q.mjs} +183 -91
  49. package/dist/doctor-fingerprint.mjs +19 -0
  50. package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-Cv1F10Kj.mjs} +40 -21
  51. package/dist/{eas-build-JyfqyHfC.mjs → eas-build-BomfRnFT.mjs} +53 -20
  52. package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-DJwZpy0c.mjs} +26 -208
  53. package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-op6A54yi.mjs} +229 -11
  54. package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-Bf0rZvG9.mjs} +172 -82
  55. package/dist/exec-H20xSg2T.d.mts +65 -0
  56. package/dist/exec-_UbtCQcG.mjs +750 -0
  57. package/dist/gc-C-WwL0yi.mjs +3 -0
  58. package/dist/{gc-qWOMdnER.mjs → gc-_-dCXdyL.mjs} +584 -796
  59. package/dist/git-C3iTsJaZ.mjs +73 -0
  60. package/dist/{guide-G9cAbVAc.mjs → guide-Yqwt2bd6.mjs} +941 -269
  61. package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-Bw-honsd.mjs} +8 -6
  62. package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DB_gBPfY.mjs} +34 -19
  63. package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CUvOf4Vr.mjs} +1 -1
  64. package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-DL_P5AdN.mjs} +77 -43
  65. package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-QI141yKk.mjs} +21 -17
  66. package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-Ksyjgzq-.mjs} +30 -21
  67. package/dist/idle-BVeRGroT.d.mts +24 -0
  68. package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DaKmZi0z.mjs} +11 -10
  69. package/dist/{ios-BwVCF7oA.mjs → ios-CQ68hG9s.mjs} +8 -8
  70. package/dist/{ios-RkW4pnQf.mjs → ios-CpByZy12.mjs} +140 -80
  71. package/dist/{ios-device-B5veSDDQ.mjs → ios-device-CXGP1bsD.mjs} +1 -1
  72. package/dist/{ios-device-D7pi2Sca.mjs → ios-device-CwFMehUS.mjs} +51 -26
  73. package/dist/{ios-project-K9hY_Jab.mjs → ios-project-WuwwBdvW.mjs} +13 -13
  74. package/dist/{ios-nOLJGVdy.mjs → ios-qNJt4dFm.mjs} +1 -1
  75. package/dist/ios-signing-DTTIChGs.mjs +489 -0
  76. package/dist/{ios-state-CBLe1xYw.mjs → ios-state-DvFX-K_l.mjs} +1 -1
  77. package/dist/{ios-Ce7bc7br.mjs → ios-ubFsBzwq.mjs} +129 -86
  78. package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Cc1dttps.mjs} +53 -26
  79. package/dist/{launch-F4sOqAhB.mjs → launch-CD1yVc7U.mjs} +91 -90
  80. package/dist/{launch-C3qJ6Agb.mjs → launch-Zq2mOSDy.mjs} +82 -541
  81. package/dist/logs-BS00Anxf.mjs +352 -0
  82. package/dist/logs-CMm05HxN.mjs +255 -0
  83. package/dist/{macos-D7uDvO-z.mjs → macos-B65--pPd.mjs} +1 -1
  84. package/dist/{macos-Ch5VJmcS.mjs → macos-C-0qJ6zq.mjs} +225 -212
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +122 -247
  87. package/dist/{metro-DnCEjCxs.mjs → metro-DEXpIhHC.mjs} +67 -12
  88. package/dist/{metro-gateway-CX9Xlkq2.mjs → metro-gateway-BMJNlj3E.mjs} +20 -12
  89. package/dist/{named-ports-4WopRPg-.mjs → named-ports-BWTzt6mu.mjs} +8 -63
  90. package/dist/native-android-JACyjTZs.mjs +844 -0
  91. package/dist/{native-gradle-inputs-BsLRrl7u.mjs → native-gradle-inputs-Bep9JcSF.mjs} +128 -140
  92. package/dist/native-inputs-TRTTTRxt.mjs +91 -0
  93. package/dist/native-run-DY_RLx3C.mjs +277 -0
  94. package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-Dg1Q7tfF.mjs} +9 -8
  95. package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-CB2iQeWq.mjs} +27 -18
  96. package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-CXX39F7o.mjs} +96 -47
  97. package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
  98. package/dist/offload-worker.d.mts +1 -46
  99. package/dist/offload-worker.mjs +197 -66
  100. package/dist/{ownership-DCtw_2IA.mjs → ownership-DEmM5vhh.mjs} +3 -3
  101. package/dist/{page-DRHy329N.mjs → page-CPaHeYqj.mjs} +7 -4
  102. package/dist/paths-BsJMoPP2.mjs +12 -0
  103. package/dist/paths-DQtTgsBf.mjs +42 -0
  104. package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BqGbTa61.mjs} +5 -5
  105. package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-Bwj-IjY1.mjs} +6 -6
  106. package/dist/{ports-DdSUicVo.mjs → ports-D-MmCTdv.mjs} +3 -3
  107. package/dist/prebuild-uD_ipCxF.mjs +449 -0
  108. package/dist/{project-_phNIJfs.mjs → project-BZNjmW_8.mjs} +65 -37
  109. package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-Lfetn8Kr.mjs} +2 -2
  110. package/dist/pull-request-zjCSAryH.d.mts +120 -0
  111. package/dist/pull-requests.d.mts +1 -119
  112. package/dist/pull-requests.mjs +1 -1
  113. package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-DuzH0cn3.mjs} +74 -48
  114. package/dist/{gradle-DSrXb6lG.mjs → react-native-build-rhk4smwQ.mjs} +292 -15
  115. package/dist/react-native-doctor-CQpuIMso.mjs +234 -0
  116. package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-Bhq1rJeV.mjs} +46 -28
  117. package/dist/{recordings-D0d5aQt0.mjs → recordings-DYteQSu4.mjs} +12 -9
  118. package/dist/{reload-C8JhxjSh.mjs → reload-CmTRFJaX.mjs} +93 -47
  119. package/dist/reload-DzjSl5P_.mjs +2 -0
  120. package/dist/{remote-BiR6aYjy.mjs → remote-ByYkXdGD.mjs} +15 -10
  121. package/dist/result-DNiS-tTP.mjs +434 -0
  122. package/dist/{run-BtPgpKks.mjs → run-D23HmJMs.mjs} +4 -5
  123. package/dist/selection-CSl9i5qU.mjs +619 -0
  124. package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CxTrvDg8.mjs} +3 -4
  125. package/dist/{server-command-CTtCkmcQ.mjs → server-command-CtzloH3v.mjs} +14 -7
  126. package/dist/server-expo-C-90EhcG.mjs +2 -0
  127. package/dist/{server-expo-DsguMGUF.mjs → server-expo-DT_F_uVI.mjs} +50 -9
  128. package/dist/{settings-B717odTD.mjs → settings-CsJDEXgK.mjs} +234 -24
  129. package/dist/{settings-vk3mJGSm.d.mts → settings-DkdmlelE.d.mts} +6 -4
  130. package/dist/settings-T1gtc3bf.mjs +114 -0
  131. package/dist/settings-write-HyVZ8eQ8.mjs +359 -0
  132. package/dist/settings.schema.json +163 -16
  133. package/dist/{simslim-CCr3rE-A.mjs → simslim-Cgk17SXV.mjs} +14 -12
  134. package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-DF8VfT64.mjs} +15 -20
  135. package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
  136. package/dist/stage-BIS0ydsV.mjs +183 -0
  137. package/dist/{start-BWqzODpb.mjs → start-7lpH7hmN.mjs} +63 -44
  138. package/dist/{start-TXeMmmO2.mjs → start-RChynRDp.mjs} +1 -1
  139. package/dist/{state-D1Icyjlu.mjs → state-C4ZtUisW.mjs} +40 -13
  140. package/dist/{state-CG_OFoaA.mjs → state-XMziexv2.mjs} +3 -2
  141. package/dist/{stats-BoY5sEPU.mjs → stats-53T7OTb9.mjs} +7 -13
  142. package/dist/stats-report-BFfMpWWF.mjs +16 -0
  143. package/dist/{status-WgFbkRzj.mjs → status-BL7jVyAB.mjs} +535 -447
  144. package/dist/status-CSi6tlRq.mjs +2 -0
  145. package/dist/{status-BTA7kKiQ.mjs → status-DqEUSSxs.mjs} +6 -4
  146. package/dist/{status-watch-C23ILtf9.mjs → status-watch-GDCfqpTE.mjs} +187 -22
  147. package/dist/status-worker.d.mts +1 -0
  148. package/dist/status-worker.mjs +19 -0
  149. package/dist/stim-desktop-C12we7lz.mjs +112 -0
  150. package/dist/{stop-DR6VoDIk.mjs → stop-Bk_1qiV6.mjs} +2 -2
  151. package/dist/{stop-BNmBMi1F.mjs → stop-CPzlM1L-.mjs} +138 -98
  152. package/dist/{stop-DPEhunlw.mjs → stop-D7h6jAXQ.mjs} +11 -10
  153. package/dist/supervisor-run.d.mts +8 -17
  154. package/dist/supervisor-run.mjs +63 -27
  155. package/dist/{support-DMTqj3Hm.mjs → support-67vraa1Q.mjs} +18 -11
  156. package/dist/{support-CwjbFs9H.mjs → support-Dlu37g6H.mjs} +12 -12
  157. package/dist/{swiftpm-macos-DRNVJWlb.mjs → swiftpm-macos-p8WZbxKU.mjs} +35 -24
  158. package/dist/toolchain-DhKwp9iI.mjs +2 -0
  159. package/dist/toolchain-Hp9sCpK4.mjs +873 -0
  160. package/dist/{trigger-CigAYjeL.mjs → trigger-BAahxUPq.mjs} +20 -5
  161. package/dist/trigger-Bf3uDcpv.mjs +2 -0
  162. package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-BHX5r0Ei.mjs} +250 -156
  163. package/dist/{web-BDBMxDpx.mjs → web-CJmIj6AI.mjs} +18 -13
  164. package/dist/web-DknQETPs.mjs +2 -0
  165. package/dist/web-run.mjs +11 -12
  166. package/dist/windows-report.d.mts +1 -0
  167. package/dist/windows-report.mjs +18 -0
  168. package/dist/windows-report.ps1 +43 -0
  169. package/dist/workspace-process-lock-1_1OoVW5.mjs +57 -0
  170. package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-Cjuigeeu.mjs} +62 -58
  171. package/dist/{workspaces-C36s2jb9.mjs → workspaces-BccMFmGQ.mjs} +1188 -1079
  172. package/dist/worktree-CK2MpJW5.mjs +2 -0
  173. package/dist/{worktree-CgvJYJ-Q.mjs → worktree-DQ6Zi2Mv.mjs} +304 -53
  174. package/dist/xcode-Zlx3qjEs.mjs +896 -0
  175. package/package.json +7 -5
  176. package/shim/android-optimizations.gradle +7 -5
  177. package/dist/activity-BSV745Kz.mjs +0 -629
  178. package/dist/api-qvOzWyfO.mjs +0 -154
  179. package/dist/api-run.mjs +0 -294
  180. package/dist/app-install-BIKtMfPq.mjs +0 -896
  181. package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
  182. package/dist/budget-f7516Zk0.mjs +0 -4106
  183. package/dist/build-facts-DcLv2HSd.d.mts +0 -111
  184. package/dist/cancellation-C_22VQTH.mjs +0 -24
  185. package/dist/client-qQAaXSUs.mjs +0 -1771
  186. package/dist/config-BYDxlMY3.mjs +0 -562
  187. package/dist/device-DuiPJQSn.mjs +0 -290
  188. package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
  189. package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
  190. package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
  191. package/dist/exec-DqKo9GKJ.mjs +0 -268
  192. package/dist/git-DDfFzkMm.mjs +0 -45
  193. package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
  194. package/dist/logs-BGrEUaEs.mjs +0 -383
  195. package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
  196. package/dist/native-android-D5qc3-_v.mjs +0 -226
  197. package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
  198. package/dist/plan-C4TjdPsR.mjs +0 -260
  199. package/dist/prebuild-DYbucPXy.mjs +0 -212
  200. package/dist/preview-78a9kR_-.mjs +0 -1011
  201. package/dist/react-native-build-CtCccJtb.mjs +0 -239
  202. package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
  203. package/dist/remote-cache-OyOyVIS3.mjs +0 -526
  204. package/dist/result-CaJg5Iat.mjs +0 -186
  205. package/dist/server-expo-Bigriskl.mjs +0 -2
  206. package/dist/settings-BORNaaCO.mjs +0 -402
  207. package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
  208. package/dist/stage-KNTQopqv.mjs +0 -157
  209. package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
  210. package/dist/tailnet-Dh-qiYzG.mjs +0 -265
  211. package/dist/toolchain-BFkAcstd.mjs +0 -2
  212. package/dist/toolchain-DxSsNssu.mjs +0 -1657
  213. package/dist/trigger-9_USgmxz.mjs +0 -2
  214. package/dist/web-Dq-i3wzJ.mjs +0 -2
  215. package/dist/worktree-DpLdCHga.mjs +0 -2
  216. package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
  217. package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
@@ -1,1011 +0,0 @@
1
- import { F as listMountedVolumes, I as measuredDirectorySize, P as isOnMountedVolume, W as projectDeviceSlots, _ as loadConfig$1, p as getProject } from "./config-BYDxlMY3.mjs";
2
- import { n as isTimeoutError } from "./exec-DqKo9GKJ.mjs";
3
- import { r as gitQuiet } from "./git-DDfFzkMm.mjs";
4
- import { n as readManifest } from "./cache-manifest-XH-fvDUj.mjs";
5
- import { E as maintenanceKeepSetting, K as resolveProjectSettings, Mt as unpushedCommits, Ot as resolveFullRef, St as listWorktrees, _t as hasPopulatedSubmodules, jt as sourceCheckoutOf, mt as dirtyPaths, yt as hasUncommittedWork } from "./settings-B717odTD.mjs";
6
- import { h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-BX1sJQ2E.mjs";
7
- import { E as workspaceStateFile, r as readWorkspaceState$1, s as workspaceLastUsed, w as workspaceLogsDir$1 } from "./workspace-state-DJt_MMvf.mjs";
8
- import { o as formatLongDuration, u as plural } from "./command-output-BTy8hZ0F.mjs";
9
- import { r as findProjectRoot } from "./project-_phNIJfs.mjs";
10
- import { ft as readParked, z as ownedAvdSerialResolver } from "./android-Mz_Inn3w.mjs";
11
- import { C as recordGcResult, I as listBuildSlots, T as canonicalPath, a as collectWorkspaceOutputs, c as listWorkspaceDirs, i as collectOrphanedWorkspaces, p as workspaceInUse, s as isInsideWorkspaces } from "./workspaces-C36s2jb9.mjs";
12
- import { _ as DEVICE_LIST_TIMEOUT_MS, at as discoverCaches, c as readVolumeSpace, i as enforceBudget, l as resolveBudget, nt as phase, o as machineNumber, r as cacheLabel, u as trimmableCaches, z as planCacheEmptying } from "./budget-f7516Zk0.mjs";
13
- import { s as metroStoreRoot } from "./errors-DzkMYh8Q.mjs";
14
- import { P as readHostMemoryPressure, d as listAllIosSims } from "./ios-RkW4pnQf.mjs";
15
- import { n as describePullRequest, o as pullRequestLookups, r as endedPullRequest } from "./pull-request-CQ8sPbZa.mjs";
16
- import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
17
- import { o as fetchDefaultBranch, s as mergeState } from "./warm-progress-BuY-Fvfn.mjs";
18
- import { a as matchWorktreeEntry, i as excludeWatchmanCookies, p as removeWorktreeTarget, r as excludePodChurn, s as reclaimKeys } from "./worktree-CgvJYJ-Q.mjs";
19
- import { existsSync } from "node:fs";
20
- import { basename, isAbsolute, join, sep } from "node:path";
21
- import { isAbsolute as isAbsolute$1, join as join$1 } from "path";
22
- import { LAST_BUILD_KEYS, MAINTENANCE_LOG_NAME, buildWorkerRoot, loadConfig, readBuildHistory, readMaintenanceState, settingDefinition, sharedCcache, sharedCompilationCache } from "@stim-cli/core/state";
23
- import { existsSync as existsSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
24
- import { buildCacheRoot, configDir, metroCacheRoot } from "@stim-cli/core";
25
- import { cpus, freemem, loadavg, totalmem } from "node:os";
26
- import chalk from "chalk";
27
- //#region src/maintenance/measure.ts
28
- function measureDisk() {
29
- return readVolumeSpace([
30
- configDir(),
31
- ...Object.keys(loadConfig()?.projects ?? {}),
32
- buildWorkerRoot(loadConfig())
33
- ]);
34
- }
35
- function measurePressure(settings, previous, now) {
36
- const level = readHostMemoryPressure();
37
- const availableBytes = process.platform === "darwin" ? null : freemem();
38
- const warningSince = level === "warning" ? previous?.warningSince ?? now : null;
39
- const pressured = settings.memoryPressureLevel !== "off" && (level === "critical" || settings.memoryPressureLevel === "warning" && warningSince !== null && now - warningSince >= settings.memoryWarningMinutes * 6e4 || availableBytes !== null && availableBytes < (settings.minAvailableMemoryGb === void 0 ? totalmem() * .1 : settings.minAvailableMemoryGb * 1024 ** 3));
40
- return {
41
- disk: measureDisk(),
42
- memory: {
43
- level,
44
- availableBytes,
45
- pressured
46
- },
47
- warningSince
48
- };
49
- }
50
- function sizeScanDeferred(settings) {
51
- return loadavg()[0] / Math.max(1, cpus().length) > settings.maxLoadPerCore;
52
- }
53
- function cacheBlocked(entry, discovered = discoverCaches()) {
54
- return planCacheEmptying([{
55
- ...entry,
56
- note: "",
57
- prune: "entries",
58
- ...discovered.some((cache) => canonicalPath(cache.dir) === canonicalPath(entry.dir) && cache.source === "registered") ? { source: "registered" } : {}
59
- }], false)[0]?.machineGlobal ?? (entry.category === "compilation-cache" && [...listBuildLocks(), ...listBuildSlots()].some((item) => item.alive || item.unresolved) ? "a build lock or slot is live or unresolved" : void 0);
60
- }
61
- function measureSizes(now, failure) {
62
- const sizes = [];
63
- const outputs = collectWorkspaceOutputs({
64
- olderThan: null,
65
- now,
66
- sizeTimeoutMs: 12e4
67
- });
68
- for (const entry of outputs.workspaces) {
69
- if (entry.bytes === null) {
70
- failure(entry.dir, entry.projectRoot ?? void 0);
71
- continue;
72
- }
73
- sizes.push({
74
- name: "workspace build outputs",
75
- category: "workspace-outputs",
76
- dir: entry.dir,
77
- bytes: entry.bytes,
78
- measuredAt: now,
79
- ...entry.projectRoot ? { workspace: canonicalPath(entry.projectRoot) } : {},
80
- idleDays: entry.idleDays,
81
- ...!entry.willClear ? { blocked: entry.keptReason ?? "workspace ownership is unresolved" } : {}
82
- });
83
- }
84
- const known = [
85
- {
86
- name: "shared build cache",
87
- dir: buildCacheRoot(),
88
- category: "build-cache"
89
- },
90
- {
91
- name: "Metro transform caches",
92
- dir: metroCacheRoot(),
93
- category: "metro-cache"
94
- },
95
- {
96
- name: "ccache",
97
- dir: sharedCcache(),
98
- category: "ccache"
99
- },
100
- {
101
- name: "Swift compilation cache",
102
- dir: sharedCompilationCache(),
103
- category: "compilation-cache"
104
- }
105
- ];
106
- const discovered = discoverCaches();
107
- for (const cache of discovered) {
108
- if (cache.source !== "registered" && !canonicalPath(cache.dir).startsWith(`${canonicalPath(configDir())}/`)) continue;
109
- if (cache.prune === "report-only") continue;
110
- const dir = canonicalPath(cache.dir);
111
- if (known.some((item) => dir === canonicalPath(item.dir) || dir.startsWith(`${canonicalPath(item.dir)}/`))) continue;
112
- const category = cache.name.startsWith("Metro transform cache") ? "metro-cache" : /^(Expo build cache|native build cache|Build cache)$/.test(cache.name) ? "build-cache" : "other";
113
- known.push({
114
- name: cache.name,
115
- dir: cache.dir,
116
- category
117
- });
118
- }
119
- for (const entry of known) {
120
- if (!existsSync(entry.dir)) continue;
121
- const bytes = measuredDirectorySize(entry.dir, { timeoutMs: 12e4 });
122
- if (bytes === null) {
123
- failure(entry.dir);
124
- continue;
125
- }
126
- const blocked = cacheBlocked(entry, discovered);
127
- sizes.push({
128
- ...entry,
129
- bytes,
130
- measuredAt: now,
131
- ...blocked ? { blocked } : {}
132
- });
133
- }
134
- return sizes;
135
- }
136
- //#endregion
137
- //#region src/commands/gc/worktrees.ts
138
- const DEFAULT_WORKTREE_IDLE_DAYS = 7;
139
- const DAY_MS = 864e5;
140
- const MINUTE_MS = 6e4;
141
- const GRACE_SETTING = "gc.worktreeGraceMinutes";
142
- const MERGE_DECIDES = /* @__PURE__ */ new Set([
143
- "unpushed",
144
- "not-merged",
145
- "last-use-unknown",
146
- "recently-used"
147
- ]);
148
- function skip(code, text) {
149
- return {
150
- code,
151
- text
152
- };
153
- }
154
- function pullRequestNote(lookup) {
155
- const pr = lookup && "pullRequest" in lookup ? lookup.pullRequest : null;
156
- if (!pr) return null;
157
- return pr.containsHead ? describePullRequest(pr) : `${describePullRequest(pr)}, and HEAD has commits it does not`;
158
- }
159
- /**
160
- * Why gc keeps a worktree, or null when it removes it. A worktree that would be removed is still kept while its last
161
- * activity, or its merge into the default branch, is less than `grace.ms` old, and kept when that activity is unknown.
162
- */
163
- function worktreeSkipReason(facts, olderThan, grace = {
164
- ms: 0,
165
- now: 0
166
- }) {
167
- return removalBlocker(facts, olderThan) ?? graceBlocker(facts, grace);
168
- }
169
- function graceBlocker(facts, grace) {
170
- if (grace.ms <= 0) return null;
171
- if (!facts.activity) return skip("activity-unknown", "its last activity could not be read");
172
- const merged = facts.merge?.merged ? facts.merge : null;
173
- const ended = endedPullRequest(facts.pullRequest);
174
- const events = [facts.activity];
175
- if (merged) events.push({
176
- at: merged.mergedAt,
177
- basis: `merged into ${merged.into}`
178
- });
179
- if (ended?.endedAt) events.push({
180
- at: ended.endedAt,
181
- basis: describePullRequest(ended)
182
- });
183
- const latest = events.reduce((a, b) => b.at > a.at ? b : a);
184
- const eligibleAt = latest.at + grace.ms;
185
- if (grace.now >= eligibleAt) return null;
186
- const ago = formatLongDuration(Math.max(0, grace.now - latest.at));
187
- return {
188
- code: "recent-activity",
189
- text: `recent activity: ${latest.basis} ${ago} ago; removable after ${new Date(eligibleAt).toISOString()}`,
190
- eligibleAt
191
- };
192
- }
193
- function removalBlocker(facts, olderThan) {
194
- if (facts.bare) return skip("bare-repository", "bare repository");
195
- if (typeof facts.source === "object") return skip("source-checkout-unknown", `source checkout unknown: ${facts.source.refusal}`);
196
- if (facts.source === "source") return skip("source-checkout", "source checkout");
197
- if (facts.locked) return skip("locked", "locked with git worktree lock");
198
- if (facts.inUse.length) return skip("in-use", `in use: ${facts.inUse.join("; ")}`);
199
- if (facts.porcelain === null) return skip("status-unreadable", "git status could not be read");
200
- const dirty = excludePodChurn(excludeWatchmanCookies(facts.porcelain).lines).lines.length;
201
- if (dirty) return skip("dirty", `dirty: ${plural(dirty, "uncommitted or untracked file")}`);
202
- if (facts.unpushed === null) return skip("unpushed-unchecked", "unpushed commits could not be checked");
203
- const merged = facts.merge?.merged ? facts.merge : null;
204
- const ended = endedPullRequest(facts.pullRequest);
205
- if (facts.unpushed.length && !merged?.coversUnpushed && ended?.state !== "merged") return skip("unpushed", `unpushed: ${plural(facts.unpushed.length, "commit")} on no remote or other branch`);
206
- if (facts.submodules === null) return skip("submodules", "initialized submodules could not be checked");
207
- if (facts.submodules) return skip("submodules", "initialized submodules");
208
- if (merged || ended) return null;
209
- const notMerged = facts.merge && !facts.merge.merged ? facts.merge : null;
210
- const details = [notMerged?.detail, pullRequestNote(facts.pullRequest)].filter(Boolean);
211
- if (olderThan === null) return skip(notMerged?.unknown ? "merge-unknown" : "not-merged", details.join("; ") || "not merged");
212
- const also = details.map((detail) => `; ${detail}`).join("");
213
- if (facts.idleDays === null) return skip("last-use-unknown", `recently used: its last use is unknown${also}`);
214
- if (facts.idleDays < olderThan) return skip("recently-used", `recently used ${facts.idleDays}d ago${also}`);
215
- return null;
216
- }
217
- /** Why gc removes a worktree it did not skip: `merged into origin/main`, `PR #12 closed` or `idle 9d`. */
218
- function worktreeRemovalReason(candidate) {
219
- if (candidate.merge?.merged) return `merged into ${candidate.merge.into}`;
220
- const ended = endedPullRequest(candidate.pullRequest);
221
- return ended ? describePullRequest(ended) : `idle ${candidate.idleDays ?? 0}d`;
222
- }
223
- /** The pull request note a kept worktree's report line carries, such as `PR #12 merged`, or null. */
224
- function worktreePullRequestNote(candidate) {
225
- return pullRequestNote(candidate.pullRequest);
226
- }
227
- function lastUsedOf(keys) {
228
- const times = keys.map(workspaceLastUsed).filter(Number.isFinite);
229
- return times.length ? Math.max(...times) : NaN;
230
- }
231
- function idleDaysOf(keys, now) {
232
- const last = lastUsedOf(keys);
233
- return Number.isFinite(last) ? Math.max(0, Math.floor((now - last) / DAY_MS)) : null;
234
- }
235
- function deviceReads() {
236
- let sims;
237
- let resolver;
238
- return {
239
- sims: () => {
240
- if (sims === void 0) try {
241
- sims = listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS });
242
- } catch {
243
- sims = null;
244
- }
245
- return sims;
246
- },
247
- avdSerial: (avdName) => (resolver ??= ownedAvdSerialResolver({ timeoutMs: DEVICE_LIST_TIMEOUT_MS }))(avdName)
248
- };
249
- }
250
- function inUseOf(keys, checks, devices = deviceReads()) {
251
- return [.../* @__PURE__ */ new Set([...keys.flatMap((key) => workspaceInUse(key, checks)), ...deviceUseOf(keys, devices)])];
252
- }
253
- function deviceUseOf(keys, devices) {
254
- const reasons = [];
255
- for (const key of keys) {
256
- let slots;
257
- try {
258
- slots = projectDeviceSlots(getProject(key));
259
- } catch (error) {
260
- reasons.push(`its device records cannot be read: ${error.message}`);
261
- continue;
262
- }
263
- for (const { platforms } of slots) {
264
- const ios = platforms.ios;
265
- if (ios?.owned && ios.deviceUdid) {
266
- const sims = devices.sims();
267
- const sim = sims?.find((entry) => entry.udid === ios.deviceUdid);
268
- if (sims === null) reasons.push(`the state of its owned simulator ${ios.deviceUdid} cannot be read`);
269
- else if (sim && sim.state !== "Shutdown") reasons.push(`its owned simulator ${sim.name} is ${sim.state}; \`stim stop\` in ${key} shuts it down`);
270
- }
271
- const android = platforms.android;
272
- if (android?.owned && android.avdName) try {
273
- if (devices.avdSerial(android.avdName).serial) reasons.push(`its owned emulator ${android.avdName} is running; \`stim stop\` in ${key} shuts it down`);
274
- } catch {
275
- reasons.push(`the state of its owned emulator ${android.avdName} cannot be read`);
276
- }
277
- }
278
- }
279
- const holders = new Set(keys.map(canonicalPath));
280
- const now = Date.now();
281
- for (const { lease } of listLeaseFiles()) if (lease && !leaseIsExpired(lease, now) && holders.has(canonicalPath(lease.holder))) reasons.push(`it holds the ${lease.platform} device lease on ${lease.deviceName ?? lease.id}`);
282
- return reasons;
283
- }
284
- function newestMtime(paths) {
285
- let newest = null;
286
- for (const { path, basis } of paths) {
287
- let mtime;
288
- try {
289
- mtime = statSync$1(path).mtimeMs;
290
- } catch (error) {
291
- if (error.code === "ENOENT") continue;
292
- return "unreadable";
293
- }
294
- if (!newest || mtime > newest.at) newest = {
295
- at: mtime,
296
- basis
297
- };
298
- }
299
- return newest;
300
- }
301
- function logFiles$1(key) {
302
- const dir = workspaceLogsDir$1(key);
303
- try {
304
- return readdirSync$1(dir).filter((name) => !name.startsWith(MAINTENANCE_LOG_NAME)).map((name) => ({
305
- path: join$1(dir, name),
306
- basis: "a Stim log write"
307
- }));
308
- } catch (error) {
309
- return error.code === "ENOENT" ? [] : null;
310
- }
311
- }
312
- function worktreeActivityOf(path, keys) {
313
- const gitDir = gitQuiet(path, [
314
- "rev-parse",
315
- "--path-format=absolute",
316
- "--git-dir"
317
- ])?.trim();
318
- if (!gitDir) return null;
319
- const paths = [
320
- {
321
- path: join$1(gitDir, "index"),
322
- basis: "a git index write"
323
- },
324
- {
325
- path: join$1(gitDir, "HEAD"),
326
- basis: "a git HEAD change"
327
- },
328
- {
329
- path: join$1(gitDir, "logs", "HEAD"),
330
- basis: "a git reflog entry"
331
- }
332
- ];
333
- for (const key of keys) {
334
- const logs = logFiles$1(key);
335
- if (logs === null) return null;
336
- paths.push({
337
- path: workspaceStateFile(key),
338
- basis: "a Stim workspace state write"
339
- }, ...logs);
340
- }
341
- const newest = newestMtime(paths);
342
- return newest === "unreadable" ? null : newest;
343
- }
344
- function graceMsSetting() {
345
- const { value, error } = machineNumber(GRACE_SETTING, loadConfig$1(), process.env);
346
- if (error) {
347
- const fallback = Number(settingDefinition(GRACE_SETTING)?.default);
348
- console.error(chalk.yellow(`${error} gc uses the default of ${fallback} minutes.`));
349
- return fallback * MINUTE_MS;
350
- }
351
- return (value ?? 0) * MINUTE_MS;
352
- }
353
- function unlessTimedOut(read, unknown) {
354
- try {
355
- return read();
356
- } catch (error) {
357
- if (isTimeoutError(error)) return unknown;
358
- throw error;
359
- }
360
- }
361
- function porcelainOf(path, gitAnswered) {
362
- if (gitAnswered === null) return null;
363
- if (!gitAnswered) return [];
364
- const lines = unlessTimedOut(() => dirtyPaths(path, { limit: Infinity }), null);
365
- return lines?.length ? lines : null;
366
- }
367
- function candidateRoots() {
368
- const registered = Object.keys(loadConfig$1()?.projects ?? {}).filter(isAbsolute$1);
369
- const recorded = listWorkspaceDirs().flatMap((entry) => entry.projectRoot ? [entry.projectRoot] : []);
370
- return [.../* @__PURE__ */ new Set([...registered, ...recorded])].filter((root) => existsSync$1(root)).toSorted();
371
- }
372
- function checkMergeStates(pending, idle, grace) {
373
- const repos = /* @__PURE__ */ new Map();
374
- for (const entry of pending) repos.set(entry.repo, [...repos.get(entry.repo) ?? [], entry]);
375
- for (const [repo, entries] of repos) {
376
- const target = fetchDefaultBranch(repo, grace.now);
377
- for (const { candidate, facts } of entries) {
378
- const merge = "error" in target ? {
379
- merged: false,
380
- unknown: true,
381
- detail: `merge state unknown: ${target.error}`
382
- } : mergeState(candidate.path, target);
383
- const verdict = worktreeSkipReason({
384
- ...facts,
385
- merge
386
- }, idle, grace);
387
- Object.assign(candidate, {
388
- merge,
389
- skipCode: verdict?.code ?? null,
390
- skipped: verdict?.text ?? null,
391
- eligibleAt: verdict?.eligibleAt ?? null
392
- });
393
- }
394
- }
395
- }
396
- /**
397
- * Classifies each Stim-managed linked worktree. A merged one is removable; with `idle`, so is one unused for
398
- * `olderThan` days. Device use is read last, in one pass against one simulator list and one AVD resolver, so
399
- * the resolver's time budget covers only device reads. Pull requests are asked once per repository, all
400
- * repositories at once. Merge state is checked only where it decides the verdict, after one fetch per repository.
401
- */
402
- async function collectWorktreeSweep({ idle, olderThan, now }) {
403
- const days = idle ? olderThan ?? DEFAULT_WORKTREE_IDLE_DAYS : null;
404
- const grace = {
405
- ms: graceMsSetting(),
406
- now
407
- };
408
- const groups = /* @__PURE__ */ new Map();
409
- const outside = [];
410
- const candidates = candidateRoots();
411
- if (candidates.length) phase("worktrees", `checking ${plural(candidates.length, "project")} for merged or idle worktrees`);
412
- for (const root of candidates) {
413
- const entry = matchWorktreeEntry(listWorktrees(root), root);
414
- if (!entry) {
415
- outside.push({
416
- path: root,
417
- keys: [root],
418
- idleDays: null,
419
- merge: null,
420
- head: null,
421
- pullRequest: null,
422
- skipCode: "not-a-worktree",
423
- skipped: "not inside a git worktree",
424
- eligibleAt: null
425
- });
426
- continue;
427
- }
428
- groups.set(entry.path, [...groups.get(entry.path) ?? [], root]);
429
- }
430
- const inspected = [];
431
- for (const [path, roots] of groups) {
432
- const entries = listWorktrees(path);
433
- const entry = matchWorktreeEntry(entries, path);
434
- const source = sourceCheckoutOf(entries);
435
- const keys = [.../* @__PURE__ */ new Set([...roots, ...reclaimKeys(path)])];
436
- const idleDays = idleDaysOf(keys, now);
437
- const linked = !("refusal" in source) && entry !== null && source.path !== entry.path;
438
- const activity = linked && grace.ms > 0 ? worktreeActivityOf(path, keys) : null;
439
- const gitAnswered = linked ? unlessTimedOut(() => hasUncommittedWork(path), null) : null;
440
- const head = linked ? resolveFullRef(path, "HEAD") : null;
441
- const facts = {
442
- source: "refusal" in source ? source : linked ? "linked" : "source",
443
- bare: Boolean(entry?.bare),
444
- locked: Boolean(entry?.locked),
445
- porcelain: porcelainOf(path, gitAnswered),
446
- unpushed: linked ? unlessTimedOut(() => unpushedCommits(path), null) : null,
447
- submodules: linked ? unlessTimedOut(() => hasPopulatedSubmodules(path), null) : false,
448
- inUse: [],
449
- idleDays,
450
- merge: null,
451
- pullRequest: null,
452
- activity
453
- };
454
- inspected.push({
455
- candidate: {
456
- path,
457
- keys,
458
- idleDays,
459
- merge: null,
460
- head,
461
- pullRequest: null,
462
- skipCode: null,
463
- skipped: null,
464
- eligibleAt: null
465
- },
466
- facts,
467
- repo: "refusal" in source ? null : source.path,
468
- branch: head && entry?.branch ? entry.branch : null
469
- });
470
- }
471
- const linked = inspected.filter(({ facts }) => facts.source === "linked");
472
- for (const { candidate, facts } of linked) facts.inUse = candidate.keys.flatMap((key) => workspaceInUse(key, { managedLocks: true }));
473
- const devices = deviceReads();
474
- for (const { candidate, facts } of linked) facts.inUse = [.../* @__PURE__ */ new Set([...facts.inUse, ...deviceUseOf(candidate.keys, devices)])];
475
- await lookUpPullRequests(inspected);
476
- const worktrees = [];
477
- const pending = [];
478
- for (const { candidate, facts, repo } of inspected) {
479
- const verdict = worktreeSkipReason(facts, days, grace);
480
- Object.assign(candidate, {
481
- pullRequest: facts.pullRequest,
482
- skipCode: verdict?.code ?? null,
483
- skipped: verdict?.text ?? null,
484
- eligibleAt: verdict?.eligibleAt ?? null
485
- });
486
- if (verdict && MERGE_DECIDES.has(verdict.code) && repo !== null) pending.push({
487
- candidate,
488
- facts,
489
- repo
490
- });
491
- worktrees.push(candidate);
492
- }
493
- checkMergeStates(pending, days, grace);
494
- const listed = [...worktrees, ...outside].filter((w) => idle || w.skipCode !== "not-a-worktree" && w.skipCode !== "source-checkout");
495
- return {
496
- idle: days === null ? null : {
497
- olderThan: days,
498
- defaulted: olderThan === null
499
- },
500
- graceMs: grace.ms,
501
- worktrees: listed
502
- };
503
- }
504
- async function lookUpPullRequests(inspected) {
505
- const repos = /* @__PURE__ */ new Map();
506
- for (const { candidate, facts, repo, branch } of inspected) {
507
- if (repo === null || branch === null || candidate.head === null) continue;
508
- const query = {
509
- cwd: candidate.path,
510
- branch,
511
- head: candidate.head
512
- };
513
- repos.set(repo, [...repos.get(repo) ?? [], {
514
- facts,
515
- query
516
- }]);
517
- }
518
- const lookup = pullRequestLookups();
519
- await Promise.all([...repos].map(async ([repo, entries]) => {
520
- const answers = await lookup(repo, entries.map(({ query }) => query));
521
- entries.forEach(({ facts }, i) => facts.pullRequest = answers[i] ?? null);
522
- }));
523
- }
524
- async function removeWorktrees(sweep, { now = Date.now() } = {}) {
525
- let failures = 0;
526
- for (const candidate of sweep.worktrees) {
527
- if (candidate.skipped) continue;
528
- let kept = [];
529
- const guard = (lockedKeys) => {
530
- const keys = [.../* @__PURE__ */ new Set([...candidate.keys, ...lockedKeys])];
531
- const unlocked = keys.filter((key) => !lockedKeys.includes(key));
532
- const reasons = [...inUseOf(lockedKeys, { managedLocks: false }), ...inUseOf(unlocked, { managedLocks: true })].map((r) => `in use: ${r}`);
533
- const mergedHead = candidate.merge?.merged ? candidate.merge.head : endedPullRequest(candidate.pullRequest) ? candidate.head : null;
534
- if (mergedHead) {
535
- if (resolveFullRef(candidate.path, "HEAD") !== mergedHead) reasons.push("its HEAD moved since gc checked it");
536
- } else {
537
- const idleDays = idleDaysOf(keys, now);
538
- 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`);
539
- }
540
- if (sweep.graceMs > 0) {
541
- const recent = graceBlocker({
542
- merge: candidate.merge,
543
- pullRequest: candidate.pullRequest,
544
- activity: worktreeActivityOf(candidate.path, keys)
545
- }, {
546
- ms: sweep.graceMs,
547
- now: Date.now()
548
- });
549
- if (recent) reasons.push(recent.text);
550
- }
551
- kept = reasons;
552
- return reasons;
553
- };
554
- let removed = false;
555
- let error = null;
556
- try {
557
- const ended = endedPullRequest(candidate.pullRequest);
558
- const mergedHead = candidate.merge?.merged && candidate.merge.coversUnpushed ? candidate.merge.head : ended?.state === "merged" ? candidate.head ?? void 0 : void 0;
559
- removed = await removeWorktreeTarget(candidate.path, {
560
- linkedOnly: true,
561
- guard,
562
- mergedHead
563
- });
564
- } catch (thrown) {
565
- error = thrown?.message || String(thrown);
566
- console.error(chalk.red(`Could not remove ${candidate.path}: ${error}`));
567
- }
568
- if (removed) {
569
- console.log(chalk.green(`Removed the worktree ${candidate.path} (${worktreeRemovalReason(candidate)})`));
570
- recordGcResult("worktree", "done", candidate.path, { detail: worktreeRemovalReason(candidate) });
571
- } else if (kept.length) {
572
- console.log(chalk.yellow(`Kept the worktree ${candidate.path}: ${kept.join("; ")}`));
573
- recordGcResult("worktree", "kept", candidate.path, { detail: kept.join("; ") });
574
- } else {
575
- failures++;
576
- console.error(chalk.yellow(`Kept the worktree ${candidate.path}; see the lines above for why.`));
577
- recordGcResult("worktree", "failed", candidate.path, { detail: error ?? "stim worktree remove did not remove it; see the details" });
578
- }
579
- }
580
- return failures;
581
- }
582
- //#endregion
583
- //#region src/maintenance/protect.ts
584
- const BUILD_RECORDS = ["lastBuild", ...Object.values(LAST_BUILD_KEYS)];
585
- function recordKeys(record) {
586
- if (record === null || typeof record !== "object") return [];
587
- const value = record;
588
- return [value.cacheKey, value.missReason?.baseline?.cacheKey].filter((key) => typeof key === "string");
589
- }
590
- function protectedCacheKeys(config = loadConfig$1()) {
591
- const keys = /* @__PURE__ */ new Set();
592
- for (const root of Object.keys(config?.projects ?? {})) {
593
- const state = readWorkspaceState$1(root);
594
- for (const name of BUILD_RECORDS) for (const key of recordKeys(state?.[name])) keys.add(key);
595
- for (const entries of Object.values(readBuildHistory(state))) {
596
- const key = entries.find((entry) => entry.result === "succeeded")?.cacheKey;
597
- if (key) keys.add(key);
598
- }
599
- }
600
- for (const platform of ["ios", "android"]) for (const record of readParked(platform, { config })) if (record.cacheKey) keys.add(record.cacheKey);
601
- for (const lock of listBuildLocks()) if ((lock.alive || lock.unresolved) && lock.key) keys.add(lock.key);
602
- return keys;
603
- }
604
- function liveMetroStores(config = loadConfig$1()) {
605
- return Object.keys(config?.projects ?? {}).filter((root) => workspaceInUse(root, {
606
- managedLocks: false,
607
- hosted: false,
608
- nativeRun: false
609
- }).length > 0).map((root) => canonicalPath(metroStoreRoot(root)));
610
- }
611
- function cacheEntryProtection(config = loadConfig$1()) {
612
- try {
613
- const keys = protectedCacheKeys(config);
614
- const stores = liveMetroStores(config);
615
- return (entry) => {
616
- if (keys.has(basename(entry))) return "a project, parked device or running build still uses this build";
617
- const path = canonicalPath(entry);
618
- if (stores.some((store) => path === store || path.startsWith(`${store}${sep}`))) return "its project has a running or unverifiable dev server";
619
- return null;
620
- };
621
- } catch (error) {
622
- const reason = `cache protection could not be read: ${error instanceof Error ? error.message : String(error)}`;
623
- return () => reason;
624
- }
625
- }
626
- function maintenancePinned(root) {
627
- try {
628
- return maintenanceKeepSetting(resolveProjectSettings(root).settings);
629
- } catch {
630
- return true;
631
- }
632
- }
633
- function recentUse(workspace, settings) {
634
- const since = Date.now() - workspaceLastUsed(workspace);
635
- return Number.isFinite(since) && since < settings.protectRecentHours * 36e5 ? `used within the last ${settings.protectRecentHours} hours` : null;
636
- }
637
- //#endregion
638
- //#region src/maintenance/plan.ts
639
- function plan({ pressure, sizes, settings, budget, diskSteps = [], protectedRoot, scoped = false, projects = [], pinned = () => false, recentlyUsed = () => null }) {
640
- const actions = [];
641
- const blocked = [];
642
- const skips = [];
643
- const add = (action) => {
644
- if (action.kind === "would-clear-outputs" && action.workspace && pinned(action.workspace)) {
645
- skips.push({
646
- target: action.workspace,
647
- reason: "pinned by maintenance.keep",
648
- workspace: action.workspace
649
- });
650
- return false;
651
- }
652
- const recent = action.kind === "would-clear-outputs" && action.workspace ? recentlyUsed(action.workspace) : null;
653
- if (recent) {
654
- skips.push({
655
- target: action.workspace,
656
- reason: recent,
657
- workspace: action.workspace
658
- });
659
- return false;
660
- }
661
- if (actions.some((entry) => entry.kind === action.kind && entry.target === action.target)) return false;
662
- actions.push(action);
663
- return true;
664
- };
665
- const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
666
- const emergency = pressure?.disk.some((disk) => disk.freeMb < budget.hardFloorDiskMb) ?? false;
667
- const low = pressure?.disk.filter((disk) => disk.freeMb < floor) ?? [];
668
- const reason = low.map((disk) => `free disk ${(disk.freeMb / 1024).toFixed(1)}G on ${disk.volume} under the ${(floor / 1024).toFixed(1)}G floor`).join("; ");
669
- const kinds = {
670
- "idle-devices": "would-shutdown-device",
671
- "idle-dev-servers": "would-stop-workspace",
672
- "workspace-outputs": "would-clear-outputs",
673
- "stale-cache-entries": "would-trim-cache"
674
- };
675
- if (low.length) {
676
- for (const step of diskSteps) {
677
- if (scoped && step.step === "idle-devices") continue;
678
- for (const target of step.targets) {
679
- const workspace = projects.find((root) => root === target || target.endsWith(` in ${root}`)) ?? (step.step === "idle-dev-servers" || step.step === "workspace-outputs" ? target : void 0);
680
- if (workspace === protectedRoot || target === protectedRoot) continue;
681
- const size = workspace && step.step !== "idle-devices" ? sizes.find((entry) => entry.workspace === workspace && entry.category === "workspace-outputs") : void 0;
682
- add({
683
- kind: kinds[step.step],
684
- target,
685
- bytes: step.step === "workspace-outputs" ? size?.bytes ?? 0 : 0,
686
- reason: `${size?.idleDays == null ? "" : `idle ${size.idleDays} days, `}${reason}`,
687
- ...workspace ? { workspace } : {},
688
- check: "disk",
689
- ...step.step === "stale-cache-entries" ? { olderThanDays: 14 } : {}
690
- });
691
- }
692
- if (step.failures) blocked.push(`Could not plan ${step.step}: ${step.failures} failures`);
693
- }
694
- if (actions.length === 0) blocked.push(`${reason}; no reclaimable disk targets`);
695
- }
696
- const caps = [
697
- [
698
- "workspace-outputs",
699
- settings.workspaceOutputsMaxGb,
700
- "would-clear-outputs"
701
- ],
702
- [
703
- "build-cache",
704
- settings.buildCacheMaxGb,
705
- "would-trim-cache"
706
- ],
707
- [
708
- "metro-cache",
709
- settings.metroCacheMaxGb,
710
- "would-trim-cache"
711
- ],
712
- [
713
- "compilation-cache",
714
- settings.swiftCompilationCacheMaxGb,
715
- "would-empty-cache"
716
- ]
717
- ];
718
- for (const [category, capGb, kind] of caps) {
719
- const entries = sizes.filter((size) => size.category === category);
720
- const total = entries.reduce((sum, size) => sum + size.bytes, 0);
721
- const cap = capGb * 1024 ** 3;
722
- if (cap === 0 || total <= cap) continue;
723
- let remaining = total;
724
- const targetBytes = cap * settings.capTargetPercent / 100;
725
- const why = `${category} ${(total / 1024 ** 3).toFixed(1)}G over the ${capGb}G cap; target ${(targetBytes / 1024 ** 3).toFixed(1)}G`;
726
- if (category === "compilation-cache" && !emergency) {
727
- skips.push({
728
- target: entries[0]?.dir ?? category,
729
- reason: `${why}; it can only be emptied whole, which runs only below the ${(budget.hardFloorDiskMb / 1024).toFixed(1)}G hard floor`
730
- });
731
- continue;
732
- }
733
- for (const size of entries.toSorted((a, b) => (b.idleDays ?? 0) - (a.idleDays ?? 0))) {
734
- if (remaining <= targetBytes) break;
735
- const kept = size.workspace === protectedRoot ? "the triggering workspace is protected" : size.blocked;
736
- if (kept) {
737
- skips.push({
738
- target: size.dir,
739
- reason: kept,
740
- ...size.workspace ? { workspace: size.workspace } : {}
741
- });
742
- continue;
743
- }
744
- const bytes = kind === "would-trim-cache" ? Math.round(Math.min(size.bytes, remaining - targetBytes)) : size.bytes;
745
- if (!add({
746
- kind,
747
- target: size.workspace ?? size.dir,
748
- bytes,
749
- reason: `${!size.workspace || size.idleDays == null ? "" : `idle ${size.idleDays} days, `}${why}`,
750
- ...size.workspace ? { workspace: size.workspace } : {},
751
- ...kind === "would-clear-outputs" ? {} : { dir: size.dir }
752
- })) continue;
753
- remaining -= bytes;
754
- }
755
- if (remaining > targetBytes) blocked.push(`${why}; eligible targets cannot reach the target`);
756
- }
757
- if (pressure?.memory.pressured) skips.push({
758
- target: "memory",
759
- reason: "memory pressure observed; automatic maintenance does not stop helpers or idle workspaces yet"
760
- });
761
- return {
762
- actions,
763
- blocked,
764
- skips
765
- };
766
- }
767
- //#endregion
768
- //#region src/maintenance/sweep.ts
769
- function planSweep({ settings, sizes, protectedRoot, pinned, now }) {
770
- const actions = [];
771
- const skips = [];
772
- const blocked = [];
773
- const days = settings.olderThanDays;
774
- for (const entry of collectWorkspaceOutputs({
775
- olderThan: days,
776
- now,
777
- measure: false
778
- }).workspaces) {
779
- const root = entry.projectRoot === null ? null : canonicalPath(entry.projectRoot);
780
- if (root === null || !entry.willClear || root === protectedRoot) continue;
781
- if (pinned(root)) {
782
- skips.push({
783
- target: root,
784
- reason: "pinned by maintenance.keep",
785
- workspace: root
786
- });
787
- continue;
788
- }
789
- const size = sizes.find((candidate) => candidate.workspace === root && candidate.category === "workspace-outputs");
790
- actions.push({
791
- kind: "would-clear-outputs",
792
- target: root,
793
- workspace: root,
794
- bytes: size?.bytes ?? 0,
795
- olderThanDays: days,
796
- reason: `idle ${entry.idleDays} days, older than ${days} days (age sweep)`,
797
- check: "sweep"
798
- });
799
- }
800
- for (const cache of trimmableCaches().filter((candidate) => !isInsideWorkspaces(candidate.dir))) actions.push({
801
- kind: "would-trim-cache",
802
- target: cacheLabel(cache),
803
- dir: cache.dir,
804
- bytes: 0,
805
- olderThanDays: days,
806
- reason: `entries unused for ${days} days (age sweep)`,
807
- check: "sweep"
808
- });
809
- try {
810
- const orphans = collectOrphanedWorkspaces(Object.keys(loadConfig$1()?.projects ?? {}), listMountedVolumes(), { measure: false });
811
- for (const orphan of orphans.orphaned) actions.push({
812
- kind: "would-remove-orphan",
813
- target: orphan.dir,
814
- bytes: orphan.bytes ?? 0,
815
- reason: `its project ${orphan.projectRoot} no longer exists`,
816
- check: "sweep"
817
- });
818
- for (const kept of orphans.skipped) skips.push({
819
- target: kept.dir,
820
- reason: kept.reason
821
- });
822
- } catch (error) {
823
- blocked.push(`Could not look for orphaned workspace directories: ${error instanceof Error ? error.message : error}`);
824
- }
825
- const mounted = listMountedVolumes();
826
- for (const cache of readManifest().caches) {
827
- if (!isAbsolute(cache.dir) || existsSync(cache.dir) || !isOnMountedVolume(cache.dir, mounted)) continue;
828
- actions.push({
829
- kind: "would-unregister-cache",
830
- target: cache.dir,
831
- dir: cache.dir,
832
- bytes: 0,
833
- reason: "its directory no longer exists",
834
- check: "sweep"
835
- });
836
- }
837
- return {
838
- actions,
839
- blocked,
840
- skips
841
- };
842
- }
843
- async function planWorktrees({ protectedRoot, pinned, now }) {
844
- const sweep = await collectWorktreeSweep({
845
- idle: false,
846
- olderThan: null,
847
- now
848
- });
849
- const actions = [];
850
- const skips = [];
851
- const keep = [];
852
- for (const worktree of sweep.worktrees) {
853
- if (worktree.skipped) continue;
854
- const path = canonicalPath(worktree.path);
855
- const reason = protectedRoot === path || protectedRoot.startsWith(`${path}${sep}`) ? "the command that started this pass runs inside it" : worktree.keys.some(pinned) ? "pinned by maintenance.keep" : null;
856
- if (reason) {
857
- skips.push({
858
- target: worktree.path,
859
- reason
860
- });
861
- continue;
862
- }
863
- keep.push(worktree);
864
- actions.push({
865
- kind: "would-remove-worktree",
866
- target: worktree.path,
867
- bytes: 0,
868
- reason: worktreeRemovalReason(worktree),
869
- check: "worktree"
870
- });
871
- }
872
- return {
873
- actions,
874
- blocked: [],
875
- skips,
876
- sweep: {
877
- ...sweep,
878
- worktrees: keep
879
- }
880
- };
881
- }
882
- //#endregion
883
- //#region src/maintenance/preview.ts
884
- function triggeringRoot() {
885
- return canonicalPath(findProjectRoot(process.cwd()) ?? join(configDir(), "maintenance", "no-project"));
886
- }
887
- async function plannedMaintenance(pressure, sizes, settings, { devices = true, sweep = false, worktrees = false } = {}) {
888
- const resolved = resolveBudget();
889
- if (resolved.error) throw new Error(resolved.error);
890
- const budget = {
891
- ...resolved.budget,
892
- maxCommittedMemoryMb: 0,
893
- maxLiveWorkspaces: 0
894
- };
895
- const root = triggeringRoot();
896
- const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
897
- const low = pressure?.disk.some((disk) => disk.freeMb < floor);
898
- const outcome = low ? await enforceBudget({
899
- root,
900
- note: () => {},
901
- dryRun: true,
902
- budget
903
- }, { volumes: () => pressure.disk }) : null;
904
- if (outcome && outcome.status !== "ok") throw new Error(outcome.refusal.message);
905
- const outputs = collectWorkspaceOutputs({
906
- olderThan: null,
907
- now: Date.now(),
908
- measure: false
909
- });
910
- const currentSizes = sizes.map((size) => {
911
- if (size.category === "compilation-cache") return {
912
- ...size,
913
- blocked: cacheBlocked(size)
914
- };
915
- if (!size.workspace) return size;
916
- const current = outputs.workspaces.find((entry) => entry.projectRoot && canonicalPath(entry.projectRoot) === size.workspace);
917
- return Object.assign({}, size, { blocked: current?.willClear ? void 0 : current?.keptReason ?? "workspace outputs cannot be resolved" });
918
- });
919
- const pins = /* @__PURE__ */ new Map();
920
- const pinned = (workspace) => {
921
- let value = pins.get(workspace);
922
- if (value === void 0) pins.set(workspace, value = maintenancePinned(workspace));
923
- return value;
924
- };
925
- const result = plan({
926
- pressure,
927
- sizes: currentSizes,
928
- settings,
929
- budget,
930
- diskSteps: outcome?.status === "ok" ? outcome.reclaimed.filter((step) => devices || step.step !== "idle-devices") : [],
931
- protectedRoot: root,
932
- scoped: Boolean(process.env.STIM_HOME),
933
- projects: Object.keys(loadConfig()?.projects ?? {}).map(canonicalPath),
934
- pinned,
935
- recentlyUsed: (workspace) => recentUse(workspace, settings)
936
- });
937
- const merge = (extra) => {
938
- for (const action of extra.actions) if (!result.actions.some((entry) => entry.kind === action.kind && entry.target === action.target)) result.actions.push(action);
939
- result.blocked.push(...extra.blocked);
940
- result.skips.push(...extra.skips);
941
- };
942
- if (sweep) merge(planSweep({
943
- settings,
944
- sizes,
945
- protectedRoot: root,
946
- pinned,
947
- now: Date.now()
948
- }));
949
- if (worktrees) {
950
- const planned = await planWorktrees({
951
- protectedRoot: root,
952
- pinned,
953
- now: Date.now()
954
- });
955
- merge(planned);
956
- result.worktreeSweep = planned.sweep;
957
- }
958
- if (low) result.skips.push(...outputs.workspaces.filter((entry) => !entry.willClear && entry.keptReason).map((entry) => ({
959
- target: entry.dir,
960
- reason: entry.keptReason,
961
- workspace: entry.projectRoot ?? void 0
962
- })));
963
- return result;
964
- }
965
- async function previewMaintenance({ devices = true } = {}) {
966
- let settings;
967
- try {
968
- settings = resolveMaintenanceSettings();
969
- const state = readMaintenanceState();
970
- if (settings.invalid && settings.mode === "off") return {
971
- mode: "off",
972
- pressure: null,
973
- actions: [],
974
- blocked: [],
975
- skips: [],
976
- invalid: settings.invalid,
977
- note: settings.invalid
978
- };
979
- if (settings.mode === "off") return {
980
- mode: "off",
981
- pressure: null,
982
- actions: [],
983
- blocked: [],
984
- skips: [],
985
- note: "maintenance.mode is off"
986
- };
987
- const pressure = measurePressure(settings, state?.pressure ?? null, Date.now());
988
- return {
989
- ...await plannedMaintenance(pressure, state?.sizes ?? [], settings, {
990
- devices,
991
- sweep: true
992
- }),
993
- mode: settings.mode,
994
- ...settings.invalid ? { invalid: settings.invalid } : {},
995
- pressure,
996
- note: settings.invalid ?? (state === null ? "no pass has run yet; sizes have not been measured" : null)
997
- };
998
- } catch (error) {
999
- return {
1000
- mode: settings?.mode ?? "off",
1001
- ...settings?.invalid ? { invalid: settings.invalid } : {},
1002
- pressure: null,
1003
- actions: [],
1004
- blocked: [String(error)],
1005
- skips: [],
1006
- note: "maintenance preview failed"
1007
- };
1008
- }
1009
- }
1010
- //#endregion
1011
- export { maintenancePinned as a, removeWorktrees as c, cacheBlocked as d, measureDisk as f, sizeScanDeferred as h, cacheEntryProtection as i, worktreePullRequestNote as l, measureSizes as m, previewMaintenance as n, recentUse as o, measurePressure as p, triggeringRoot as r, collectWorktreeSweep as s, plannedMaintenance as t, worktreeRemovalReason as u };