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.
Files changed (182) hide show
  1. package/README.md +1 -1
  2. package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  3. package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
  4. package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
  5. package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  6. package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
  7. package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
  8. package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
  9. package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
  10. package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
  13. package/dist/api.d.mts +20 -7
  14. package/dist/api.mjs +42 -32
  15. package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
  16. package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
  17. package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
  18. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  19. package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
  20. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  21. package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
  22. package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
  23. package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
  24. package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  25. package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  26. package/dist/cache-manifest.mjs +1 -1
  27. package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
  28. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  29. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  30. package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
  31. package/dist/cli.mjs +1 -1
  32. package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
  33. package/dist/collector-run.d.mts +10 -9
  34. package/dist/collector-run.mjs +42 -27
  35. package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  36. package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
  37. package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
  38. package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  39. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  40. package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
  41. package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
  42. package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
  43. package/dist/device-host-worker.mjs +19 -21
  44. package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
  45. package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  46. package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  47. package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
  48. package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  49. package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
  50. package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
  51. package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
  52. package/dist/doctor-fingerprint.mjs +1 -1
  53. package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  54. package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  55. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  56. package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  57. package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  58. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  59. package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
  60. package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
  61. package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
  62. package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  63. package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
  64. package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
  65. package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
  66. package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  67. package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
  68. package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
  69. package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
  70. package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
  71. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  72. package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
  73. package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  74. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  75. package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  76. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  77. package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  78. package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
  79. package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
  80. package/dist/lightened-BCsdAGj_.mjs +171 -0
  81. package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
  82. package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
  83. package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
  84. package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +30 -13
  87. package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
  88. package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
  89. package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  90. package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
  91. package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  92. package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
  93. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  94. package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  95. package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
  96. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  97. package/dist/offload-worker.d.mts +3 -1
  98. package/dist/offload-worker.mjs +34 -25
  99. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  100. package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
  101. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  102. package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  103. package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  104. package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  105. package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
  106. package/dist/prebuild-BDpToDgR.mjs +1246 -0
  107. package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
  108. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  109. package/dist/pull-requests.mjs +1 -1
  110. package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
  111. package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  112. package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
  113. package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
  114. package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
  115. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  116. package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
  117. package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
  118. package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
  119. package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
  120. package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
  121. package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
  122. package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  123. package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
  124. package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  125. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  126. package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
  127. package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
  128. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  129. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  130. package/dist/settings.schema.json +84 -39
  131. package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
  132. package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  133. package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
  134. package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
  135. package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
  136. package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
  137. package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
  138. package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
  139. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  140. package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
  141. package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
  142. package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
  143. package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
  144. package/dist/status-worker.mjs +1 -1
  145. package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  146. package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
  147. package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
  148. package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
  149. package/dist/supervisor-run.d.mts +1 -0
  150. package/dist/supervisor-run.mjs +17 -18
  151. package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
  152. package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
  153. package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  154. package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  155. package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
  156. package/dist/toolchain-JYAOquPg.mjs +2 -0
  157. package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
  158. package/dist/trigger-qp2ji5wE.mjs +2 -0
  159. package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
  160. package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
  161. package/dist/web-Je6abcpz.mjs +2 -0
  162. package/dist/web-run.mjs +9 -9
  163. package/dist/windows-report.mjs +1 -1
  164. package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
  165. package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
  166. package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
  167. package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
  168. package/package.json +8 -8
  169. package/dist/ccache-h1FKhZeg.mjs +0 -143
  170. package/dist/created-devices-D2Aca-xu.mjs +0 -52
  171. package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
  172. package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
  173. package/dist/native-run-D5OJsGwA.mjs +0 -276
  174. package/dist/paths-DQtTgsBf.mjs +0 -42
  175. package/dist/prebuild-JUaqOLpc.mjs +0 -501
  176. package/dist/server-expo-C16Zf2k7.mjs +0 -2
  177. package/dist/settings-write-n0zwfohx.mjs +0 -360
  178. package/dist/toolchain-DB1aeqxO.mjs +0 -2
  179. package/dist/toolchain-VzLJzZ7O.mjs +0 -908
  180. package/dist/trigger-BLsHgAGR.mjs +0 -2
  181. package/dist/web-DS4UDWvf.mjs +0 -2
  182. package/dist/xcode-CiIIDnuD.mjs +0 -910
@@ -1,39 +1,77 @@
1
- import { D as ownership_claim_exports, E as debugLog, S as process_identity_exports, b as operationState, c as throwIfCancelled, d as workspaceProcessLockError, l as WorkspaceProcessCleanupError, n as isTimeoutError, o as cancellableSleep, s as cancelledError, t as getExecutor, u as withWorkspaceProcessLock, y as isTermination } from "./exec-VFRpu1iA.mjs";
2
- import { d as workspaceDir, f as workspaceId$1, g as workspaceStateFile$1, i as sharedBuildCache, m as workspaceLogsDir$1 } from "./paths-DQtTgsBf.mjs";
3
- import { D as withConfigLock$1, F as isOnMountedVolume, G as projectDeviceSlots, H as deviceSlotPlatforms, L as measuredDirectorySizeAsync, M as directorySize, N as directorySizeAsync, O as withConfigLockAsync$1, P as formatBytes, R as volumeRootFor, _ as loadConfig$1, a as clearDevice, d as getConfigDir, o as clearSupervisor, p as getProject } from "./config-BZhXY15H.mjs";
1
+ import { D as ownership_claim_exports, E as debugLog, S as process_identity_exports, b as operationState, c as throwIfCancelled, d as workspaceProcessLockError, l as WorkspaceProcessCleanupError, n as isTimeoutError, o as cancellableSleep, s as cancelledError, t as getExecutor, u as withWorkspaceProcessLock, y as isTermination } from "./exec-aULwtNly.mjs";
2
+ import { D as workspaceId$1, E as workspaceDir, M as withDirLock$1, b as sharedCcache$1, d as writeWorkspaceState, j as workspaceStateFile$1, k as workspaceLogsDir$1, l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, t as clearWorkspaceStateKey, y as sharedBuildCache } from "./workspace-state-BjqdjnJR.mjs";
3
+ import { F as isOnMountedVolume, G as projectDeviceSlots, H as deviceSlotPlatforms, L as measuredDirectorySizeAsync, M as directorySize, N as directorySizeAsync, O as withConfigLockAsync$1, P as formatBytes, R as volumeRootFor, _ as loadConfig$1, a as clearDevice, d as getConfigDir, o as clearSupervisor, p as getProject } from "./config-Cn94Jc0g.mjs";
4
4
  import { n as stripAnsi } from "./process-output-Fpl8PYi8.mjs";
5
- import { a as formatElapsed, l as phaseLine, o as formatLongDuration, s as isAppLaunchError, u as plural } from "./command-output-BBvS1H6v.mjs";
6
- import { a as killOnAbort, n as runOperationFileQuiet, r as childEnvironment, t as runOperationFile } from "./operation-exec-CUZifp6Z.mjs";
7
- import { d as writeWorkspaceState, l as withWorkspaceStateLockAsync, o as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-CPSp3mav.mjs";
8
- import { Y as resolveProjectSettingsAsync, d as androidLayoutSetting, ut as planMetroReach } from "./settings-B5tCoq9N.mjs";
9
- import { C as resolvePackageJson, M as readMetroTunnel, N as readPidFile, P as readRemoteSession, i as findProjectRoot$1, j as clearWorkspaceSupervisor, k as clearRemoteSession, s as ownedDeviceLabel, v as detectIsExpo } from "./project-BiC68mIp.mjs";
10
- import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, I as listInstalledSystemImages, J as parseAvdSystemImage, V as ownedAvdDirectory, W as ownedAvdSerialResolver, d as androidToolPath, dt as isStimOwnedSim, m as avdIniPaths, mt as dropParked, o as androidHome, q as parseAvdDeviceProfile, ut as isStimOwnedAvd, v as avdStorageRoots, yt as readParked } from "./android-Cjcj8MCw.mjs";
11
- import { a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, h as isMetroRunning, r as killMetroTreeAsync, u as processCwds } from "./metro-1pJgCX4U.mjs";
12
- import { r as createNdjsonWriter, s as readNdjsonGenerations$1 } from "./ndjson-CgE_o3Dl.mjs";
13
- import { C as environmentState, a as removeEasSessionClaim, d as parseWatchRoots, f as parseWatchmanClients, i as recordEasSessionClaim, l as parseSubscriberNames, n as easMachineStateRoot$1, o as removeEasSessionClaimAsync, p as parseWatchmanPid, r as readEasSessionLedger$1, s as compareStimVersions, t as assertEasMachineRootWritable, u as parseTriggerCount, w as formatSpace, y as capacity } from "./eas-session-ledger-BMCA1gdO.mjs";
14
- import { i as registeredCaches } from "./cache-manifest-BPXRONJz.mjs";
5
+ import { a as formatElapsed, l as phaseLine, o as formatLongDuration, s as isAppLaunchError, u as plural } from "./command-output-Dj-WT4BP.mjs";
6
+ import { a as killOnAbort, n as runOperationFileQuiet, r as childEnvironment, t as runOperationFile } from "./operation-exec-Dp4FuIGR.mjs";
7
+ import { Z as resolveProjectSettingsAsync, d as androidLayoutSetting, ft as planMetroReach, m as defaultAndroidLayout } from "./settings-BDVI8Ivc.mjs";
8
+ import { C as resolvePackageJson, M as readMetroTunnel, N as readPidFile, P as readRemoteSession, i as findProjectRoot$1, j as clearWorkspaceSupervisor, k as clearRemoteSession, s as ownedDeviceLabel, v as detectIsExpo } from "./project-DYj-HVx6.mjs";
9
+ import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, I as listInstalledSystemImages, J as parseAvdSystemImage, V as ownedAvdDirectory, d as androidToolPath, dt as isStimOwnedSim, m as avdIniPaths, mt as dropParked, o as androidHome, q as parseAvdDeviceProfile, ut as isStimOwnedAvd, v as avdStorageRoots, yt as readParked } from "./android-DriNoAVy.mjs";
10
+ import { a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, h as isMetroRunning, r as killMetroTreeAsync, u as processCwds } from "./metro-CvyD-xwe.mjs";
11
+ import { c as readNdjsonGenerations$1, i as createNdjsonWriter, r as createNdjsonTail } from "./ndjson-Dy2W4vRa.mjs";
12
+ import { C as environmentState, a as removeEasSessionClaim, d as parseWatchRoots, f as parseWatchmanClients, i as recordEasSessionClaim, l as parseSubscriberNames, n as easMachineStateRoot$1, o as removeEasSessionClaimAsync, p as parseWatchmanPid, r as readEasSessionLedger$1, s as compareStimVersions, t as assertEasMachineRootWritable, u as parseTriggerCount, w as formatSpace, y as capacity } from "./eas-session-ledger-DrP4OuuM.mjs";
13
+ import { i as registeredCaches, r as register } from "./cache-manifest-DEjqGGat.mjs";
14
+ import { A as collectIdleDevicesAsync, Ct as createGcResults, Et as mapLimited, I as shutDownIdleDevices, L as workspaceBuildInProgress, M as collectOwnedDeviceActivityAsync, Ot as sizeConcurrency, P as idleShutdownCandidates, St as GC_OUTPUT, _ as withIdleWorkspace, _t as measureDu, b as resolveSupervisorTarget, bt as listBuildSlots, ct as eraseParkedIosSim, ft as teardownOwnedIosSim, gt as canonical$1, ht as probePublicHttp, i as clearWorkspaceOutputs, mt as teardownParkedIosSim, o as collectWorkspaceOutputs, pt as teardownParkedAvd, st as eraseParkedAvd, ut as teardownOwnedAvd, vt as writeUsageCache } from "./workspaces-DFbj07jp.mjs";
15
15
  import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
16
- import { c as nativeRunWaitNotice, d as runCancellationSignal, m as claimForEmptying } from "./native-run-D5OJsGwA.mjs";
17
- import { A as teardownOwnedIosSim, B as GC_OUTPUT, E as eraseParkedIosSim, F as measureDu, I as writeUsageCache, M as teardownParkedIosSim, N as probePublicHttp, O as teardownOwnedAvd, P as canonical, R as listBuildSlots, T as eraseParkedAvd, a as collectWorkspaceOutputs, g as resolveSupervisorTarget, j as teardownParkedAvd, p as withIdleWorkspace, r as clearWorkspaceOutputs } from "./workspaces-C9w4aeXF.mjs";
18
- import { N as listBuildLocks, b as leaseIsExpired, g as workspaceActivity, i as createActivityReaderAsync, o as envDir, p as readHostProcessTableAsync, r as createActivityReader, s as idleForMs, x as listLeaseFiles } from "./activity-BpjwrQXU.mjs";
19
- import { c as expoConfigContents } from "./build-cache-CKCzxzxA.mjs";
20
- import { D as sanitizeDeviceLabel, H as deviceHoldsApk, U as deviceHoldsBundle, b as parseSimctlList, f as listAllIosSimsAsync, g as listUserApps, k as uninstallIosApp, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion } from "./ios-BQzvJlzw.mjs";
16
+ import { lt as claimForEmptying, ot as runCancellationSignal, rt as nativeRunWaitNotice } from "./toolchain-CbCG6C5Z.mjs";
17
+ import { N as listBuildLocks, b as leaseIsExpired, g as workspaceActivity, o as envDir, p as readHostProcessTableAsync, x as listLeaseFiles } from "./activity-Cd-ysFes.mjs";
18
+ import { l as expoConfigContents } from "./build-cache-gBJ-WKzA.mjs";
19
+ import { G as deviceHoldsBundle, O as sanitizeDeviceLabel, W as deviceHoldsApk, b as parseSimctlList, f as listAllIosSimsAsync, g as listUserApps, j as uninstallIosApp, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion } from "./ios-E26qSpkb.mjs";
21
20
  import { n as supervisorError } from "./errors-DHm_U5gv.mjs";
22
- import { c as findBinUpward, n as expoBinFromPackage, r as expoBinPath, s as expoTelemetryEnv } from "./server-expo-Ck8EIidM.mjs";
23
- import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-B6HK951C.mjs";
21
+ import { c as findBinUpward, n as expoBinFromPackage, r as expoBinPath, s as expoTelemetryEnv } from "./server-expo-ABQs2XFA.mjs";
22
+ import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-CiBA-er9.mjs";
23
+ import { t as resolveMaintenanceSettings } from "./settings-Djl-mpjX.mjs";
24
24
  import { createRequire } from "node:module";
25
- import { randomUUID } from "node:crypto";
26
- import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, writeFileSync } from "node:fs";
25
+ import { createHash, randomUUID } from "node:crypto";
26
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, unlinkSync, writeFileSync } from "node:fs";
27
27
  import { homedir, platform, totalmem } from "node:os";
28
28
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
29
- import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, ambientContext, cacheDirRefusal, compiledHelper, configDir, withAmbientContext } from "@stim-cli/core";
30
- import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, DEV_SERVER_STOP_REQUEST_KEY, LAST_BUILD_KEYS, MACHINE_OWNER_KINDS, STATS_VERSION, buildWorkerRoot, decodeStats, isJsonObject, loadConfig, machineCacheUsageFile, parseMissReason, readDevServerStop, readDevServerStopRequest, readJsonObject, readMachineCacheUsage, readRunEstimates, readStats as readStats$1, runId, settingDefinition, settingValueError, statsFile, statsProjectKey as statsProjectKey$1, trimCapacityRefusals, trimCapacityWaits, trimSamples, wholePhases, workspaceBuildDetailFile } from "@stim-cli/core/state";
29
+ import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, ambientContext, cacheDirRefusal, compiledHelper, configDir, ensurePrivateDir, rotatedLogPath, withAmbientContext, withDirLock } from "@stim-cli/core";
30
+ import { DEV_SERVER_STOP_REQUEST_KEY, MACHINE_OWNER_KINDS, MAINTENANCE_LOG_NAME, budgetSweepFile, budgetSweepLock, buildWorkerRoot, gcLogFile, isJsonObject, loadConfig, machineCacheUsageFile, maintenanceNdjsonFile, readDevServerStop, readDevServerStopRequest, readJsonObject, readMachineCacheUsage, runId, settingDefinition, settingValueError, workspaceLogsDir } from "@stim-cli/core/state";
31
31
  import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
32
32
  import { fileURLToPath } from "node:url";
33
33
  import chalk from "chalk";
34
34
  import { existsSync as existsSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
35
35
  import { access, lstat, readFile, readdir, rm, stat, statfs } from "node:fs/promises";
36
36
  import { homedir as homedir$1, tmpdir as tmpdir$1 } from "os";
37
+ //#region src/budget-sweep.ts
38
+ const EMPTY_SWEEP_WINDOW_MS = 3e5;
39
+ function sweepMarker(caches, olderThanDays) {
40
+ const hash = createHash("sha256").update(String(olderThanDays));
41
+ for (const line of caches.map((cache) => `${cache.name}\0${cache.dir}`).toSorted()) hash.update("\n").update(line);
42
+ return hash.digest("hex");
43
+ }
44
+ function canSkipEmptySweep(recorded, now) {
45
+ if (!recorded || recorded.marker !== now.marker) return false;
46
+ const age = now.at - recorded.at;
47
+ return age >= 0 && age < 3e5 && recorded.freeMb - now.freeMb <= 1024;
48
+ }
49
+ function readEmptySweep() {
50
+ const value = readJsonObject(budgetSweepFile());
51
+ if (!value) return null;
52
+ const { at, freeMb, marker } = value;
53
+ return typeof at === "number" && Number.isFinite(at) && typeof freeMb === "number" && Number.isFinite(freeMb) && typeof marker === "string" ? {
54
+ at,
55
+ freeMb,
56
+ marker
57
+ } : null;
58
+ }
59
+ function writeEmptySweep(sweep) {
60
+ const file = budgetSweepFile();
61
+ try {
62
+ mkdirSync(dirname(file), {
63
+ recursive: true,
64
+ mode: 448
65
+ });
66
+ withDirLock$1(budgetSweepLock(), () => {
67
+ if (!sweep) return rmSync(file, { force: true });
68
+ const tmp = `${file}.tmp-${process.pid}`;
69
+ writeFileSync(tmp, JSON.stringify(sweep), { mode: 384 });
70
+ renameSync(tmp, file);
71
+ });
72
+ } catch {}
73
+ }
74
+ //#endregion
37
75
  //#region src/cache/caches.ts
38
76
  function xcodeDerivedDataRoot(env) {
39
77
  return join$1(env.HOME || env.USERPROFILE || homedir$1(), "Library", "Developer", "Xcode", "DerivedData");
@@ -204,17 +242,15 @@ function canonicalCacheDir(dir) {
204
242
  }
205
243
  const CACHE_SIZE_TIMEOUT_MS = 6e4;
206
244
  async function sizeCaches(caches, operation) {
207
- const sized = [];
208
- for (const cache of caches) {
245
+ return mapLimited(caches, sizeConcurrency(operation), async (cache) => {
209
246
  operation?.signal.throwIfAborted();
210
247
  const bytes = cache.bytes ?? await measuredDirectorySizeAsync(cache.dir, {
211
248
  timeoutMs: CACHE_SIZE_TIMEOUT_MS,
212
249
  operation,
213
250
  signal: operation?.signal
214
251
  });
215
- sized.push(Object.assign({}, cache, bytes === null ? { sizeError: "Size unavailable: measurement failed or exceeded 60 seconds." } : { bytes }));
216
- }
217
- return sized;
252
+ return Object.assign({}, cache, bytes === null ? { sizeError: "Size unavailable: measurement failed or exceeded 60 seconds." } : { bytes });
253
+ });
218
254
  }
219
255
  function pruneCache(cache, { olderThanDays, now = Date.now(), protect, evictBytes, byMtime = false } = {}) {
220
256
  const cutoff = now - olderThanDays * 24 * 60 * 60 * 1e3;
@@ -296,18 +332,22 @@ async function pruneCacheAsync(cache, { olderThanDays, now = Date.now(), signal,
296
332
  };
297
333
  const cutoff = now - olderThanDays * 24 * 60 * 60 * 1e3;
298
334
  let entries = cache.files ?? [cache.dir];
335
+ let unlisted = 0;
299
336
  for (let depth = 0; !cache.files && depth < (cache.entriesDepth ?? 1); depth++) {
300
337
  const next = [];
301
338
  for (const parent of entries) {
302
339
  signal?.throwIfAborted();
303
- const names = await readdir(parent).catch(() => []);
340
+ const names = await readdir(parent).catch((error) => {
341
+ if (error.code !== "ENOENT" && error.code !== "ENOTDIR") unlisted++;
342
+ return [];
343
+ });
304
344
  next.push(...names.map((name) => join$1(parent, name)));
305
345
  }
306
346
  entries = next;
307
347
  }
308
348
  let removed = 0;
309
349
  let bytes = 0;
310
- let failed = 0;
350
+ let failed = unlisted;
311
351
  for (const entry of entries) {
312
352
  signal?.throwIfAborted();
313
353
  try {
@@ -2983,9 +3023,32 @@ async function launchedIosAppAfterNoHandle(error, udid, bundleId, exec) {
2983
3023
  }
2984
3024
  async function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null, devMenuParams = false, consolePaths }, { exec = null } = {}) {
2985
3025
  const e = exec || getExecutor();
2986
- const running = await iosAppProcess(udid, bundleId, { exec: e });
2987
- const stateUnknown = running.state === "unknown";
2988
3026
  const launchRestarts = metroPort === null || !devClientScheme || Boolean(consolePaths);
3027
+ const writeJsLocation = async (port) => {
3028
+ try {
3029
+ await e.runFileAsync("xcrun", [
3030
+ "simctl",
3031
+ "spawn",
3032
+ udid,
3033
+ "defaults",
3034
+ "write",
3035
+ bundleId,
3036
+ "RCT_jsLocation",
3037
+ jsLocationValue(port)
3038
+ ], { timeoutMs: 6e4 });
3039
+ return null;
3040
+ } catch (err) {
3041
+ return {
3042
+ failed: true,
3043
+ code: LAUNCH_ERROR,
3044
+ reason: `Could not point ${bundleId} at Metro port ${port} (defaults write RCT_jsLocation): ${await describeIosSimulatorFailure(err, e)}`
3045
+ };
3046
+ }
3047
+ };
3048
+ const runningProbe = iosAppProcess(udid, bundleId, { exec: e });
3049
+ const earlyWrite = metroPort !== null && launchRestarts ? writeJsLocation(metroPort) : null;
3050
+ const running = await runningProbe;
3051
+ const stateUnknown = running.state === "unknown";
2989
3052
  const launchArgs = [
2990
3053
  "simctl",
2991
3054
  "launch",
@@ -3014,24 +3077,8 @@ async function launchIosApp({ udid, bundleId, metroPort, devClientScheme = null,
3014
3077
  restart.restartedPid = running.pid;
3015
3078
  }
3016
3079
  if (metroPort !== null) {
3017
- try {
3018
- await e.runFileAsync("xcrun", [
3019
- "simctl",
3020
- "spawn",
3021
- udid,
3022
- "defaults",
3023
- "write",
3024
- bundleId,
3025
- "RCT_jsLocation",
3026
- jsLocationValue(metroPort)
3027
- ], { timeoutMs: 6e4 });
3028
- } catch (err) {
3029
- return {
3030
- failed: true,
3031
- code: LAUNCH_ERROR,
3032
- reason: `Could not point ${bundleId} at Metro port ${metroPort} (defaults write RCT_jsLocation): ${await describeIosSimulatorFailure(err, e)}`
3033
- };
3034
- }
3080
+ const writeFailure = await (earlyWrite ?? writeJsLocation(metroPort));
3081
+ if (writeFailure) return writeFailure;
3035
3082
  if (devClientScheme) {
3036
3083
  const url = devClientUrl(devClientScheme, metroPort, "localhost", { devMenuParams });
3037
3084
  const viaInitialUrl = Boolean(consolePaths) || stateUnknown;
@@ -3684,6 +3731,7 @@ const MANIFEST_VERIFY_TIMEOUT_MS = 6e4;
3684
3731
  const STABILITY_WINDOW_MS = 3e3;
3685
3732
  const JS_RUNNING_SETTLE_MS = 500;
3686
3733
  const VERIFY_POLL_MS = 500;
3734
+ const DEVICE_LOG_LAG_LIMIT_MS = 1e4;
3687
3735
  const BUNDLE_EVENTS = /* @__PURE__ */ new Set([
3688
3736
  "bundle_build_started",
3689
3737
  "bundle_build_done",
@@ -3737,6 +3785,43 @@ function jsRunningFields(jsRunningAt) {
3737
3785
  function expoBundleLine(msg) {
3738
3786
  return /\bBundl(?:ing|ed)\b/.test(msg);
3739
3787
  }
3788
+ function createCrashProbe({ firstAt, everyMs, now, run }) {
3789
+ let nextAt = firstAt;
3790
+ let pending = null;
3791
+ let outcome = null;
3792
+ const consume = () => {
3793
+ const done = outcome;
3794
+ if (!done) return null;
3795
+ pending = null;
3796
+ outcome = null;
3797
+ if ("error" in done) throw done.error;
3798
+ return done.result;
3799
+ };
3800
+ return {
3801
+ get pending() {
3802
+ return pending;
3803
+ },
3804
+ poll() {
3805
+ const found = consume();
3806
+ if (found) return found;
3807
+ if (!pending && now() >= nextAt) {
3808
+ nextAt = now() + everyMs;
3809
+ pending = (async () => {
3810
+ try {
3811
+ outcome = { result: await run() };
3812
+ } catch (error) {
3813
+ outcome = { error };
3814
+ }
3815
+ })();
3816
+ }
3817
+ return null;
3818
+ },
3819
+ async settle() {
3820
+ await pending;
3821
+ return consume();
3822
+ }
3823
+ };
3824
+ }
3740
3825
  async function nativeLaunchFailure({ readCrashes, since, platform, processAlive, mode, waitedMs }) {
3741
3826
  if (!readCrashes) return null;
3742
3827
  const crashes = (await readCrashes()).filter((record) => record.event === "native_crash" && after(record, since) && recordCouldBelongToPlatform(record, platform));
@@ -3762,15 +3847,22 @@ function manifestDeadline(records, { startedAt, timeoutMs }) {
3762
3847
  if (open.size > 0) return cap;
3763
3848
  return servedAt === null ? base : Math.min(cap, Math.max(base, servedAt + MANIFEST_GRACE_MS));
3764
3849
  }
3765
- async function verifyLaunch({ slot, appPid, platformShared, logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, onActivity, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
3850
+ async function verifyLaunch({ slot, appPid, platformShared, logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, onActivity, deviceLogsThrough, now = Date.now, signal, sleep = (ms) => cancellableSleep(ms, {
3851
+ signal,
3852
+ what: "launch readiness check"
3853
+ }) } = {}) {
3766
3854
  const delivered = (record) => deliveryOwner(record, {
3767
3855
  slot,
3768
3856
  appPid,
3769
3857
  platformShared
3770
3858
  }) === "this";
3771
- const read = readRecords || (() => readMetroRecords(logsDir));
3772
- const readDevice = readDeviceRecords || (() => readNdjson(logsDir, "device.ndjson"));
3773
- const readClient = readClientRecords || (() => readNdjson(logsDir, "client.ndjson"));
3859
+ const read = readRecords || tailNdjson(logsDir, "metro.ndjson");
3860
+ const readDevice = readDeviceRecords || tailNdjson(logsDir, "device.ndjson");
3861
+ const readClient = readClientRecords || tailNdjson(logsDir, "client.ndjson");
3862
+ const deviceNow = () => {
3863
+ const at = now();
3864
+ return deviceLogsThrough ? Math.max(Math.min(at, deviceLogsThrough() ?? -Infinity), at - DEVICE_LOG_LAG_LIMIT_MS) : at;
3865
+ };
3774
3866
  const startedAt = now();
3775
3867
  const baseTimeoutMs = Math.max(0, timeoutMs);
3776
3868
  let bundleDeadline = startedAt + baseTimeoutMs;
@@ -3781,7 +3873,19 @@ async function verifyLaunch({ slot, appPid, platformShared, logsDir, since, metr
3781
3873
  let deliveryId = null;
3782
3874
  let runtimeLoadingAt = null;
3783
3875
  let runtimeStartedAt = null;
3784
- let nextCrashCheck = startedAt + 1e3;
3876
+ const crashProbe = createCrashProbe({
3877
+ firstAt: startedAt + 1e3,
3878
+ everyMs: 2e3,
3879
+ now,
3880
+ run: () => nativeLaunchFailure({
3881
+ readCrashes: readNativeCrashes,
3882
+ since,
3883
+ platform,
3884
+ processAlive,
3885
+ mode,
3886
+ waitedMs: 0
3887
+ })
3888
+ });
3785
3889
  let bundlingAt = null;
3786
3890
  let reported = null;
3787
3891
  const report = (name, at) => {
@@ -3789,131 +3893,149 @@ async function verifyLaunch({ slot, appPid, platformShared, logsDir, since, metr
3789
3893
  reported = name;
3790
3894
  onActivity?.(name, at);
3791
3895
  };
3792
- while (true) {
3793
- const metroRecords = read().filter((record) => after(record, since));
3794
- const bundleRecords = metroRecords.filter((record) => (!requireBundleResponse || String(record.event).startsWith("bundle_response_")) && delivered(record));
3795
- const deviceRecords = readDevice().filter((record) => after(record, since) && (slot === void 0 || (record.slot ?? "default") === slot));
3796
- const clientRecords = readClient().filter((record) => after(record, since) && !platformShared);
3797
- bundleDeadline = manifestDeadline(metroRecords.filter((record) => String(record.event).startsWith("manifest_request_") && (!platform || record.platform === platform)), {
3798
- startedAt,
3799
- timeoutMs: baseTimeoutMs
3800
- });
3801
- if (now() >= nextCrashCheck) {
3802
- nextCrashCheck = now() + 2e3;
3803
- const crash = await nativeLaunchFailure({
3804
- readCrashes: readNativeCrashes,
3805
- since,
3806
- platform,
3807
- processAlive,
3808
- mode,
3809
- waitedMs: now() - startedAt
3896
+ const loop = async () => {
3897
+ while (true) {
3898
+ const metroRecords = read().filter((record) => after(record, since));
3899
+ const bundleRecords = metroRecords.filter((record) => (!requireBundleResponse || String(record.event).startsWith("bundle_response_")) && delivered(record));
3900
+ const deviceRecords = readDevice().filter((record) => after(record, since) && (slot === void 0 || (record.slot ?? "default") === slot));
3901
+ const clientRecords = readClient().filter((record) => after(record, since) && !platformShared);
3902
+ bundleDeadline = manifestDeadline(metroRecords.filter((record) => String(record.event).startsWith("manifest_request_") && (!platform || record.platform === platform)), {
3903
+ startedAt,
3904
+ timeoutMs: baseTimeoutMs
3810
3905
  });
3811
- if (crash) return crash;
3812
- }
3813
- if (deliveryId === null) {
3814
- const request = bundleRecords.find((record) => record.event === "bundle_response_started" && typeof record.requestId === "string" && isBundleProof(record, since, platform));
3815
- if (request) {
3816
- deliveryId = request.requestId;
3817
- bundlingAt = request;
3818
- proof = null;
3819
- stabilityDeadline = null;
3906
+ const crash = crashProbe.poll();
3907
+ if (crash) return {
3908
+ ...crash,
3909
+ waitedMs: now() - startedAt
3910
+ };
3911
+ if (deliveryId === null) {
3912
+ const request = bundleRecords.find((record) => record.event === "bundle_response_started" && typeof record.requestId === "string" && isBundleProof(record, since, platform));
3913
+ if (request) {
3914
+ deliveryId = request.requestId;
3915
+ bundlingAt = request;
3916
+ proof = null;
3917
+ stabilityDeadline = null;
3918
+ }
3820
3919
  }
3821
- }
3822
- for (const record of bundleRecords) {
3823
- if (isBundleProof(record, since, platform)) activity = record;
3824
- const completed = deliveryId === null ? isBundleReadyProof(record, since, platform) : record.event === "bundle_response_finished" && record.requestId === deliveryId && isBundleProof(record, since, platform);
3825
- if (!proof && completed) {
3826
- proof = record;
3827
- stabilityDeadline = Number(record.ts) + Math.max(0, stabilityMs);
3920
+ for (const record of bundleRecords) {
3921
+ if (isBundleProof(record, since, platform)) activity = record;
3922
+ const completed = deliveryId === null ? isBundleReadyProof(record, since, platform) : record.event === "bundle_response_finished" && record.requestId === deliveryId && isBundleProof(record, since, platform);
3923
+ if (!proof && completed) {
3924
+ proof = record;
3925
+ stabilityDeadline = Number(record.ts) + Math.max(0, stabilityMs);
3926
+ }
3828
3927
  }
3829
- }
3830
- const signals = deviceRecords.filter((record) => Number(record.ts) <= now()).toSorted((a, b) => Number(a.ts) - Number(b.ts));
3831
- if (platform === "android" && runtimeLoadingAt === null) {
3832
- const loading = signals.find((record) => record.src === "device" && record.platform === "android" && /^(?:unknown:)?BridgelessReact\(\d+\)$/.test(String(record.proc)) && /^ReactHost\{\d+\}\.getOrCreateReactInstanceTask\(\): Loading JS Bundle$/.test(String(record.msg)) && (stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
3833
- if (loading) runtimeLoadingAt = Number(loading.ts);
3834
- }
3835
- if (runtimeLoadingAt !== null && runtimeStartedAt === null) {
3836
- const executing = signals.find((record) => record.src === "device" && record.platform === "android" && Number(record.ts) >= runtimeLoadingAt && (/^ReactNativeJS\(\d+\)$/.test(String(record.proc)) || appReadinessSignal(record, platform) !== null));
3837
- if (executing) runtimeStartedAt = Number(executing.ts);
3838
- }
3839
- const runtimeWaiting = runtimeLoadingAt !== null && runtimeStartedAt === null;
3840
- const completedAt = proof ? Math.max(Number(proof.ts), runtimeStartedAt ?? 0) : null;
3841
- if (completedAt !== null) stabilityDeadline = completedAt + Math.max(0, stabilityMs);
3842
- const jsRunningAt = jsRunningSince(signals, completedAt, runtimeWaiting, platform);
3843
- const windowEnd = settledWindowEnd(stabilityDeadline, jsRunningAt);
3844
- const readinessDeadline = completedAt !== null ? completedAt + APP_READINESS_TIMEOUT_MS : bundleDeadline;
3845
- if (pendingAt === null) {
3846
- const pending = signals.find((record) => appReadinessSignal(record, platform) === "pending" && (runtimeWaiting || stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
3847
- if (pending) {
3848
- pendingAt = Number(pending.ts);
3849
- onReadinessPending?.();
3928
+ const signals = deviceRecords.filter((record) => Number(record.ts) <= now()).toSorted((a, b) => Number(a.ts) - Number(b.ts));
3929
+ if (platform === "android" && runtimeLoadingAt === null) {
3930
+ const loading = signals.find((record) => record.src === "device" && record.platform === "android" && /^(?:unknown:)?BridgelessReact\(\d+\)$/.test(String(record.proc)) && /^ReactHost\{\d+\}\.getOrCreateReactInstanceTask\(\): Loading JS Bundle$/.test(String(record.msg)) && (stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
3931
+ if (loading) runtimeLoadingAt = Number(loading.ts);
3850
3932
  }
3851
- }
3852
- report(...launchActivity(pendingAt, proof, bundlingAt));
3853
- const ready = pendingAt !== null && signals.some((record) => Number(record.ts) >= pendingAt && Number(record.ts) <= readinessDeadline && appReadinessSignal(record, platform) === "ready");
3854
- const bundleErrors = bundleRecords.filter((record) => isFatalLaunchError(record, platform) || deliveryId !== null && record.event === "bundle_response_failed" && record.requestId === deliveryId && isBundleProof(record, since, platform));
3855
- if (bundleErrors.length) {
3856
- const alive = processAlive ? await processAlive() : null;
3857
- const fatalRecord = bundleErrors[bundleErrors.length - 1];
3858
- const fatalAt = Number(fatalRecord?.ts ?? since ?? 0);
3859
- return {
3860
- verified: false,
3861
- fatal: true,
3862
- errors: metroRecords.filter((record) => after(record, fatalAt) && recordCouldBelongToPlatform(record, platform) && isLaunchError(record, platform)),
3863
- processAlive: alive,
3864
- record: fatalRecord,
3865
- mode,
3866
- waitedMs: now() - startedAt,
3867
- ...pendingAt !== null ? { readiness: "error" } : {}
3868
- };
3869
- }
3870
- if (proof && windowEnd !== null && (pendingAt !== null || now() >= windowEnd)) {
3871
- const errorSince = pendingAt === null ? Number(proof.ts ?? since ?? 0) : Number(since ?? 0);
3872
- const errors = [
3873
- ...metroRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
3874
- ...deviceRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
3875
- ...clientRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform))
3876
- ].filter((record) => isLaunchError(record, platform));
3877
- const alive = processAlive ? await processAlive() : null;
3878
- const waitedMs = now() - startedAt;
3879
- if (alive === false) return {
3880
- verified: false,
3881
- fatal: true,
3882
- errors,
3883
- processAlive: alive,
3884
- record: proof,
3885
- mode,
3886
- waitedMs,
3887
- ...pendingAt !== null ? { readiness: "error" } : {}
3888
- };
3889
- const actionableErrors = errors.filter((record) => !isIosConnectionRefusal(record, platform));
3890
- const appErrors = actionableErrors.some(isAppLaunchError);
3891
- if (!runtimeWaiting && pendingAt === null || appErrors || ready || now() >= readinessDeadline) return {
3892
- verified: true,
3893
- record: proof,
3894
- errors: actionableErrors,
3895
- processAlive: alive,
3933
+ if (runtimeLoadingAt !== null && runtimeStartedAt === null) {
3934
+ const executing = signals.find((record) => record.src === "device" && record.platform === "android" && Number(record.ts) >= runtimeLoadingAt && (/^ReactNativeJS\(\d+\)$/.test(String(record.proc)) || appReadinessSignal(record, platform) !== null));
3935
+ if (executing) runtimeStartedAt = Number(executing.ts);
3936
+ }
3937
+ const runtimeWaiting = runtimeLoadingAt !== null && runtimeStartedAt === null;
3938
+ const completedAt = proof ? Math.max(Number(proof.ts), runtimeStartedAt ?? 0) : null;
3939
+ if (completedAt !== null) stabilityDeadline = completedAt + Math.max(0, stabilityMs);
3940
+ const jsRunningAt = jsRunningSince(signals, completedAt, runtimeWaiting, platform);
3941
+ const windowEnd = settledWindowEnd(stabilityDeadline, jsRunningAt);
3942
+ const readinessDeadline = completedAt !== null ? completedAt + APP_READINESS_TIMEOUT_MS : bundleDeadline;
3943
+ if (pendingAt === null) {
3944
+ const pending = signals.find((record) => appReadinessSignal(record, platform) === "pending" && (runtimeWaiting || stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
3945
+ if (pending) {
3946
+ pendingAt = Number(pending.ts);
3947
+ onReadinessPending?.();
3948
+ }
3949
+ }
3950
+ report(...launchActivity(pendingAt, proof, bundlingAt));
3951
+ const ready = pendingAt !== null && signals.some((record) => Number(record.ts) >= pendingAt && Number(record.ts) <= readinessDeadline && appReadinessSignal(record, platform) === "ready");
3952
+ const bundleErrors = bundleRecords.filter((record) => isFatalLaunchError(record, platform) || deliveryId !== null && record.event === "bundle_response_failed" && record.requestId === deliveryId && isBundleProof(record, since, platform));
3953
+ if (bundleErrors.length) {
3954
+ const alive = processAlive ? await processAlive() : null;
3955
+ const fatalRecord = bundleErrors[bundleErrors.length - 1];
3956
+ const fatalAt = Number(fatalRecord?.ts ?? since ?? 0);
3957
+ return {
3958
+ verified: false,
3959
+ fatal: true,
3960
+ errors: metroRecords.filter((record) => after(record, fatalAt) && recordCouldBelongToPlatform(record, platform) && isLaunchError(record, platform)),
3961
+ processAlive: alive,
3962
+ record: fatalRecord,
3963
+ mode,
3964
+ waitedMs: now() - startedAt,
3965
+ ...pendingAt !== null ? { readiness: "error" } : {}
3966
+ };
3967
+ }
3968
+ if (proof && windowEnd !== null && (pendingAt !== null || deviceNow() >= windowEnd)) {
3969
+ const errorSince = pendingAt === null ? Number(proof.ts ?? since ?? 0) : Number(since ?? 0);
3970
+ const errors = [
3971
+ ...metroRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
3972
+ ...deviceRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
3973
+ ...clientRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform))
3974
+ ].filter((record) => isLaunchError(record, platform));
3975
+ const alive = processAlive ? await processAlive() : null;
3976
+ const waitedMs = now() - startedAt;
3977
+ if (alive === false) return {
3978
+ verified: false,
3979
+ fatal: true,
3980
+ errors,
3981
+ processAlive: alive,
3982
+ record: proof,
3983
+ mode,
3984
+ waitedMs,
3985
+ ...pendingAt !== null ? { readiness: "error" } : {}
3986
+ };
3987
+ const actionableErrors = errors.filter((record) => !isIosConnectionRefusal(record, platform));
3988
+ const appErrors = actionableErrors.some(isAppLaunchError);
3989
+ if (!runtimeWaiting && pendingAt === null || appErrors || ready || deviceNow() >= readinessDeadline) return {
3990
+ verified: true,
3991
+ record: proof,
3992
+ errors: actionableErrors,
3993
+ processAlive: alive,
3994
+ mode,
3995
+ waitedMs,
3996
+ ...pendingAt !== null ? { readiness: appErrors ? "error" : ready ? "ready" : "timed-out" } : {},
3997
+ ...jsRunningFields(jsRunningAt)
3998
+ };
3999
+ }
4000
+ if ((!proof || runtimeWaiting) && now() >= bundleDeadline) return await bundleTimeoutOutcome({
4001
+ requested: findBundleRequest(deviceRecords, since, metroPort, platform) ?? activity,
4002
+ unattributed: metroRecords.find((record) => record.event === "bundle_response_finished" && deliveryOwner(record, {
4003
+ slot,
4004
+ appPid,
4005
+ platformShared
4006
+ }) === "unknown" && isBundleProof(record, since, platform)) ?? null,
4007
+ processAlive,
4008
+ deviceRecords,
4009
+ platform,
3896
4010
  mode,
3897
- waitedMs,
3898
- ...pendingAt !== null ? { readiness: appErrors ? "error" : ready ? "ready" : "timed-out" } : {},
3899
- ...jsRunningFields(jsRunningAt)
3900
- };
4011
+ waitedMs: now() - startedAt
4012
+ });
4013
+ const nap = sleep(Math.min(pollMs, Math.max(0, (runtimeWaiting ? bundleDeadline : proof && pendingAt !== null ? readinessDeadline : windowEnd ?? bundleDeadline) - deviceNow())));
4014
+ await (crashProbe.pending ? Promise.race([nap, crashProbe.pending]) : nap);
3901
4015
  }
3902
- if ((!proof || runtimeWaiting) && now() >= bundleDeadline) return await bundleTimeoutOutcome({
3903
- requested: findBundleRequest(deviceRecords, since, metroPort, platform) ?? activity,
3904
- unattributed: metroRecords.find((record) => record.event === "bundle_response_finished" && deliveryOwner(record, {
3905
- slot,
3906
- appPid,
3907
- platformShared
3908
- }) === "unknown" && isBundleProof(record, since, platform)) ?? null,
3909
- processAlive,
3910
- deviceRecords,
3911
- platform,
3912
- mode,
3913
- waitedMs: now() - startedAt
3914
- });
3915
- await sleep(Math.min(pollMs, Math.max(0, (runtimeWaiting ? bundleDeadline : proof && pendingAt !== null ? readinessDeadline : windowEnd ?? bundleDeadline) - now())));
4016
+ };
4017
+ let result;
4018
+ try {
4019
+ result = await loop();
4020
+ } catch (error) {
4021
+ await crashProbe.settle().catch(() => {});
4022
+ throw error;
3916
4023
  }
4024
+ const probed = crashProbe.pending !== null;
4025
+ const late = await crashProbe.settle();
4026
+ if (late) return {
4027
+ ...late,
4028
+ waitedMs: now() - startedAt
4029
+ };
4030
+ if (probed && result.verified && await processAlive?.() === false) return {
4031
+ ...result,
4032
+ verified: false,
4033
+ fatal: true,
4034
+ processAlive: false,
4035
+ waitedMs: now() - startedAt,
4036
+ ...result.readiness ? { readiness: "error" } : {}
4037
+ };
4038
+ return result;
3917
4039
  }
3918
4040
  function launchActivity(pendingAt, proof, bundling) {
3919
4041
  if (pendingAt !== null) return ["waiting-ready", pendingAt];
@@ -4007,6 +4129,9 @@ function readCollectorRecords(logsDir) {
4007
4129
  function readNdjson(logsDir, name) {
4008
4130
  return logsDir ? readNdjsonGenerations$1(join(logsDir, name)) : [];
4009
4131
  }
4132
+ function tailNdjson(logsDir, name) {
4133
+ return logsDir ? createNdjsonTail(join(logsDir, name)) : () => [];
4134
+ }
4010
4135
  function unattributedLaunchLines({ platform, metroPort, slot = "default", siblings }) {
4011
4136
  const others = siblings.length === 1 ? `Slot ${siblings[0]} also runs` : `Slots ${siblings.join(", ")} also run`;
4012
4137
  return [
@@ -6209,11 +6334,11 @@ function directories(dir, notices) {
6209
6334
  }
6210
6335
  function scopedOutside(gradleHome, env) {
6211
6336
  if (!deviceSweepIsScoped(env)) return false;
6212
- const rel = relative(canonical(getConfigDir()), gradleHome);
6337
+ const rel = relative(canonical$1(getConfigDir()), gradleHome);
6213
6338
  return rel === "" || rel.startsWith("..") || isAbsolute(rel);
6214
6339
  }
6215
6340
  function gradleUserHome(env, cwd) {
6216
- return canonical(envDir("GRADLE_USER_HOME", env, cwd) ?? join(env?.HOME || env?.USERPROFILE || homedir(), ".gradle"));
6341
+ return canonical$1(envDir("GRADLE_USER_HOME", env, cwd) ?? join(env?.HOME || env?.USERPROFILE || homedir(), ".gradle"));
6217
6342
  }
6218
6343
  /** Each Gradle version's `caches/<version>` and `wrapper/dists/gradle-<version>-*` directories under `gradleHome`. */
6219
6344
  function gradleVersionDirs(gradleHome, notices) {
@@ -7122,7 +7247,7 @@ async function emptyCache(cache, signal, operation) {
7122
7247
  }
7123
7248
  function reportFailures(cache, failed, { log, record, fail }) {
7124
7249
  if (!failed) return false;
7125
- const message = `${failed} entr${failed === 1 ? "y" : "ies"} in ${cache.dir} could not be removed`;
7250
+ const message = `${failed} entr${failed === 1 ? "y" : "ies"} in ${cache.dir} could not be read or removed`;
7126
7251
  log(chalk.red(`${cache.name}: ${message}`));
7127
7252
  record("cache", "failed", cache.name, {
7128
7253
  id: cache.dir,
@@ -7136,6 +7261,7 @@ async function trimCaches(caches, olderThan, output = GC_OUTPUT, signal, operati
7136
7261
  let cacheBytes = 0;
7137
7262
  let failures = 0;
7138
7263
  const trimmed = [];
7264
+ let unchanged = 0;
7139
7265
  for (const c of caches) {
7140
7266
  signal?.throwIfAborted();
7141
7267
  if (c.machineGlobal) {
@@ -7165,9 +7291,10 @@ async function trimCaches(caches, olderThan, output = GC_OUTPUT, signal, operati
7165
7291
  bytes: r.bytes
7166
7292
  });
7167
7293
  log(chalk.green(`Trimmed ${c.name}: ${r.removed} entr${r.removed === 1 ? "y" : "ies"} (${formatBytes(r.bytes)})`));
7168
- } else if (!r.failed) log(chalk.dim(`${c.name}: nothing older than ${olderThan}d`));
7294
+ } else if (!r.failed) unchanged++;
7169
7295
  if (reportFailures(c, r.failed, output)) failures++;
7170
7296
  }
7297
+ if (unchanged) log(chalk.dim(`${unchanged} cache${unchanged === 1 ? "" : "s"}: nothing older than ${olderThan}d`));
7171
7298
  if (cacheBytes) log(chalk.dim(`Trimmed ${formatBytes(cacheBytes)} of shared cache. The next build that wanted those entries pays to rebuild them.`));
7172
7299
  return {
7173
7300
  trimmed,
@@ -7177,6 +7304,7 @@ async function trimCaches(caches, olderThan, output = GC_OUTPUT, signal, operati
7177
7304
  async function emptyCaches(caches, output = GC_OUTPUT, signal, operation) {
7178
7305
  const { log, record } = output;
7179
7306
  let cacheBytes = 0;
7307
+ let failures = 0;
7180
7308
  for (const c of caches) {
7181
7309
  signal?.throwIfAborted();
7182
7310
  if (!c.willEmpty) {
@@ -7202,1355 +7330,188 @@ async function emptyCaches(caches, output = GC_OUTPUT, signal, operation) {
7202
7330
  });
7203
7331
  log(chalk.green(`Emptied ${c.name}: ${r.removed} entr${r.removed === 1 ? "y" : "ies"} (${formatBytes(r.bytes)})`));
7204
7332
  } else if (!r.failed) log(chalk.dim(`${c.name}: already empty`));
7205
- reportFailures(c, r.failed, output);
7333
+ if (reportFailures(c, r.failed, output)) failures++;
7206
7334
  }
7207
7335
  if (cacheBytes) log(chalk.dim(`Emptied ${formatBytes(cacheBytes)} of shared cache. Every build that wanted any of it now pays to rebuild it.`));
7336
+ return failures;
7208
7337
  }
7209
7338
  //#endregion
7210
- //#region src/engine/stats.ts
7211
- const PLACEMENT_MAX_AGE_MS = 6048e5;
7212
- function emptyStats() {
7213
- return {
7214
- version: STATS_VERSION,
7215
- machine: {},
7216
- projects: {}
7217
- };
7218
- }
7219
- function updateStats(record, run, now) {
7220
- const at = new Date(now).toISOString();
7221
- const durationMs = wholeMs(run.durationMs);
7222
- const phases = {
7223
- coldBuildMs: wholeMs(run.coldBuildMs),
7224
- podsMs: wholeMs(run.podsMs)
7225
- };
7226
- const projects = { ...record.projects };
7227
- const scope = { ...projects[run.projectKey] };
7228
- const machine = { ...record.machine };
7229
- const before = scope[run.platform] ?? null;
7230
- const credit = creditMs(before, run, durationMs);
7231
- scope[run.platform] = applyRun(before, run, {
7232
- at,
7233
- durationMs,
7234
- credit,
7235
- phases
7236
- });
7237
- projects[run.projectKey] = scope;
7238
- machine[run.platform] = applyRun(machine[run.platform] ?? null, run, {
7239
- at,
7240
- durationMs,
7241
- credit,
7242
- phases
7243
- });
7244
- const history = { ...record.history };
7245
- if (run.phases && !run.failed && !run.waitedForBuild && !run.offloadedTo) {
7246
- const outcome = isHit(run.cacheHit) ? "hit" : "cold";
7247
- const project = { ...history[run.projectKey] };
7248
- const lists = { ...project[run.platform] };
7249
- const sample = {
7250
- at,
7251
- durationMs,
7252
- phases: wholePhases(run.phases),
7253
- ...run.deviceSetup === void 0 ? {} : { deviceSetup: run.deviceSetup }
7254
- };
7255
- lists[outcome] = trimSamples([...lists[outcome] ?? [], sample]);
7256
- project[run.platform] = lists;
7257
- history[run.projectKey] = project;
7258
- }
7259
- let placements = record.placements;
7260
- let buildMachines = record.buildMachines;
7261
- if (run.placement) {
7262
- const placement = buildPlacement(run, before, at);
7263
- placements = trimPlacements([...placements ?? [], placement], now);
7264
- if (placement.machine && placement.decision !== "here") buildMachines = {
7265
- ...buildMachines,
7266
- [placement.machine]: applyPlacement(buildMachines?.[placement.machine], placement)
7267
- };
7268
- }
7269
- const capacityRefusals = trimCapacityRefusals(record.capacityRefusals ?? [], now);
7270
- const capacityWaits = trimCapacityWaits(record.capacityWaits ?? [], now);
7271
- return {
7272
- version: STATS_VERSION,
7273
- machine,
7274
- projects,
7275
- ...Object.keys(history).length ? { history } : {},
7276
- ...placements?.length ? { placements } : {},
7277
- ...capacityRefusals.length ? { capacityRefusals } : {},
7278
- ...capacityWaits.length ? { capacityWaits } : {},
7279
- ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
7280
- };
7281
- }
7282
- function buildPlacement(run, before, at) {
7283
- const placement = run.placement;
7284
- const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
7285
- const localEstimateMs = wholeMs(before?.lastColdBuildMs);
7286
- const slotWaitMs = wholeMs(placement.slotWaitMs);
7287
- const deviceSlotWaitMs = wholeMs(placement.deviceSlotWaitMs);
7288
- return {
7289
- at,
7290
- project: run.projectKey,
7291
- platform: run.platform,
7292
- decision: placement.decision,
7293
- reason: placement.reason,
7294
- ...placement.machine ? { machine: placement.machine } : {},
7295
- ...buildMs > 0 ? { buildMs } : {},
7296
- ...slotWaitMs > 0 ? { slotWaitMs } : {},
7297
- ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {},
7298
- ...localEstimateMs > 0 ? { localEstimateMs } : {},
7299
- ...run.failed ? { failed: true } : {}
7300
- };
7301
- }
7302
- function applyPlacement(totals, placement) {
7303
- const next = totals ? { ...totals } : {
7304
- offloaded: 0,
7305
- offloadedMs: 0,
7306
- savedMs: 0,
7307
- fallbacks: 0
7308
- };
7309
- if (placement.decision === "fell-back") {
7310
- next.fallbacks += 1;
7311
- next.lastFallbackAt = placement.at;
7312
- return next;
7313
- }
7314
- next.offloaded += 1;
7315
- next.offloadedMs += placement.buildMs ?? 0;
7316
- if (placement.localEstimateMs && placement.buildMs) next.savedMs += placement.localEstimateMs - placement.buildMs;
7317
- next.lastOffloadAt = placement.at;
7318
- return next;
7319
- }
7320
- function trimPlacements(placements, now) {
7321
- return placements.filter((each) => now - Date.parse(each.at) <= PLACEMENT_MAX_AGE_MS).slice(-100);
7322
- }
7323
- function recordRunStats(run, now) {
7324
- return withConfigLock$1(() => {
7325
- const path = statsFile();
7326
- const loaded = loadForUpdate(path, now);
7327
- if (loaded.newerVersion !== null) return {
7328
- recorded: false,
7329
- note: `Run statistics at ${path} are version ${loaded.newerVersion}, which this Stim does not understand, so this run was not recorded.`
7330
- };
7331
- writeStats(path, updateStats(loaded.record, run, now));
7332
- return {
7333
- recorded: true,
7334
- note: loaded.note
7335
- };
7339
+ //#region src/maintenance/log.ts
7340
+ function openRetainedLog(file, settings) {
7341
+ ensurePrivateDir(dirname(file));
7342
+ const rotated = rotatedLogPath(file);
7343
+ withDirLock(`${file}.lock`, () => {
7344
+ try {
7345
+ if (Date.now() - statSync(rotated).mtimeMs > settings.logRetentionDays * 864e5) unlinkSync(rotated);
7346
+ } catch {}
7336
7347
  });
7348
+ return createNdjsonWriter(file, { maxBytes: settings.logMaxMb * 1024 ** 2 });
7337
7349
  }
7338
- function recordCapacityRefusal(event, now) {
7339
- try {
7340
- withConfigLock$1(() => {
7341
- const path = statsFile();
7342
- const loaded = loadForUpdate(path, now, false);
7343
- if (!loaded.record || loaded.newerVersion !== null) return;
7344
- const capacityRefusals = trimCapacityRefusals([...loaded.record.capacityRefusals ?? [], {
7345
- ...event,
7346
- at: new Date(now).toISOString(),
7347
- kind: "device"
7348
- }], now);
7349
- writeStats(path, {
7350
- ...loaded.record,
7351
- capacityRefusals
7352
- });
7353
- });
7354
- } catch {}
7355
- }
7356
- function recordCapacityWait(event, now) {
7357
- try {
7358
- withConfigLock$1(() => {
7359
- const path = statsFile();
7360
- const loaded = loadForUpdate(path, now, false);
7361
- if (!loaded.record || loaded.newerVersion !== null) return;
7362
- const capacityWaits = trimCapacityWaits([...loaded.record.capacityWaits ?? [], {
7363
- platform: event.platform,
7364
- max: event.max,
7365
- workspace: event.workspace,
7366
- ms: wholeMs(event.ms),
7367
- ...event.reclaimed && Number.isInteger(event.reclaimed) && event.reclaimed > 0 ? { reclaimed: event.reclaimed } : {},
7368
- at: new Date(now).toISOString(),
7369
- kind: "device-wait"
7370
- }], now);
7371
- writeStats(path, {
7372
- ...loaded.record,
7373
- capacityWaits
7374
- });
7375
- });
7376
- } catch {}
7377
- }
7378
- function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
7379
- let projectKey = null;
7380
- let cacheKey = null;
7381
- let coldBuildMs = 0;
7382
- let podsMs = 0;
7383
- let placement = null;
7384
- let deviceSlotWaitMs = 0;
7385
- let recorded = false;
7386
- return {
7387
- setProject(key) {
7388
- projectKey = key;
7389
- },
7390
- setCacheKey(key) {
7391
- cacheKey = key;
7392
- },
7393
- setBuildMs(ms) {
7394
- coldBuildMs = wholeMs(ms);
7395
- },
7396
- setPodsMs(ms) {
7397
- podsMs = wholeMs(ms);
7398
- },
7399
- addDeviceSlotWaitMs(ms) {
7400
- deviceSlotWaitMs += wholeMs(ms);
7401
- },
7402
- deviceSlotWaitMs() {
7403
- return deviceSlotWaitMs || void 0;
7404
- },
7405
- setPlacement(next) {
7406
- placement = next;
7407
- },
7408
- record({ failed, cacheHit = false, waited = null, durationMs, offloadedTo = null }) {
7409
- if (!projectKey || !cacheKey || recorded) return;
7410
- recorded = true;
7411
- const ran = failed ? {} : phases?.() ?? {};
7412
- try {
7413
- const outcome = write({
7414
- platform,
7415
- projectKey,
7416
- failed,
7417
- cacheHit,
7418
- waitedForBuild: Boolean(waited),
7419
- durationMs,
7420
- ...offloadedTo ? { offloadedTo } : {},
7421
- ...coldBuildMs > 0 ? { coldBuildMs } : {},
7422
- ...podsMs > 0 ? { podsMs } : {},
7423
- ...Object.keys(ran).length ? { phases: ran } : {},
7424
- ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
7425
- ...placement ? { placement: {
7426
- ...placement,
7427
- ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {}
7428
- } } : {}
7429
- }, now());
7430
- if (outcome?.note) note(outcome.note);
7431
- } catch (error) {
7432
- note(`Run statistics could not be recorded: ${error?.message || error}`);
7433
- }
7434
- }
7435
- };
7436
- }
7437
- function loadForUpdate(path, now, recoverCorrupt = true) {
7438
- let text;
7439
- try {
7440
- text = readFileSync(path, "utf-8");
7441
- } catch {
7442
- return {
7443
- record: emptyStats(),
7444
- note: null,
7445
- newerVersion: null
7446
- };
7447
- }
7448
- const parsed = decodeStats(text);
7449
- if (!parsed.record && parsed.newerVersion === null) {
7450
- if (!recoverCorrupt) return {
7451
- record: null,
7452
- note: null,
7453
- newerVersion: null
7454
- };
7455
- const aside = `${path}.corrupt-${now}`;
7456
- renameSync(path, aside);
7457
- return {
7458
- record: emptyStats(),
7459
- note: `Run statistics at ${path} could not be read, so they were moved to ${aside} and a new file was started.`,
7460
- newerVersion: null
7461
- };
7462
- }
7463
- if (parsed.newerVersion !== null) return {
7464
- record: emptyStats(),
7465
- note: null,
7466
- newerVersion: parsed.newerVersion
7467
- };
7468
- return {
7469
- record: parsed.record,
7470
- note: null,
7471
- newerVersion: null
7472
- };
7473
- }
7474
- function writeStats(path, record) {
7475
- const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
7476
- writeFileSync(tmp, `${JSON.stringify(record)}\n`);
7477
- try {
7478
- renameSync(tmp, path);
7479
- } catch (error) {
7480
- rmSync(tmp, { force: true });
7481
- throw error;
7482
- }
7483
- }
7484
- function applyRun(bucket, run, { at, durationMs, credit, phases }) {
7485
- const next = bucket ? { ...bucket } : {
7486
- runs: 0,
7487
- failed: 0,
7488
- hits: 0,
7489
- misses: 0,
7490
- coldRuns: 0,
7491
- coldRunMs: 0,
7492
- hitRuns: 0,
7493
- hitRunMs: 0,
7494
- timeSavedMs: 0,
7495
- firstRunAt: at,
7496
- lastRunAt: at
7497
- };
7498
- next.runs += 1;
7499
- next.lastRunAt = at;
7500
- if (phases.coldBuildMs > 0) next.lastColdBuildMs = phases.coldBuildMs;
7501
- if (phases.podsMs > 0) next.lastPodsMs = phases.podsMs;
7502
- if (run.failed) {
7503
- next.failed += 1;
7504
- return next;
7505
- }
7506
- if (isHit(run.cacheHit)) {
7507
- next.hits += 1;
7508
- if (!run.waitedForBuild) {
7509
- next.hitRuns += 1;
7510
- next.hitRunMs += durationMs;
7511
- next.timeSavedMs += credit;
7512
- }
7513
- return next;
7514
- }
7515
- next.misses += 1;
7516
- if (run.offloadedTo) {
7517
- next.offloadedRuns = (next.offloadedRuns ?? 0) + 1;
7518
- next.offloadedRunMs = (next.offloadedRunMs ?? 0) + durationMs;
7519
- next.lastOffloadHost = run.offloadedTo;
7520
- return next;
7521
- }
7522
- next.coldRuns += 1;
7523
- next.coldRunMs += durationMs;
7524
- return next;
7525
- }
7526
- function creditMs(bucket, run, durationMs) {
7527
- if (run.failed || !isHit(run.cacheHit) || run.waitedForBuild) return 0;
7528
- if (!bucket || bucket.coldRuns <= 0) return 0;
7529
- return Math.max(0, Math.round(bucket.coldRunMs / bucket.coldRuns) - durationMs);
7530
- }
7531
- function isHit(cacheHit) {
7532
- return cacheHit === "local" || cacheHit === "remote";
7533
- }
7534
- function wholeMs(value) {
7535
- const ms = Number(value);
7536
- return Number.isFinite(ms) && ms > 0 ? Math.round(ms) : 0;
7537
- }
7538
- //#endregion
7539
- //#region src/engine/build-detail.ts
7540
- const LINE_MAX = 160;
7541
- const TARGET_GRAPH = /^note: Target dependency graph \((\d+) targets?\)/;
7542
- const XCODE_ACTION = /^([A-Z][A-Za-z]+)\s(.*)\s\(in target '([^']+)' from project '([^']+)'\)$/;
7543
- const XCODE_CONFIGURE = /^(ComputeTargetDependencyGraph|CreateBuildDescription|CreateBuildRequest|Resolve Package Graph|Prepare packages)$/;
7544
- const GRADLE_TASK = /^> Task (:\S+)/;
7545
- /**
7546
- * xcodebuild actions that end a target's work: a clean build touches each product last, and an incremental build
7547
- * that touches nothing still signs framework products last. Targets that end any other way are not counted.
7548
- */
7549
- const XCODE_TARGET_DONE = /^(Touch|RegisterExecutionPolicyException|CodeSign)$/;
7550
- const GRADLE_CONFIGURE = /^> Configure project /;
7551
- /**
7552
- * SwiftPM prints `[done / total] name` once it plans the build (with U+2009 thin spaces around the slash in Swift 6.4
7553
- * output), `[done/total] Compiling ...` in older toolchains; Swift 6.4 names the target being worked on, and
7554
- * `<product>-product` is the link of a product.
7555
- */
7556
- const SWIFTPM_PROGRESS = /^\[(\d+)\s*\/\s*(\d+)\](?:\s+(.*))?$/;
7557
- const SWIFTPM_CONFIGURE = /^(Fetching|Fetched|Computing version|Computed|Creating working copy|Working copy of)\b|^\[(Pre-planning|Planning|Using on-disk description)/;
7558
- const XCODE_STEPS = [
7559
- [/^(CpResource|CopyPNGFile|CopyStringsFile|CompileAssetCatalog\w*|CompileStoryboard|CompileXIB|LinkStoryboards|ProcessInfoPlistFile)$/, "resources"],
7560
- [/^(Compile\w*|Swift\w*|ScanDependencies|Precompile\w*)$/, "compile"],
7561
- [/^(Ld|Libtool|CreateUniversalBinary|GenerateDSYMFile)$/, "link"],
7562
- [/^PhaseScriptExecution$/, "script"],
7563
- [/^CodeSign$/, "sign"]
7564
- ];
7565
- const GRADLE_STEPS = [
7566
- [/[Dd]ex/, "dex"],
7567
- [/^(createBundle\w*JsAndAssets|generateCodegen\w*)$/, "script"],
7568
- [/(Resources?|Assets|Manifest|ResValues|RFile|JavaRes)$/, "resources"],
7569
- [/^(compile|kapt|ksp|javaPreCompile|buildCMake|configureCMake|externalNativeBuild)/, "compile"],
7570
- [/^(package|assemble|install|zipalign|validateSigning|merge\w*NativeLibs|strip\w*Symbols)/, "package"]
7571
- ];
7572
- function stepOf(name, table) {
7573
- return table.find(([pattern]) => pattern.test(name))?.[1] ?? null;
7574
- }
7575
- function clip(text) {
7576
- return text.length > LINE_MAX ? `${text.slice(0, 157)}...` : text;
7577
- }
7578
- function unescape(token) {
7579
- return token.replace(/\\(.)/g, "$1");
7580
- }
7581
- function baseName(path) {
7582
- return path.split("/").findLast(Boolean) ?? path;
7583
- }
7584
- /**
7585
- * An xcodebuild action line in short form: the action, the file it works on and its target, as in
7586
- * `CompileC sqlite3.c (sqlite3)`. The file is the last absolute path in the arguments, which xcodebuild prints
7587
- * as the source for compile, copy and plist steps and as the product for link and sign steps; a script phase
7588
- * shows its name instead.
7589
- */
7590
- function shortXcodeAction(action, args, target) {
7591
- const tokens = args.match(/(?:\\.|\S)+/g) ?? [];
7592
- const subject = action === "PhaseScriptExecution" ? unescape(tokens[0] ?? "") : baseName(unescape(tokens.findLast((token) => token.startsWith("/")) ?? ""));
7593
- return clip(`${action}${subject ? ` ${subject}` : ""} (${target})`);
7594
- }
7595
- function swiftpmStep(text) {
7596
- return /^Linking\b|-product$/.test(text) ? "link" : "compile";
7597
- }
7598
- /** What one line of xcodebuild, Gradle or SwiftPM output says about the build's progress, or null for any other line. */
7599
- function parseBuildToolLine(msg) {
7600
- const counted = SWIFTPM_PROGRESS.exec(msg);
7601
- if (counted) {
7602
- const text = counted[3]?.trim() ?? "";
7603
- return {
7604
- kind: "count",
7605
- done: Number(counted[1]),
7606
- total: Number(counted[2]),
7607
- step: swiftpmStep(text),
7608
- line: text ? clip(text) : null
7609
- };
7610
- }
7611
- if (SWIFTPM_CONFIGURE.test(msg)) return { kind: "configure" };
7612
- const graph = TARGET_GRAPH.exec(msg);
7613
- if (graph) return {
7614
- kind: "total",
7615
- unit: "targets",
7616
- total: Number(graph[1])
7617
- };
7618
- if (XCODE_CONFIGURE.test(msg) || GRADLE_CONFIGURE.test(msg)) return { kind: "configure" };
7619
- const action = XCODE_ACTION.exec(msg);
7620
- if (action) {
7621
- const [, name, args, target, project] = action;
7622
- const step = stepOf(name, XCODE_STEPS);
7623
- return {
7624
- kind: "unit",
7625
- unit: "targets",
7626
- id: `${project}/${target}`,
7627
- step,
7628
- line: step ? shortXcodeAction(name, args, target) : null,
7629
- finished: XCODE_TARGET_DONE.test(name)
7630
- };
7631
- }
7632
- const task = GRADLE_TASK.exec(msg);
7633
- if (task) {
7634
- const path = task[1];
7635
- return {
7636
- kind: "unit",
7637
- unit: "tasks",
7638
- id: path,
7639
- step: stepOf(path.slice(path.lastIndexOf(":") + 1), GRADLE_STEPS),
7640
- line: clip(`> Task ${path}`),
7641
- finished: false
7642
- };
7643
- }
7644
- return null;
7645
- }
7646
- /**
7647
- * Accumulates a build's tool output into its detail: xcodebuild counts the distinct targets it finished against the
7648
- * total its dependency graph note gives; Gradle counts the task lines it printed and has no total; SwiftPM reports
7649
- * its own `[done / total]` counts, which the parser passes through.
7650
- */
7651
- function createBuildDetailParser() {
7652
- let seen = false;
7653
- let step = null;
7654
- let unit = null;
7655
- let total = null;
7656
- let line = null;
7657
- let tasks = 0;
7658
- let counted = null;
7659
- const targets = /* @__PURE__ */ new Set();
7350
+ function maintenanceLogger(settings, pass, trigger) {
7351
+ const file = maintenanceNdjsonFile();
7352
+ const writer = openRetainedLog(file, settings);
7660
7353
  return {
7661
- push(msg) {
7662
- const parsed = parseBuildToolLine(msg);
7663
- if (!parsed) return false;
7664
- seen = true;
7665
- if (parsed.kind === "total") {
7666
- unit = "targets";
7667
- total = parsed.total;
7668
- return true;
7669
- }
7670
- if (parsed.kind === "configure") {
7671
- step = "configure";
7672
- return true;
7673
- }
7674
- if (parsed.kind === "count") {
7675
- unit = "steps";
7676
- counted = {
7677
- done: parsed.done,
7678
- total: parsed.total
7679
- };
7680
- step = parsed.step;
7681
- if (parsed.line) line = parsed.line;
7682
- return true;
7354
+ write(event, level, msg, fields = {}) {
7355
+ if (level === "debug" && !settings.logChecks) return null;
7356
+ const record = {
7357
+ ...fields,
7358
+ ts: Date.now(),
7359
+ src: "maintenance",
7360
+ level,
7361
+ msg,
7362
+ event,
7363
+ pass,
7364
+ trigger,
7365
+ mode: settings.mode === "on" ? "on" : "report"
7366
+ };
7367
+ if (!writer.write(record)) throw writer.lastError ?? /* @__PURE__ */ new Error(`Could not append ${file}`);
7368
+ if (typeof fields.workspace === "string" && [
7369
+ "maintenance_action",
7370
+ "maintenance_failure",
7371
+ "maintenance_skip"
7372
+ ].includes(event)) {
7373
+ const workspace = createNdjsonWriter(join(workspaceLogsDir(fields.workspace), MAINTENANCE_LOG_NAME), { maxBytes: LOG_ROTATE_BYTES });
7374
+ try {
7375
+ if (!workspace.write(record)) throw workspace.lastError ?? /* @__PURE__ */ new Error("Could not append workspace maintenance log");
7376
+ } finally {
7377
+ workspace.close();
7378
+ }
7683
7379
  }
7684
- unit = parsed.unit;
7685
- if (parsed.unit === "tasks") tasks += 1;
7686
- else if (parsed.finished) targets.add(parsed.id);
7687
- if (parsed.step) step = parsed.step;
7688
- if (parsed.line) line = parsed.line;
7689
- return true;
7380
+ return record;
7690
7381
  },
7691
- detail(updatedAt) {
7692
- if (!seen) return null;
7693
- if (unit === "steps" && counted) return {
7694
- step,
7695
- unit,
7696
- done: Math.min(counted.done, counted.total),
7697
- total: counted.total,
7698
- line,
7699
- updatedAt
7700
- };
7701
- const done = unit === "tasks" ? tasks : unit === "targets" ? targets.size : null;
7702
- return {
7703
- step,
7704
- unit,
7705
- done: done !== null && total !== null ? Math.min(done, total) : done,
7706
- total: unit === "tasks" ? null : total,
7707
- line,
7708
- updatedAt
7709
- };
7710
- }
7382
+ close: () => writer.close()
7711
7383
  };
7712
7384
  }
7713
7385
  //#endregion
7714
- //#region src/engine/build-progress.ts
7715
- const ACTIVE_BUILD_KEY = "activeBuild";
7716
- const NO_BUILD_PROGRESS = {
7717
- step: () => {},
7718
- activity: () => {},
7719
- estimate: () => {},
7720
- miss: () => {},
7721
- hit: () => {},
7722
- deviceSetup: () => {},
7723
- deviceSetupKnown: () => void 0,
7724
- place: () => {},
7725
- waitingOn: () => {},
7726
- waitingFor: () => {},
7727
- output: () => {},
7728
- durations: () => ({}),
7729
- phase: () => null,
7730
- steps: () => null,
7731
- clear: () => {}
7386
+ //#region src/commands/gc/log.ts
7387
+ const VERBS = {
7388
+ done: "Cleared",
7389
+ kept: "Kept",
7390
+ failed: "Failed to clear"
7732
7391
  };
7733
- /** `writer`, with each record it writes also passed to `progress.output`. */
7734
- function tapBuildLog(writer, progress) {
7735
- if (progress === NO_BUILD_PROGRESS) return writer;
7392
+ function gcLogRecord(run, result, ts) {
7393
+ const path = [result.id, result.label].find((value) => value !== null && isAbsolute(value)) ?? null;
7394
+ const size = result.bytes ? ` (${formatBytes(result.bytes)})` : "";
7395
+ const why = result.detail ? `: ${result.detail}` : "";
7736
7396
  return {
7737
- get file() {
7738
- return writer.file;
7739
- },
7740
- write(record) {
7741
- progress.output(record);
7742
- return writer.write(record);
7743
- },
7744
- close: () => writer.close(),
7745
- get written() {
7746
- return writer.written;
7747
- },
7748
- get dropped() {
7749
- return writer.dropped;
7750
- },
7751
- get lastError() {
7752
- return writer.lastError;
7753
- }
7754
- };
7755
- }
7756
- function runFailureRecord({ code, message, phase }) {
7757
- return {
7758
- src: "build",
7759
- level: "error",
7760
- event: "run_failed",
7761
- ...code ? { code } : {},
7762
- ...phase ? { phase } : {},
7763
- msg: [code, message].filter(Boolean).join(": ") || "the run failed"
7764
- };
7765
- }
7766
- function thrownRunFailureRecord(error, phase) {
7767
- return runFailureRecord({
7768
- code: typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0,
7769
- message: error instanceof Error ? error.message : String(error),
7770
- phase
7771
- });
7772
- }
7773
- const DETAIL_WRITE_MS = 2e3;
7774
- function toolLine(record) {
7775
- if (!record || typeof record !== "object") return null;
7776
- const { src, level, msg } = record;
7777
- return src === "build" && level === "debug" && typeof msg === "string" ? msg : null;
7778
- }
7779
- function writeBuildDetail(root, claimId, detail) {
7780
- const file = workspaceBuildDetailFile(root);
7781
- const tmp = `${file}.tmp-${process.pid}`;
7782
- mkdirSync(dirname(file), { recursive: true });
7783
- writeFileSync(tmp, JSON.stringify({
7784
- claimId,
7785
- detail
7786
- }));
7787
- renameSync(tmp, file);
7788
- }
7789
- const COLD_PHASES = [
7790
- "prebuild",
7791
- "pods",
7792
- "compile"
7793
- ];
7794
- const HIT_PHASES = ["install", "launch"];
7795
- function settledOutcome(phase, entered) {
7796
- if (COLD_PHASES.includes(phase)) return "cold";
7797
- if (phase === "device") return entered.some((entry) => entry.phase === "cache-lookup") ? "hit" : null;
7798
- return HIT_PHASES.includes(phase) ? "hit" : null;
7799
- }
7800
- function projectHistory(projectKey) {
7801
- try {
7802
- return readStats$1().record?.history?.[projectKey];
7803
- } catch {
7804
- return;
7805
- }
7806
- }
7807
- function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
7808
- const startedAt = new Date(now()).toISOString();
7809
- const record = {
7810
- platform,
7811
- slot,
7812
- startedAt,
7813
- phase: "prepare",
7814
- phaseStartedAt: startedAt,
7815
- phases: [{
7816
- phase: "prepare",
7817
- startedAt
7818
- }],
7819
- claim: {
7820
- root: claim.root,
7821
- path: claim.path,
7822
- claimId: claim.claimId,
7823
- pid: claim.owner.pid
7824
- }
7825
- };
7826
- let warned = false;
7827
- const guard = (fn) => {
7397
+ ts,
7398
+ src: "gc",
7399
+ level: result.status === "failed" ? "error" : "info",
7400
+ msg: `${VERBS[result.status]} ${result.kind} ${result.label}${size}${why}`,
7401
+ event: "gc_item",
7402
+ runId: run.runId ?? runId(),
7403
+ trigger: run.trigger,
7404
+ ...run.via === void 0 ? {} : { via: run.via },
7405
+ ...run.options === void 0 ? {} : { options: run.options },
7406
+ kind: result.kind,
7407
+ status: result.status,
7408
+ label: result.label,
7409
+ id: result.id,
7410
+ path,
7411
+ bytes: result.bytes,
7412
+ detail: result.detail
7413
+ };
7414
+ }
7415
+ function openGcLog(run) {
7416
+ let failed = false;
7417
+ let acted = run.holdKept !== true;
7418
+ const held = [];
7419
+ const append = (record) => {
7828
7420
  try {
7829
- fn();
7830
- } catch (error) {
7831
- if (warned) return;
7832
- warned = true;
7833
- note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
7834
- }
7835
- };
7836
- const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
7837
- ...before(state),
7838
- [ACTIVE_BUILD_KEY]: record
7839
- })));
7840
- write(withInterruptedBuild);
7841
- let projectKey = null;
7842
- const parser = createBuildDetailParser();
7843
- let detailWrittenAt = -Infinity;
7844
- let detailTimer = null;
7845
- const flushDetail = () => {
7846
- detailTimer = null;
7847
- detailWrittenAt = now();
7848
- const detail = parser.detail(new Date(detailWrittenAt).toISOString());
7849
- if (detail) guard(() => writeBuildDetail(root, record.claim.claimId, detail));
7850
- };
7851
- const settle = (outcome) => {
7852
- record.outcome = outcome;
7853
- if (projectKey && platform !== "macos") record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
7854
- };
7855
- const waits = /* @__PURE__ */ new Map();
7856
- return {
7857
- step(phase) {
7858
- if (phase === record.phase) return;
7859
- const at = new Date(now()).toISOString();
7860
- record.phase = phase;
7861
- record.phaseStartedAt = at;
7862
- record.phases.push({
7863
- phase,
7864
- startedAt: at
7865
- });
7866
- if (phase !== "wait") delete record.waitingOn;
7867
- delete record.activity;
7868
- const outcome = record.outcome || platform === "macos" ? null : settledOutcome(phase, record.phases);
7869
- if (outcome) settle(outcome);
7870
- write();
7871
- },
7872
- activity(name, at) {
7873
- if ((record.activity?.name ?? null) === name) return;
7874
- if (name) record.activity = {
7875
- name,
7876
- startedAt: new Date(at ?? now()).toISOString()
7877
- };
7878
- else delete record.activity;
7879
- write();
7880
- },
7881
- estimate(key) {
7882
- if (platform === "macos") return;
7883
- projectKey = key;
7884
- record.estimate = estimateBuild(projectHistory(key), platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
7885
- write();
7886
- },
7887
- miss(reason, provisional = false) {
7888
- record.missReason = reason;
7889
- settle("cold");
7890
- if (provisional) record.missProvisional = true;
7891
- else delete record.missProvisional;
7892
- write();
7893
- },
7894
- hit() {
7895
- delete record.missReason;
7896
- delete record.missProvisional;
7897
- settle("hit");
7898
- write();
7899
- },
7900
- deviceSetup(setup) {
7901
- if (setup === void 0) return;
7902
- record.deviceSetup = setup;
7903
- write();
7904
- },
7905
- deviceSetupKnown() {
7906
- return record.deviceSetup;
7907
- },
7908
- place(remote) {
7909
- const current = record.placement;
7910
- if (!remote) {
7911
- if (!current) return;
7912
- delete record.placement;
7913
- return write();
7914
- }
7915
- if (current?.host === remote.host && current.phase === remote.phase) return;
7916
- const at = new Date(now()).toISOString();
7917
- record.placement = {
7918
- host: remote.host,
7919
- phase: remote.phase,
7920
- startedAt: current?.host === remote.host ? current.startedAt : at,
7921
- phaseStartedAt: at
7922
- };
7923
- write();
7924
- },
7925
- waitingFor(info, kind) {
7926
- if (info) waits.set(info.kind, info);
7927
- else if (kind) waits.delete(kind);
7928
- else waits.clear();
7929
- const first = [...waits.values()].toSorted((a, b) => Date.parse(a.since) - Date.parse(b.since))[0];
7930
- if (first) record.waitingFor = first;
7931
- else delete record.waitingFor;
7932
- write();
7933
- },
7934
- waitingOn(holder) {
7935
- if (holder === null) {
7936
- if (!record.waitingOn) return;
7937
- delete record.waitingOn;
7938
- } else {
7939
- if (record.waitingOn?.path === holder) return;
7940
- record.waitingOn = { path: holder };
7421
+ const writer = openRetainedLog(gcLogFile(), resolveMaintenanceSettings(loadConfig$1(), run.env));
7422
+ try {
7423
+ if (!writer.write(record)) throw writer.lastError ?? /* @__PURE__ */ new Error("write failed");
7424
+ } finally {
7425
+ writer.close();
7941
7426
  }
7942
- write();
7943
- },
7944
- output(line) {
7945
- const msg = toolLine(line);
7946
- if (msg === null || !parser.push(msg) || detailTimer) return;
7947
- const wait = detailWrittenAt + DETAIL_WRITE_MS - now();
7948
- if (wait <= 0) return flushDetail();
7949
- detailTimer = setTimeout(flushDetail, wait);
7950
- detailTimer.unref?.();
7951
- },
7952
- durations() {
7953
- return phaseDurations(record.phases, now());
7954
- },
7955
- phase() {
7956
- return record.phase;
7957
- },
7958
- steps() {
7959
- const detail = parser.detail("");
7960
- return detail?.unit === "steps" ? detail.total : null;
7961
- },
7962
- clear() {
7963
- if (detailTimer) clearTimeout(detailTimer);
7964
- detailTimer = null;
7965
- guard(() => rmSync(workspaceBuildDetailFile(root), { force: true }));
7966
- guard(() => {
7967
- clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
7968
- });
7969
- }
7970
- };
7971
- }
7972
- function phaseDurations(phases, now) {
7973
- const out = {};
7974
- phases.forEach(({ phase, startedAt }, index) => {
7975
- const start = Date.parse(startedAt);
7976
- const next = phases[index + 1];
7977
- const end = next ? Date.parse(next.startedAt) : now;
7978
- if (!Number.isFinite(start) || !Number.isFinite(end)) return;
7979
- out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
7980
- });
7981
- return out;
7982
- }
7983
- function completedPhaseDurations(phases) {
7984
- if (phases.length < 2) return void 0;
7985
- const durations = phaseDurations(phases.slice(0, -1), Date.parse(phases.at(-1).startedAt));
7986
- return Object.keys(durations).length ? durations : void 0;
7987
- }
7988
- const HISTORY_FIELDS = [
7989
- "devicePlacement",
7990
- "platform",
7991
- "status",
7992
- "configuration",
7993
- "fingerprint",
7994
- "cacheKey",
7995
- "cacheHit",
7996
- "cacheSkipped",
7997
- "durationMs",
7998
- "startedAt",
7999
- "errorCode",
8000
- "missReason",
8001
- "buildMachine",
8002
- "builtOn",
8003
- "offloadedTo",
8004
- "offloadFallback",
8005
- "diagnostics",
8006
- "compileSteps"
8007
- ];
8008
- function withHistoryEntry(state, platform, entry) {
8009
- const saved = state[BUILD_HISTORY_KEY];
8010
- const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
8011
- const list = Array.isArray(history[platform]) ? history[platform] : [];
8012
- return {
8013
- ...state,
8014
- [BUILD_HISTORY_KEY]: {
8015
- ...history,
8016
- [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
7427
+ } catch (error) {
7428
+ if (failed) return;
7429
+ failed = true;
7430
+ console.error(`Could not write the gc log ${gcLogFile()}: ${error instanceof Error ? error.message : error}`);
8017
7431
  }
8018
7432
  };
8019
- }
8020
- function finishedResult(record) {
8021
- if (record.status === "ok") return "succeeded";
8022
- return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
8023
- }
8024
- function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now, artifactOnly = false } = {}) {
8025
- const platform = record.platform;
8026
- update(root, (state) => {
8027
- const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
8028
- const own = active?.platform === platform ? active : null;
8029
- const entry = {
8030
- ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
8031
- result: finishedResult(record),
8032
- slot: own?.slot ?? "default",
8033
- phases: own ? phaseDurations(own.phases, now()) : {}
8034
- };
8035
- if (artifactOnly || platform === "macos") return withHistoryEntry(state, platform, entry);
8036
- const slot = own?.slot ?? "default";
8037
- const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
8038
- const savedSlot = isJsonObject(slots[slot]) ? slots[slot] : {};
8039
- const savedDevice = slot === "default" ? state[platform] : savedSlot[platform];
8040
- if (record.devicePlacement || isJsonObject(savedDevice)) {
8041
- const device = isJsonObject(savedDevice) ? { ...savedDevice } : {};
8042
- if (record.devicePlacement) device.devicePlacement = record.devicePlacement;
8043
- else delete device.devicePlacement;
8044
- state = slot === "default" ? {
8045
- ...state,
8046
- [platform]: device
8047
- } : {
8048
- ...state,
8049
- deviceSlots: {
8050
- ...slots,
8051
- [slot]: {
8052
- ...savedSlot,
8053
- [platform]: device
8054
- }
8055
- }
8056
- };
8057
- }
8058
- return withHistoryEntry({
8059
- ...state,
8060
- lastBuild: record,
8061
- [LAST_BUILD_KEYS[platform]]: record
8062
- }, platform, entry);
8063
- });
8064
- }
8065
- /** Adds one phase's duration to the recorded run of `platform` that started at `startedAt`. */
8066
- function recordBuildPhase(root, platform, startedAt, phase, ms) {
8067
- updateWorkspaceState(root, (state) => {
8068
- const saved = state[BUILD_HISTORY_KEY];
8069
- const list = isJsonObject(saved) && Array.isArray(saved[platform]) ? saved[platform] : [];
8070
- const index = list.findIndex((entry) => isJsonObject(entry) && entry.startedAt === startedAt);
8071
- const entry = list[index];
8072
- if (!isJsonObject(entry)) return state;
8073
- const phases = isJsonObject(entry.phases) ? entry.phases : {};
8074
- const next = [...list];
8075
- next[index] = {
8076
- ...entry,
8077
- phases: {
8078
- ...phases,
8079
- [phase]: Math.round(ms)
8080
- }
8081
- };
8082
- return {
8083
- ...state,
8084
- [BUILD_HISTORY_KEY]: {
8085
- ...saved,
8086
- [platform]: next
8087
- }
8088
- };
8089
- });
8090
- }
8091
- function withInterruptedBuild(state) {
8092
- const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
8093
- if (!left) return state;
8094
- const last = state[BUILD_HISTORY_KEY]?.[left.platform]?.[0];
8095
- if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
8096
- return withHistoryEntry(state, left.platform, {
8097
- platform: left.platform,
8098
- status: "failed",
8099
- result: "interrupted",
8100
- slot: left.slot,
8101
- configuration: null,
8102
- fingerprint: null,
8103
- cacheKey: null,
8104
- cacheHit: false,
8105
- cacheSkipped: false,
8106
- durationMs: null,
8107
- startedAt: left.startedAt,
8108
- phases: {
8109
- ...phaseDurations(left.phases, Date.parse(left.phaseStartedAt)),
8110
- [left.phase]: 0
7433
+ return { record(result) {
7434
+ const record = gcLogRecord(run, result, Date.now());
7435
+ if (!acted && result.status === "kept") {
7436
+ held.push(record);
7437
+ return;
8111
7438
  }
8112
- });
8113
- }
8114
- function parseActiveBuild(value) {
8115
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8116
- const record = value;
8117
- const claim = record.claim;
8118
- if (record.platform !== "ios" && record.platform !== "android" && record.platform !== "macos") return null;
8119
- if (!isPhase(record.phase)) return null;
8120
- if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
8121
- if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
8122
- const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
8123
- const missReason = parseMissReason(record.missReason);
8124
- const placement = parsePlacement(record.placement);
8125
- const waitingOn = parseWaitingOn(record.waitingOn);
8126
- const waitingFor = parseWaitingFor(record.waitingFor);
8127
- const estimate = parseEstimate(record.estimate);
8128
- const activity = parseActivity(record.activity);
8129
- return {
8130
- platform: record.platform,
8131
- slot: typeof record.slot === "string" ? record.slot : "default",
8132
- startedAt: record.startedAt,
8133
- phase: record.phase,
8134
- phaseStartedAt: record.phaseStartedAt,
8135
- phases,
8136
- ...missReason ? { missReason } : {},
8137
- ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
8138
- ...placement ? { placement } : {},
8139
- ...waitingOn ? { waitingOn } : {},
8140
- ...waitingFor ? { waitingFor } : {},
8141
- ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
8142
- ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
8143
- ...estimate ? { estimate } : {},
8144
- ...activity ? { activity } : {},
8145
- claim: {
8146
- root: claim.root,
8147
- path: typeof claim.path === "string" ? claim.path : "",
8148
- claimId: claim.claimId,
8149
- pid: typeof claim.pid === "number" ? claim.pid : 0
7439
+ if (!acted) {
7440
+ acted = true;
7441
+ for (const earlier of held.splice(0)) append(earlier);
8150
7442
  }
8151
- };
8152
- }
8153
- function parsePlacement(value) {
8154
- if (!value || typeof value !== "object") return null;
8155
- const { host, phase, startedAt, phaseStartedAt } = value;
8156
- if ([
8157
- host,
8158
- phase,
8159
- startedAt,
8160
- phaseStartedAt
8161
- ].some((field) => typeof field !== "string")) return null;
8162
- return {
8163
- host,
8164
- phase,
8165
- startedAt,
8166
- phaseStartedAt
8167
- };
7443
+ append(record);
7444
+ } };
8168
7445
  }
8169
- function parseWaitingFor(value) {
8170
- if (!value || typeof value !== "object") return null;
8171
- const { kind, inUse, max, since } = value;
8172
- if (kind !== "build-slot" && kind !== "device-slot") return null;
8173
- if (!Number.isInteger(inUse) || inUse < 0 || !Number.isInteger(max) || max <= 0) return null;
8174
- if (typeof since !== "string" || !Number.isFinite(Date.parse(since))) return null;
7446
+ const MAINTENANCE_KINDS = {
7447
+ "clear-outputs": "workspaceOutputs",
7448
+ "trim-cache": "cache",
7449
+ "empty-cache": "cache",
7450
+ "unregister-cache": "cache",
7451
+ "remove-worktree": "worktree",
7452
+ "remove-orphan": "workspaceDirectory",
7453
+ "delete-parked-device": "parkedDevice",
7454
+ "delete-devices": "device",
7455
+ "stop-workspace": "device",
7456
+ "shutdown-device": "idleDevice",
7457
+ "delete-temp-leftovers": "tempLeftover",
7458
+ "stop-gradle-daemons": "gradleDaemon",
7459
+ "delete-gradle-version": "gradleVersion",
7460
+ "clear-build-dirs": "buildDirectory",
7461
+ "delete-dependencies": "dependencies"
7462
+ };
7463
+ function maintenanceItem(action, outcome) {
7464
+ const name = action.kind.replace(/^would-/, "");
7465
+ const done = outcome.status === "done";
8175
7466
  return {
8176
- kind,
8177
- inUse,
8178
- max,
8179
- since
7467
+ kind: MAINTENANCE_KINDS[name] ?? name,
7468
+ status: outcome.status,
7469
+ label: action.target,
7470
+ id: action.dir ?? action.workspace ?? null,
7471
+ bytes: done && outcome.bytes ? outcome.bytes : null,
7472
+ detail: done ? outcome.detail ? `${action.reason} (${outcome.detail})` : action.reason : outcome.detail
8180
7473
  };
8181
7474
  }
8182
- function parseActivity(value) {
8183
- if (!value || typeof value !== "object") return null;
8184
- const { name, startedAt } = value;
8185
- if (name !== "booting" && name !== "bundling" && name !== "waiting-ready") return null;
8186
- return typeof startedAt === "string" ? {
8187
- name,
8188
- startedAt
8189
- } : null;
8190
- }
8191
- function parseWaitingOn(value) {
8192
- const path = value?.path;
8193
- return typeof path === "string" && path ? { path } : null;
8194
- }
8195
- function parseEstimate(value) {
8196
- if (!value || typeof value !== "object") return null;
8197
- const { outcome, expectedMs, basis, phaseMs, planned } = value;
8198
- if (outcome !== null && outcome !== "hit" && outcome !== "cold") return null;
8199
- if (expectedMs !== null && typeof expectedMs !== "number") return null;
8200
- if (typeof basis !== "number" || !Array.isArray(planned) || !phaseMs || typeof phaseMs !== "object") return null;
8201
- const durations = Object.entries(phaseMs).filter((entry) => isPhase(entry[0]) && typeof entry[1] === "number");
7475
+ function gcOptionsOf(opts) {
8202
7476
  return {
8203
- outcome,
8204
- expectedMs,
8205
- basis,
8206
- phaseMs: Object.fromEntries(durations),
8207
- planned: planned.filter(isPhase)
8208
- };
8209
- }
8210
- function isPhase(value) {
8211
- return BUILD_PHASES.includes(value);
8212
- }
8213
- function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
8214
- if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
8215
- if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
8216
- return survey.unresolved.length ? "unknown" : "stale";
8217
- }
8218
- function median(values) {
8219
- if (!values.length) return null;
8220
- const sorted = values.toSorted((a, b) => a - b);
8221
- const mid = Math.floor(sorted.length / 2);
8222
- return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
8223
- }
8224
- function estimateBuild(history, platform, known, deviceSetup) {
8225
- const lists = platform === "macos" ? void 0 : history?.[platform];
8226
- const outcome = known ?? latestOutcome(lists);
8227
- const samples = matchDeviceSetup(outcome && lists?.[outcome] || [], deviceSetup);
8228
- const phaseMs = {};
8229
- const planned = [];
8230
- for (const phase of BUILD_PHASES) {
8231
- const durations = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
8232
- const ms = median(durations);
8233
- if (ms === null) continue;
8234
- phaseMs[phase] = ms;
8235
- if (durations.length * 2 >= samples.length) planned.push(phase);
8236
- }
8237
- return {
8238
- outcome,
8239
- expectedMs: median(samples.map((sample) => sample.durationMs)),
8240
- basis: samples.length,
8241
- phaseMs,
8242
- planned
8243
- };
8244
- }
8245
- const MIN_TAGGED_SAMPLES = 3;
8246
- function matchDeviceSetup(samples, deviceSetup) {
8247
- if (deviceSetup === void 0) return samples;
8248
- const tagged = samples.filter((sample) => sample.deviceSetup === deviceSetup);
8249
- if (deviceSetup || tagged.length >= MIN_TAGGED_SAMPLES) return tagged;
8250
- return samples.filter((sample) => sample.deviceSetup !== true);
8251
- }
8252
- function latestOutcome(lists) {
8253
- let latest = null;
8254
- for (const outcome of ["hit", "cold"]) {
8255
- const last = lists?.[outcome]?.at(-1);
8256
- const at = last ? Date.parse(last.at) : NaN;
8257
- if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
8258
- outcome,
8259
- at
8260
- };
8261
- }
8262
- return latest?.outcome ?? null;
8263
- }
8264
- function buildReport(record, { state, history }) {
8265
- const estimate = record.estimate ?? estimateBuild(history, record.platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
8266
- const plannedPhases = estimate.planned.flatMap((phase) => {
8267
- const expectedMs = estimate.phaseMs[phase];
8268
- return expectedMs === void 0 ? [] : [{
8269
- phase,
8270
- expectedMs
8271
- }];
8272
- });
8273
- const completedPhaseMs = completedPhaseDurations(record.phases);
8274
- return {
8275
- platform: record.platform,
8276
- slot: record.slot,
8277
- state,
8278
- phase: record.phase,
8279
- startedAt: record.startedAt,
8280
- phaseStartedAt: record.phaseStartedAt,
8281
- outcome: estimate.outcome,
8282
- outcomeKnown: record.outcome !== void 0,
8283
- ...record.outcome && record.phases.some(({ phase }) => phase === "cache-lookup") ? { cacheLookupOutcome: record.outcome === "hit" ? "hit" : "miss" } : {},
8284
- expectedMs: estimate.expectedMs,
8285
- expectedPhaseMs: estimate.phaseMs[record.phase] ?? null,
8286
- ...completedPhaseMs ? { completedPhaseMs } : {},
8287
- basis: estimate.basis,
8288
- plannedPhases: plannedPhases.length ? plannedPhases : null,
8289
- ...record.missReason ? { missReason: record.missReason } : {},
8290
- ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
8291
- placement: record.placement ?? "local",
8292
- ...record.waitingOn ? { waitingOn: record.waitingOn } : {},
8293
- ...record.waitingFor ? { waitingFor: record.waitingFor } : {},
8294
- ...record.activity ? { activity: { ...record.activity } } : {}
7477
+ ...opts.delete ? { delete: true } : {},
7478
+ ...opts.worktrees ? { worktrees: true } : {},
7479
+ ...opts.olderThan === void 0 ? {} : { olderThan: opts.olderThan },
7480
+ ...opts.cache === void 0 ? {} : { cache: opts.cache },
7481
+ ...opts.idle === void 0 ? {} : { idleMs: opts.idle }
8295
7482
  };
8296
7483
  }
7484
+ //#endregion
7485
+ //#region src/maintenance/pressure.ts
8297
7486
  /**
8298
- * The run's `bundling` activity with Metro's percent, when the request Metro reports in flight is the one the activity
8299
- * started on: same platform and same start, since both come from the request's `bundle_response_started` record.
7487
+ * The free-disk floor of each level in MiB. Level 2 has no floor of its own: it runs when level 1 left free disk
7488
+ * under the budget floor, and stops once free disk is back above it. Its urgent threshold only sets the reported level.
8300
7489
  */
8301
- function bundlingActivity(activity, platform, bundle) {
8302
- if (activity?.name !== "bundling" || !bundle?.bundling || bundle.percent === void 0) return activity;
8303
- if (bundle.platform !== platform || bundle.startedAt !== activity.startedAt) return activity;
8304
- return {
8305
- ...activity,
8306
- percent: bundle.percent
8307
- };
8308
- }
8309
- function remainingText(remainingMs) {
8310
- return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
8311
- }
8312
- function activityText({ name, percent, startedAt }, now) {
8313
- return `${name === "booting" ? "booting" : name === "waiting-ready" ? "waiting for app ready" : percent === void 0 ? "bundling JS" : `bundling JS ${percent}%`}, ${formatElapsed(Math.max(0, now - Date.parse(startedAt)))}`;
8314
- }
8315
- function buildStatusLine(report, now) {
8316
- const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
8317
- if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
8318
- const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
8319
- const remote = typeof report.placement === "object" ? ` on ${report.placement.host} (${report.placement.phase}, ${formatElapsed(Math.max(0, now - Date.parse(report.placement.phaseStartedAt)))})` : "";
8320
- const waiting = report.waitingOn ? ` on ${report.waitingOn.path}` : "";
8321
- const activity = report.activity ? ` (${activityText(report.activity, now)})` : "";
8322
- const head = `build: ${report.platform}${slot} ${report.phase}${activity}${waiting}${remote}, ${formatElapsed(elapsedMs)} elapsed`;
8323
- if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
8324
- if (report.expectedMs === null) return head;
8325
- const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
8326
- const remainingMs = report.expectedMs - elapsedMs;
8327
- return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
8328
- }
8329
- //#endregion
8330
- //#region src/commands/gc/idle.ts
8331
- function findOwnedDeviceActivity({ config, sims, androidSerial, readActivity, deadProjects = [] }) {
8332
- const booted = new Map(sims.filter((sim) => sim.state === "Booted").map((sim) => [sim.udid, sim.name]));
8333
- const dead = new Set(deadProjects);
8334
- const found = [];
8335
- for (const [project, record] of Object.entries(config?.projects ?? {})) {
8336
- if (dead.has(project)) continue;
8337
- for (const { slot, platforms } of projectDeviceSlots(record)) {
8338
- const ios = platforms.ios;
8339
- if (ios?.owned && ios.deviceUdid && booted.has(ios.deviceUdid)) found.push({
8340
- kind: "ios",
8341
- id: ios.deviceUdid,
8342
- name: booted.get(ios.deviceUdid) ?? ios.deviceUdid,
8343
- project,
8344
- slot,
8345
- activity: readActivity({
8346
- platform: "ios",
8347
- id: ios.deviceUdid,
8348
- slot,
8349
- workspace: project
8350
- })
8351
- });
8352
- const android = platforms.android;
8353
- const serial = android?.owned && android.avdName ? androidSerial(android.avdName) : null;
8354
- if (android?.avdName && serial) found.push({
8355
- kind: "android",
8356
- id: android.avdName,
8357
- name: android.avdName,
8358
- project,
8359
- slot,
8360
- activity: readActivity({
8361
- platform: "android",
8362
- id: serial,
8363
- slot,
8364
- workspace: project
8365
- })
8366
- });
8367
- }
8368
- }
8369
- return found;
8370
- }
8371
- function findIdleDevices({ buildInProgress, now, ...inputs }) {
8372
- return idleDevicesFromActivity(findOwnedDeviceActivity(inputs), buildInProgress, now);
8373
- }
8374
- function idleDevicesFromActivity(devices, buildInProgress, now) {
8375
- return devices.filter((device) => device.activity.state === "idle").map(({ kind, id, name, project, slot, activity }) => ({
8376
- kind,
8377
- id,
8378
- name,
8379
- project,
8380
- slot,
8381
- lastActivityAt: activity.lastActivityAt ?? null,
8382
- idleForMs: idleForMs(activity, now),
8383
- buildInProgress: buildInProgress(project)
8384
- }));
8385
- }
8386
- function idleShutdownCandidates(devices, idleMs) {
8387
- return devices.filter((d) => !d.buildInProgress && d.idleForMs !== null && d.idleForMs >= idleMs);
8388
- }
8389
- function parseIdleDuration(value) {
8390
- const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value.trim());
8391
- if (!match) return null;
8392
- const unit = {
8393
- m: 6e4,
8394
- h: 36e5,
8395
- d: 864e5
8396
- }[match[2]];
8397
- return Number(match[1]) * unit;
8398
- }
8399
- function workspaceBuildInProgress(project) {
8400
- const record = parseActiveBuild(readWorkspaceState$1(project)?.[ACTIVE_BUILD_KEY]);
8401
- if (!record) return false;
8402
- try {
8403
- return activeBuildState(record.claim) !== "stale";
8404
- } catch {
8405
- return true;
8406
- }
8407
- }
8408
- function deviceActivityInputs(config, sims, now, env) {
8409
- const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
7490
+ function levelFloors(budget, settings) {
7491
+ const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
8410
7492
  return {
8411
- config,
8412
- sims,
8413
- readActivity: createActivityReader({
8414
- now,
8415
- env
8416
- }),
8417
- androidSerial: (avdName) => {
8418
- try {
8419
- return resolve(avdName).serial ?? null;
8420
- } catch {
8421
- return null;
8422
- }
8423
- }
7493
+ 1: floor,
7494
+ 2: floor,
7495
+ 3: Math.max(settings.criticalFreeDiskGb * 1024, budget.hardFloorDiskMb),
7496
+ urgent: settings.urgentFreeDiskGb * 1024
8424
7497
  };
8425
7498
  }
8426
- function collectOwnedDeviceActivity(config, sims, now = Date.now(), env) {
8427
- return findOwnedDeviceActivity(deviceActivityInputs(config, sims, now, env));
8428
- }
8429
- function collectIdleDevices(config, sims, deadProjects, now = Date.now(), env) {
8430
- return findIdleDevices({
8431
- ...deviceActivityInputs(config, sims, now, env),
8432
- deadProjects,
8433
- now,
8434
- buildInProgress: workspaceBuildInProgress
8435
- });
7499
+ function diskPressureLevel(freeMb, budget, settings) {
7500
+ const floors = levelFloors(budget, settings);
7501
+ if (freeMb < floors[3]) return 3;
7502
+ if (freeMb < floors.urgent) return 2;
7503
+ return freeMb < floors[1] ? 1 : 0;
8436
7504
  }
8437
- async function deviceActivityInputsAsync(config, sims, now, env, deadProjects = [], signal, operation) {
8438
- const resolve = ownedAvdSerialResolverAsync({
8439
- timeoutMs: 5e3,
8440
- signal,
8441
- operation
8442
- });
8443
- const serials = /* @__PURE__ */ new Map();
8444
- for (const [project, record] of Object.entries(config?.projects ?? {})) {
8445
- signal?.throwIfAborted();
8446
- if (deadProjects.includes(project)) continue;
8447
- for (const { platforms } of projectDeviceSlots(record)) {
8448
- const android = platforms.android;
8449
- if (!android?.owned || !android.avdName || serials.has(android.avdName)) continue;
8450
- try {
8451
- serials.set(android.avdName, (await resolve(android.avdName)).serial ?? null);
8452
- } catch (error) {
8453
- if (error instanceof WorkspaceProcessCleanupError) throw error;
8454
- signal?.throwIfAborted();
8455
- serials.set(android.avdName, null);
8456
- }
8457
- }
8458
- }
8459
- return {
8460
- config,
8461
- sims,
8462
- readActivity: createActivityReaderAsync({
8463
- now,
8464
- env,
8465
- signal,
8466
- operation
8467
- }),
8468
- androidSerial: (name) => serials.get(name) ?? null
8469
- };
7505
+ function lowestFree(disk) {
7506
+ return disk.length ? Math.min(...disk.map((volume) => volume.freeMb)) : Infinity;
8470
7507
  }
8471
- async function collectOwnedDeviceActivityAsync(config, sims, now = Date.now(), env, signal, operation) {
8472
- const devices = findOwnedDeviceActivity(await deviceActivityInputsAsync(config, sims, now, env, [], signal, operation));
8473
- return Promise.all(devices.map(async (device) => Object.assign({}, device, { activity: await device.activity })));
7508
+ /** Whether an action planned at a pressure level may still run with this much free disk. */
7509
+ function levelStillShort(action, freeMb, budget, settings) {
7510
+ return action.level === void 0 || freeMb < levelFloors(budget, settings)[action.level];
8474
7511
  }
8475
- async function collectIdleDevicesAsync(config, sims, deadProjects, now = Date.now(), env, signal, operation) {
8476
- const pending = findOwnedDeviceActivity({
8477
- ...await deviceActivityInputsAsync(config, sims, now, env, deadProjects, signal, operation),
8478
- deadProjects
8479
- });
8480
- return idleDevicesFromActivity(await Promise.all(pending.map(async (device) => Object.assign({}, device, { activity: await device.activity }))), workspaceBuildInProgress, now);
8481
- }
8482
- function idleDeviceLines(devices) {
8483
- if (devices.length === 0) return [];
8484
- return [`Idle owned devices (${devices.length}) - booted with no driver, claim, or recent activity:`, ...devices.map((d) => {
8485
- const slot = d.slot === "default" ? "" : ` [${d.slot}]`;
8486
- const idle = d.idleForMs === null ? "idle for an unknown time" : `idle ${formatLongDuration(d.idleForMs)}`;
8487
- const build = d.buildInProgress ? ", build in progress" : "";
8488
- return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
8489
- })];
8490
- }
8491
- async function shutDownIdleDevices(devices, idleMs, recollect, { log, record } = GC_OUTPUT, signal, operation) {
8492
- signal?.throwIfAborted();
8493
- const candidates = idleShutdownCandidates(devices, idleMs);
8494
- const shutDown = [];
8495
- if (candidates.length === 0) {
8496
- log(chalk.dim(`No owned device has been idle for ${formatLongDuration(idleMs)} or more.`));
8497
- return {
8498
- failures: 0,
8499
- shutDown
8500
- };
8501
- }
8502
- const fresh = idleShutdownCandidates(await recollect(), idleMs);
8503
- signal?.throwIfAborted();
8504
- let failures = 0;
8505
- for (const device of candidates) {
8506
- signal?.throwIfAborted();
8507
- const what = `${device.kind} ${device.name}`;
8508
- const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
8509
- if (!current) {
8510
- log(chalk.dim(`Kept ${what}: it is no longer idle for ${formatLongDuration(idleMs)}.`));
8511
- record("idleDevice", "kept", device.name, {
8512
- id: device.id,
8513
- detail: `it is no longer idle for ${formatLongDuration(idleMs)}`
8514
- });
8515
- continue;
8516
- }
8517
- const outcome = device.kind === "ios" ? await teardownOwnedIosSim(device.id, {
8518
- label: device.name,
8519
- operation
8520
- }) : await teardownOwnedAvd(device.id, {
8521
- owner: {
8522
- projectPath: device.project,
8523
- slot: device.slot
8524
- },
8525
- operation
8526
- });
8527
- if (outcome.status === "torn-down") {
8528
- shutDown.push(device);
8529
- log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
8530
- record("idleDevice", "done", device.name, {
8531
- id: device.id,
8532
- detail: `idle ${formatLongDuration(current.idleForMs)}`
8533
- });
8534
- } else if (outcome.status === "missing") log(chalk.dim(`${what} is already gone; nothing to shut down.`));
8535
- else if (outcome.status === "skipped") {
8536
- log(chalk.yellow(`Skipped ${what}: ${outcome.reason}`));
8537
- record("idleDevice", "kept", device.name, {
8538
- id: device.id,
8539
- detail: outcome.reason
8540
- });
8541
- } else {
8542
- failures++;
8543
- log(chalk.red(`Failed to shut down ${what}: ${outcome.reason}`));
8544
- record("idleDevice", "failed", device.name, {
8545
- id: device.id,
8546
- detail: outcome.reason
8547
- });
8548
- }
8549
- }
8550
- return {
8551
- failures,
8552
- shutDown
8553
- };
7512
+ /** The protection window in milliseconds: protectRecentHours, or maintenance.pressureProtectMinutes for actions of level 2 and 3. */
7513
+ function protectWindowMs(action, settings) {
7514
+ return action.level === void 0 ? settings.protectRecentHours * 36e5 : settings.pressureProtectMinutes * 6e4;
8554
7515
  }
8555
7516
  //#endregion
8556
7517
  //#region src/supervisor/stop-cause.ts
@@ -8929,7 +7890,7 @@ function isOtherWorkspace(root) {
8929
7890
  function deviceLabel(device) {
8930
7891
  return `${device.kind} ${device.name} in ${device.project}`;
8931
7892
  }
8932
- async function reclaimIdleDevices({ root, dryRun, note, now, env, signal, operation }) {
7893
+ async function reclaimIdleDevices({ root, dryRun, note, record, now, env, signal, operation }) {
8933
7894
  if (deviceSweepIsScoped(env)) {
8934
7895
  note(chalk.dim(phaseLine("budget", "idle devices left alone: STIM_HOME scopes this config, devices are global")));
8935
7896
  return {
@@ -8949,7 +7910,7 @@ async function reclaimIdleDevices({ root, dryRun, note, now, env, signal, operat
8949
7910
  targets: idle.map(deviceLabel),
8950
7911
  failures: 0
8951
7912
  };
8952
- const { failures, shutDown } = await shutDownIdleDevices(idle, 0, () => collect(Date.now()), stepOutput(note), signal, operation);
7913
+ const { failures, shutDown } = await shutDownIdleDevices(idle, 0, () => collect(Date.now()), stepOutput(note, record), signal, operation);
8953
7914
  return {
8954
7915
  targets: shutDown.map(deviceLabel),
8955
7916
  failures
@@ -8960,7 +7921,7 @@ function idleSince(path, now) {
8960
7921
  const at = Date.parse(activity.lastActivityAt ?? "");
8961
7922
  return activity.state === "idle" && Number.isFinite(at) ? at : null;
8962
7923
  }
8963
- async function reclaimIdleDevServers({ root, dryRun, note, now, env, signal, operation }) {
7924
+ async function reclaimIdleDevServers({ root, dryRun, note, record, now, env, signal, operation }) {
8964
7925
  const config = loadConfig$1();
8965
7926
  const other = isOtherWorkspace(root);
8966
7927
  const serving = Object.entries(config?.projects ?? {}).flatMap(([path, project]) => {
@@ -9029,22 +7990,27 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env, signal, ope
9029
7990
  signal?.throwIfAborted();
9030
7991
  failures++;
9031
7992
  note(chalk.red(`Could not stop the dev server of ${path}: ${error?.message || error}`));
7993
+ record?.("devServer", "failed", path, { detail: error instanceof Error ? error.message : String(error) });
9032
7994
  continue;
9033
7995
  }
9034
7996
  if (!run.ran) {
9035
7997
  note(chalk.yellow(`Kept the dev server of ${path}: ${run.reasons.join("; ")}`));
7998
+ record?.("devServer", "kept", path, { detail: run.reasons.join("; ") });
9036
7999
  continue;
9037
8000
  }
9038
8001
  const stopped = run.value;
9039
8002
  if (stopped === "busy" || stopped === "starting") {
9040
8003
  const why = stopped === "busy" ? "it is no longer idle" : "a stim start holds its metro-start lock";
9041
8004
  note(chalk.dim(`Kept the dev server of ${path}: ${why}`));
8005
+ record?.("devServer", "kept", path, { detail: why });
9042
8006
  } else if (stopped.status === "stopped") {
9043
8007
  targets.push(path);
9044
8008
  note(chalk.green(`Stopped the idle dev server of ${path}`));
8009
+ record?.("devServer", "done", path);
9045
8010
  } else if ("reason" in stopped) {
9046
8011
  if (stopped.status === "stuck") failures++;
9047
8012
  note(chalk.yellow(`Kept the dev server of ${path}: ${stopped.reason}`));
8013
+ record?.("devServer", stopped.status === "stuck" ? "failed" : "kept", path, { detail: stopped.reason });
9048
8014
  }
9049
8015
  }
9050
8016
  return {
@@ -9052,7 +8018,7 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env, signal, ope
9052
8018
  failures
9053
8019
  };
9054
8020
  }
9055
- async function reclaimWorkspaceOutputs({ root, dryRun, now, note, diskRecovered, signal, operation }) {
8021
+ async function reclaimWorkspaceOutputs({ root, dryRun, now, note, record, diskRecovered, signal, operation }) {
9056
8022
  const other = isOtherWorkspace(root);
9057
8023
  const report = await collectWorkspaceOutputs({
9058
8024
  olderThan: null,
@@ -9084,7 +8050,7 @@ async function reclaimWorkspaceOutputs({ root, dryRun, now, note, diskRecovered,
9084
8050
  now,
9085
8051
  signal,
9086
8052
  operation
9087
- }, stepOutput(note));
8053
+ }, stepOutput(note, record));
9088
8054
  failures += result.failures;
9089
8055
  targets.push(...result.cleared);
9090
8056
  if (await diskRecovered()) break;
@@ -9100,23 +8066,36 @@ function cacheLabel(cache) {
9100
8066
  function trimmableCaches(env = process.env, cwd) {
9101
8067
  return planCacheEmptying(discoverCaches(env, cwd), false, env).filter((cache) => cache.prune !== "report-only" && cache.prune !== "atomic" && !cache.machineGlobal);
9102
8068
  }
9103
- async function reclaimStaleCacheEntries({ dryRun, note, env, cwd, signal }) {
8069
+ async function reclaimStaleCacheEntries({ dryRun, note, record, env, cwd, now, freeMb, signal }) {
9104
8070
  const caches = trimmableCaches(env, cwd);
9105
8071
  if (dryRun) return {
9106
8072
  targets: caches.map(cacheLabel),
9107
8073
  failures: 0
9108
8074
  };
9109
- const { trimmed, failures } = await trimCaches(caches, 14, stepOutput(note), signal);
8075
+ const current = {
8076
+ at: now,
8077
+ freeMb,
8078
+ marker: sweepMarker(caches, 14)
8079
+ };
8080
+ if (canSkipEmptySweep(readEmptySweep(), current)) {
8081
+ note(chalk.dim(phaseLine("budget", `cache trim skipped: the last one, under ${EMPTY_SWEEP_WINDOW_MS / 6e4} minutes ago with the same caches and about the same free disk, found nothing older than 14d`)));
8082
+ return {
8083
+ targets: [],
8084
+ failures: 0
8085
+ };
8086
+ }
8087
+ const { trimmed, failures } = await trimCaches(caches, 14, stepOutput(note, record), signal);
8088
+ writeEmptySweep(trimmed.length === 0 && failures === 0 ? current : null);
9110
8089
  return {
9111
8090
  targets: trimmed.map(cacheLabel),
9112
8091
  failures
9113
8092
  };
9114
8093
  }
9115
- function stepOutput(note) {
8094
+ function stepOutput(note, record = () => {}) {
9116
8095
  return {
9117
8096
  log: note,
9118
8097
  error: note,
9119
- record: () => {},
8098
+ record,
9120
8099
  fail: () => {}
9121
8100
  };
9122
8101
  }
@@ -9154,23 +8133,22 @@ async function spaceUsers(env = process.env, signal, operation) {
9154
8133
  ["Android emulators", join(home, ".android", "avd")]
9155
8134
  ];
9156
8135
  const measured = /* @__PURE__ */ new Set();
9157
- const users = [];
8136
+ const targets = [];
9158
8137
  for (const [label, path] of candidates) {
9159
8138
  const canonical = canonicalPath(path);
9160
8139
  if (measured.has(canonical) || [...measured].some((dir) => canonical.startsWith(`${dir}${sep}`))) continue;
9161
8140
  measured.add(canonical);
9162
- if (!existsSync(path)) continue;
9163
- const bytes = await measuredDirectorySizeAsync(path, {
9164
- timeoutMs: USAGE_TIMEOUT_MS,
9165
- signal,
9166
- operation
9167
- });
9168
- if (bytes) users.push({
9169
- label: `${label} ${path}`,
9170
- bytes
9171
- });
8141
+ if (existsSync(path)) targets.push([label, path]);
9172
8142
  }
9173
- return users.toSorted((a, b) => b.bytes - a.bytes).map(({ label, bytes }) => `${label}: ${formatBytes(bytes)}`);
8143
+ const sizes = await Promise.all(targets.map(([, path]) => measuredDirectorySizeAsync(path, {
8144
+ timeoutMs: USAGE_TIMEOUT_MS,
8145
+ signal,
8146
+ operation
8147
+ })));
8148
+ return targets.flatMap(([label, path], index) => sizes[index] ? [{
8149
+ label: `${label} ${path}`,
8150
+ bytes: sizes[index]
8151
+ }] : []).toSorted((a, b) => b.bytes - a.bytes).map(({ label, bytes }) => `${label}: ${formatBytes(bytes)}`);
9174
8152
  }
9175
8153
  const DEFAULT_DEPS = {
9176
8154
  volumes: readVolumeSpace,
@@ -9216,6 +8194,12 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
9216
8194
  const reclaimed = [];
9217
8195
  const plan = reclaimPlan(short);
9218
8196
  if (plan.length) note(chalk.yellow(phaseLine("budget", `${describeShortfall(budget, measure, short).join("; ")}; ${dryRun ? "would reclaim" : "reclaiming"}`)));
8197
+ const gcLog = dryRun ? null : openGcLog({
8198
+ trigger: "budget",
8199
+ holdKept: true,
8200
+ env
8201
+ });
8202
+ const gcResults = gcLog && createGcResults(gcLog.record);
9219
8203
  for (const step of plan) {
9220
8204
  signal?.throwIfAborted();
9221
8205
  if (!dryRun && (!overBudget(short) || DISK_ONLY.includes(step) && short.disk.length === 0)) break;
@@ -9226,7 +8210,9 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
9226
8210
  env,
9227
8211
  dryRun,
9228
8212
  note,
8213
+ record: gcResults?.record,
9229
8214
  now: deps.now(),
8215
+ freeMb: lowestFree(measure.volumes),
9230
8216
  diskRecovered: async () => budgetShortfalls(budget, {
9231
8217
  volumes: await (deps.volumesAsync?.(paths) ?? deps.volumes(paths)),
9232
8218
  memory: null
@@ -9383,5 +8369,139 @@ function budgetLine(report) {
9383
8369
  const { budget } = report;
9384
8370
  return `${report.volumes.length ? `${report.volumes.map((v) => `${v.freeGb} GB free on ${v.volume}`).join(", ")} (reclaims below ${Math.max(budget.minFreeDiskGb, budget.hardFloorDiskGb)} GB, ${budget.hardFloorDiskGb ? `refuses below ${budget.hardFloorDiskGb} GB` : "never refuses"})` : "disk budget off"}; ${report.memory ? `roughly ${report.memory.committedGb} GB of a ${budget.maxCommittedMemoryGb} GB memory budget committed` : "memory budget off"}`;
9385
8371
  }
8372
+ function ccacheInstallCommand(platform = process.platform) {
8373
+ return platform === "darwin" ? "brew install ccache" : platform === "linux" ? "sudo apt install ccache" : "install ccache";
8374
+ }
8375
+ const CCACHE_SLOPPINESS = "pch_defines,time_macros";
8376
+ const STATS_LOG_FILE = "ccache-stats.log";
8377
+ const CCACHE_UNAVAILABLE = {
8378
+ status: "unavailable",
8379
+ hits: null,
8380
+ misses: null,
8381
+ hitRatePercent: null
8382
+ };
8383
+ const CCACHE_NOT_RUN = {
8384
+ status: "not-run",
8385
+ hits: null,
8386
+ misses: null,
8387
+ hitRatePercent: null
8388
+ };
8389
+ function ccacheStatsLog(root) {
8390
+ return join(workspaceLogsDir$1(root), STATS_LOG_FILE);
8391
+ }
8392
+ function ccacheMaxSize(config = loadConfig$1(), env = process.env) {
8393
+ const { value, error } = machineNumber("caches.ccacheMaxGb", config, env);
8394
+ return error || value === void 0 ? "5G" : `${value}G`;
8395
+ }
8396
+ function ccacheEnvironment({ binary, dir, workspaceRoot, statsLog, maxSize = "5G" }) {
8397
+ return {
8398
+ CMAKE_C_COMPILER_LAUNCHER: binary,
8399
+ CMAKE_CXX_COMPILER_LAUNCHER: binary,
8400
+ CCACHE_DIR: dir,
8401
+ CCACHE_BASEDIR: workspaceRoot,
8402
+ CCACHE_NOHASHDIR: "true",
8403
+ CCACHE_SLOPPINESS,
8404
+ CCACHE_MAXSIZE: maxSize,
8405
+ CCACHE_STATSLOG: statsLog
8406
+ };
8407
+ }
8408
+ function parseCcacheBinary(output) {
8409
+ const first = String(output ?? "").split("\n")[0]?.trim();
8410
+ if (!first || !isAbsolute(first)) return null;
8411
+ return first;
8412
+ }
8413
+ const CMAKE_LAUNCHER = /CMAKE_(C|CXX)_COMPILER_LAUNCHER\b/;
8414
+ function projectCmakeLauncher(source) {
8415
+ if (typeof source !== "string") return null;
8416
+ const match = CMAKE_LAUNCHER.exec(source);
8417
+ return match ? `CMAKE_${match[1]}_COMPILER_LAUNCHER` : null;
8418
+ }
8419
+ const HIT_COUNTERS = /* @__PURE__ */ new Set(["direct_cache_hit", "preprocessed_cache_hit"]);
8420
+ function parseCcacheStatsLog(text) {
8421
+ if (typeof text !== "string") return null;
8422
+ let hits = 0;
8423
+ let misses = 0;
8424
+ for (const raw of text.split("\n")) {
8425
+ const line = raw.trim();
8426
+ if (HIT_COUNTERS.has(line)) hits += 1;
8427
+ else if (line === "cache_miss") misses += 1;
8428
+ }
8429
+ const total = hits + misses;
8430
+ if (total === 0) return null;
8431
+ return {
8432
+ status: "reported",
8433
+ hits,
8434
+ misses,
8435
+ hitRatePercent: Math.round(hits / total * 1e3) / 10
8436
+ };
8437
+ }
8438
+ function readCcacheActivity(statsLog) {
8439
+ let text;
8440
+ try {
8441
+ text = readFileSync(statsLog, "utf-8");
8442
+ } catch {
8443
+ return CCACHE_UNAVAILABLE;
8444
+ }
8445
+ return parseCcacheStatsLog(text) ?? CCACHE_UNAVAILABLE;
8446
+ }
8447
+ function ccacheActivityLine(activity) {
8448
+ if (activity.status === "reported") return `${activity.hits} hits / ${activity.misses} misses (${activity.hitRatePercent}%)`;
8449
+ if (activity.status === "not-run") return "not run; artifact cache supplied the app";
8450
+ return "unavailable; no C++ compile went through ccache";
8451
+ }
8452
+ function lookupCcache() {
8453
+ try {
8454
+ return parseCcacheBinary(getExecutor().findExecutable("ccache"));
8455
+ } catch {
8456
+ return null;
8457
+ }
8458
+ }
8459
+ function readOrNull(file) {
8460
+ try {
8461
+ return readFileSync(file, "utf-8");
8462
+ } catch {
8463
+ return null;
8464
+ }
8465
+ }
8466
+ function canonical(root) {
8467
+ try {
8468
+ return realpathSync(root);
8469
+ } catch {
8470
+ return root;
8471
+ }
8472
+ }
8473
+ function resolveCcache({ root, layout = defaultAndroidLayout(root), dir = sharedCcache$1(), lookup = lookupCcache, env = process.env, onNote = (line) => console.error(line) }) {
8474
+ const binary = lookup();
8475
+ if (!binary) {
8476
+ onNote(chalk.dim(phaseLine("cache", `ccache off (not installed; ${ccacheInstallCommand()})`)));
8477
+ return null;
8478
+ }
8479
+ const { moduleDir } = layout;
8480
+ const declared = projectCmakeLauncher(readOrNull(join(moduleDir, "build.gradle")) ?? readOrNull(join(moduleDir, "build.gradle.kts")));
8481
+ if (declared) {
8482
+ onNote(chalk.dim(phaseLine("cache", `ccache off (${join(relative(root, moduleDir), "build.gradle")} sets ${declared} itself)`)));
8483
+ return null;
8484
+ }
8485
+ const statsLog = ccacheStatsLog(root);
8486
+ register({
8487
+ dir,
8488
+ registeredBy: root,
8489
+ name: "ccache",
8490
+ prune: "atomic",
8491
+ note: "ccache keeps itself under CCACHE_MAXSIZE, so it is emptied whole or not at all"
8492
+ });
8493
+ onNote(chalk.dim(phaseLine("cache", `ccache on (${dir})`)));
8494
+ return {
8495
+ dir,
8496
+ statsLog,
8497
+ env: ccacheEnvironment({
8498
+ binary,
8499
+ dir,
8500
+ workspaceRoot: canonical(root),
8501
+ statsLog,
8502
+ maxSize: ccacheMaxSize(loadConfig$1(), env)
8503
+ })
8504
+ };
8505
+ }
9386
8506
  //#endregion
9387
- export { includesArchives as $, bundleInvocation as $n, RELEASE_VERIFY_WAIT_MS as $t, workspaceBuildInProgress as A, reverseMetroPorts as An, findStaleDeviceRecords as At, recordFinishedBuild as B, RESOLVE_TIMEOUT_MS as Bn, bundleEntryPoint as Bt, collectIdleDevices as C, installConflictKind as Cn, discoverCaches as Cr, deleteParkedSims as Ct, idleDeviceLines as D, launchAndroidReleaseApp as Dn, deviceSweepIsScoped as Dt, collectOwnedDeviceActivityAsync as E, launchAndroidApp as En, describeUnverifiableDevices as Et, buildStatusLine as F, readRemoteSimulatorArch as Fn, deleteEasSessions as Ft, createRunRecorder as G, isEasAuthFailureText as Gn, remoteAndroidDeps as Gt, startBuildProgress as H, cacheLevel as Hn, ensureMetroReachable as Ht, bundlingActivity as I, gateMetroOrigin as In, describeError as It, recordCapacityRefusal as J, providerFromConfig as Jn, withEasProjectLock as Jt, readRunEstimates as K, loadProjectProvider as Kn, remoteIosDeps as Kt, estimateBuild as L, exitAfterFlush as Ln, withRemoteSessionGcLocks as Lt, NO_BUILD_PROGRESS as M, pickDevClientScheme as Mn, unverifiedDeviceCommand as Mt, activeBuildState as N, readBundleSchemes as Nn, withAndroidAvdSizes as Nt, parseIdleDuration as O, launchIosApp as On, eraseParkedDevices as Ot, buildReport as P, schemesFromInfoPlist as Pn, collectEasSessionSweep as Pt, emptyCaches as Q, uploadRemote as Qn, MANIFEST_VERIFY_TIMEOUT_MS as Qt, parseActiveBuild as R, processContext as Rn, REMOTE_SESSION_ERROR as Rt, withdrawDevServerStopRequest as S, installAndroidApp as Sn, readFootprints as Sr, deleteParkedAvds as St, collectOwnedDeviceActivity as T, iosAppProcess as Tn, sizeCaches as Tr, describeParkedSims as Tt, tapBuildLog as U, checkEasAuth as Un, ensureRemoteBootOwned as Ut, runFailureRecord as V, UPLOAD_TIMEOUT_MS as Vn, endRecordedSession as Vt, thrownRunFailureRecord as W, easAuthNote as Wn, readInstalledEasCliVersionAsync as Wt, recordRunStats as X, resolveEasCliBin as Xn, LAUNCH_FATAL as Xt, recordCapacityWait as Y, readProjectConfig as Yn, LAUNCH_BUNDLING as Yt, statsProjectKey$1 as Z, resolveRemote as Zn, LAUNCH_UNVERIFIED as Zt, devServerStopRecord as _, clearOtherUserApps as _n, reclaimMemory as _r, collectInventory as _t, inspectBudget as a, unverifiedLaunchLines as an, easCliUpgradeRemedy as ar, previewScopedEmptying as at, requestDevServerStop as b, ensureMetroReverse as bn, phase as br, collectParkedAvds as bt, readVolumeSpace as c, verifyReleaseLaunch as cn, GRADLE_DAEMONS_KIND as cr, trimCaches as ct, trimmableCaches as d, DEFAULT_METRO_PORT as dn, inspectWatchmanMemory as dr, deleteGradleVersions as dt, VERIFY_TIMEOUT_MS as en, remoteCliInvocation as er, includesParkedDevices as et, stopOwnedMetro as f, INSTALL_ERROR as fn, memoryCacheKind as fr, gradleVersionLines as ft, devServerStopLevel as g, appProcessFromPid as gn, memoryWorkPending as gr, getMachineCacheReport as gt, describeMetroLastStop as h, androidDevClientUrl as hn, memorySweepIsScoped as hr, waitForGradleExit as ht, enforceBudget as i, unattributedLaunchLines as in, easCliSupport as ir, planCacheEmptying as it, ACTIVE_BUILD_KEY as j, writeDebugHttpHost as jn, findStaleProjectDevices as jt, shutDownIdleDevices as k, restartedAppNote as kn, findOrphanedDevices as kt, readVolumeSpaceAsync as l, APP_READINESS_TIMEOUT_MS as ln, WATCHMAN_KIND as lr, collectGradleVersions as lt, describeDevServerStop as m, androidAppProcess as mn, memoryScope as mr, includesGradleVersions as mt, budgetLine as n, readCollectorRecords as nn, MIN_EAS_CLI_DEVICE_VERSION as nr, includesWorkspaceOutputs as nt, machineNumber as o, verifyAndroidReleaseLaunch as on, ownedSessionName as or, selectCaches as ot, stopOwnedMetroForReset as p, IOS_DEV_MENU_OFF_DEFAULTS_PLIST as pn, memoryLines as pr, gradleVersionsPending as pt, readStats$1 as q, ownerFromConfig as qn, resolveRemoteContext as qt, cacheLabel as r, readMetroRecords as rn, MIN_EAS_CLI_SIMULATOR_VERSION as rr, isWorkspacesScope as rt, peekBudget as s, verifyLaunch as sn, parseArchiveSelector as sr, selectsBeyondCaches as st, budgetGate as t, isBundleProof as tn, remoteCommand as tr, includesRecordings as tt, resolveBudget as u, ADB_INSTALL_TIMEOUT_MS as un, collectMemoryReport as ur, deleteGradleVersion as ut, logVanishedSupervisor as v, devClientUrl as vn, scopedMemoryReport as vr, DEVICE_LIST_TIMEOUT_MS$1 as vt, collectIdleDevicesAsync as w, installIosApp as wn, pruneCache as wr, deleteProjectDevices as wt, vanishedSupervisorMessage as x, hasMetroReverse as xn, attributeMachineUsage as xr, collectParkedSims as xt, metroLastStop as y, deviceShellArg as yn, stopIdleGradleDaemons as yr, collectDeviceLeases as yt, recordBuildPhase as z, runProcessOperation as zn, binOnPath as zt };
8507
+ export { gradleVersionLines as $, memoryCacheKind as $n, INSTALL_ERROR as $t, diskPressureLevel as A, checkEasAuth as An, ensureRemoteBootOwned as At, includesArchives as B, bundleInvocation as Bn, RELEASE_VERIFY_WAIT_MS as Bt, devServerStopLevel as C, gateMetroOrigin as Cn, describeError as Ct, requestDevServerStop as D, RESOLVE_TIMEOUT_MS as Dn, bundleEntryPoint as Dt, metroLastStop as E, runProcessOperation as En, binOnPath as Et, gcOptionsOf as F, providerFromConfig as Fn, withEasProjectLock as Ft, planCacheEmptying as G, easCliSupport as Gn, unattributedLaunchLines as Gt, includesRecordings as H, remoteCommand as Hn, isBundleProof as Ht, maintenanceItem as I, readProjectConfig as In, LAUNCH_BUNDLING as It, selectsBeyondCaches as J, parseArchiveSelector as Jn, verifyLaunch as Jt, previewScopedEmptying as K, easCliUpgradeRemedy as Kn, unverifiedLaunchLines as Kt, openGcLog as L, resolveEasCliBin as Ln, LAUNCH_FATAL as Lt, levelStillShort as M, isEasAuthFailureText as Mn, remoteAndroidDeps as Mt, lowestFree as N, loadProjectProvider as Nn, remoteIosDeps as Nt, vanishedSupervisorMessage as O, UPLOAD_TIMEOUT_MS as On, endRecordedSession as Ot, protectWindowMs as P, ownerFromConfig as Pn, resolveRemoteContext as Pt, deleteGradleVersions as Q, inspectWatchmanMemory as Qn, DEFAULT_METRO_PORT as Qt, maintenanceLogger as R, resolveRemote as Rn, LAUNCH_UNVERIFIED as Rt, describeMetroLastStop as S, readRemoteSimulatorArch as Sn, deleteEasSessions as St, logVanishedSupervisor as T, processContext as Tn, REMOTE_SESSION_ERROR as Tt, includesWorkspaceOutputs as U, MIN_EAS_CLI_DEVICE_VERSION as Un, readCollectorRecords as Ut, includesParkedDevices as V, remoteCliInvocation as Vn, VERIFY_TIMEOUT_MS as Vt, isWorkspacesScope as W, MIN_EAS_CLI_SIMULATOR_VERSION as Wn, readMetroRecords as Wt, collectGradleVersions as X, WATCHMAN_KIND as Xn, APP_READINESS_TIMEOUT_MS as Xt, trimCaches as Y, GRADLE_DAEMONS_KIND as Yn, verifyReleaseLaunch as Yt, deleteGradleVersion as Z, collectMemoryReport as Zn, ADB_INSTALL_TIMEOUT_MS as Zt, resolveBudget as _, reverseMetroPorts as _n, findStaleDeviceRecords as _t, projectCmakeLauncher as a, devClientUrl as an, scopedMemoryReport as ar, DEVICE_LIST_TIMEOUT_MS$1 as at, stopOwnedMetroForReset as b, readBundleSchemes as bn, withAndroidAvdSizes as bt, budgetGate as c, hasMetroReverse as cn, attributeMachineUsage as cr, collectParkedSims as ct, enforceBudget as d, installIosApp as dn, pruneCache as dr, deleteProjectDevices as dt, IOS_DEV_MENU_OFF_DEFAULTS_PLIST as en, memoryLines as er, gradleVersionsPending as et, inspectBudget as f, iosAppProcess as fn, sizeCaches as fr, describeParkedSims as ft, readVolumeSpaceAsync as g, restartedAppNote as gn, findOrphanedDevices as gt, readVolumeSpace as h, launchIosApp as hn, eraseParkedDevices as ht, ccacheInstallCommand as i, clearOtherUserApps as in, reclaimMemory as ir, collectInventory as it, levelFloors as j, easAuthNote as jn, readInstalledEasCliVersionAsync as jt, withdrawDevServerStopRequest as k, cacheLevel as kn, ensureMetroReachable as kt, budgetLine as l, installAndroidApp as ln, readFootprints as lr, deleteParkedAvds as lt, peekBudget as m, launchAndroidReleaseApp as mn, deviceSweepIsScoped as mt, CCACHE_UNAVAILABLE as n, androidDevClientUrl as nn, memorySweepIsScoped as nr, waitForGradleExit as nt, readCcacheActivity as o, deviceShellArg as on, stopIdleGradleDaemons as or, collectDeviceLeases as ot, machineNumber as p, launchAndroidApp as pn, describeUnverifiableDevices as pt, selectCaches as q, ownedSessionName as qn, verifyAndroidReleaseLaunch as qt, ccacheActivityLine as r, appProcessFromPid as rn, memoryWorkPending as rr, getMachineCacheReport as rt, resolveCcache as s, ensureMetroReverse as sn, phase as sr, collectParkedAvds as st, CCACHE_NOT_RUN as t, androidAppProcess as tn, memoryScope as tr, includesGradleVersions as tt, cacheLabel as u, installConflictKind as un, discoverCaches as ur, deleteParkedSims as ut, trimmableCaches as v, writeDebugHttpHost as vn, findStaleProjectDevices as vt, devServerStopRecord as w, exitAfterFlush as wn, withRemoteSessionGcLocks as wt, describeDevServerStop as x, schemesFromInfoPlist as xn, collectEasSessionSweep as xt, stopOwnedMetro as y, pickDevClientScheme as yn, unverifiedDeviceCommand as yt, emptyCaches as z, uploadRemote as zn, MANIFEST_VERIFY_TIMEOUT_MS as zt };