stim 1.17.1 → 1.18.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 (150) hide show
  1. package/README.md +50 -1
  2. package/dist/{activity-IRvJY_ru.mjs → activity-BxnkDZDi.mjs} +4 -4
  3. package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-CZ66YA4T.mjs} +8 -8
  4. package/dist/android-BKA16DTf.mjs +4 -0
  5. package/dist/{android-B6rDWrTm.mjs → android-BvwuMUJ9.mjs} +192 -98
  6. package/dist/{android-DE4v59Mb.mjs → android-CBwCNicO.mjs} +2 -2
  7. package/dist/{android-B_YNZy61.mjs → android-DC3ZLuw0.mjs} +9 -9
  8. package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-BBFyr9CA.mjs} +5 -5
  9. package/dist/android-cas-compiler.mjs +2 -2
  10. package/dist/api-D0VtFhqk.mjs +152 -0
  11. package/dist/api-run.d.mts +1 -0
  12. package/dist/api-run.mjs +145 -0
  13. package/dist/api.d.mts +212 -0
  14. package/dist/api.mjs +2 -0
  15. package/dist/{app-install-VPkI5WEg.mjs → app-install-D-PUENug.mjs} +3 -3
  16. package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
  17. package/dist/{budget-CUDz870h.mjs → budget-DIhL2BKx.mjs} +1581 -61
  18. package/dist/build-facts-DcLv2HSd.d.mts +111 -0
  19. package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-BI7ozfmH.mjs} +3 -3
  20. package/dist/cache-manifest.mjs +1 -1
  21. package/dist/{chrome-BrcVWQyD.mjs → chrome-DJnmnS5q.mjs} +1 -1
  22. package/dist/cli-DNJMwADx.mjs +68 -0
  23. package/dist/cli.mjs +1 -1
  24. package/dist/{client-CxNotugx.mjs → client-DzhapoMB.mjs} +184 -49
  25. package/dist/collector-run.mjs +9 -9
  26. package/dist/{command-output-CDAIFD-8.mjs → command-output-CBtzyIgF.mjs} +2 -2
  27. package/dist/{config-D0DbpZtM.mjs → config-CgYMahz7.mjs} +5 -5
  28. package/dist/{created-devices-CpvzBL25.mjs → created-devices-DKbeWsiK.mjs} +2 -2
  29. package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-xKoOypBm.mjs} +2 -2
  30. package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-BLfcgc4O.mjs} +1 -1
  31. package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CLnIH02x.mjs} +2 -2
  32. package/dist/{device-C8IOoJPv.mjs → device-D7oCHGWi.mjs} +8 -8
  33. package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-EUOOYr8T.mjs} +13 -13
  34. package/dist/device-host-worker.mjs +19 -19
  35. package/dist/{device-ios-C2Um-XUR.mjs → device-ios-6MewKKnc.mjs} +13 -13
  36. package/dist/{device-lease-DpoS36_u.mjs → device-lease-Co-EwifR.mjs} +9 -9
  37. package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-w1xhCqIW.mjs} +4 -4
  38. package/dist/{device-pool-3o7s5lo4.mjs → device-pool-F5WvRPtD.mjs} +3 -3
  39. package/dist/{device-remote-hFSkpfQu.mjs → device-remote-tHbd1fKy.mjs} +15 -15
  40. package/dist/{doctor-BF_435OC.mjs → doctor-B9ckYUoU.mjs} +33 -30
  41. package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics-BRXOqDzG.mjs} +11 -11
  42. package/dist/{exec-CaErVv7t.mjs → exec-DWhCZDGm.mjs} +65 -2
  43. package/dist/{gc-BsWKa_-d.mjs → gc-Dh6mXOKC.mjs} +23 -469
  44. package/dist/{gradle-bsBRy6i9.mjs → gradle-C6GKnunQ.mjs} +6 -6
  45. package/dist/{guide-status-Du-ZQ3FD.mjs → guide-status-Bhf9SJXc.mjs} +3 -3
  46. package/dist/{guide-DC3n6-NH.mjs → guide-tm6igAaa.mjs} +314 -72
  47. package/dist/{hosted-android-DgU31wY3.mjs → hosted-android-Bv285t_t.mjs} +3 -3
  48. package/dist/{hosted-client-CdSoH-G6.mjs → hosted-client-C-rMtppF.mjs} +2 -2
  49. package/dist/{hosted-ios-BTaVj4Jb.mjs → hosted-ios-D5mNmtj4.mjs} +1 -1
  50. package/dist/{hosted-logs-SxfBvSvc.mjs → hosted-logs-DN8Iz4mJ.mjs} +9 -9
  51. package/dist/{hosted-macos-CT9E3meL.mjs → hosted-macos-B-wEjlwG.mjs} +5 -5
  52. package/dist/{hosted-native-CZLOrMrN.mjs → hosted-native-DeeOG8xZ.mjs} +6 -6
  53. package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-jjtxAn5C.mjs} +7 -8
  54. package/dist/{ios-DSfnPfse.mjs → ios-B3SbOWdw.mjs} +19 -7
  55. package/dist/{ios-Cr1h5gnO.mjs → ios-Bvbf0SGY.mjs} +1 -1
  56. package/dist/{ios-C7JuT1bI.mjs → ios-CPdkoKaa.mjs} +181 -95
  57. package/dist/ios-DNpdygiv.mjs +7 -0
  58. package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-Bn7jWDB1.mjs} +1 -1
  59. package/dist/{ios-device-pAN4t54-.mjs → ios-device-vOaByMhw.mjs} +3 -3
  60. package/dist/{ios-state-8L3lgum6.mjs → ios-state-B2pES6Vs.mjs} +1 -1
  61. package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-hskNcWkZ.mjs} +5 -5
  62. package/dist/{logs-DNxKcok9.mjs → logs-1Ylc1Ri6.mjs} +13 -13
  63. package/dist/macos-Ck7OBMNY.mjs +2 -0
  64. package/dist/{macos-BEs8cwKx.mjs → macos-DF6C7wUU.mjs} +118 -34
  65. package/dist/macos-run.mjs +1 -1
  66. package/dist/maintenance-run.mjs +349 -47
  67. package/dist/{metro-g8NqiJp-.mjs → metro-Dh8T4Ytd.mjs} +5 -5
  68. package/dist/{metro-gateway-CHqK0p-a.mjs → metro-gateway-CEUw9RMH.mjs} +4 -4
  69. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
  70. package/dist/{named-ports-C8m8EQs2.mjs → named-ports-BFIz1Shz.mjs} +61 -6
  71. package/dist/{native-run-D1Q56rAD.mjs → native-run-B06FkmZx.mjs} +4 -4
  72. package/dist/{native-runtime-YhaSxP4v.mjs → native-runtime-D_603JiC.mjs} +6 -6
  73. package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
  74. package/dist/offload-worker.d.mts +3 -17
  75. package/dist/offload-worker.mjs +10 -11
  76. package/dist/{ownership-DyRQru0q.mjs → ownership-D6Se7gg_.mjs} +3 -3
  77. package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
  78. package/dist/placement-log-BcPhRwR4.mjs +84 -0
  79. package/dist/{build-plan-BJSXzsCL.mjs → plan-placement-CtxkCO54.mjs} +122 -48
  80. package/dist/{ports-4nYqxPWC.mjs → ports-BLTtfBq-.mjs} +3 -3
  81. package/dist/{prebuild-D7RUMkwN.mjs → prebuild-juCyreaR.mjs} +5 -5
  82. package/dist/preview-D5V07Nrl.mjs +987 -0
  83. package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
  84. package/dist/{project-D47Rh0ly.mjs → project-C3ChRLZy.mjs} +4 -68
  85. package/dist/projects-DD7dBTCj.mjs +92 -0
  86. package/dist/{pull-request-pxnDCQzn.mjs → pull-request-u4vp7zmK.mjs} +1 -1
  87. package/dist/pull-requests.mjs +1 -1
  88. package/dist/{recordings-BNgsD1Tr.mjs → recordings-BAWltsVt.mjs} +1 -1
  89. package/dist/{reload-byOjE3P4.mjs → reload-DvyWzVjJ.mjs} +15 -15
  90. package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-BGg7aGIZ.mjs} +5 -5
  91. package/dist/{run-CkE9y0nc.mjs → run-vhtDgpHO.mjs} +5 -5
  92. package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-B9E7S1aR.mjs} +4 -5
  93. package/dist/server-expo-Db9HBUoa.mjs +2 -0
  94. package/dist/{server-expo-CHTyXD0G.mjs → server-expo-DoBHhuzK.mjs} +5 -6
  95. package/dist/{settings-SR9OmW9h.mjs → settings-Byp2AQ_E.mjs} +6 -5
  96. package/dist/{settings-Cfd-0r2d.mjs → settings-CQyMElEP.mjs} +24 -763
  97. package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
  98. package/dist/settings.schema.json +128 -5
  99. package/dist/{simslim-BWNrc3M3.mjs → simslim-EHBQ2R4G.mjs} +7 -7
  100. package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-C1nqh4mu.mjs} +6 -6
  101. package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
  102. package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
  103. package/dist/{stage-CzaiwFsI.mjs → stage-g_WqWeaE.mjs} +14 -3
  104. package/dist/{start-BHIersSm.mjs → start-D-IeOyoK.mjs} +19 -19
  105. package/dist/{start-BEP-XmEA.mjs → start-NTc1Ifqs.mjs} +1 -1
  106. package/dist/{state-PeQiJuIu.mjs → state-BG-aAk4k.mjs} +4 -4
  107. package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
  108. package/dist/{state-DyaNTgfR.mjs → state-xnqpx3AD.mjs} +2 -2
  109. package/dist/{stats-Bq4fJOdB.mjs → stats-DY6ekz8F.mjs} +5 -5
  110. package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
  111. package/dist/{status-CqeWbIWW.mjs → status-DCjZeE-K.mjs} +34 -35
  112. package/dist/{status-CzS4bPO9.mjs → status-Dcr0Qexd.mjs} +5 -5
  113. package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-Behws_fi.mjs} +1 -1
  114. package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-aR_WsNOA.mjs} +1 -1
  115. package/dist/{stop-D03prQnW.mjs → stop-BGhKMOuH.mjs} +5 -5
  116. package/dist/{stop-Cj01je8D.mjs → stop-BTjs3xbc.mjs} +17 -18
  117. package/dist/{stop-DOxwkH4j.mjs → stop-DIJtD2GL.mjs} +2 -2
  118. package/dist/supervisor-run.d.mts +24 -22
  119. package/dist/supervisor-run.mjs +21 -22
  120. package/dist/{support-BE-tbOP9.mjs → support-BaayeYCf.mjs} +7 -7
  121. package/dist/{support-WvD__tSV.mjs → support-Bwvbrq5-.mjs} +5 -5
  122. package/dist/{toolchain-CdrxgUeA.mjs → toolchain-Bnyd_K3A.mjs} +480 -27
  123. package/dist/{trigger-Bemu8ytw.mjs → trigger-CJMhwXe8.mjs} +5 -5
  124. package/dist/trigger-DWAq9hkw.mjs +2 -0
  125. package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-CjB-MP6H.mjs} +7 -7
  126. package/dist/web-B6_bFDn7.mjs +2 -0
  127. package/dist/{web-DddUdJzr.mjs → web-DTU0uiWj.mjs} +22 -22
  128. package/dist/web-run.mjs +10 -10
  129. package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-C59dqG-Z.mjs} +3 -3
  130. package/dist/{ownership-BMLqyBgJ.mjs → workspaces-xXItD8Dj.mjs} +577 -23
  131. package/dist/{worktree-CsAjSmEo.mjs → worktree-BJfnU6r5.mjs} +1 -1
  132. package/dist/{worktree-AK_m-d_m.mjs → worktree-BSYlofkq.mjs} +234 -35
  133. package/dist/worktree-CsKYexJM.mjs +753 -0
  134. package/package.json +10 -5
  135. package/dist/build-progress-D0WkjCPd.mjs +0 -1036
  136. package/dist/build-slots-41v_d2x9.mjs +0 -142
  137. package/dist/cli-BLQqDGlX.mjs +0 -51
  138. package/dist/deps-CVxlsRFn.mjs +0 -438
  139. package/dist/errors-B6W7FkxQ.mjs +0 -15
  140. package/dist/idle-BXG__1BA.mjs +0 -176
  141. package/dist/in-use-CvGOUn-x.mjs +0 -130
  142. package/dist/preview-C_JjkEy4.mjs +0 -318
  143. package/dist/process-identity-BmRm1qV0.mjs +0 -12
  144. package/dist/server-expo-CeakhI8P.mjs +0 -2
  145. package/dist/state-BKa8wNN5.mjs +0 -148
  146. package/dist/stop-cause-rhmGfwvt.mjs +0 -97
  147. package/dist/trigger-CXts6qt1.mjs +0 -2
  148. package/dist/watchman-APYjXSQa.mjs +0 -48
  149. package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
  150. package/dist/workspaces-Bcib97-6.mjs +0 -306
@@ -1,38 +1,32 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- import { A as directorySize, D as withConfigLock$1, F as volumeRootFor, H as projectDeviceSlots, M as isOnMountedVolume, P as measuredDirectorySize, _ as loadConfig$1, a as clearDevice, d as getConfigDir, j as formatBytes, o as clearSupervisor, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
3
- import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
- import { i as registeredCaches } from "./cache-manifest-DnUHBJaU.mjs";
5
- import { l as phaseLine, o as formatLongDuration, u as plural } from "./command-output-CDAIFD-8.mjs";
6
- import { l as resolveProjectMetro, r as killMetroTree } from "./metro-g8NqiJp-.mjs";
7
- import { d as sharedBuildCache, r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
8
- import { a as capacity, l as environmentState, u as formatSpace } from "./status-CzS4bPO9.mjs";
9
- import { i as recordGcResult, n as withIdleWorkspace, o as canonicalPath } from "./in-use-CvGOUn-x.mjs";
10
- import { i as createDeviceProcessTables, p as workspaceActivity } from "./activity-IRvJY_ru.mjs";
11
- import { h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-DpoS36_u.mjs";
12
- import { n as listBuildSlots } from "./build-slots-41v_d2x9.mjs";
13
- import { l as readPidFile, s as clearWorkspaceSupervisor } from "./state-BKa8wNN5.mjs";
14
- import { a as parseWatchmanPid, i as parseWatchmanClients, n as parseTriggerCount, r as parseWatchRoots, t as parseSubscriberNames } from "./watchman-APYjXSQa.mjs";
15
- import { A as listAvds, I as ownedAvdDirectory, at as isStimOwnedSim, ct as dropParked, it as isStimOwnedAvd, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_YNZy61.mjs";
16
- import { _ as parseRuntimeVersion, f as listIosDeviceTypes, u as listAllIosSims } from "./ios-DSfnPfse.mjs";
17
- import { _ as teardownParkedAvd, d as eraseParkedAvd, f as eraseParkedIosSim, g as teardownOwnedIosSim, m as teardownOwnedAvd, t as resolveSupervisorTarget, v as teardownParkedIosSim } from "./ownership-BMLqyBgJ.mjs";
18
- import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
19
- import { i as idleShutdownCandidates, n as collectOwnedDeviceActivity, o as shutDownIdleDevices, s as workspaceBuildInProgress, t as collectIdleDevices } from "./idle-BXG__1BA.mjs";
20
- import { a as collectWorkspaceOutputs, r as clearWorkspaceOutputs } from "./workspaces-Bcib97-6.mjs";
21
- import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
22
- import { l as withdrawDevServerStopRequest, s as requestDevServerStop } from "./stop-cause-rhmGfwvt.mjs";
23
- import { existsSync, readFileSync, statSync, statfsSync } from "node:fs";
1
+ import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-Biv3arCO.mjs";
2
+ import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
3
+ import { a as formatElapsed, l as phaseLine, o as formatLongDuration, u as plural } from "./command-output-CBtzyIgF.mjs";
4
+ import { A as directorySize, B as parseDeviceSlotKey, D as withConfigLock$1, F as volumeRootFor, H as projectDeviceSlots, M as isOnMountedVolume, P as measuredDirectorySize, R as deviceSlotKey, _ as loadConfig$1, a as clearDevice, d as getConfigDir, j as formatBytes, o as clearSupervisor, p as getProject, z as deviceSlotPlatforms } from "./config-CgYMahz7.mjs";
5
+ import { l as resolveProjectMetro, r as killMetroTree } from "./metro-Dh8T4Ytd.mjs";
6
+ import { C as workspaceStateFile, a as updateWorkspaceState, c as writeWorkspaceState, d as sharedBuildCache, h as supervisorPidFile, o as withWorkspaceStateLock, r as readWorkspaceState$1, t as clearWorkspaceStateKey, v as workspaceDir, x as workspaceLogsDir$1 } from "./workspace-state-C59dqG-Z.mjs";
7
+ import { a as capacity, l as environmentState, u as formatSpace } from "./status-Dcr0Qexd.mjs";
8
+ import { i as registeredCaches } from "./cache-manifest-BI7ozfmH.mjs";
9
+ import { A as teardownParkedIosSim, C as eraseParkedAvd, E as teardownOwnedAvd, I as canonicalPath, M as listBuildSlots, O as teardownOwnedIosSim, P as recordGcResult, a as collectWorkspaceOutputs, f as withIdleWorkspace, k as teardownParkedAvd, m as resolveSupervisorTarget, r as clearWorkspaceOutputs, w as eraseParkedIosSim } from "./workspaces-xXItD8Dj.mjs";
10
+ import { i as createDeviceProcessTables, o as idleForMs, p as workspaceActivity, r as createActivityReader } from "./activity-BxnkDZDi.mjs";
11
+ import { h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-Co-EwifR.mjs";
12
+ import { A as listAvds, I as ownedAvdDirectory, at as isStimOwnedSim, ct as dropParked, it as isStimOwnedAvd, pt as readParked, z as ownedAvdSerialResolver } from "./android-DC3ZLuw0.mjs";
13
+ import { d as listAllIosSims, p as listIosDeviceTypes, v as parseRuntimeVersion } from "./ios-B3SbOWdw.mjs";
14
+ import { a as withWorkspaceProcessLock, o as workspaceProcessLockError } from "./named-ports-BFIz1Shz.mjs";
15
+ import { c as supervisorError } from "./metro-store-BAHFcJm6.mjs";
16
+ import { r as createNdjsonWriter } from "./ndjson-LGZAMsqa.mjs";
17
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, statfsSync, writeFileSync } from "node:fs";
24
18
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
25
- import { existsSync as existsSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
26
- import { homedir, tmpdir } from "os";
27
19
  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";
28
- import { METRO_NAMED_CACHE_LAYOUT, compiledHelper, configDir } from "@stim-cli/core";
29
- import { MACHINE_OWNER_KINDS, buildWorkerRoot, loadConfig, settingDefinition, settingValueError } from "@stim-cli/core/state";
20
+ 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, parseMissReason, readDevServerStop, readDevServerStopRequest, readRunEstimates, readStats as readStats$1, settingDefinition, settingValueError, statsFile, statsProjectKey as statsProjectKey$1, trimCapacityRefusals, trimCapacityWaits, trimSamples, wholePhases, workspaceBuildDetailFile } from "@stim-cli/core/state";
30
21
  import { fileURLToPath } from "node:url";
31
- import { homedir as homedir$1, platform, totalmem as totalmem$1 } from "node:os";
22
+ import { homedir, platform, totalmem } from "node:os";
32
23
  import chalk from "chalk";
24
+ import { existsSync as existsSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
25
+ import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, compiledHelper, configDir } from "@stim-cli/core";
26
+ import { homedir as homedir$1, tmpdir as tmpdir$1 } from "os";
33
27
  //#region src/cache/caches.ts
34
28
  function xcodeDerivedDataRoot() {
35
- return join$1(homedir(), "Library", "Developer", "Xcode", "DerivedData");
29
+ return join$1(homedir$1(), "Library", "Developer", "Xcode", "DerivedData");
36
30
  }
37
31
  function compilationCache() {
38
32
  const dir = join$1(xcodeDerivedDataRoot(), "CompilationCache.noindex");
@@ -45,7 +39,7 @@ function compilationCache() {
45
39
  };
46
40
  }
47
41
  function gradleBuildCache() {
48
- const gradleHome = process.env.GRADLE_USER_HOME || join$1(homedir(), ".gradle");
42
+ const gradleHome = process.env.GRADLE_USER_HOME || join$1(homedir$1(), ".gradle");
49
43
  const dir = resolve$1(gradleHome, "caches", "build-cache-1");
50
44
  if (!existsSync$1(dir)) return null;
51
45
  return {
@@ -56,7 +50,7 @@ function gradleBuildCache() {
56
50
  };
57
51
  }
58
52
  function metroFileMaps() {
59
- const root = tmpdir();
53
+ const root = tmpdir$1();
60
54
  if (!existsSync$1(root)) return null;
61
55
  let names;
62
56
  try {
@@ -201,7 +195,7 @@ function canonicalCacheDir(dir) {
201
195
  function sizeCaches(caches) {
202
196
  return caches.map((c) => Object.assign({}, c, { bytes: c.bytes ?? directorySize(c.dir) }));
203
197
  }
204
- function pruneCache(cache, { olderThanDays, now = Date.now() } = {}) {
198
+ function pruneCache(cache, { olderThanDays, now = Date.now(), protect, evictBytes, byMtime = false } = {}) {
205
199
  const cutoff = now - olderThanDays * 24 * 60 * 60 * 1e3;
206
200
  if (cache.prune === "report-only") return {
207
201
  removed: 0,
@@ -214,27 +208,38 @@ function pruneCache(cache, { olderThanDays, now = Date.now() } = {}) {
214
208
  skipped: "index-backed; empty it whole or not at all"
215
209
  };
216
210
  const entries = cache.files ?? entriesAtDepth(cache.dir, cache.entriesDepth ?? 1);
211
+ const candidates = [];
212
+ let protectedEntries = 0;
213
+ for (const entry of entries) try {
214
+ const st = statSync$1(entry);
215
+ const used = byMtime ? st.mtimeMs : Math.max(st.atimeMs, st.mtimeMs);
216
+ if (used >= cutoff) continue;
217
+ if (protect?.(entry)) {
218
+ protectedEntries++;
219
+ continue;
220
+ }
221
+ candidates.push({
222
+ entry,
223
+ used,
224
+ isDirectory: st.isDirectory(),
225
+ size: st.size
226
+ });
227
+ } catch {}
228
+ if (evictBytes !== void 0) candidates.sort((a, b) => a.used - b.used);
217
229
  let removed = 0;
218
230
  let bytes = 0;
219
231
  let failed = 0;
220
- for (const entry of entries) {
221
- let used;
222
- let size;
223
- try {
224
- const st = statSync$1(entry);
225
- used = Math.max(st.atimeMs, st.mtimeMs);
226
- size = st.isDirectory() ? directorySize(entry) : st.size;
227
- } catch {
228
- continue;
229
- }
230
- if (used >= cutoff) continue;
232
+ for (const { entry, isDirectory, size } of candidates) {
233
+ if (evictBytes !== void 0 && bytes >= evictBytes) break;
231
234
  try {
235
+ if (!existsSync$1(entry) || statSync$1(entry).mtimeMs >= cutoff) continue;
236
+ const entryBytes = isDirectory ? directorySize(entry) : size;
232
237
  rmSync$1(entry, {
233
238
  recursive: true,
234
239
  force: true
235
240
  });
236
241
  removed++;
237
- bytes += size;
242
+ bytes += entryBytes;
238
243
  } catch {
239
244
  failed++;
240
245
  }
@@ -243,7 +248,8 @@ function pruneCache(cache, { olderThanDays, now = Date.now() } = {}) {
243
248
  removed,
244
249
  bytes,
245
250
  failed,
246
- skipped: null
251
+ skipped: null,
252
+ protectedEntries
247
253
  };
248
254
  }
249
255
  function entriesAtDepth(dir, depth) {
@@ -545,6 +551,194 @@ function attributeMachineUsage({ processes, footprints, environments, roots, sim
545
551
  };
546
552
  }
547
553
  //#endregion
554
+ //#region src/supervisor/state.ts
555
+ const MODE_BARE = "bare-inproc";
556
+ const MODE_EXPO = "expo-child";
557
+ function readMetroTunnel(root) {
558
+ const record = readWorkspaceState$1(root)?.metroTunnel;
559
+ if (!record || typeof record !== "object") return null;
560
+ const kind = record.kind;
561
+ const url = record.url;
562
+ if (typeof url !== "string" || url.length === 0) return null;
563
+ if (kind === "expo") return {
564
+ kind: "expo",
565
+ url
566
+ };
567
+ if (kind === "managed") {
568
+ const provider = record.provider;
569
+ const pid = record.pid;
570
+ const port = record.port;
571
+ const startedAt = record.startedAt;
572
+ const processToken = record.processToken;
573
+ const logFile = record.logFile;
574
+ if (provider !== "ngrok" && provider !== "cloudflared" && provider !== "tailscale" || typeof pid !== "number" || typeof port !== "number") return null;
575
+ return {
576
+ kind: "managed",
577
+ provider,
578
+ pid,
579
+ url,
580
+ port,
581
+ startedAt: typeof startedAt === "string" ? startedAt : "",
582
+ processToken: typeof processToken === "string" && processToken.length > 0 ? processToken : null,
583
+ ...typeof logFile === "string" && logFile.length > 0 ? { logFile } : {}
584
+ };
585
+ }
586
+ return null;
587
+ }
588
+ function parseWorkspaceLaunchRecord(value) {
589
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
590
+ const record = value;
591
+ if (typeof record.appId !== "string" || record.appId.length === 0) return null;
592
+ if (typeof record.deviceId !== "string" || record.deviceId.length === 0) return null;
593
+ if (record.metroPort !== null && typeof record.metroPort !== "number") return null;
594
+ if (typeof record.release !== "boolean") return null;
595
+ if (typeof record.launchedAt !== "string") return null;
596
+ return record;
597
+ }
598
+ function readWorkspaceLaunches(root) {
599
+ const launches = readWorkspaceState$1(root)?.launches;
600
+ if (!launches || typeof launches !== "object" || Array.isArray(launches)) return {};
601
+ const records = {};
602
+ for (const [key, value] of Object.entries(launches)) {
603
+ const record = parseWorkspaceLaunchRecord(value);
604
+ if (parseDeviceSlotKey(key) && record) records[key] = record;
605
+ }
606
+ return records;
607
+ }
608
+ function writeWorkspaceLaunch(root, platform, record, slot = "default") {
609
+ updateWorkspaceState(root, (state) => {
610
+ const launches = state.launches && typeof state.launches === "object" ? state.launches : {};
611
+ return {
612
+ ...state,
613
+ launches: {
614
+ ...launches,
615
+ [deviceSlotKey(platform, slot)]: record
616
+ }
617
+ };
618
+ });
619
+ }
620
+ function clearWorkspaceLaunches(root, slot) {
621
+ updateWorkspaceState(root, (state) => {
622
+ if (!state.launches || typeof state.launches !== "object") return state;
623
+ const launches = { ...state.launches };
624
+ delete launches[deviceSlotKey("ios", slot)];
625
+ delete launches[deviceSlotKey("android", slot)];
626
+ return {
627
+ ...state,
628
+ launches
629
+ };
630
+ });
631
+ }
632
+ function readRemoteSession(root) {
633
+ const record = readWorkspaceState$1(root)?.remoteDevice;
634
+ if (!record || typeof record !== "object") return null;
635
+ const id = record.sessionId;
636
+ if (typeof id !== "string" || id.length === 0) return null;
637
+ const platform = record.platform;
638
+ const startedAt = record.startedAt;
639
+ const webPreviewUrl = record.webPreviewUrl;
640
+ const deviceType = record.deviceType;
641
+ return {
642
+ platform: platform === "ios" || platform === "android" ? platform : null,
643
+ sessionId: id,
644
+ startedAt: typeof startedAt === "string" ? startedAt : null,
645
+ webPreviewUrl: typeof webPreviewUrl === "string" && webPreviewUrl.length > 0 ? webPreviewUrl : null,
646
+ deviceType: typeof deviceType === "string" && deviceType.length > 0 ? deviceType : null
647
+ };
648
+ }
649
+ function readRemoteSessionId(root) {
650
+ return readRemoteSession(root)?.sessionId ?? null;
651
+ }
652
+ function clearRemoteSession(root, expectedSessionId) {
653
+ clearWorkspaceStateKey(root, "remoteDevice", (value) => {
654
+ if (typeof value !== "object" || value === null) return false;
655
+ return value.sessionId === expectedSessionId;
656
+ });
657
+ }
658
+ function clearWorkspaceSupervisor(root, expected) {
659
+ return withWorkspaceStateLock(root, () => {
660
+ const current = readWorkspaceState$1(root)?.supervisor;
661
+ if (current && expected !== void 0 && !(0, process_identity_exports.sameProcessRecord)(current, expected)) return false;
662
+ clearWorkspaceStateKey(root, "supervisor", () => true);
663
+ clearExpoMetroTunnel(root);
664
+ if (expected === void 0 || readPidFile(root) === expected?.pid) rmSync(supervisorPidFile(root), { force: true });
665
+ return true;
666
+ });
667
+ }
668
+ function clearExpoMetroTunnel(root) {
669
+ clearWorkspaceStateKey(root, "metroTunnel", (value) => {
670
+ return typeof value === "object" && value !== null && value.kind === "expo";
671
+ });
672
+ }
673
+ function clearManagedMetroTunnel(root, expected) {
674
+ if (!existsSync(workspaceStateFile(root))) return true;
675
+ return clearWorkspaceStateKey(root, "metroTunnel", (value) => {
676
+ if (typeof value !== "object" || value === null) return false;
677
+ const record = value;
678
+ return record.kind === "managed" && record.provider === expected.provider && record.pid === expected.pid && record.url === expected.url && record.port === expected.port && record.startedAt === expected.startedAt && (record.processToken ?? null) === (expected.processToken ?? null) && (record.logFile ?? null) === (expected.logFile ?? null);
679
+ });
680
+ }
681
+ function writePidFile(root, pid) {
682
+ const file = supervisorPidFile(root);
683
+ mkdirSync(dirname(file), { recursive: true });
684
+ writeFileSync(file, `${pid}\n`);
685
+ return file;
686
+ }
687
+ function readPidFile(root) {
688
+ try {
689
+ const pid = parseInt(readFileSync(supervisorPidFile(root), "utf-8").trim(), 10);
690
+ return Number.isFinite(pid) ? pid : null;
691
+ } catch {
692
+ return null;
693
+ }
694
+ }
695
+ //#endregion
696
+ //#region src/watchman.ts
697
+ /** Runs one watchman command with `--no-spawn`, so Stim never starts a daemon, and parses its JSON answer. */
698
+ const runWatchman = async (args, timeoutMs) => JSON.parse(await getExecutor().runFileAsync("watchman", ["--no-spawn", ...args], { timeoutMs }));
699
+ function parseWatchRoots(payload) {
700
+ const roots = payload?.roots;
701
+ if (!Array.isArray(roots)) return null;
702
+ return roots.filter((root) => typeof root === "string");
703
+ }
704
+ function parseSubscriberNames(payload) {
705
+ if (!payload || typeof payload !== "object" || "error" in payload) return null;
706
+ const subscribers = payload.subscribers;
707
+ if (subscribers === void 0) return [];
708
+ if (!Array.isArray(subscribers)) return null;
709
+ const names = [];
710
+ for (const subscriber of subscribers) {
711
+ const name = subscriber?.info?.name;
712
+ if (typeof name !== "string") return null;
713
+ names.push(name);
714
+ }
715
+ return names;
716
+ }
717
+ function parseTriggerCount(payload) {
718
+ const triggers = payload?.triggers;
719
+ return Array.isArray(triggers) ? triggers.length : null;
720
+ }
721
+ function parseWatchmanPid(payload) {
722
+ const pid = payload?.pid;
723
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : null;
724
+ }
725
+ /** The connected clients in a `debug-status` answer, or null when the answer does not list them. */
726
+ function parseWatchmanClients(payload) {
727
+ if (!payload || typeof payload !== "object" || "error" in payload) return null;
728
+ const clients = payload.clients;
729
+ if (!Array.isArray(clients)) return null;
730
+ const parsed = [];
731
+ for (const client of clients) {
732
+ const peer = client?.peer;
733
+ if (typeof peer?.pid !== "number") return null;
734
+ parsed.push({
735
+ pid: peer.pid,
736
+ name: typeof peer.name === "string" ? peer.name : null
737
+ });
738
+ }
739
+ return parsed;
740
+ }
741
+ //#endregion
548
742
  //#region src/commands/gc/progress.ts
549
743
  function phase(label, text) {
550
744
  console.error(chalk.dim(phaseLine(label, text)));
@@ -1524,8 +1718,8 @@ function ownsItsDirectory(cache) {
1524
1718
  const dir = canonicalPath(cache.dir);
1525
1719
  if (dirname$1(dir) === dir) return false;
1526
1720
  return ![
1527
- homedir(),
1528
- tmpdir(),
1721
+ homedir$1(),
1722
+ tmpdir$1(),
1529
1723
  getConfigDir()
1530
1724
  ].map(canonicalPath).includes(dir);
1531
1725
  }
@@ -2112,6 +2306,1322 @@ function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords)
2112
2306
  return deleteFailures;
2113
2307
  }
2114
2308
  //#endregion
2309
+ //#region src/engine/stats.ts
2310
+ const PLACEMENT_MAX_AGE_MS = 6048e5;
2311
+ function emptyStats() {
2312
+ return {
2313
+ version: STATS_VERSION,
2314
+ machine: {},
2315
+ projects: {}
2316
+ };
2317
+ }
2318
+ function updateStats(record, run, now) {
2319
+ const at = new Date(now).toISOString();
2320
+ const durationMs = wholeMs(run.durationMs);
2321
+ const phases = {
2322
+ coldBuildMs: wholeMs(run.coldBuildMs),
2323
+ podsMs: wholeMs(run.podsMs)
2324
+ };
2325
+ const projects = { ...record.projects };
2326
+ const scope = { ...projects[run.projectKey] };
2327
+ const machine = { ...record.machine };
2328
+ const before = scope[run.platform] ?? null;
2329
+ const credit = creditMs(before, run, durationMs);
2330
+ scope[run.platform] = applyRun(before, run, {
2331
+ at,
2332
+ durationMs,
2333
+ credit,
2334
+ phases
2335
+ });
2336
+ projects[run.projectKey] = scope;
2337
+ machine[run.platform] = applyRun(machine[run.platform] ?? null, run, {
2338
+ at,
2339
+ durationMs,
2340
+ credit,
2341
+ phases
2342
+ });
2343
+ const history = { ...record.history };
2344
+ if (run.phases && !run.failed && !run.waitedForBuild && !run.offloadedTo) {
2345
+ const outcome = isHit(run.cacheHit) ? "hit" : "cold";
2346
+ const project = { ...history[run.projectKey] };
2347
+ const lists = { ...project[run.platform] };
2348
+ const sample = {
2349
+ at,
2350
+ durationMs,
2351
+ phases: wholePhases(run.phases),
2352
+ ...run.deviceSetup === void 0 ? {} : { deviceSetup: run.deviceSetup }
2353
+ };
2354
+ lists[outcome] = trimSamples([...lists[outcome] ?? [], sample]);
2355
+ project[run.platform] = lists;
2356
+ history[run.projectKey] = project;
2357
+ }
2358
+ let placements = record.placements;
2359
+ let buildMachines = record.buildMachines;
2360
+ if (run.placement) {
2361
+ const placement = buildPlacement(run, before, at);
2362
+ placements = trimPlacements([...placements ?? [], placement], now);
2363
+ if (placement.machine && placement.decision !== "here") buildMachines = {
2364
+ ...buildMachines,
2365
+ [placement.machine]: applyPlacement(buildMachines?.[placement.machine], placement)
2366
+ };
2367
+ }
2368
+ const capacityRefusals = trimCapacityRefusals(record.capacityRefusals ?? [], now);
2369
+ const capacityWaits = trimCapacityWaits(record.capacityWaits ?? [], now);
2370
+ return {
2371
+ version: STATS_VERSION,
2372
+ machine,
2373
+ projects,
2374
+ ...Object.keys(history).length ? { history } : {},
2375
+ ...placements?.length ? { placements } : {},
2376
+ ...capacityRefusals.length ? { capacityRefusals } : {},
2377
+ ...capacityWaits.length ? { capacityWaits } : {},
2378
+ ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
2379
+ };
2380
+ }
2381
+ function buildPlacement(run, before, at) {
2382
+ const placement = run.placement;
2383
+ const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
2384
+ const localEstimateMs = wholeMs(before?.lastColdBuildMs);
2385
+ const slotWaitMs = wholeMs(placement.slotWaitMs);
2386
+ const deviceSlotWaitMs = wholeMs(placement.deviceSlotWaitMs);
2387
+ return {
2388
+ at,
2389
+ project: run.projectKey,
2390
+ platform: run.platform,
2391
+ decision: placement.decision,
2392
+ reason: placement.reason,
2393
+ ...placement.machine ? { machine: placement.machine } : {},
2394
+ ...buildMs > 0 ? { buildMs } : {},
2395
+ ...slotWaitMs > 0 ? { slotWaitMs } : {},
2396
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {},
2397
+ ...localEstimateMs > 0 ? { localEstimateMs } : {},
2398
+ ...run.failed ? { failed: true } : {}
2399
+ };
2400
+ }
2401
+ function applyPlacement(totals, placement) {
2402
+ const next = totals ? { ...totals } : {
2403
+ offloaded: 0,
2404
+ offloadedMs: 0,
2405
+ savedMs: 0,
2406
+ fallbacks: 0
2407
+ };
2408
+ if (placement.decision === "fell-back") {
2409
+ next.fallbacks += 1;
2410
+ next.lastFallbackAt = placement.at;
2411
+ return next;
2412
+ }
2413
+ next.offloaded += 1;
2414
+ next.offloadedMs += placement.buildMs ?? 0;
2415
+ if (placement.localEstimateMs && placement.buildMs) next.savedMs += placement.localEstimateMs - placement.buildMs;
2416
+ next.lastOffloadAt = placement.at;
2417
+ return next;
2418
+ }
2419
+ function trimPlacements(placements, now) {
2420
+ return placements.filter((each) => now - Date.parse(each.at) <= PLACEMENT_MAX_AGE_MS).slice(-100);
2421
+ }
2422
+ function recordRunStats(run, now) {
2423
+ return withConfigLock$1(() => {
2424
+ const path = statsFile();
2425
+ const loaded = loadForUpdate(path, now);
2426
+ if (loaded.newerVersion !== null) return {
2427
+ recorded: false,
2428
+ note: `Run statistics at ${path} are version ${loaded.newerVersion}, which this Stim does not understand, so this run was not recorded.`
2429
+ };
2430
+ writeStats(path, updateStats(loaded.record, run, now));
2431
+ return {
2432
+ recorded: true,
2433
+ note: loaded.note
2434
+ };
2435
+ });
2436
+ }
2437
+ function recordCapacityRefusal(event, now) {
2438
+ try {
2439
+ withConfigLock$1(() => {
2440
+ const path = statsFile();
2441
+ const loaded = loadForUpdate(path, now, false);
2442
+ if (!loaded.record || loaded.newerVersion !== null) return;
2443
+ const capacityRefusals = trimCapacityRefusals([...loaded.record.capacityRefusals ?? [], {
2444
+ ...event,
2445
+ at: new Date(now).toISOString(),
2446
+ kind: "device"
2447
+ }], now);
2448
+ writeStats(path, {
2449
+ ...loaded.record,
2450
+ capacityRefusals
2451
+ });
2452
+ });
2453
+ } catch {}
2454
+ }
2455
+ function recordCapacityWait(event, now) {
2456
+ try {
2457
+ withConfigLock$1(() => {
2458
+ const path = statsFile();
2459
+ const loaded = loadForUpdate(path, now, false);
2460
+ if (!loaded.record || loaded.newerVersion !== null) return;
2461
+ const capacityWaits = trimCapacityWaits([...loaded.record.capacityWaits ?? [], {
2462
+ platform: event.platform,
2463
+ max: event.max,
2464
+ workspace: event.workspace,
2465
+ ms: wholeMs(event.ms),
2466
+ ...event.reclaimed && Number.isInteger(event.reclaimed) && event.reclaimed > 0 ? { reclaimed: event.reclaimed } : {},
2467
+ at: new Date(now).toISOString(),
2468
+ kind: "device-wait"
2469
+ }], now);
2470
+ writeStats(path, {
2471
+ ...loaded.record,
2472
+ capacityWaits
2473
+ });
2474
+ });
2475
+ } catch {}
2476
+ }
2477
+ function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
2478
+ let projectKey = null;
2479
+ let cacheKey = null;
2480
+ let coldBuildMs = 0;
2481
+ let podsMs = 0;
2482
+ let placement = null;
2483
+ let deviceSlotWaitMs = 0;
2484
+ let recorded = false;
2485
+ return {
2486
+ setProject(key) {
2487
+ projectKey = key;
2488
+ },
2489
+ setCacheKey(key) {
2490
+ cacheKey = key;
2491
+ },
2492
+ setBuildMs(ms) {
2493
+ coldBuildMs = wholeMs(ms);
2494
+ },
2495
+ setPodsMs(ms) {
2496
+ podsMs = wholeMs(ms);
2497
+ },
2498
+ addDeviceSlotWaitMs(ms) {
2499
+ deviceSlotWaitMs += wholeMs(ms);
2500
+ },
2501
+ deviceSlotWaitMs() {
2502
+ return deviceSlotWaitMs || void 0;
2503
+ },
2504
+ setPlacement(next) {
2505
+ placement = next;
2506
+ },
2507
+ record({ failed, cacheHit = false, waited = null, durationMs, offloadedTo = null }) {
2508
+ if (!projectKey || !cacheKey || recorded) return;
2509
+ recorded = true;
2510
+ const ran = failed ? {} : phases?.() ?? {};
2511
+ try {
2512
+ const outcome = write({
2513
+ platform,
2514
+ projectKey,
2515
+ failed,
2516
+ cacheHit,
2517
+ waitedForBuild: Boolean(waited),
2518
+ durationMs,
2519
+ ...offloadedTo ? { offloadedTo } : {},
2520
+ ...coldBuildMs > 0 ? { coldBuildMs } : {},
2521
+ ...podsMs > 0 ? { podsMs } : {},
2522
+ ...Object.keys(ran).length ? { phases: ran } : {},
2523
+ ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
2524
+ ...placement ? { placement: {
2525
+ ...placement,
2526
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {}
2527
+ } } : {}
2528
+ }, now());
2529
+ if (outcome?.note) note(outcome.note);
2530
+ } catch (error) {
2531
+ note(`Run statistics could not be recorded: ${error?.message || error}`);
2532
+ }
2533
+ }
2534
+ };
2535
+ }
2536
+ function loadForUpdate(path, now, recoverCorrupt = true) {
2537
+ let text;
2538
+ try {
2539
+ text = readFileSync(path, "utf-8");
2540
+ } catch {
2541
+ return {
2542
+ record: emptyStats(),
2543
+ note: null,
2544
+ newerVersion: null
2545
+ };
2546
+ }
2547
+ const parsed = decodeStats(text);
2548
+ if (!parsed.record && parsed.newerVersion === null) {
2549
+ if (!recoverCorrupt) return {
2550
+ record: null,
2551
+ note: null,
2552
+ newerVersion: null
2553
+ };
2554
+ const aside = `${path}.corrupt-${now}`;
2555
+ renameSync(path, aside);
2556
+ return {
2557
+ record: emptyStats(),
2558
+ note: `Run statistics at ${path} could not be read, so they were moved to ${aside} and a new file was started.`,
2559
+ newerVersion: null
2560
+ };
2561
+ }
2562
+ if (parsed.newerVersion !== null) return {
2563
+ record: emptyStats(),
2564
+ note: null,
2565
+ newerVersion: parsed.newerVersion
2566
+ };
2567
+ return {
2568
+ record: parsed.record,
2569
+ note: null,
2570
+ newerVersion: null
2571
+ };
2572
+ }
2573
+ function writeStats(path, record) {
2574
+ const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
2575
+ writeFileSync(tmp, `${JSON.stringify(record)}\n`);
2576
+ try {
2577
+ renameSync(tmp, path);
2578
+ } catch (error) {
2579
+ rmSync(tmp, { force: true });
2580
+ throw error;
2581
+ }
2582
+ }
2583
+ function applyRun(bucket, run, { at, durationMs, credit, phases }) {
2584
+ const next = bucket ? { ...bucket } : {
2585
+ runs: 0,
2586
+ failed: 0,
2587
+ hits: 0,
2588
+ misses: 0,
2589
+ coldRuns: 0,
2590
+ coldRunMs: 0,
2591
+ hitRuns: 0,
2592
+ hitRunMs: 0,
2593
+ timeSavedMs: 0,
2594
+ firstRunAt: at,
2595
+ lastRunAt: at
2596
+ };
2597
+ next.runs += 1;
2598
+ next.lastRunAt = at;
2599
+ if (phases.coldBuildMs > 0) next.lastColdBuildMs = phases.coldBuildMs;
2600
+ if (phases.podsMs > 0) next.lastPodsMs = phases.podsMs;
2601
+ if (run.failed) {
2602
+ next.failed += 1;
2603
+ return next;
2604
+ }
2605
+ if (isHit(run.cacheHit)) {
2606
+ next.hits += 1;
2607
+ if (!run.waitedForBuild) {
2608
+ next.hitRuns += 1;
2609
+ next.hitRunMs += durationMs;
2610
+ next.timeSavedMs += credit;
2611
+ }
2612
+ return next;
2613
+ }
2614
+ next.misses += 1;
2615
+ if (run.offloadedTo) {
2616
+ next.offloadedRuns = (next.offloadedRuns ?? 0) + 1;
2617
+ next.offloadedRunMs = (next.offloadedRunMs ?? 0) + durationMs;
2618
+ next.lastOffloadHost = run.offloadedTo;
2619
+ return next;
2620
+ }
2621
+ next.coldRuns += 1;
2622
+ next.coldRunMs += durationMs;
2623
+ return next;
2624
+ }
2625
+ function creditMs(bucket, run, durationMs) {
2626
+ if (run.failed || !isHit(run.cacheHit) || run.waitedForBuild) return 0;
2627
+ if (!bucket || bucket.coldRuns <= 0) return 0;
2628
+ return Math.max(0, Math.round(bucket.coldRunMs / bucket.coldRuns) - durationMs);
2629
+ }
2630
+ function isHit(cacheHit) {
2631
+ return cacheHit === "local" || cacheHit === "remote";
2632
+ }
2633
+ function wholeMs(value) {
2634
+ const ms = Number(value);
2635
+ return Number.isFinite(ms) && ms > 0 ? Math.round(ms) : 0;
2636
+ }
2637
+ //#endregion
2638
+ //#region src/engine/build-detail.ts
2639
+ const LINE_MAX = 160;
2640
+ const TARGET_GRAPH = /^note: Target dependency graph \((\d+) targets?\)/;
2641
+ const XCODE_ACTION = /^([A-Z][A-Za-z]+)\s(.*)\s\(in target '([^']+)' from project '([^']+)'\)$/;
2642
+ const XCODE_CONFIGURE = /^(ComputeTargetDependencyGraph|CreateBuildDescription|CreateBuildRequest|Resolve Package Graph|Prepare packages)$/;
2643
+ const GRADLE_TASK = /^> Task (:\S+)/;
2644
+ /**
2645
+ * xcodebuild actions that end a target's work: a clean build touches each product last, and an incremental build
2646
+ * that touches nothing still signs framework products last. Targets that end any other way are not counted.
2647
+ */
2648
+ const XCODE_TARGET_DONE = /^(Touch|RegisterExecutionPolicyException|CodeSign)$/;
2649
+ const GRADLE_CONFIGURE = /^> Configure project /;
2650
+ /**
2651
+ * SwiftPM prints `[done / total] name` once it plans the build (with U+2009 thin spaces around the slash in Swift 6.4
2652
+ * output), `[done/total] Compiling ...` in older toolchains; Swift 6.4 names the target being worked on, and
2653
+ * `<product>-product` is the link of a product.
2654
+ */
2655
+ const SWIFTPM_PROGRESS = /^\[(\d+)\s*\/\s*(\d+)\](?:\s+(.*))?$/;
2656
+ const SWIFTPM_CONFIGURE = /^(Fetching|Fetched|Computing version|Computed|Creating working copy|Working copy of)\b|^\[(Pre-planning|Planning|Using on-disk description)/;
2657
+ const XCODE_STEPS = [
2658
+ [/^(CpResource|CopyPNGFile|CopyStringsFile|CompileAssetCatalog\w*|CompileStoryboard|CompileXIB|LinkStoryboards|ProcessInfoPlistFile)$/, "resources"],
2659
+ [/^(Compile\w*|Swift\w*|ScanDependencies|Precompile\w*)$/, "compile"],
2660
+ [/^(Ld|Libtool|CreateUniversalBinary|GenerateDSYMFile)$/, "link"],
2661
+ [/^PhaseScriptExecution$/, "script"],
2662
+ [/^CodeSign$/, "sign"]
2663
+ ];
2664
+ const GRADLE_STEPS = [
2665
+ [/[Dd]ex/, "dex"],
2666
+ [/^(createBundle\w*JsAndAssets|generateCodegen\w*)$/, "script"],
2667
+ [/(Resources?|Assets|Manifest|ResValues|RFile|JavaRes)$/, "resources"],
2668
+ [/^(compile|kapt|ksp|javaPreCompile|buildCMake|configureCMake|externalNativeBuild)/, "compile"],
2669
+ [/^(package|assemble|install|zipalign|validateSigning|merge\w*NativeLibs|strip\w*Symbols)/, "package"]
2670
+ ];
2671
+ function stepOf(name, table) {
2672
+ return table.find(([pattern]) => pattern.test(name))?.[1] ?? null;
2673
+ }
2674
+ function clip(text) {
2675
+ return text.length > LINE_MAX ? `${text.slice(0, 157)}...` : text;
2676
+ }
2677
+ function unescape(token) {
2678
+ return token.replace(/\\(.)/g, "$1");
2679
+ }
2680
+ function baseName(path) {
2681
+ return path.split("/").findLast(Boolean) ?? path;
2682
+ }
2683
+ /**
2684
+ * An xcodebuild action line in short form: the action, the file it works on and its target, as in
2685
+ * `CompileC sqlite3.c (sqlite3)`. The file is the last absolute path in the arguments, which xcodebuild prints
2686
+ * as the source for compile, copy and plist steps and as the product for link and sign steps; a script phase
2687
+ * shows its name instead.
2688
+ */
2689
+ function shortXcodeAction(action, args, target) {
2690
+ const tokens = args.match(/(?:\\.|\S)+/g) ?? [];
2691
+ const subject = action === "PhaseScriptExecution" ? unescape(tokens[0] ?? "") : baseName(unescape(tokens.findLast((token) => token.startsWith("/")) ?? ""));
2692
+ return clip(`${action}${subject ? ` ${subject}` : ""} (${target})`);
2693
+ }
2694
+ function swiftpmStep(text) {
2695
+ return /^Linking\b|-product$/.test(text) ? "link" : "compile";
2696
+ }
2697
+ /** What one line of xcodebuild, Gradle or SwiftPM output says about the build's progress, or null for any other line. */
2698
+ function parseBuildToolLine(msg) {
2699
+ const counted = SWIFTPM_PROGRESS.exec(msg);
2700
+ if (counted) {
2701
+ const text = counted[3]?.trim() ?? "";
2702
+ return {
2703
+ kind: "count",
2704
+ done: Number(counted[1]),
2705
+ total: Number(counted[2]),
2706
+ step: swiftpmStep(text),
2707
+ line: text ? clip(text) : null
2708
+ };
2709
+ }
2710
+ if (SWIFTPM_CONFIGURE.test(msg)) return { kind: "configure" };
2711
+ const graph = TARGET_GRAPH.exec(msg);
2712
+ if (graph) return {
2713
+ kind: "total",
2714
+ unit: "targets",
2715
+ total: Number(graph[1])
2716
+ };
2717
+ if (XCODE_CONFIGURE.test(msg) || GRADLE_CONFIGURE.test(msg)) return { kind: "configure" };
2718
+ const action = XCODE_ACTION.exec(msg);
2719
+ if (action) {
2720
+ const [, name, args, target, project] = action;
2721
+ const step = stepOf(name, XCODE_STEPS);
2722
+ return {
2723
+ kind: "unit",
2724
+ unit: "targets",
2725
+ id: `${project}/${target}`,
2726
+ step,
2727
+ line: step ? shortXcodeAction(name, args, target) : null,
2728
+ finished: XCODE_TARGET_DONE.test(name)
2729
+ };
2730
+ }
2731
+ const task = GRADLE_TASK.exec(msg);
2732
+ if (task) {
2733
+ const path = task[1];
2734
+ return {
2735
+ kind: "unit",
2736
+ unit: "tasks",
2737
+ id: path,
2738
+ step: stepOf(path.slice(path.lastIndexOf(":") + 1), GRADLE_STEPS),
2739
+ line: clip(`> Task ${path}`),
2740
+ finished: false
2741
+ };
2742
+ }
2743
+ return null;
2744
+ }
2745
+ /**
2746
+ * Accumulates a build's tool output into its detail: xcodebuild counts the distinct targets it finished against the
2747
+ * total its dependency graph note gives; Gradle counts the task lines it printed and has no total; SwiftPM reports
2748
+ * its own `[done / total]` counts, which the parser passes through.
2749
+ */
2750
+ function createBuildDetailParser() {
2751
+ let seen = false;
2752
+ let step = null;
2753
+ let unit = null;
2754
+ let total = null;
2755
+ let line = null;
2756
+ let tasks = 0;
2757
+ let counted = null;
2758
+ const targets = /* @__PURE__ */ new Set();
2759
+ return {
2760
+ push(msg) {
2761
+ const parsed = parseBuildToolLine(msg);
2762
+ if (!parsed) return false;
2763
+ seen = true;
2764
+ if (parsed.kind === "total") {
2765
+ unit = "targets";
2766
+ total = parsed.total;
2767
+ return true;
2768
+ }
2769
+ if (parsed.kind === "configure") {
2770
+ step = "configure";
2771
+ return true;
2772
+ }
2773
+ if (parsed.kind === "count") {
2774
+ unit = "steps";
2775
+ counted = {
2776
+ done: parsed.done,
2777
+ total: parsed.total
2778
+ };
2779
+ step = parsed.step;
2780
+ if (parsed.line) line = parsed.line;
2781
+ return true;
2782
+ }
2783
+ unit = parsed.unit;
2784
+ if (parsed.unit === "tasks") tasks += 1;
2785
+ else if (parsed.finished) targets.add(parsed.id);
2786
+ if (parsed.step) step = parsed.step;
2787
+ if (parsed.line) line = parsed.line;
2788
+ return true;
2789
+ },
2790
+ detail(updatedAt) {
2791
+ if (!seen) return null;
2792
+ if (unit === "steps" && counted) return {
2793
+ step,
2794
+ unit,
2795
+ done: Math.min(counted.done, counted.total),
2796
+ total: counted.total,
2797
+ line,
2798
+ updatedAt
2799
+ };
2800
+ const done = unit === "tasks" ? tasks : unit === "targets" ? targets.size : null;
2801
+ return {
2802
+ step,
2803
+ unit,
2804
+ done: done !== null && total !== null ? Math.min(done, total) : done,
2805
+ total: unit === "tasks" ? null : total,
2806
+ line,
2807
+ updatedAt
2808
+ };
2809
+ }
2810
+ };
2811
+ }
2812
+ //#endregion
2813
+ //#region src/engine/build-progress.ts
2814
+ const ACTIVE_BUILD_KEY = "activeBuild";
2815
+ const NO_BUILD_PROGRESS = {
2816
+ step: () => {},
2817
+ estimate: () => {},
2818
+ miss: () => {},
2819
+ hit: () => {},
2820
+ deviceSetup: () => {},
2821
+ deviceSetupKnown: () => void 0,
2822
+ place: () => {},
2823
+ waitingOn: () => {},
2824
+ waitingFor: () => {},
2825
+ output: () => {},
2826
+ durations: () => ({}),
2827
+ steps: () => null,
2828
+ clear: () => {}
2829
+ };
2830
+ /** `writer`, with each record it writes also passed to `progress.output`. */
2831
+ function tapBuildLog(writer, progress) {
2832
+ if (progress === NO_BUILD_PROGRESS) return writer;
2833
+ return {
2834
+ get file() {
2835
+ return writer.file;
2836
+ },
2837
+ write(record) {
2838
+ progress.output(record);
2839
+ return writer.write(record);
2840
+ },
2841
+ close: () => writer.close(),
2842
+ get written() {
2843
+ return writer.written;
2844
+ },
2845
+ get dropped() {
2846
+ return writer.dropped;
2847
+ },
2848
+ get lastError() {
2849
+ return writer.lastError;
2850
+ }
2851
+ };
2852
+ }
2853
+ const DETAIL_WRITE_MS = 2e3;
2854
+ function toolLine(record) {
2855
+ if (!record || typeof record !== "object") return null;
2856
+ const { src, level, msg } = record;
2857
+ return src === "build" && level === "debug" && typeof msg === "string" ? msg : null;
2858
+ }
2859
+ function writeBuildDetail(root, claimId, detail) {
2860
+ const file = workspaceBuildDetailFile(root);
2861
+ const tmp = `${file}.tmp-${process.pid}`;
2862
+ mkdirSync(dirname(file), { recursive: true });
2863
+ writeFileSync(tmp, JSON.stringify({
2864
+ claimId,
2865
+ detail
2866
+ }));
2867
+ renameSync(tmp, file);
2868
+ }
2869
+ const COLD_PHASES = [
2870
+ "prebuild",
2871
+ "pods",
2872
+ "compile"
2873
+ ];
2874
+ const HIT_PHASES = ["install", "launch"];
2875
+ function settledOutcome(phase, entered) {
2876
+ if (COLD_PHASES.includes(phase)) return "cold";
2877
+ if (phase === "device") return entered.some((entry) => entry.phase === "cache-lookup") ? "hit" : null;
2878
+ return HIT_PHASES.includes(phase) ? "hit" : null;
2879
+ }
2880
+ function projectHistory(projectKey) {
2881
+ try {
2882
+ return readStats$1().record?.history?.[projectKey];
2883
+ } catch {
2884
+ return;
2885
+ }
2886
+ }
2887
+ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
2888
+ const startedAt = new Date(now()).toISOString();
2889
+ const record = {
2890
+ platform,
2891
+ slot,
2892
+ startedAt,
2893
+ phase: "prepare",
2894
+ phaseStartedAt: startedAt,
2895
+ phases: [{
2896
+ phase: "prepare",
2897
+ startedAt
2898
+ }],
2899
+ claim: {
2900
+ root: claim.root,
2901
+ path: claim.path,
2902
+ claimId: claim.claimId,
2903
+ pid: claim.owner.pid
2904
+ }
2905
+ };
2906
+ let warned = false;
2907
+ const guard = (fn) => {
2908
+ try {
2909
+ fn();
2910
+ } catch (error) {
2911
+ if (warned) return;
2912
+ warned = true;
2913
+ note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
2914
+ }
2915
+ };
2916
+ const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
2917
+ ...before(state),
2918
+ [ACTIVE_BUILD_KEY]: record
2919
+ })));
2920
+ write(withInterruptedBuild);
2921
+ let projectKey = null;
2922
+ const parser = createBuildDetailParser();
2923
+ let detailWrittenAt = -Infinity;
2924
+ let detailTimer = null;
2925
+ const flushDetail = () => {
2926
+ detailTimer = null;
2927
+ detailWrittenAt = now();
2928
+ const detail = parser.detail(new Date(detailWrittenAt).toISOString());
2929
+ if (detail) guard(() => writeBuildDetail(root, record.claim.claimId, detail));
2930
+ };
2931
+ const settle = (outcome) => {
2932
+ record.outcome = outcome;
2933
+ if (projectKey && platform !== "macos") record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
2934
+ };
2935
+ const waits = /* @__PURE__ */ new Map();
2936
+ return {
2937
+ step(phase) {
2938
+ if (phase === record.phase) return;
2939
+ const at = new Date(now()).toISOString();
2940
+ record.phase = phase;
2941
+ record.phaseStartedAt = at;
2942
+ record.phases.push({
2943
+ phase,
2944
+ startedAt: at
2945
+ });
2946
+ if (phase !== "wait") delete record.waitingOn;
2947
+ const outcome = record.outcome || platform === "macos" ? null : settledOutcome(phase, record.phases);
2948
+ if (outcome) settle(outcome);
2949
+ write();
2950
+ },
2951
+ estimate(key) {
2952
+ if (platform === "macos") return;
2953
+ projectKey = key;
2954
+ record.estimate = estimateBuild(projectHistory(key), platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
2955
+ write();
2956
+ },
2957
+ miss(reason, provisional = false) {
2958
+ record.missReason = reason;
2959
+ settle("cold");
2960
+ if (provisional) record.missProvisional = true;
2961
+ else delete record.missProvisional;
2962
+ write();
2963
+ },
2964
+ hit() {
2965
+ delete record.missReason;
2966
+ delete record.missProvisional;
2967
+ settle("hit");
2968
+ write();
2969
+ },
2970
+ deviceSetup(setup) {
2971
+ if (setup === void 0) return;
2972
+ record.deviceSetup = setup;
2973
+ write();
2974
+ },
2975
+ deviceSetupKnown() {
2976
+ return record.deviceSetup;
2977
+ },
2978
+ place(remote) {
2979
+ const current = record.placement;
2980
+ if (!remote) {
2981
+ if (!current) return;
2982
+ delete record.placement;
2983
+ return write();
2984
+ }
2985
+ if (current?.host === remote.host && current.phase === remote.phase) return;
2986
+ const at = new Date(now()).toISOString();
2987
+ record.placement = {
2988
+ host: remote.host,
2989
+ phase: remote.phase,
2990
+ startedAt: current?.host === remote.host ? current.startedAt : at,
2991
+ phaseStartedAt: at
2992
+ };
2993
+ write();
2994
+ },
2995
+ waitingFor(info, kind) {
2996
+ if (info) waits.set(info.kind, info);
2997
+ else if (kind) waits.delete(kind);
2998
+ else waits.clear();
2999
+ const first = [...waits.values()].toSorted((a, b) => Date.parse(a.since) - Date.parse(b.since))[0];
3000
+ if (first) record.waitingFor = first;
3001
+ else delete record.waitingFor;
3002
+ write();
3003
+ },
3004
+ waitingOn(holder) {
3005
+ if (holder === null) {
3006
+ if (!record.waitingOn) return;
3007
+ delete record.waitingOn;
3008
+ } else {
3009
+ if (record.waitingOn?.path === holder) return;
3010
+ record.waitingOn = { path: holder };
3011
+ }
3012
+ write();
3013
+ },
3014
+ output(line) {
3015
+ const msg = toolLine(line);
3016
+ if (msg === null || !parser.push(msg) || detailTimer) return;
3017
+ const wait = detailWrittenAt + DETAIL_WRITE_MS - now();
3018
+ if (wait <= 0) return flushDetail();
3019
+ detailTimer = setTimeout(flushDetail, wait);
3020
+ detailTimer.unref?.();
3021
+ },
3022
+ durations() {
3023
+ return phaseDurations(record.phases, now());
3024
+ },
3025
+ steps() {
3026
+ const detail = parser.detail("");
3027
+ return detail?.unit === "steps" ? detail.total : null;
3028
+ },
3029
+ clear() {
3030
+ if (detailTimer) clearTimeout(detailTimer);
3031
+ detailTimer = null;
3032
+ guard(() => rmSync(workspaceBuildDetailFile(root), { force: true }));
3033
+ guard(() => {
3034
+ clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
3035
+ });
3036
+ }
3037
+ };
3038
+ }
3039
+ function phaseDurations(phases, now) {
3040
+ const out = {};
3041
+ phases.forEach(({ phase, startedAt }, index) => {
3042
+ const start = Date.parse(startedAt);
3043
+ const next = phases[index + 1];
3044
+ const end = next ? Date.parse(next.startedAt) : now;
3045
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return;
3046
+ out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
3047
+ });
3048
+ return out;
3049
+ }
3050
+ function completedPhaseDurations(phases) {
3051
+ if (phases.length < 2) return void 0;
3052
+ const durations = phaseDurations(phases.slice(0, -1), Date.parse(phases.at(-1).startedAt));
3053
+ return Object.keys(durations).length ? durations : void 0;
3054
+ }
3055
+ const HISTORY_FIELDS = [
3056
+ "devicePlacement",
3057
+ "platform",
3058
+ "status",
3059
+ "configuration",
3060
+ "fingerprint",
3061
+ "cacheKey",
3062
+ "cacheHit",
3063
+ "cacheSkipped",
3064
+ "durationMs",
3065
+ "startedAt",
3066
+ "errorCode",
3067
+ "missReason",
3068
+ "buildMachine",
3069
+ "builtOn",
3070
+ "offloadedTo",
3071
+ "offloadFallback",
3072
+ "diagnostics",
3073
+ "compileSteps"
3074
+ ];
3075
+ function withHistoryEntry(state, platform, entry) {
3076
+ const saved = state[BUILD_HISTORY_KEY];
3077
+ const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
3078
+ const list = Array.isArray(history[platform]) ? history[platform] : [];
3079
+ return {
3080
+ ...state,
3081
+ [BUILD_HISTORY_KEY]: {
3082
+ ...history,
3083
+ [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
3084
+ }
3085
+ };
3086
+ }
3087
+ function finishedResult(record) {
3088
+ if (record.status === "ok") return "succeeded";
3089
+ return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
3090
+ }
3091
+ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now } = {}) {
3092
+ const platform = record.platform;
3093
+ update(root, (state) => {
3094
+ const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
3095
+ const own = active?.platform === platform ? active : null;
3096
+ const entry = {
3097
+ ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
3098
+ result: finishedResult(record),
3099
+ slot: own?.slot ?? "default",
3100
+ phases: own ? phaseDurations(own.phases, now()) : {}
3101
+ };
3102
+ const slot = own?.slot ?? "default";
3103
+ const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
3104
+ const savedSlot = isJsonObject(slots[slot]) ? slots[slot] : {};
3105
+ const savedDevice = slot === "default" ? state[platform] : savedSlot[platform];
3106
+ if (platform !== "macos" && (record.devicePlacement || isJsonObject(savedDevice))) {
3107
+ const device = isJsonObject(savedDevice) ? { ...savedDevice } : {};
3108
+ if (record.devicePlacement) device.devicePlacement = record.devicePlacement;
3109
+ else delete device.devicePlacement;
3110
+ state = slot === "default" ? {
3111
+ ...state,
3112
+ [platform]: device
3113
+ } : {
3114
+ ...state,
3115
+ deviceSlots: {
3116
+ ...slots,
3117
+ [slot]: {
3118
+ ...savedSlot,
3119
+ [platform]: device
3120
+ }
3121
+ }
3122
+ };
3123
+ }
3124
+ if (platform === "macos") return withHistoryEntry(state, platform, entry);
3125
+ return withHistoryEntry({
3126
+ ...state,
3127
+ lastBuild: record,
3128
+ [LAST_BUILD_KEYS[platform]]: record
3129
+ }, platform, entry);
3130
+ });
3131
+ }
3132
+ /** Adds one phase's duration to the recorded run of `platform` that started at `startedAt`. */
3133
+ function recordBuildPhase(root, platform, startedAt, phase, ms) {
3134
+ updateWorkspaceState(root, (state) => {
3135
+ const saved = state[BUILD_HISTORY_KEY];
3136
+ const list = isJsonObject(saved) && Array.isArray(saved[platform]) ? saved[platform] : [];
3137
+ const index = list.findIndex((entry) => isJsonObject(entry) && entry.startedAt === startedAt);
3138
+ const entry = list[index];
3139
+ if (!isJsonObject(entry)) return state;
3140
+ const phases = isJsonObject(entry.phases) ? entry.phases : {};
3141
+ const next = [...list];
3142
+ next[index] = {
3143
+ ...entry,
3144
+ phases: {
3145
+ ...phases,
3146
+ [phase]: Math.round(ms)
3147
+ }
3148
+ };
3149
+ return {
3150
+ ...state,
3151
+ [BUILD_HISTORY_KEY]: {
3152
+ ...saved,
3153
+ [platform]: next
3154
+ }
3155
+ };
3156
+ });
3157
+ }
3158
+ function withInterruptedBuild(state) {
3159
+ const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
3160
+ if (!left) return state;
3161
+ const last = left.platform === "macos" ? state[BUILD_HISTORY_KEY]?.macos?.[0] : state[LAST_BUILD_KEYS[left.platform]];
3162
+ if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
3163
+ return withHistoryEntry(state, left.platform, {
3164
+ platform: left.platform,
3165
+ status: "failed",
3166
+ result: "interrupted",
3167
+ slot: left.slot,
3168
+ configuration: null,
3169
+ fingerprint: null,
3170
+ cacheKey: null,
3171
+ cacheHit: false,
3172
+ cacheSkipped: false,
3173
+ durationMs: null,
3174
+ startedAt: left.startedAt,
3175
+ phases: {
3176
+ ...phaseDurations(left.phases, Date.parse(left.phaseStartedAt)),
3177
+ [left.phase]: 0
3178
+ }
3179
+ });
3180
+ }
3181
+ function parseActiveBuild(value) {
3182
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3183
+ const record = value;
3184
+ const claim = record.claim;
3185
+ if (record.platform !== "ios" && record.platform !== "android" && record.platform !== "macos") return null;
3186
+ if (!isPhase(record.phase)) return null;
3187
+ if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
3188
+ if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
3189
+ const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
3190
+ const missReason = parseMissReason(record.missReason);
3191
+ const placement = parsePlacement(record.placement);
3192
+ const waitingOn = parseWaitingOn(record.waitingOn);
3193
+ const waitingFor = parseWaitingFor(record.waitingFor);
3194
+ const estimate = parseEstimate(record.estimate);
3195
+ return {
3196
+ platform: record.platform,
3197
+ slot: typeof record.slot === "string" ? record.slot : "default",
3198
+ startedAt: record.startedAt,
3199
+ phase: record.phase,
3200
+ phaseStartedAt: record.phaseStartedAt,
3201
+ phases,
3202
+ ...missReason ? { missReason } : {},
3203
+ ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
3204
+ ...placement ? { placement } : {},
3205
+ ...waitingOn ? { waitingOn } : {},
3206
+ ...waitingFor ? { waitingFor } : {},
3207
+ ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
3208
+ ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
3209
+ ...estimate ? { estimate } : {},
3210
+ claim: {
3211
+ root: claim.root,
3212
+ path: typeof claim.path === "string" ? claim.path : "",
3213
+ claimId: claim.claimId,
3214
+ pid: typeof claim.pid === "number" ? claim.pid : 0
3215
+ }
3216
+ };
3217
+ }
3218
+ function parsePlacement(value) {
3219
+ if (!value || typeof value !== "object") return null;
3220
+ const { host, phase, startedAt, phaseStartedAt } = value;
3221
+ if ([
3222
+ host,
3223
+ phase,
3224
+ startedAt,
3225
+ phaseStartedAt
3226
+ ].some((field) => typeof field !== "string")) return null;
3227
+ return {
3228
+ host,
3229
+ phase,
3230
+ startedAt,
3231
+ phaseStartedAt
3232
+ };
3233
+ }
3234
+ function parseWaitingFor(value) {
3235
+ if (!value || typeof value !== "object") return null;
3236
+ const { kind, inUse, max, since } = value;
3237
+ if (kind !== "build-slot" && kind !== "device-slot") return null;
3238
+ if (!Number.isInteger(inUse) || inUse < 0 || !Number.isInteger(max) || max <= 0) return null;
3239
+ if (typeof since !== "string" || !Number.isFinite(Date.parse(since))) return null;
3240
+ return {
3241
+ kind,
3242
+ inUse,
3243
+ max,
3244
+ since
3245
+ };
3246
+ }
3247
+ function parseWaitingOn(value) {
3248
+ const path = value?.path;
3249
+ return typeof path === "string" && path ? { path } : null;
3250
+ }
3251
+ function parseEstimate(value) {
3252
+ if (!value || typeof value !== "object") return null;
3253
+ const { outcome, expectedMs, basis, phaseMs, planned } = value;
3254
+ if (outcome !== null && outcome !== "hit" && outcome !== "cold") return null;
3255
+ if (expectedMs !== null && typeof expectedMs !== "number") return null;
3256
+ if (typeof basis !== "number" || !Array.isArray(planned) || !phaseMs || typeof phaseMs !== "object") return null;
3257
+ const durations = Object.entries(phaseMs).filter((entry) => isPhase(entry[0]) && typeof entry[1] === "number");
3258
+ return {
3259
+ outcome,
3260
+ expectedMs,
3261
+ basis,
3262
+ phaseMs: Object.fromEntries(durations),
3263
+ planned: planned.filter(isPhase)
3264
+ };
3265
+ }
3266
+ function isPhase(value) {
3267
+ return BUILD_PHASES.includes(value);
3268
+ }
3269
+ function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
3270
+ if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
3271
+ if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
3272
+ return survey.unresolved.length ? "unknown" : "stale";
3273
+ }
3274
+ function median(values) {
3275
+ if (!values.length) return null;
3276
+ const sorted = values.toSorted((a, b) => a - b);
3277
+ const mid = Math.floor(sorted.length / 2);
3278
+ return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
3279
+ }
3280
+ function estimateBuild(history, platform, known, deviceSetup) {
3281
+ const lists = platform === "macos" ? void 0 : history?.[platform];
3282
+ const outcome = known ?? latestOutcome(lists);
3283
+ const samples = matchDeviceSetup(outcome && lists?.[outcome] || [], deviceSetup);
3284
+ const phaseMs = {};
3285
+ const planned = [];
3286
+ for (const phase of BUILD_PHASES) {
3287
+ const durations = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
3288
+ const ms = median(durations);
3289
+ if (ms === null) continue;
3290
+ phaseMs[phase] = ms;
3291
+ if (durations.length * 2 >= samples.length) planned.push(phase);
3292
+ }
3293
+ return {
3294
+ outcome,
3295
+ expectedMs: median(samples.map((sample) => sample.durationMs)),
3296
+ basis: samples.length,
3297
+ phaseMs,
3298
+ planned
3299
+ };
3300
+ }
3301
+ const MIN_TAGGED_SAMPLES = 3;
3302
+ function matchDeviceSetup(samples, deviceSetup) {
3303
+ if (deviceSetup === void 0) return samples;
3304
+ const tagged = samples.filter((sample) => sample.deviceSetup === deviceSetup);
3305
+ if (deviceSetup || tagged.length >= MIN_TAGGED_SAMPLES) return tagged;
3306
+ return samples.filter((sample) => sample.deviceSetup !== true);
3307
+ }
3308
+ function latestOutcome(lists) {
3309
+ let latest = null;
3310
+ for (const outcome of ["hit", "cold"]) {
3311
+ const last = lists?.[outcome]?.at(-1);
3312
+ const at = last ? Date.parse(last.at) : NaN;
3313
+ if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
3314
+ outcome,
3315
+ at
3316
+ };
3317
+ }
3318
+ return latest?.outcome ?? null;
3319
+ }
3320
+ function buildReport(record, { state, history }) {
3321
+ const estimate = record.estimate ?? estimateBuild(history, record.platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
3322
+ const plannedPhases = estimate.planned.flatMap((phase) => {
3323
+ const expectedMs = estimate.phaseMs[phase];
3324
+ return expectedMs === void 0 ? [] : [{
3325
+ phase,
3326
+ expectedMs
3327
+ }];
3328
+ });
3329
+ const completedPhaseMs = completedPhaseDurations(record.phases);
3330
+ return {
3331
+ platform: record.platform,
3332
+ slot: record.slot,
3333
+ state,
3334
+ phase: record.phase,
3335
+ startedAt: record.startedAt,
3336
+ phaseStartedAt: record.phaseStartedAt,
3337
+ outcome: estimate.outcome,
3338
+ outcomeKnown: record.outcome !== void 0,
3339
+ ...record.outcome && record.phases.some(({ phase }) => phase === "cache-lookup") ? { cacheLookupOutcome: record.outcome === "hit" ? "hit" : "miss" } : {},
3340
+ expectedMs: estimate.expectedMs,
3341
+ expectedPhaseMs: estimate.phaseMs[record.phase] ?? null,
3342
+ ...completedPhaseMs ? { completedPhaseMs } : {},
3343
+ basis: estimate.basis,
3344
+ plannedPhases: plannedPhases.length ? plannedPhases : null,
3345
+ ...record.missReason ? { missReason: record.missReason } : {},
3346
+ ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
3347
+ placement: record.placement ?? "local",
3348
+ ...record.waitingOn ? { waitingOn: record.waitingOn } : {},
3349
+ ...record.waitingFor ? { waitingFor: record.waitingFor } : {}
3350
+ };
3351
+ }
3352
+ function remainingText(remainingMs) {
3353
+ return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
3354
+ }
3355
+ function buildStatusLine(report, now) {
3356
+ const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
3357
+ if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
3358
+ const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
3359
+ const remote = typeof report.placement === "object" ? ` on ${report.placement.host} (${report.placement.phase}, ${formatElapsed(Math.max(0, now - Date.parse(report.placement.phaseStartedAt)))})` : "";
3360
+ const waiting = report.waitingOn ? ` on ${report.waitingOn.path}` : "";
3361
+ const head = `build: ${report.platform}${slot} ${report.phase}${waiting}${remote}, ${formatElapsed(elapsedMs)} elapsed`;
3362
+ if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
3363
+ if (report.expectedMs === null) return head;
3364
+ const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
3365
+ const remainingMs = report.expectedMs - elapsedMs;
3366
+ return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
3367
+ }
3368
+ //#endregion
3369
+ //#region src/commands/gc/idle.ts
3370
+ function findOwnedDeviceActivity({ config, sims, androidSerial, readActivity, deadProjects = [] }) {
3371
+ const booted = new Map(sims.filter((sim) => sim.state === "Booted").map((sim) => [sim.udid, sim.name]));
3372
+ const dead = new Set(deadProjects);
3373
+ const found = [];
3374
+ for (const [project, record] of Object.entries(config?.projects ?? {})) {
3375
+ if (dead.has(project)) continue;
3376
+ for (const { slot, platforms } of projectDeviceSlots(record)) {
3377
+ const ios = platforms.ios;
3378
+ if (ios?.owned && ios.deviceUdid && booted.has(ios.deviceUdid)) found.push({
3379
+ kind: "ios",
3380
+ id: ios.deviceUdid,
3381
+ name: booted.get(ios.deviceUdid) ?? ios.deviceUdid,
3382
+ project,
3383
+ slot,
3384
+ activity: readActivity({
3385
+ platform: "ios",
3386
+ id: ios.deviceUdid,
3387
+ slot,
3388
+ workspace: project
3389
+ })
3390
+ });
3391
+ const android = platforms.android;
3392
+ const serial = android?.owned && android.avdName ? androidSerial(android.avdName) : null;
3393
+ if (android?.avdName && serial) found.push({
3394
+ kind: "android",
3395
+ id: android.avdName,
3396
+ name: android.avdName,
3397
+ project,
3398
+ slot,
3399
+ activity: readActivity({
3400
+ platform: "android",
3401
+ id: serial,
3402
+ slot,
3403
+ workspace: project
3404
+ })
3405
+ });
3406
+ }
3407
+ }
3408
+ return found;
3409
+ }
3410
+ function findIdleDevices({ buildInProgress, now, ...inputs }) {
3411
+ return findOwnedDeviceActivity(inputs).filter((device) => device.activity.state === "idle").map(({ kind, id, name, project, slot, activity }) => ({
3412
+ kind,
3413
+ id,
3414
+ name,
3415
+ project,
3416
+ slot,
3417
+ lastActivityAt: activity.lastActivityAt ?? null,
3418
+ idleForMs: idleForMs(activity, now),
3419
+ buildInProgress: buildInProgress(project)
3420
+ }));
3421
+ }
3422
+ function idleShutdownCandidates(devices, idleMs) {
3423
+ return devices.filter((d) => !d.buildInProgress && d.idleForMs !== null && d.idleForMs >= idleMs);
3424
+ }
3425
+ function parseIdleDuration(value) {
3426
+ const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value.trim());
3427
+ if (!match) return null;
3428
+ const unit = {
3429
+ m: 6e4,
3430
+ h: 36e5,
3431
+ d: 864e5
3432
+ }[match[2]];
3433
+ return Number(match[1]) * unit;
3434
+ }
3435
+ function workspaceBuildInProgress(project) {
3436
+ const record = parseActiveBuild(readWorkspaceState$1(project)?.[ACTIVE_BUILD_KEY]);
3437
+ if (!record) return false;
3438
+ try {
3439
+ return activeBuildState(record.claim) !== "stale";
3440
+ } catch {
3441
+ return true;
3442
+ }
3443
+ }
3444
+ function deviceActivityInputs(config, sims, now) {
3445
+ const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
3446
+ return {
3447
+ config,
3448
+ sims,
3449
+ readActivity: createActivityReader({ now }),
3450
+ androidSerial: (avdName) => {
3451
+ try {
3452
+ return resolve(avdName).serial ?? null;
3453
+ } catch {
3454
+ return null;
3455
+ }
3456
+ }
3457
+ };
3458
+ }
3459
+ function collectOwnedDeviceActivity(config, sims, now = Date.now()) {
3460
+ return findOwnedDeviceActivity(deviceActivityInputs(config, sims, now));
3461
+ }
3462
+ function collectIdleDevices(config, sims, deadProjects, now = Date.now()) {
3463
+ return findIdleDevices({
3464
+ ...deviceActivityInputs(config, sims, now),
3465
+ deadProjects,
3466
+ now,
3467
+ buildInProgress: workspaceBuildInProgress
3468
+ });
3469
+ }
3470
+ function idleDeviceLines(devices) {
3471
+ if (devices.length === 0) return [];
3472
+ return [`Idle owned devices (${devices.length}) - booted with no driver, claim, or recent activity:`, ...devices.map((d) => {
3473
+ const slot = d.slot === "default" ? "" : ` [${d.slot}]`;
3474
+ const idle = d.idleForMs === null ? "idle for an unknown time" : `idle ${formatLongDuration(d.idleForMs)}`;
3475
+ const build = d.buildInProgress ? ", build in progress" : "";
3476
+ return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
3477
+ })];
3478
+ }
3479
+ function shutDownIdleDevices(devices, idleMs, recollect) {
3480
+ const candidates = idleShutdownCandidates(devices, idleMs);
3481
+ const shutDown = [];
3482
+ if (candidates.length === 0) {
3483
+ console.log(chalk.dim(`No owned device has been idle for ${formatLongDuration(idleMs)} or more.`));
3484
+ return {
3485
+ failures: 0,
3486
+ shutDown
3487
+ };
3488
+ }
3489
+ const fresh = idleShutdownCandidates(recollect(), idleMs);
3490
+ let failures = 0;
3491
+ for (const device of candidates) {
3492
+ const what = `${device.kind} ${device.name}`;
3493
+ const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
3494
+ if (!current) {
3495
+ console.log(chalk.dim(`Kept ${what}: it is no longer idle for ${formatLongDuration(idleMs)}.`));
3496
+ recordGcResult("idleDevice", "kept", device.name, {
3497
+ id: device.id,
3498
+ detail: `it is no longer idle for ${formatLongDuration(idleMs)}`
3499
+ });
3500
+ continue;
3501
+ }
3502
+ const outcome = device.kind === "ios" ? teardownOwnedIosSim(device.id, { label: device.name }) : teardownOwnedAvd(device.id, { owner: {
3503
+ projectPath: device.project,
3504
+ slot: device.slot
3505
+ } });
3506
+ if (outcome.status === "torn-down") {
3507
+ shutDown.push(device);
3508
+ console.log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
3509
+ recordGcResult("idleDevice", "done", device.name, {
3510
+ id: device.id,
3511
+ detail: `idle ${formatLongDuration(current.idleForMs)}`
3512
+ });
3513
+ } else if (outcome.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to shut down.`));
3514
+ else if (outcome.status === "skipped") {
3515
+ console.log(chalk.yellow(`Skipped ${what}: ${outcome.reason}`));
3516
+ recordGcResult("idleDevice", "kept", device.name, {
3517
+ id: device.id,
3518
+ detail: outcome.reason
3519
+ });
3520
+ } else {
3521
+ failures++;
3522
+ console.log(chalk.red(`Failed to shut down ${what}: ${outcome.reason}`));
3523
+ recordGcResult("idleDevice", "failed", device.name, {
3524
+ id: device.id,
3525
+ detail: outcome.reason
3526
+ });
3527
+ }
3528
+ }
3529
+ return {
3530
+ failures,
3531
+ shutDown
3532
+ };
3533
+ }
3534
+ //#endregion
3535
+ //#region src/supervisor/stop-cause.ts
3536
+ function requestDevServerStop(root, processToken, requester) {
3537
+ if (!processToken) return;
3538
+ try {
3539
+ writeWorkspaceState(root, { [DEV_SERVER_STOP_REQUEST_KEY]: {
3540
+ processToken,
3541
+ by: requester.by,
3542
+ pid: process.pid,
3543
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3544
+ ...requester.workspace ? { workspace: requester.workspace } : {}
3545
+ } });
3546
+ } catch {}
3547
+ }
3548
+ function withdrawDevServerStopRequest(root, processToken) {
3549
+ if (!processToken) return;
3550
+ try {
3551
+ clearWorkspaceStateKey(root, DEV_SERVER_STOP_REQUEST_KEY, (value) => {
3552
+ return readDevServerStopRequest({ [DEV_SERVER_STOP_REQUEST_KEY]: value })?.processToken === processToken;
3553
+ });
3554
+ } catch {}
3555
+ }
3556
+ function devServerStopRecord(trigger, request, at) {
3557
+ if (request) return {
3558
+ reason: "requested",
3559
+ at,
3560
+ by: request.by,
3561
+ byPid: request.pid,
3562
+ ...request.workspace ? { byWorkspace: request.workspace } : {}
3563
+ };
3564
+ return trigger.kind === "signal" ? {
3565
+ reason: "signal",
3566
+ at,
3567
+ signal: trigger.signal
3568
+ } : {
3569
+ reason: "server-exited",
3570
+ at,
3571
+ mode: trigger.mode,
3572
+ code: trigger.code,
3573
+ signal: trigger.signal
3574
+ };
3575
+ }
3576
+ function describeDevServerStop(record) {
3577
+ if (record.reason === "idle") return `idle for ${record.idleMinutes} minutes (metro.idleStopMinutes)`;
3578
+ if (record.reason === "requested") return `stop requested by ${record.by} (pid ${record.byPid}${record.byWorkspace ? ` in ${record.byWorkspace}` : ""})`;
3579
+ if (record.reason === "signal") return `received ${record.signal} with no Stim stop request recorded, so it likely came from outside Stim`;
3580
+ const detail = record.signal ? `signal ${record.signal}` : `exit code ${record.code ?? "unknown"}`;
3581
+ const expoSignal = record.mode === "expo-child" && record.code === 0 ? "; Expo CLI also exits 0 on SIGTERM, and no Stim stop request was recorded" : "";
3582
+ return `the ${record.mode} dev server exited unexpectedly (${detail})${expoSignal}`;
3583
+ }
3584
+ function devServerStopLevel(record) {
3585
+ if (record.reason === "server-exited") return "error";
3586
+ return record.reason === "signal" ? "warn" : "info";
3587
+ }
3588
+ /** The log line for a recorded supervisor whose process is proven gone, or null while it may still run. */
3589
+ function vanishedSupervisorMessage(record) {
3590
+ if (!record || typeof record.pid !== "number") return null;
3591
+ const identity = (0, process_identity_exports.inspectProcessIdentity)(record);
3592
+ if (identity !== "gone" && identity !== "different") return null;
3593
+ const started = typeof record.startedAt === "string" ? ` (started ${record.startedAt})` : "";
3594
+ return `supervisor pid ${record.pid}${started} ${VANISHED}`;
3595
+ }
3596
+ const VANISHED = "is gone and recorded no cause: usually SIGKILL, a crash or a machine restart";
3597
+ function logVanishedSupervisor(root, record) {
3598
+ const msg = vanishedSupervisorMessage(record);
3599
+ if (!msg) return;
3600
+ const writer = createNdjsonWriter(join(workspaceLogsDir$1(root), "metro.ndjson"), { maxBytes: LOG_ROTATE_BYTES });
3601
+ writer.write({
3602
+ src: "metro",
3603
+ level: "warn",
3604
+ event: "supervisor_vanished",
3605
+ msg
3606
+ });
3607
+ writer.close();
3608
+ }
3609
+ /** The recorded stop, else a vanished supervisor when status proved the recorded one gone. */
3610
+ function metroLastStop(state, supervisor) {
3611
+ const recorded = readDevServerStop(state);
3612
+ if (recorded) return recorded;
3613
+ if (supervisor?.status !== "stale" || typeof supervisor.pid !== "number") return null;
3614
+ const startedAt = typeof supervisor.startedAt === "string" ? supervisor.startedAt : null;
3615
+ return {
3616
+ reason: "vanished",
3617
+ pid: supervisor.pid,
3618
+ startedAt
3619
+ };
3620
+ }
3621
+ function describeMetroLastStop(stop) {
3622
+ return stop.reason === "vanished" ? `supervisor pid ${stop.pid} ${VANISHED}` : describeDevServerStop(stop);
3623
+ }
3624
+ //#endregion
2115
3625
  //#region src/supervisor/cache-reset.ts
2116
3626
  async function stopOwnedMetro(root, requester) {
2117
3627
  const project = getProject(root);
@@ -2163,7 +3673,6 @@ async function stopOwnedMetroForReset(root) {
2163
3673
  const MB = 1048576;
2164
3674
  const GB_IN_MB = 1024;
2165
3675
  const COMMITTED_MEMORY_SHARE = .6;
2166
- const STALE_CACHE_DAYS = 14;
2167
3676
  const DEVICE_LIST_TIMEOUT_MS = 1e4;
2168
3677
  const USAGE_TIMEOUT_MS = 1e4;
2169
3678
  const BUDGET_SETTING_REMEDY = "Fix the value with `stim settings set <key> <value>`, or unset the environment variable. Run `stim guide settings` for the budget keys.";
@@ -2207,7 +3716,7 @@ function machineNumber(key, config, env) {
2207
3716
  error: null
2208
3717
  };
2209
3718
  }
2210
- function resolveBudget({ config = loadConfig$1(), env = process.env, totalMemoryMb = totalmem$1() / MB } = {}) {
3719
+ function resolveBudget({ config = loadConfig$1(), env = process.env, totalMemoryMb = totalmem() / MB } = {}) {
2211
3720
  const minFree = machineNumber("budget.minFreeDiskGb", config, env);
2212
3721
  const hardFloor = machineNumber("budget.hardFloorDiskGb", config, env);
2213
3722
  const memory = machineNumber("budget.maxCommittedMemoryGb", config, env);
@@ -2280,6 +3789,13 @@ function listSims() {
2280
3789
  return null;
2281
3790
  }
2282
3791
  }
3792
+ function snapshotSims(snapshot) {
3793
+ try {
3794
+ return snapshot.read().filter((sim) => sim.available);
3795
+ } catch {
3796
+ return null;
3797
+ }
3798
+ }
2283
3799
  function runningSupervisorPid(path, project) {
2284
3800
  const target = resolveSupervisorTarget({
2285
3801
  state: readWorkspaceState$1(path)?.supervisor,
@@ -2288,11 +3804,11 @@ function runningSupervisorPid(path, project) {
2288
3804
  });
2289
3805
  return target.status === "ours" ? target.pid ?? null : null;
2290
3806
  }
2291
- function readMemoryUse() {
3807
+ function readMemoryUse(simSnapshot) {
2292
3808
  const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
2293
3809
  return estimateMemoryUse({
2294
3810
  config: loadConfig$1(),
2295
- sims: listSims(),
3811
+ sims: simSnapshot ? snapshotSims(simSnapshot) : listSims(),
2296
3812
  androidSerial: (avdName) => {
2297
3813
  try {
2298
3814
  return resolve(avdName).serial ?? null;
@@ -2475,14 +3991,17 @@ async function reclaimWorkspaceOutputs({ root, dryRun, now, diskRecovered }) {
2475
3991
  function cacheLabel(cache) {
2476
3992
  return `${cache.name} ${cache.dir}`;
2477
3993
  }
3994
+ function trimmableCaches() {
3995
+ return planCacheEmptying(discoverCaches(), false).filter((cache) => cache.prune !== "report-only" && cache.prune !== "atomic" && !cache.machineGlobal);
3996
+ }
2478
3997
  function reclaimStaleCacheEntries({ dryRun }) {
2479
- const caches = planCacheEmptying(discoverCaches(), false).filter((cache) => cache.prune !== "report-only" && cache.prune !== "atomic" && !cache.machineGlobal);
3998
+ const caches = trimmableCaches();
2480
3999
  if (dryRun) return {
2481
4000
  targets: caches.map(cacheLabel),
2482
4001
  failures: 0
2483
4002
  };
2484
4003
  return {
2485
- targets: trimCaches(caches, STALE_CACHE_DAYS).map(cacheLabel),
4004
+ targets: trimCaches(caches, 14).map(cacheLabel),
2486
4005
  failures: 0
2487
4006
  };
2488
4007
  }
@@ -2496,7 +4015,7 @@ const STEP_ACTIONS = {
2496
4015
  "idle-devices": "shut down idle owned devices",
2497
4016
  "idle-dev-servers": "stop idle dev servers",
2498
4017
  "workspace-outputs": "clear build outputs of idle workspaces",
2499
- "stale-cache-entries": `trim cache entries unused for ${STALE_CACHE_DAYS} days`
4018
+ "stale-cache-entries": `trim cache entries unused for 14 days`
2500
4019
  };
2501
4020
  async function runStep(step, context) {
2502
4021
  const log = console.log;
@@ -2520,7 +4039,7 @@ async function runStep(step, context) {
2520
4039
  }
2521
4040
  }
2522
4041
  function spaceUsers() {
2523
- const home = homedir$1();
4042
+ const home = homedir();
2524
4043
  const candidates = [
2525
4044
  ["Stim state", getConfigDir()],
2526
4045
  ["shared build cache", sharedBuildCache()],
@@ -2555,10 +4074,10 @@ function describeShortfall(budget, measure, short) {
2555
4074
  return [
2556
4075
  ...short.disk.map((v) => `${formatSpace(v.freeMb)} free on ${v.volume}, below the ${formatSpace(floor)} floor`),
2557
4076
  ...short.memory && measure.memory ? [`roughly ${formatSpace(measure.memory.committedMb)} of memory committed, over the ${formatSpace(budget.maxCommittedMemoryMb)} budget`] : [],
2558
- ...short.workspaces && measure.memory ? [`${measure.memory.liveWorkspaces} live workspaces, over the limit of ${budget.maxLiveWorkspaces}`] : []
4077
+ ...short.workspaces && measure.memory ? [`${measure.memory.liveWorkspaces} active workspaces, over the limit of ${budget.maxLiveWorkspaces}`] : []
2559
4078
  ];
2560
4079
  }
2561
- async function enforceBudget({ root, note, dryRun = false, budget: resolved }, overrides = {}) {
4080
+ async function enforceBudget({ root, note, dryRun = false, budget: resolved, simSnapshot }, overrides = {}) {
2562
4081
  const deps = {
2563
4082
  ...DEFAULT_DEPS,
2564
4083
  ...overrides
@@ -2579,7 +4098,7 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved }, o
2579
4098
  const paths = [root, getConfigDir()];
2580
4099
  const measureNow = () => ({
2581
4100
  volumes: budget.minFreeDiskMb > 0 || budget.hardFloorDiskMb > 0 ? deps.volumes(paths) : [],
2582
- memory: budget.maxCommittedMemoryMb > 0 || budget.maxLiveWorkspaces > 0 ? deps.memory() : null
4101
+ memory: budget.maxCommittedMemoryMb > 0 || budget.maxLiveWorkspaces > 0 ? deps.memory(simSnapshot) : null
2583
4102
  });
2584
4103
  let measure = measureNow();
2585
4104
  let short = budgetShortfalls(budget, measure);
@@ -2600,6 +4119,7 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved }, o
2600
4119
  }).disk.length === 0
2601
4120
  });
2602
4121
  if (!dryRun) {
4122
+ simSnapshot?.invalidate();
2603
4123
  measure = measureNow();
2604
4124
  short = budgetShortfalls(budget, measure);
2605
4125
  }
@@ -2677,12 +4197,12 @@ function budgetFindings(budget, measure, short, plan) {
2677
4197
  }
2678
4198
  if (short.memory || short.workspaces) findings.push({
2679
4199
  level: "note",
2680
- title: "Live environments are over the memory budget",
4200
+ title: "Active environments are over the memory budget",
2681
4201
  detail: `${describeShortfall(budget, measure, {
2682
4202
  ...short,
2683
4203
  disk: []
2684
4204
  }).join("; ")}. The next ios, android or start shuts down idle owned devices and stops idle dev servers in other workspaces first.`,
2685
- fix: "Run `stim status` to see what is live, and `stim stop` in workspaces you own that you no longer need."
4205
+ fix: "Run `stim status` to see what is active, and `stim stop` in workspaces you own that you no longer need."
2686
4206
  });
2687
4207
  return findings;
2688
4208
  }
@@ -2734,4 +4254,4 @@ function budgetLine(report) {
2734
4254
  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"}`;
2735
4255
  }
2736
4256
  //#endregion
2737
- export { sizeCaches as $, includesWorkspaceOutputs as A, collectMemoryReport as B, findStaleProjectDevices as C, includesArchives as D, emptyCaches as E, selectsBeyondCaches as F, memorySweepIsScoped as G, memoryCacheKind as H, trimCaches as I, scopedMemoryReport as J, memoryWorkPending as K, parseArchiveSelector as L, planCacheEmptying as M, previewScopedEmptying as N, includesParkedDevices as O, selectCaches as P, discoverCaches as Q, GRADLE_DAEMONS_KIND as R, findStaleDeviceRecords as S, withAndroidAvdSizes as T, memoryLines as U, inspectWatchmanMemory as V, memoryScope as W, attributeMachineUsage as X, phase as Y, readFootprints as Z, describeParkedSims as _, machineNumber as a, eraseParkedDevices as b, resolveBudget as c, collectDeviceLeases as d, collectParkedAvds as f, deleteProjectDevices as g, deleteParkedSims as h, inspectBudget as i, isWorkspacesScope as j, includesRecordings as k, stopOwnedMetroForReset as l, deleteParkedAvds as m, budgetLine as n, peekBudget as o, collectParkedSims as p, reclaimMemory as q, enforceBudget as r, readVolumeSpace as s, budgetGate as t, DEVICE_LIST_TIMEOUT_MS$1 as u, describeUnverifiableDevices as v, unverifiedDeviceCommand as w, findOrphanedDevices as x, deviceSweepIsScoped as y, WATCHMAN_KIND as z };
4257
+ export { describeUnverifiableDevices as $, sizeCaches as $t, buildReport as A, phase as At, readStats$1 as B, clearWorkspaceLaunches as Bt, idleDeviceLines as C, memoryCacheKind as Ct, ACTIVE_BUILD_KEY as D, memoryWorkPending as Dt, workspaceBuildInProgress as E, memorySweepIsScoped as Et, recordFinishedBuild as F, MODE_BARE as Ft, DEVICE_LIST_TIMEOUT_MS$1 as G, readRemoteSessionId as Gt, recordCapacityWait as H, readMetroTunnel as Ht, startBuildProgress as I, MODE_EXPO as It, collectParkedSims as J, writeWorkspaceLaunch as Jt, collectDeviceLeases as K, readWorkspaceLaunches as Kt, tapBuildLog as L, clearExpoMetroTunnel as Lt, estimateBuild as M, parseTriggerCount as Mt, parseActiveBuild as N, parseWatchRoots as Nt, NO_BUILD_PROGRESS as O, reclaimMemory as Ot, recordBuildPhase as P, runWatchman as Pt, describeParkedSims as Q, pruneCache as Qt, createRunRecorder as R, clearManagedMetroTunnel as Rt, collectOwnedDeviceActivity as S, inspectWatchmanMemory as St, shutDownIdleDevices as T, memoryScope as Tt, recordRunStats as U, readPidFile as Ut, recordCapacityRefusal as V, clearWorkspaceSupervisor as Vt, statsProjectKey$1 as W, readRemoteSession as Wt, deleteParkedSims as X, readFootprints as Xt, deleteParkedAvds as Y, attributeMachineUsage as Yt, deleteProjectDevices as Z, discoverCaches as Zt, metroLastStop as _, trimCaches as _t, inspectBudget as a, unverifiedDeviceCommand as at, withdrawDevServerStopRequest as b, WATCHMAN_KIND as bt, readVolumeSpace as c, includesArchives as ct, stopOwnedMetroForReset as d, includesWorkspaceOutputs as dt, deviceSweepIsScoped as et, describeDevServerStop as f, isWorkspacesScope as ft, logVanishedSupervisor as g, selectsBeyondCaches as gt, devServerStopRecord as h, selectCaches as ht, enforceBudget as i, findStaleProjectDevices as it, buildStatusLine as j, parseSubscriberNames as jt, activeBuildState as k, scopedMemoryReport as kt, resolveBudget as l, includesParkedDevices as lt, devServerStopLevel as m, previewScopedEmptying as mt, budgetLine as n, findOrphanedDevices as nt, machineNumber as o, withAndroidAvdSizes as ot, describeMetroLastStop as p, planCacheEmptying as pt, collectParkedAvds as q, writePidFile as qt, cacheLabel as r, findStaleDeviceRecords as rt, peekBudget as s, emptyCaches as st, budgetGate as t, eraseParkedDevices as tt, trimmableCaches as u, includesRecordings as ut, requestDevServerStop as v, parseArchiveSelector as vt, parseIdleDuration as w, memoryLines as wt, collectIdleDevices as x, collectMemoryReport as xt, vanishedSupervisorMessage as y, GRADLE_DAEMONS_KIND as yt, readRunEstimates as z, clearRemoteSession as zt };