stim 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -7
- package/dist/{acquire-artifact-CD4lbiWE.mjs → acquire-artifact-ybouwJSR.mjs} +21 -3
- package/dist/{act-DHAw2A2S.mjs → act-DG54PWtJ.mjs} +298 -164
- package/dist/{activity-BzVfHSrY.mjs → activity-B5RtpFda.mjs} +33 -12
- package/dist/{agent-device-usage-output-zh1vhbLp.mjs → agent-device-usage-output-UJ5gC80l.mjs} +7 -7
- package/dist/{android-BLgbNqox.mjs → android-BoUJKbfz.mjs} +66 -52
- package/dist/{android-DUYkNF4u.mjs → android-C7sVmyI9.mjs} +160 -59
- package/dist/{android-CV_GCELZ.mjs → android-CHH4KLOR.mjs} +5 -5
- package/dist/{android-BEJ8Jpl7.mjs → android-DR5lZ2Az.mjs} +2 -2
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{android-cas-Ct5b1xCK.mjs → android-cas-ngferVq2.mjs} +3 -3
- package/dist/{client-B7QVOlCr.d.mts → android-project-DVWyCbWU.d.mts} +145 -7
- package/dist/api.d.mts +132 -118
- package/dist/api.mjs +239 -42
- package/dist/{artifact-DAuZ-FNU.mjs → artifact-5RteZem7.mjs} +69 -40
- package/dist/{artifact-B8T3S5gi.mjs → artifact-Dwdo7k1E.mjs} +40 -35
- package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
- package/dist/{browser-web-QQzVjz3O.mjs → browser-web-CeEVlGug.mjs} +187 -59
- package/dist/{budget-Bs6SxxzO.mjs → budget-DnPX3DXj.mjs} +830 -699
- package/dist/{build-CoPzQRNw.mjs → build-CqufZFqy.mjs} +34 -27
- package/dist/{build-cache-DmyR9Zzc.mjs → build-cache-Dv7RN200.mjs} +4 -4
- package/dist/{build-plan-CaFskB7_.mjs → build-plan-C8CIvRyi.mjs} +74 -14
- package/dist/{cache-manifest-DkkqguoK.mjs → cache-manifest-DSeQjt-Q.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-BJRZ9pG3.mjs → ccache-RcAy92gH.mjs} +6 -6
- package/dist/{cdp-CWmIUMbw.mjs → cdp-3fwICjzi.mjs} +1 -1
- package/dist/{chrome-B6tfCta1.mjs → chrome-Bbw689-6.mjs} +1 -1
- package/dist/{cli-0DYf8QhQ.mjs → cli-D-J9fWGV.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-UXD70ZbM.mjs → client-yyso1bwi.mjs} +218 -47
- package/dist/collector-run.d.mts +1 -1
- package/dist/collector-run.mjs +6 -6
- package/dist/{command-output-B1UrjZHL.mjs → command-output-Db8BqzxH.mjs} +1 -1
- package/dist/{config-CYcBF478.mjs → config-DlrsduwG.mjs} +17 -11
- package/dist/{created-devices-CqeBlrEn.mjs → created-devices-x9rN6bhs.mjs} +13 -3
- package/dist/{dependency-state-BbKQW83h.mjs → dependency-state-1I9KZT1Y.mjs} +2 -2
- package/dist/{detached-entry-DFRyG1_o.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
- package/dist/{dev-client-UOeESKm6.mjs → dev-client-BnYSDiKa.mjs} +2 -2
- package/dist/{device-DCxH7_By.mjs → device-BvzPUBs3.mjs} +16 -16
- package/dist/{device-ExJq8Ei7.mjs → device-CCkWmkAA.mjs} +71 -14
- package/dist/{device-capacity-CMlqEcvw.mjs → device-capacity-CjaSdrhg.mjs} +9 -10
- package/dist/device-host-worker.mjs +25 -32
- package/dist/{device-ios-DRUeKaFV.mjs → device-ios-uOiEETX2.mjs} +39 -16
- package/dist/{device-lease-run-C8raGFoP.mjs → device-lease-run-BgwPHc6u.mjs} +5 -5
- package/dist/{device-pool-DMRpVX9a.mjs → device-pool-C-ptmZTa.mjs} +3 -3
- package/dist/{doctor-DTvaDOda.mjs → doctor-8sr2tFOL.mjs} +24 -37
- package/dist/{doctor-Dhqebm0q.mjs → doctor-D2SN3Ltx.mjs} +19 -16
- package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-BLHCYxzi.mjs → doctor-pxxvipYC.mjs} +1 -1
- package/dist/{doctor-watchman-Cv1F10Kj.mjs → doctor-watchman-sy_RNhFt.mjs} +5 -5
- package/dist/{eas-build-BomfRnFT.mjs → eas-build-CyjU5K7E.mjs} +4 -4
- package/dist/{eas-fallback-DJwZpy0c.mjs → eas-fallback-DV3Wytf1.mjs} +27 -18
- package/dist/{eas-session-ledger-op6A54yi.mjs → eas-session-ledger-BzzsX97d.mjs} +8 -7
- package/dist/{error-diagnostics-Bf0rZvG9.mjs → error-diagnostics-C6sVoeqO.mjs} +10 -10
- package/dist/{exec-_UbtCQcG.mjs → exec-DEoAhcze.mjs} +155 -47
- package/dist/{exec-H20xSg2T.d.mts → exec-DnYc4HGH.d.mts} +17 -4
- package/dist/{gc-_-dCXdyL.mjs → gc-CDoogxAI.mjs} +587 -313
- package/dist/gc-DLbzvTP2.mjs +3 -0
- package/dist/{guide-Yqwt2bd6.mjs → guide-BC37GcDG.mjs} +288 -59
- package/dist/{hosted-android-Bw-honsd.mjs → hosted-android-CkclQig_.mjs} +6 -6
- package/dist/{hosted-client-DB_gBPfY.mjs → hosted-client-ByuMMO5s.mjs} +4 -4
- package/dist/{hosted-ios-CUvOf4Vr.mjs → hosted-ios-DtYHeuYR.mjs} +1 -1
- package/dist/{hosted-logs-DL_P5AdN.mjs → hosted-logs-Br84qWy1.mjs} +5 -5
- package/dist/{hosted-macos-QI141yKk.mjs → hosted-macos-DdtYajZt.mjs} +21 -12
- package/dist/{hosted-native-Ksyjgzq-.mjs → hosted-native-UPI9zdZ4.mjs} +27 -14
- package/dist/{idle-BVeRGroT.d.mts → idle-C4cZ757K.d.mts} +3 -3
- package/dist/{idle-shutdown-DaKmZi0z.mjs → idle-shutdown-DO7oIRlc.mjs} +7 -7
- package/dist/ios-CDvlmUs7.d.mts +24 -0
- package/dist/{ios-ubFsBzwq.mjs → ios-DZZbNYlY.mjs} +59 -50
- package/dist/{ios-device-CXGP1bsD.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
- package/dist/{ios-device-CwFMehUS.mjs → ios-device-JC3uWM4K.mjs} +3 -3
- package/dist/{ios-CpByZy12.mjs → ios-hcBpnEGk.mjs} +81 -34
- package/dist/{ios-project-WuwwBdvW.mjs → ios-project-CVyNwjAF.mjs} +5 -5
- package/dist/{ios-signing-DTTIChGs.mjs → ios-signing-BtAffm0A.mjs} +2 -2
- package/dist/{ios-state-DvFX-K_l.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
- package/dist/{ios-CQ68hG9s.mjs → ios-vbUOqGXu.mjs} +8 -8
- package/dist/{ios-qNJt4dFm.mjs → ios-xtlRHaeB.mjs} +1 -1
- package/dist/{js-swap-Cc1dttps.mjs → js-swap-C3QxUFJR.mjs} +7 -29
- package/dist/{launch-CD1yVc7U.mjs → launch-Bz7JtaC4.mjs} +16 -16
- package/dist/{launch-Zq2mOSDy.mjs → launch-CHY3p-yz.mjs} +40 -39
- package/dist/{logs-CMm05HxN.mjs → logs-CtNeNDuq.mjs} +10 -17
- package/dist/{logs-BS00Anxf.mjs → logs-mxqsWpJs.mjs} +151 -24
- package/dist/{macos-B65--pPd.mjs → macos-inNFbBWc.mjs} +1 -1
- package/dist/macos-run.mjs +1 -1
- package/dist/{macos-C-0qJ6zq.mjs → macos-u0BoHKnO.mjs} +143 -46
- package/dist/maintenance-run.mjs +8 -8
- package/dist/{metro-DEXpIhHC.mjs → metro-DiXtA3Ty.mjs} +59 -37
- package/dist/{metro-gateway-BMJNlj3E.mjs → metro-gateway-CKZqKqx7.mjs} +120 -22
- package/dist/{named-ports-BWTzt6mu.mjs → named-ports-DkEzOfzO.mjs} +40 -33
- package/dist/{native-android-JACyjTZs.mjs → native-android-CdCSylTn.mjs} +43 -242
- package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
- package/dist/{native-gradle-inputs-Bep9JcSF.mjs → native-gradle-inputs-Dnh84Zbd.mjs} +3 -129
- package/dist/{native-run-DY_RLx3C.mjs → native-run-BUTu8T7A.mjs} +4 -5
- package/dist/{native-runtime-Dg1Q7tfF.mjs → native-runtime-CRSOZxKY.mjs} +8 -7
- package/dist/native-source-Y0yLhFyQ.mjs +133 -0
- package/dist/{native-xcode-inputs-CB2iQeWq.mjs → native-xcode-inputs-G6rkkhxu.mjs} +2 -3
- package/dist/{native-xcode-ios-CXX39F7o.mjs → native-xcode-ios-_os2PlCD.mjs} +27 -32
- package/dist/offload-worker.d.mts +65 -2
- package/dist/offload-worker.mjs +305 -73
- package/dist/operation-exec-BW0em61G.mjs +171 -0
- package/dist/{ownership-DEmM5vhh.mjs → ownership-C5fo5Edb.mjs} +3 -3
- package/dist/{page-CPaHeYqj.mjs → page-DBL2PiUU.mjs} +8 -4
- package/dist/{placement-log-BqGbTa61.mjs → placement-log-BnGgJRRZ.mjs} +1 -1
- package/dist/{plan-placement-Bwj-IjY1.mjs → plan-placement-BU42oOyT.mjs} +12 -8
- package/dist/{ports-D-MmCTdv.mjs → ports-BSmQKzW7.mjs} +3 -3
- package/dist/{prebuild-uD_ipCxF.mjs → prebuild-BFm63bQ0.mjs} +63 -16
- package/dist/{project-BZNjmW_8.mjs → project-CTLNHCfE.mjs} +16 -16
- package/dist/{pull-request-Lfetn8Kr.mjs → pull-request-W1IuLXDt.mjs} +102 -20
- package/dist/{pull-request-zjCSAryH.d.mts → pull-request-eODyIqZF.d.mts} +6 -2
- package/dist/pull-requests.d.mts +2 -2
- package/dist/pull-requests.mjs +2 -2
- package/dist/{react-native-android-DuzH0cn3.mjs → react-native-android-CFgUroxW.mjs} +73 -53
- package/dist/{react-native-build-rhk4smwQ.mjs → react-native-build-CmJkTljQ.mjs} +8 -8
- package/dist/{react-native-doctor-CQpuIMso.mjs → react-native-doctor-LTF-Xt8l.mjs} +7 -7
- package/dist/{react-native-ios-Bhq1rJeV.mjs → react-native-ios-ChBF1MkB.mjs} +43 -29
- package/dist/{recordings-DYteQSu4.mjs → recordings-CQmfzAh7.mjs} +1 -1
- package/dist/registration-BcRgF6Ox.mjs +51 -0
- package/dist/{reload-CmTRFJaX.mjs → reload-DF9hqUUB.mjs} +16 -15
- package/dist/{reload-DzjSl5P_.mjs → reload-RBi1nj6Z.mjs} +1 -1
- package/dist/{remote-ByYkXdGD.mjs → remote-BmvMCcJw.mjs} +18 -15
- package/dist/{result-DNiS-tTP.mjs → result-Cx5CRLvS.mjs} +24 -23
- package/dist/{run-D23HmJMs.mjs → run-DidHghD1.mjs} +3 -2
- package/dist/{selection-CSl9i5qU.mjs → selection-DSMbWEX2.mjs} +6 -5
- package/dist/{server-bare-CxTrvDg8.mjs → server-bare-CafzuyFB.mjs} +2 -2
- package/dist/{server-command-CtzloH3v.mjs → server-command-BTiGPEjS.mjs} +5 -5
- package/dist/server-expo-C-Buqvn_.mjs +2 -0
- package/dist/{server-expo-DT_F_uVI.mjs → server-expo-D9lEz4aq.mjs} +7 -5
- package/dist/{settings-CsJDEXgK.mjs → settings-C7O8A6ZI.mjs} +223 -243
- package/dist/{settings-T1gtc3bf.mjs → settings-eKF_vJJs.mjs} +1 -1
- package/dist/{settings-write-HyVZ8eQ8.mjs → settings-write-79mQRr9C.mjs} +7 -6
- package/dist/settings.schema.json +13 -0
- package/dist/{simslim-Cgk17SXV.mjs → simslim-BNckWpXi.mjs} +2 -2
- package/dist/{slot-launch-DF8VfT64.mjs → slot-launch-D7M2nE4b.mjs} +6 -6
- package/dist/{stage-BIS0ydsV.mjs → stage-DKNM4ZPd.mjs} +21 -4
- package/dist/{start-RChynRDp.mjs → start-CRfN1kWV.mjs} +1 -1
- package/dist/{start-7lpH7hmN.mjs → start-bjfiScgz.mjs} +622 -393
- package/dist/{state-C4ZtUisW.mjs → state--smjFSTL.mjs} +22 -7
- package/dist/{state-XMziexv2.mjs → state-08I3hw3_.mjs} +2 -2
- package/dist/{stats-53T7OTb9.mjs → stats-DioA6cBU.mjs} +5 -5
- package/dist/{stats-report-BFfMpWWF.mjs → stats-report-BzaPrRm6.mjs} +1 -1
- package/dist/{status-DqEUSSxs.mjs → status-BRROykC8.mjs} +2 -2
- package/dist/{status-CSi6tlRq.mjs → status-Eqx_20WC.mjs} +1 -1
- package/dist/{status-BL7jVyAB.mjs → status-kzbeWk4x.mjs} +43 -29
- package/dist/{status-watch-GDCfqpTE.mjs → status-watch-BIqqbJ6q.mjs} +48 -16
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-C12we7lz.mjs → stim-desktop-D5OGpYZM.mjs} +2 -2
- package/dist/{stop-Bk_1qiV6.mjs → stop-B18d8KDw.mjs} +2 -2
- package/dist/{stop-CPzlM1L-.mjs → stop-B2uLQfoM.mjs} +15 -16
- package/dist/{stop-D7h6jAXQ.mjs → stop-DzLJc7Ei.mjs} +31 -22
- package/dist/supervisor-run.d.mts +3 -3
- package/dist/supervisor-run.mjs +28 -36
- package/dist/{support-Dlu37g6H.mjs → support-BTpZ6RAJ.mjs} +15 -11
- package/dist/{support-67vraa1Q.mjs → support-DTBV56gy.mjs} +8 -8
- package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
- package/dist/swiftpm-macos-r_WSKlXr.mjs +532 -0
- package/dist/{toolchain-Hp9sCpK4.mjs → toolchain-CWeGJgCc.mjs} +9 -6
- package/dist/toolchain-GVgnf8d1.mjs +2 -0
- package/dist/{trigger-BAahxUPq.mjs → trigger-Cbyc41d_.mjs} +3 -3
- package/dist/trigger-yb_nFzPF.mjs +2 -0
- package/dist/{warm-progress-BHX5r0Ei.mjs → warm-progress-CA3TgT3-.mjs} +29 -16
- package/dist/web-Cduk0NSE.mjs +2 -0
- package/dist/{web-CJmIj6AI.mjs → web-CwREhjD-.mjs} +11 -10
- package/dist/web-run.mjs +11 -9
- package/dist/windows-report.mjs +1 -1
- package/dist/{workspace-state-Cjuigeeu.mjs → workspace-state-U08dEzhG.mjs} +3 -3
- package/dist/{workspaces-BccMFmGQ.mjs → workspaces-BxzYlo3P.mjs} +594 -266
- package/dist/{worktree-DQ6Zi2Mv.mjs → worktree-8lqn_lBp.mjs} +850 -551
- package/dist/worktree-BxfVxG_b.mjs +2 -0
- package/dist/{xcode-Zlx3qjEs.mjs → xcode-iQnROM5o.mjs} +5 -5
- package/package.json +7 -7
- package/shim/android-audio/Main.java +24 -0
- package/shim/android-audio/README.md +17 -0
- package/shim/android-audio.jar +0 -0
- package/dist/gc-C-WwL0yi.mjs +0 -3
- package/dist/git-C3iTsJaZ.mjs +0 -73
- package/dist/server-expo-C-90EhcG.mjs +0 -2
- package/dist/settings-DkdmlelE.d.mts +0 -15
- package/dist/swiftpm-macos-p8WZbxKU.mjs +0 -130
- package/dist/toolchain-DhKwp9iI.mjs +0 -2
- package/dist/trigger-Bf3uDcpv.mjs +0 -2
- package/dist/web-DknQETPs.mjs +0 -2
- package/dist/workspace-process-lock-1_1OoVW5.mjs +0 -57
- package/dist/worktree-CK2MpJW5.mjs +0 -2
|
@@ -1,40 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as workspaceProcessLockError, b as debugLog, g as process_identity_exports, i as withWorkspaceProcessLock, m as operationState, n as isTimeoutError, p as isTermination, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
|
|
2
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,
|
|
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-DlrsduwG.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-
|
|
6
|
-
import { d as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { a as formatElapsed, l as phaseLine, o as formatLongDuration, s as isAppLaunchError, u as plural } from "./command-output-Db8BqzxH.mjs";
|
|
6
|
+
import { a as killOnAbort, d as cancelledError, f as throwIfCancelled, n as runOperationFileQuiet, r as childEnvironment, t as runOperationFile, u as cancellableSleep } from "./operation-exec-BW0em61G.mjs";
|
|
7
|
+
import { d as writeWorkspaceState, l as withWorkspaceStateLockAsync, o as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-U08dEzhG.mjs";
|
|
8
|
+
import { J as resolveProjectSettingsAsync, d as androidLayoutSetting, lt as planMetroReach } from "./settings-C7O8A6ZI.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-CTLNHCfE.mjs";
|
|
9
10
|
import { r as createNdjsonWriter, s as readNdjsonGenerations$1 } from "./ndjson-CgE_o3Dl.mjs";
|
|
10
|
-
import {
|
|
11
|
-
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-
|
|
12
|
-
import { i as registeredCaches } from "./cache-manifest-
|
|
11
|
+
import { a as listeningPidsAsync, c as pidExists, d as resolveProjectMetro, h as isMetroRunning, r as killMetroTreeAsync, u as processCwds } from "./metro-DiXtA3Ty.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-BzzsX97d.mjs";
|
|
13
|
+
import { i as registeredCaches } from "./cache-manifest-DSeQjt-Q.mjs";
|
|
13
14
|
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
14
|
-
import { c as nativeRunWaitNotice, d as runCancellationSignal, m as claimForEmptying } from "./native-run-
|
|
15
|
-
import { A as
|
|
16
|
-
import { N as listBuildLocks,
|
|
17
|
-
import {
|
|
18
|
-
import { B as deviceHoldsBundle, D as sanitizeDeviceLabel, b as parseSimctlList, f as listAllIosSimsAsync, g as listUserApps, k as uninstallIosApp, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion, z as deviceHoldsApk } from "./ios-
|
|
19
|
-
import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-1_1OoVW5.mjs";
|
|
15
|
+
import { c as nativeRunWaitNotice, d as runCancellationSignal, m as claimForEmptying } from "./native-run-BUTu8T7A.mjs";
|
|
16
|
+
import { A as teardownParkedAvd, D as teardownOwnedAvd, F as writeUsageCache, L as listBuildSlots, M as probePublicHttp, N as canonical, P as measureDu, T as eraseParkedIosSim, a as collectWorkspaceOutputs, f as withIdleWorkspace, h as resolveSupervisorTarget, j as teardownParkedIosSim, k as teardownOwnedIosSim, r as clearWorkspaceOutputs, w as eraseParkedAvd, z as GC_OUTPUT } from "./workspaces-BxzYlo3P.mjs";
|
|
17
|
+
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-B5RtpFda.mjs";
|
|
18
|
+
import { F as listAvdsAsync, G as ownedAvdSerialResolverAsync, I as listInstalledSystemImages, J as parseAvdSystemImage, V as ownedAvdDirectory, W as ownedAvdSerialResolver, d as androidToolPath, lt as isStimOwnedAvd, m as avdIniPaths, o as androidHome, pt as dropParked, q as parseAvdDeviceProfile, ut as isStimOwnedSim, v as avdStorageRoots, vt as readParked } from "./android-C7sVmyI9.mjs";
|
|
19
|
+
import { B as deviceHoldsBundle, D as sanitizeDeviceLabel, b as parseSimctlList, f as listAllIosSimsAsync, g as listUserApps, k as uninstallIosApp, p as listIosDeviceTypesAsync, u as iosSimulatorFailureAdvice, y as parseRuntimeVersion, z as deviceHoldsApk } from "./ios-hcBpnEGk.mjs";
|
|
20
20
|
import { n as supervisorError } from "./errors-ccaPc4ox.mjs";
|
|
21
|
-
import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-
|
|
22
|
-
import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-
|
|
21
|
+
import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-D9lEz4aq.mjs";
|
|
22
|
+
import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-BXZ0c7Kb.mjs";
|
|
23
23
|
import { createRequire } from "node:module";
|
|
24
24
|
import { randomUUID } from "node:crypto";
|
|
25
25
|
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, statfsSync, writeFileSync } from "node:fs";
|
|
26
|
-
import { format } from "node:util";
|
|
27
26
|
import { homedir, platform, totalmem } from "node:os";
|
|
28
27
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
29
28
|
import { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, ambientContext, cacheDirRefusal, compiledHelper, configDir, withAmbientContext } from "@stim-cli/core";
|
|
30
29
|
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";
|
|
31
30
|
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
|
-
import { fileURLToPath
|
|
31
|
+
import { fileURLToPath } from "node:url";
|
|
33
32
|
import chalk from "chalk";
|
|
34
33
|
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
|
-
import { access, lstat, readFile, readdir, stat, statfs } from "node:fs/promises";
|
|
34
|
+
import { access, lstat, readFile, readdir, rm, stat, statfs } from "node:fs/promises";
|
|
36
35
|
import { homedir as homedir$1, tmpdir as tmpdir$1 } from "os";
|
|
37
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
38
36
|
//#region src/cache/caches.ts
|
|
39
37
|
function xcodeDerivedDataRoot(env) {
|
|
40
38
|
return join$1(env.HOME || env.USERPROFILE || homedir$1(), "Library", "Developer", "Xcode", "DerivedData");
|
|
@@ -204,10 +202,15 @@ function canonicalCacheDir(dir) {
|
|
|
204
202
|
}
|
|
205
203
|
}
|
|
206
204
|
const CACHE_SIZE_TIMEOUT_MS = 6e4;
|
|
207
|
-
async function sizeCaches(caches) {
|
|
205
|
+
async function sizeCaches(caches, operation) {
|
|
208
206
|
const sized = [];
|
|
209
207
|
for (const cache of caches) {
|
|
210
|
-
|
|
208
|
+
operation?.signal.throwIfAborted();
|
|
209
|
+
const bytes = cache.bytes ?? await measuredDirectorySizeAsync(cache.dir, {
|
|
210
|
+
timeoutMs: CACHE_SIZE_TIMEOUT_MS,
|
|
211
|
+
operation,
|
|
212
|
+
signal: operation?.signal
|
|
213
|
+
});
|
|
211
214
|
sized.push(Object.assign({}, cache, bytes === null ? { sizeError: "Size unavailable: measurement failed or exceeded 60 seconds." } : { bytes }));
|
|
212
215
|
}
|
|
213
216
|
return sized;
|
|
@@ -274,6 +277,69 @@ function pruneCache(cache, { olderThanDays, now = Date.now(), protect, evictByte
|
|
|
274
277
|
protectedEntries
|
|
275
278
|
};
|
|
276
279
|
}
|
|
280
|
+
async function pruneCacheAsync(cache, { olderThanDays, now = Date.now(), signal, operation }) {
|
|
281
|
+
if (cache.prune === "report-only") return {
|
|
282
|
+
removed: 0,
|
|
283
|
+
bytes: 0,
|
|
284
|
+
skipped: "report-only shared cache; Stim never deletes it"
|
|
285
|
+
};
|
|
286
|
+
if (cache.prune === "atomic") return {
|
|
287
|
+
removed: 0,
|
|
288
|
+
bytes: 0,
|
|
289
|
+
skipped: "index-backed; empty it whole or not at all"
|
|
290
|
+
};
|
|
291
|
+
if (!cache.files && cacheDirRefusal(cache.dir)) return {
|
|
292
|
+
removed: 0,
|
|
293
|
+
bytes: 0,
|
|
294
|
+
skipped: `${cache.dir} is not a directory this cache owns`
|
|
295
|
+
};
|
|
296
|
+
const cutoff = now - olderThanDays * 24 * 60 * 60 * 1e3;
|
|
297
|
+
let entries = cache.files ?? [cache.dir];
|
|
298
|
+
for (let depth = 0; !cache.files && depth < (cache.entriesDepth ?? 1); depth++) {
|
|
299
|
+
const next = [];
|
|
300
|
+
for (const parent of entries) {
|
|
301
|
+
signal?.throwIfAborted();
|
|
302
|
+
const names = await readdir(parent).catch(() => []);
|
|
303
|
+
next.push(...names.map((name) => join$1(parent, name)));
|
|
304
|
+
}
|
|
305
|
+
entries = next;
|
|
306
|
+
}
|
|
307
|
+
let removed = 0;
|
|
308
|
+
let bytes = 0;
|
|
309
|
+
let failed = 0;
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
signal?.throwIfAborted();
|
|
312
|
+
try {
|
|
313
|
+
const st = await stat(entry);
|
|
314
|
+
if (Math.max(st.atimeMs, st.mtimeMs) >= cutoff) continue;
|
|
315
|
+
const size = st.isDirectory() ? await measuredDirectorySizeAsync(entry, {
|
|
316
|
+
timeoutMs: CACHE_SIZE_TIMEOUT_MS,
|
|
317
|
+
signal,
|
|
318
|
+
operation
|
|
319
|
+
}) : st.size;
|
|
320
|
+
signal?.throwIfAborted();
|
|
321
|
+
if ((await stat(entry)).mtimeMs >= cutoff) continue;
|
|
322
|
+
signal?.throwIfAborted();
|
|
323
|
+
await rm(entry, {
|
|
324
|
+
recursive: true,
|
|
325
|
+
force: true
|
|
326
|
+
});
|
|
327
|
+
removed++;
|
|
328
|
+
bytes += size ?? 0;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
331
|
+
signal?.throwIfAborted();
|
|
332
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) failed++;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
signal?.throwIfAborted();
|
|
336
|
+
return {
|
|
337
|
+
removed,
|
|
338
|
+
bytes,
|
|
339
|
+
failed,
|
|
340
|
+
skipped: null
|
|
341
|
+
};
|
|
342
|
+
}
|
|
277
343
|
function entriesAtDepth(dir, depth) {
|
|
278
344
|
let level = [dir];
|
|
279
345
|
for (let i = 0; i < depth; i++) {
|
|
@@ -303,7 +369,10 @@ function helperSource() {
|
|
|
303
369
|
}
|
|
304
370
|
async function buildHelper(operation) {
|
|
305
371
|
const exec = getExecutor();
|
|
306
|
-
if (!(operation ? await
|
|
372
|
+
if (!(operation ? await runOperationFileQuiet(operation, exec, "xcode-select", ["-p"], {
|
|
373
|
+
timeoutMs: READ_TIMEOUT_MS,
|
|
374
|
+
cwd: operation.cwd
|
|
375
|
+
}) : exec.runFileQuiet("xcode-select", ["-p"], { timeoutMs: READ_TIMEOUT_MS }))) return null;
|
|
307
376
|
const source = helperSource();
|
|
308
377
|
try {
|
|
309
378
|
return await compiledHelper({
|
|
@@ -313,7 +382,7 @@ async function buildHelper(operation) {
|
|
|
313
382
|
version: "",
|
|
314
383
|
prune: !operation,
|
|
315
384
|
compile: async (output) => {
|
|
316
|
-
await exec
|
|
385
|
+
await runOperationFile(operation, exec, "xcrun", [
|
|
317
386
|
"swiftc",
|
|
318
387
|
"-O",
|
|
319
388
|
"-swift-version",
|
|
@@ -321,10 +390,15 @@ async function buildHelper(operation) {
|
|
|
321
390
|
"-o",
|
|
322
391
|
output,
|
|
323
392
|
source
|
|
324
|
-
], {
|
|
393
|
+
], {
|
|
394
|
+
timeoutMs: COMPILE_TIMEOUT_MS,
|
|
395
|
+
cwd: operation?.cwd
|
|
396
|
+
});
|
|
325
397
|
}
|
|
326
398
|
});
|
|
327
|
-
} catch {
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
401
|
+
operation?.signal.throwIfAborted();
|
|
328
402
|
return null;
|
|
329
403
|
}
|
|
330
404
|
}
|
|
@@ -334,6 +408,7 @@ let built = null;
|
|
|
334
408
|
let failure = null;
|
|
335
409
|
/** The `stim-footprint` helper, compiled on first use. A success is kept for the process's life; a failure retries after a doubling backoff instead of being pinned forever. */
|
|
336
410
|
function footprintHelper(operation) {
|
|
411
|
+
if (operation) return buildHelper(operation);
|
|
337
412
|
const home = configDir();
|
|
338
413
|
if (built?.home === home) return built.promise;
|
|
339
414
|
if (failure?.home === home && Date.now() < failure.nextRetryAt) return Promise.resolve(null);
|
|
@@ -368,7 +443,10 @@ function parseFootprints(output) {
|
|
|
368
443
|
/** Every readable process's physical footprint in bytes, or null when the helper is unavailable or fails. */
|
|
369
444
|
async function readFootprints(operation) {
|
|
370
445
|
const helper = await footprintHelper(operation);
|
|
371
|
-
const output = helper ? operation ? await
|
|
446
|
+
const output = helper ? operation ? await runOperationFileQuiet(operation, getExecutor(), helper, [], {
|
|
447
|
+
timeoutMs: READ_TIMEOUT_MS,
|
|
448
|
+
cwd: operation.cwd
|
|
449
|
+
}) : getExecutor().runFileQuiet(helper, [], { timeoutMs: READ_TIMEOUT_MS }) : null;
|
|
372
450
|
return output === null ? null : parseFootprints(output);
|
|
373
451
|
}
|
|
374
452
|
//#endregion
|
|
@@ -890,16 +968,23 @@ function stimWorkspaceOf(pid, parentOf, supervisors) {
|
|
|
890
968
|
function reclaimableBytes(report, kind) {
|
|
891
969
|
return report.processes.filter((entry) => entry.reclaimable && (kind === void 0 || entry.cacheKind === kind)).reduce((sum, entry) => sum + entry.bytes, 0);
|
|
892
970
|
}
|
|
893
|
-
const boundWatchman = async (args) => {
|
|
894
|
-
const
|
|
971
|
+
const boundWatchman = async (args, operation) => {
|
|
972
|
+
const options = {
|
|
973
|
+
timeoutMs: WATCHMAN_TIMEOUT_MS,
|
|
974
|
+
cwd: operation?.cwd
|
|
975
|
+
};
|
|
976
|
+
const output = await runOperationFile(operation, getExecutor(), "watchman", ["--no-spawn", ...args], options);
|
|
977
|
+
const answer = JSON.parse(output);
|
|
895
978
|
const error = answer?.error;
|
|
896
979
|
if (error !== void 0) throw new Error(String(error));
|
|
897
980
|
return answer;
|
|
898
981
|
};
|
|
899
|
-
async function quietly(fn) {
|
|
982
|
+
async function quietly(fn, operation) {
|
|
900
983
|
try {
|
|
901
984
|
return await fn();
|
|
902
|
-
} catch {
|
|
985
|
+
} catch (error) {
|
|
986
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
987
|
+
operation?.signal.throwIfAborted();
|
|
903
988
|
return null;
|
|
904
989
|
}
|
|
905
990
|
}
|
|
@@ -908,10 +993,11 @@ async function quietly(fn) {
|
|
|
908
993
|
* known: debug-status lists the asking CLI as a client too.
|
|
909
994
|
*/
|
|
910
995
|
async function watchmanClients(operation) {
|
|
911
|
-
if (
|
|
996
|
+
if (operation) {
|
|
912
997
|
let self;
|
|
913
|
-
const output = await getExecutor()
|
|
998
|
+
const output = await runOperationFileQuiet(operation, getExecutor(), "watchman", ["--no-spawn", "debug-status"], {
|
|
914
999
|
timeoutMs: WATCHMAN_TIMEOUT_MS,
|
|
1000
|
+
cwd: operation.cwd,
|
|
915
1001
|
onSpawn: (child) => {
|
|
916
1002
|
self = child.pid;
|
|
917
1003
|
}
|
|
@@ -955,25 +1041,29 @@ async function watchmanClients(operation) {
|
|
|
955
1041
|
}
|
|
956
1042
|
async function collectWatchmanRoots(pid, progress, operation) {
|
|
957
1043
|
const watchman = boundWatchman;
|
|
958
|
-
const paths = parseWatchRoots(await quietly(() => watchman(["watch-list"])));
|
|
1044
|
+
const paths = parseWatchRoots(await quietly(() => watchman(["watch-list"], operation), operation));
|
|
959
1045
|
if (!paths) return null;
|
|
960
1046
|
if (progress) phase("daemons", `watchman pid ${pid}: checking ${plural(paths.length, "root")}`, operation);
|
|
961
1047
|
const roots = [];
|
|
962
1048
|
let next = 0;
|
|
963
1049
|
const probe = async () => {
|
|
964
1050
|
while (next < paths.length) {
|
|
1051
|
+
operation?.signal.throwIfAborted();
|
|
965
1052
|
const index = next++;
|
|
966
1053
|
const path = paths[index];
|
|
967
|
-
const
|
|
1054
|
+
const subscriptions = () => quietly(() => watchman(["debug-get-subscriptions", path], operation), operation);
|
|
1055
|
+
const triggers = () => quietly(() => watchman(["trigger-list", path], operation), operation);
|
|
1056
|
+
const [subscribers, triggered] = operation ? [await subscriptions(), await triggers()] : await Promise.all([subscriptions(), triggers()]);
|
|
968
1057
|
roots[index] = {
|
|
969
1058
|
path,
|
|
970
1059
|
stale: watchmanRootStaleness(path),
|
|
971
|
-
subscribers: parseSubscriberNames(
|
|
972
|
-
triggers: parseTriggerCount(
|
|
1060
|
+
subscribers: parseSubscriberNames(subscribers),
|
|
1061
|
+
triggers: parseTriggerCount(triggered)
|
|
973
1062
|
};
|
|
974
1063
|
}
|
|
975
1064
|
};
|
|
976
|
-
|
|
1065
|
+
if (operation) await probe();
|
|
1066
|
+
else await Promise.all(Array.from({ length: Math.min(ROOT_PROBES_AT_ONCE, paths.length) }, probe));
|
|
977
1067
|
return roots;
|
|
978
1068
|
}
|
|
979
1069
|
function measureOf(pid, rows, footprints) {
|
|
@@ -995,39 +1085,60 @@ function supervisorPids() {
|
|
|
995
1085
|
}
|
|
996
1086
|
return supervisors;
|
|
997
1087
|
}
|
|
998
|
-
async function readLsof(pids) {
|
|
1088
|
+
async function readLsof(pids, operation) {
|
|
999
1089
|
if (pids.length === 0) return /* @__PURE__ */ new Map();
|
|
1000
1090
|
try {
|
|
1001
|
-
return parseLsof(await getExecutor()
|
|
1091
|
+
return parseLsof(await runOperationFile(operation, getExecutor(), "lsof", [
|
|
1002
1092
|
"-n",
|
|
1003
1093
|
"-P",
|
|
1004
1094
|
"-F",
|
|
1005
1095
|
"pnT",
|
|
1006
1096
|
"-p",
|
|
1007
1097
|
pids.join(",")
|
|
1008
|
-
], {
|
|
1098
|
+
], {
|
|
1099
|
+
timeoutMs: LSOF_TIMEOUT_MS,
|
|
1100
|
+
cwd: operation?.cwd,
|
|
1101
|
+
signal: operation?.signal
|
|
1102
|
+
}));
|
|
1009
1103
|
} catch (error) {
|
|
1104
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1105
|
+
operation?.signal.throwIfAborted();
|
|
1010
1106
|
const { status, signal: killed, stdout } = error;
|
|
1011
1107
|
return status === 1 && !killed && typeof stdout === "string" && stdout ? parseLsof(stdout) : null;
|
|
1012
1108
|
}
|
|
1013
1109
|
}
|
|
1014
|
-
async function gradleStatus(command, home) {
|
|
1110
|
+
async function gradleStatus(command, home, operation) {
|
|
1015
1111
|
if (!command.distribution) return { unknown: "could not find its Gradle distribution on its classpath" };
|
|
1016
1112
|
const launcher = join(command.distribution, "bin", "gradle");
|
|
1017
1113
|
if (!existsSync(launcher)) return { unknown: `its distribution has no ${launcher}` };
|
|
1018
1114
|
try {
|
|
1019
|
-
|
|
1115
|
+
const environment = operation ? childEnvironment(operation.env) : {
|
|
1116
|
+
env: {},
|
|
1117
|
+
omitEnv: []
|
|
1118
|
+
};
|
|
1119
|
+
return parseGradleStatus(await runOperationFile(operation, getExecutor(), launcher, [
|
|
1020
1120
|
"--status",
|
|
1021
1121
|
"-q",
|
|
1022
1122
|
"-g",
|
|
1023
1123
|
home
|
|
1024
1124
|
], {
|
|
1125
|
+
...environment,
|
|
1025
1126
|
cwd: home,
|
|
1026
|
-
env: {
|
|
1027
|
-
|
|
1127
|
+
env: {
|
|
1128
|
+
...environment.env,
|
|
1129
|
+
JAVA_HOME: command.javaHome
|
|
1130
|
+
},
|
|
1131
|
+
omitEnv: [
|
|
1132
|
+
...environment.omitEnv,
|
|
1133
|
+
"GRADLE_OPTS",
|
|
1134
|
+
"JAVA_OPTS"
|
|
1135
|
+
],
|
|
1136
|
+
signal: operation?.signal,
|
|
1028
1137
|
timeoutMs: GRADLE_STATUS_TIMEOUT_MS
|
|
1029
1138
|
}));
|
|
1030
1139
|
} catch (error) {
|
|
1140
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1141
|
+
operation?.signal.throwIfAborted();
|
|
1031
1142
|
return { unknown: `gradle --status failed: ${error.message.split("\n")[0]}` };
|
|
1032
1143
|
}
|
|
1033
1144
|
}
|
|
@@ -1053,7 +1164,7 @@ async function collectGradleFacts(rows, byPid, footprints, stimServerRunning, pr
|
|
|
1053
1164
|
kotlin: []
|
|
1054
1165
|
};
|
|
1055
1166
|
if (progress) phase("daemons", `checking ${plural(gradleRows.length, "Gradle daemon")} and ${plural(kotlinRows.length, "Kotlin daemon")}`, operation);
|
|
1056
|
-
const lsof = await readLsof([...gradleRows.map(({ row }) => row.pid), ...kotlinRows.map((row) => row.pid)]);
|
|
1167
|
+
const lsof = await readLsof([...gradleRows.map(({ row }) => row.pid), ...kotlinRows.map((row) => row.pid)], operation);
|
|
1057
1168
|
const workerRoot = buildWorkerRoot();
|
|
1058
1169
|
const homes = gradleRows.map(({ row, command }) => {
|
|
1059
1170
|
const files = lsof?.get(row.pid)?.files;
|
|
@@ -1061,14 +1172,16 @@ async function collectGradleFacts(rows, byPid, footprints, stimServerRunning, pr
|
|
|
1061
1172
|
});
|
|
1062
1173
|
const offload = gradleRows.map(({ row }, index) => offloadClientOf(row.command, workerRoot, homes[index] ?? null));
|
|
1063
1174
|
const statuses = /* @__PURE__ */ new Map();
|
|
1064
|
-
|
|
1175
|
+
for (const [index, { command }] of gradleRows.entries()) {
|
|
1065
1176
|
const home = homes[index];
|
|
1066
|
-
if (offload[index] !== null && stimServerRunning)
|
|
1177
|
+
if (offload[index] !== null && stimServerRunning) continue;
|
|
1067
1178
|
if (home && !statuses.has(groupKey(command, home))) {
|
|
1068
1179
|
if (progress) phase("daemons", `gradle --status for Gradle ${command.version} in ${home}`, operation);
|
|
1069
|
-
|
|
1180
|
+
const status = gradleStatus(command, home, operation);
|
|
1181
|
+
statuses.set(groupKey(command, home), status);
|
|
1182
|
+
if (operation) await status;
|
|
1070
1183
|
}
|
|
1071
|
-
}
|
|
1184
|
+
}
|
|
1072
1185
|
const gradle = [];
|
|
1073
1186
|
for (const [index, { row, command }] of gradleRows.entries()) {
|
|
1074
1187
|
const home = homes[index] ?? null;
|
|
@@ -1102,7 +1215,7 @@ async function collectGradleFacts(rows, byPid, footprints, stimServerRunning, pr
|
|
|
1102
1215
|
};
|
|
1103
1216
|
}
|
|
1104
1217
|
async function collectWatchmanFacts(byPid, footprints, progress, operation) {
|
|
1105
|
-
const pid = parseWatchmanPid(await quietly(() => boundWatchman(["get-pid"])));
|
|
1218
|
+
const pid = parseWatchmanPid(await quietly(() => boundWatchman(["get-pid"], operation), operation));
|
|
1106
1219
|
if (pid === null) return null;
|
|
1107
1220
|
const roots = await collectWatchmanRoots(pid, progress, operation);
|
|
1108
1221
|
const clients = (await watchmanClients(operation))?.filter((client) => client.pid !== pid) ?? null;
|
|
@@ -1140,7 +1253,8 @@ async function collectMemoryReport(scope, progress = false, operation) {
|
|
|
1140
1253
|
notices: []
|
|
1141
1254
|
};
|
|
1142
1255
|
if (process.platform === "win32") return report;
|
|
1143
|
-
const rows = await readHostProcessTableAsync();
|
|
1256
|
+
const rows = await readHostProcessTableAsync(operation?.signal, operation);
|
|
1257
|
+
operation?.signal.throwIfAborted();
|
|
1144
1258
|
if (rows === null) {
|
|
1145
1259
|
report.notices.push("memory sweep skipped: ps did not answer");
|
|
1146
1260
|
return report;
|
|
@@ -1206,7 +1320,8 @@ function sameProcess(entry, rows) {
|
|
|
1206
1320
|
const row = rows?.get(entry.pid);
|
|
1207
1321
|
return Boolean(row && entry.startedAt !== null && row.startedAt === entry.startedAt);
|
|
1208
1322
|
}
|
|
1209
|
-
function signal(pid) {
|
|
1323
|
+
function signal(pid, abort) {
|
|
1324
|
+
abort?.throwIfAborted();
|
|
1210
1325
|
try {
|
|
1211
1326
|
process.kill(pid, "SIGTERM");
|
|
1212
1327
|
return null;
|
|
@@ -1214,64 +1329,69 @@ function signal(pid) {
|
|
|
1214
1329
|
return error.code === "ESRCH" ? null : error.message;
|
|
1215
1330
|
}
|
|
1216
1331
|
}
|
|
1217
|
-
function keep(entry, kind, detail) {
|
|
1218
|
-
|
|
1219
|
-
|
|
1332
|
+
function keep(output, entry, kind, detail) {
|
|
1333
|
+
output.log(chalk.yellow(`Kept ${processLabel(entry)}: ${detail}`));
|
|
1334
|
+
output.record(kind, "kept", processLabel(entry), {
|
|
1220
1335
|
id: String(entry.pid),
|
|
1221
1336
|
bytes: entry.bytes,
|
|
1222
1337
|
detail
|
|
1223
1338
|
});
|
|
1224
1339
|
}
|
|
1225
|
-
async function reclaimWatchman(before) {
|
|
1340
|
+
async function reclaimWatchman(before, output, operation) {
|
|
1226
1341
|
let failures = 0;
|
|
1227
1342
|
const fresh = await collectMemoryReport({
|
|
1228
1343
|
watchman: true,
|
|
1229
1344
|
gradle: false
|
|
1230
|
-
}, true);
|
|
1345
|
+
}, true, operation);
|
|
1231
1346
|
const daemon = fresh.processes.find((entry) => entry.kind === "watchman");
|
|
1232
1347
|
const planned = before.processes.find((entry) => entry.kind === "watchman");
|
|
1233
1348
|
if (!daemon || !planned || daemon.pid !== planned.pid) {
|
|
1234
|
-
if (planned) keep(planned, "watchman", "the daemon changed or stopped since the report");
|
|
1349
|
+
if (planned) keep(output, planned, "watchman", "the daemon changed or stopped since the report");
|
|
1235
1350
|
return 0;
|
|
1236
1351
|
}
|
|
1237
1352
|
for (const root of fresh.watchmanRoots.filter((r) => r.stale !== null)) {
|
|
1353
|
+
operation?.signal.throwIfAborted();
|
|
1238
1354
|
if (!root.removable) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1355
|
+
output.log(chalk.yellow(`Kept the watchman root ${root.path}: ${root.detail}`));
|
|
1356
|
+
output.record("watchmanRoot", "kept", root.path, { detail: root.detail });
|
|
1241
1357
|
continue;
|
|
1242
1358
|
}
|
|
1243
1359
|
try {
|
|
1244
|
-
phase("daemons", `removing stale watchman root ${root.path}
|
|
1245
|
-
await boundWatchman(["watch-del", root.path]);
|
|
1246
|
-
|
|
1247
|
-
|
|
1360
|
+
phase("daemons", `removing stale watchman root ${root.path}`, operation);
|
|
1361
|
+
await boundWatchman(["watch-del", root.path], operation);
|
|
1362
|
+
output.log(chalk.green(`Removed the stale watchman root ${root.path} (${root.detail})`));
|
|
1363
|
+
output.record("watchmanRoot", "done", root.path, { detail: root.detail });
|
|
1248
1364
|
} catch (error) {
|
|
1365
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1366
|
+
operation?.signal.throwIfAborted();
|
|
1249
1367
|
failures++;
|
|
1250
|
-
|
|
1251
|
-
|
|
1368
|
+
output.log(chalk.red(`Could not remove the watchman root ${root.path}: ${error.message}`));
|
|
1369
|
+
output.record("watchmanRoot", "failed", root.path, { detail: error.message });
|
|
1252
1370
|
}
|
|
1253
1371
|
}
|
|
1254
1372
|
const current = (await collectMemoryReport({
|
|
1255
1373
|
watchman: true,
|
|
1256
1374
|
gradle: false
|
|
1257
|
-
}, true)).processes.find((entry) => entry.kind === "watchman");
|
|
1375
|
+
}, true, operation)).processes.find((entry) => entry.kind === "watchman");
|
|
1258
1376
|
if (!current || current.pid !== daemon.pid) return failures;
|
|
1259
1377
|
if (!current.reclaimable) {
|
|
1260
|
-
keep(current, "watchman", current.detail ?? "it could not be proven unused");
|
|
1378
|
+
keep(output, current, "watchman", current.detail ?? "it could not be proven unused");
|
|
1261
1379
|
return failures;
|
|
1262
1380
|
}
|
|
1263
1381
|
try {
|
|
1264
|
-
phase("daemons", `shutting down watchman pid ${current.pid}: no client uses it
|
|
1265
|
-
await boundWatchman(["shutdown-server"]);
|
|
1266
|
-
|
|
1267
|
-
|
|
1382
|
+
phase("daemons", `shutting down watchman pid ${current.pid}: no client uses it`, operation);
|
|
1383
|
+
await boundWatchman(["shutdown-server"], operation);
|
|
1384
|
+
output.log(chalk.green(`Shut down watchman pid ${current.pid}, freeing ${formatBytes(current.bytes)}; the next client that needs it starts it again`));
|
|
1385
|
+
output.record("watchman", "done", processLabel(current), {
|
|
1268
1386
|
id: String(current.pid),
|
|
1269
1387
|
bytes: current.bytes
|
|
1270
1388
|
});
|
|
1271
1389
|
} catch (error) {
|
|
1390
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1391
|
+
operation?.signal.throwIfAborted();
|
|
1272
1392
|
failures++;
|
|
1273
|
-
|
|
1274
|
-
|
|
1393
|
+
output.log(chalk.red(`Could not shut down watchman pid ${current.pid}: ${error.message}`));
|
|
1394
|
+
output.record("watchman", "failed", processLabel(current), {
|
|
1275
1395
|
id: String(current.pid),
|
|
1276
1396
|
bytes: current.bytes,
|
|
1277
1397
|
detail: error.message
|
|
@@ -1279,23 +1399,23 @@ async function reclaimWatchman(before) {
|
|
|
1279
1399
|
}
|
|
1280
1400
|
return failures;
|
|
1281
1401
|
}
|
|
1282
|
-
function stopKotlin(entry, rows) {
|
|
1402
|
+
function stopKotlin(entry, rows, output, operation) {
|
|
1283
1403
|
if (!sameProcess(entry, rows)) {
|
|
1284
|
-
keep(entry, "kotlinDaemon", "the process changed or exited since the check");
|
|
1404
|
+
keep(output, entry, "kotlinDaemon", "the process changed or exited since the check");
|
|
1285
1405
|
return 0;
|
|
1286
1406
|
}
|
|
1287
|
-
const error = signal(entry.pid);
|
|
1407
|
+
const error = signal(entry.pid, operation?.signal);
|
|
1288
1408
|
if (error) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1409
|
+
output.log(chalk.red(`Could not stop ${processLabel(entry)}: ${error}`));
|
|
1410
|
+
output.record("kotlinDaemon", "failed", processLabel(entry), {
|
|
1291
1411
|
id: String(entry.pid),
|
|
1292
1412
|
bytes: entry.bytes,
|
|
1293
1413
|
detail: error
|
|
1294
1414
|
});
|
|
1295
1415
|
return 1;
|
|
1296
1416
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1417
|
+
output.log(chalk.green(`Stopped ${processLabel(entry)}, freeing ${formatBytes(entry.bytes)}`));
|
|
1418
|
+
output.record("kotlinDaemon", "done", processLabel(entry), {
|
|
1299
1419
|
id: String(entry.pid),
|
|
1300
1420
|
bytes: entry.bytes
|
|
1301
1421
|
});
|
|
@@ -1306,33 +1426,34 @@ function stopKotlin(entry, rows) {
|
|
|
1306
1426
|
* version is one of them, the daemon's own `gradle --stop` runs for that home; otherwise each idle one gets SIGTERM,
|
|
1307
1427
|
* because `--stop` also stops the busy daemons of that home and version.
|
|
1308
1428
|
*/
|
|
1309
|
-
async function stopGradle(idle, rows) {
|
|
1429
|
+
async function stopGradle(idle, rows, output, operation) {
|
|
1310
1430
|
let failures = 0;
|
|
1311
1431
|
const groups = /* @__PURE__ */ new Map();
|
|
1312
1432
|
for (const entry of idle) {
|
|
1313
1433
|
const command = parseGradleDaemonCommand(rows?.get(entry.pid)?.command ?? "");
|
|
1314
1434
|
if (!command || !entry.gradleHome || !sameProcess(entry, rows)) {
|
|
1315
|
-
keep(entry, "gradleDaemon", "the process changed or exited since the check");
|
|
1435
|
+
keep(output, entry, "gradleDaemon", "the process changed or exited since the check");
|
|
1316
1436
|
continue;
|
|
1317
1437
|
}
|
|
1318
1438
|
const key = groupKey(command, entry.gradleHome);
|
|
1319
1439
|
groups.set(key, [...groups.get(key) ?? [], entry]);
|
|
1320
1440
|
}
|
|
1321
1441
|
for (const [key, entries] of groups) {
|
|
1442
|
+
operation?.signal.throwIfAborted();
|
|
1322
1443
|
const [home, , distribution, javaHome] = JSON.parse(key);
|
|
1323
1444
|
const command = parseGradleDaemonCommand(rows.get(entries[0].pid).command);
|
|
1324
1445
|
const building = runningBuild(listBuildLocks(), listBuildSlots());
|
|
1325
1446
|
if (building) {
|
|
1326
|
-
for (const entry of entries) keep(entry, "gradleDaemon", building);
|
|
1447
|
+
for (const entry of entries) keep(output, entry, "gradleDaemon", building);
|
|
1327
1448
|
continue;
|
|
1328
1449
|
}
|
|
1329
|
-
phase("daemons", `gradle --status for Gradle ${command.version} in ${home}
|
|
1330
|
-
let states = await gradleStatus(command, home);
|
|
1331
|
-
let current = hostRows();
|
|
1450
|
+
phase("daemons", `gradle --status for Gradle ${command.version} in ${home}`, operation);
|
|
1451
|
+
let states = await gradleStatus(command, home, operation);
|
|
1452
|
+
let current = await hostRows(operation);
|
|
1332
1453
|
const listed = states instanceof Map ? [...states].filter(([, state]) => state !== "stopped") : [];
|
|
1333
1454
|
if (distribution !== null && listed.length > 0 && listed.every(([pid, state]) => state === "idle" && entries.some((entry) => entry.pid === pid))) try {
|
|
1334
|
-
phase("daemons", `gradle --stop for Gradle ${command.version} in ${home}
|
|
1335
|
-
await getExecutor()
|
|
1455
|
+
phase("daemons", `gradle --stop for Gradle ${command.version} in ${home}`, operation);
|
|
1456
|
+
await runOperationFile(operation, getExecutor(), join(distribution, "bin", "gradle"), [
|
|
1336
1457
|
"--stop",
|
|
1337
1458
|
"-q",
|
|
1338
1459
|
"-g",
|
|
@@ -1344,41 +1465,43 @@ async function stopGradle(idle, rows) {
|
|
|
1344
1465
|
timeoutMs: GRADLE_STOP_TIMEOUT_MS
|
|
1345
1466
|
});
|
|
1346
1467
|
for (const entry of entries) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1468
|
+
output.log(chalk.green(`Stopped ${processLabel(entry)} with gradle --stop, freeing ${formatBytes(entry.bytes)}`));
|
|
1469
|
+
output.record("gradleDaemon", "done", processLabel(entry), {
|
|
1349
1470
|
id: String(entry.pid),
|
|
1350
1471
|
bytes: entry.bytes
|
|
1351
1472
|
});
|
|
1352
1473
|
}
|
|
1353
1474
|
continue;
|
|
1354
1475
|
} catch (error) {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1476
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
1477
|
+
operation?.signal.throwIfAborted();
|
|
1478
|
+
output.log(chalk.dim(`gradle --stop failed for ${home}; signalling each idle daemon: ${error.message.split("\n")[0]}`));
|
|
1479
|
+
states = await gradleStatus(command, home, operation);
|
|
1480
|
+
current = await hostRows(operation);
|
|
1358
1481
|
}
|
|
1359
1482
|
for (const entry of entries) {
|
|
1360
1483
|
const state = states instanceof Map ? states.get(entry.pid) : void 0;
|
|
1361
1484
|
if (!sameProcess(entry, current)) {
|
|
1362
|
-
keep(entry, "gradleDaemon", "the process changed or exited since the check");
|
|
1485
|
+
keep(output, entry, "gradleDaemon", "the process changed or exited since the check");
|
|
1363
1486
|
continue;
|
|
1364
1487
|
}
|
|
1365
1488
|
if (state !== "idle") {
|
|
1366
|
-
keep(entry, "gradleDaemon", state ? `gradle --status now reports it ${state}` : "gradle --status no longer lists it idle");
|
|
1489
|
+
keep(output, entry, "gradleDaemon", state ? `gradle --status now reports it ${state}` : "gradle --status no longer lists it idle");
|
|
1367
1490
|
continue;
|
|
1368
1491
|
}
|
|
1369
|
-
const error = signal(entry.pid);
|
|
1492
|
+
const error = signal(entry.pid, operation?.signal);
|
|
1370
1493
|
if (error) {
|
|
1371
1494
|
failures++;
|
|
1372
|
-
|
|
1373
|
-
|
|
1495
|
+
output.log(chalk.red(`Could not stop ${processLabel(entry)}: ${error}`));
|
|
1496
|
+
output.record("gradleDaemon", "failed", processLabel(entry), {
|
|
1374
1497
|
id: String(entry.pid),
|
|
1375
1498
|
bytes: entry.bytes,
|
|
1376
1499
|
detail: error
|
|
1377
1500
|
});
|
|
1378
1501
|
continue;
|
|
1379
1502
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1503
|
+
output.log(chalk.green(`Stopped ${processLabel(entry)}, freeing ${formatBytes(entry.bytes)}`));
|
|
1504
|
+
output.record("gradleDaemon", "done", processLabel(entry), {
|
|
1382
1505
|
id: String(entry.pid),
|
|
1383
1506
|
bytes: entry.bytes
|
|
1384
1507
|
});
|
|
@@ -1390,8 +1513,9 @@ function stillPlanned(fresh, before, kind) {
|
|
|
1390
1513
|
const planned = new Map(before.processes.filter((entry) => entry.kind === kind).map((entry) => [entry.pid, entry]));
|
|
1391
1514
|
return fresh.processes.filter((entry) => entry.kind === kind && planned.get(entry.pid)?.startedAt === entry.startedAt);
|
|
1392
1515
|
}
|
|
1393
|
-
function hostRows() {
|
|
1394
|
-
const rows =
|
|
1516
|
+
async function hostRows(operation) {
|
|
1517
|
+
const rows = await readHostProcessTableAsync(operation?.signal, operation);
|
|
1518
|
+
operation?.signal.throwIfAborted();
|
|
1395
1519
|
return rows ? new Map(rows.map((row) => [row.pid, row])) : null;
|
|
1396
1520
|
}
|
|
1397
1521
|
const EXIT_WAIT_MS = 1e4;
|
|
@@ -1403,37 +1527,40 @@ function alive(pid) {
|
|
|
1403
1527
|
return error.code !== "ESRCH";
|
|
1404
1528
|
}
|
|
1405
1529
|
}
|
|
1406
|
-
async function waitForExit(pids) {
|
|
1530
|
+
async function waitForExit(pids, operation) {
|
|
1407
1531
|
const running = pids.filter(alive);
|
|
1408
1532
|
if (running.length === 0) return;
|
|
1409
|
-
phase("daemons", `waiting up to ${EXIT_WAIT_MS / 1e3}s for ${plural(running.length, "Gradle daemon")} to exit: pid ${running.join(", ")}
|
|
1533
|
+
phase("daemons", `waiting up to ${EXIT_WAIT_MS / 1e3}s for ${plural(running.length, "Gradle daemon")} to exit: pid ${running.join(", ")}`, operation);
|
|
1410
1534
|
const deadline = Date.now() + EXIT_WAIT_MS;
|
|
1411
|
-
while (running.some(alive) && Date.now() < deadline) await
|
|
1535
|
+
while (running.some(alive) && Date.now() < deadline) await cancellableSleep(200, {
|
|
1536
|
+
signal: operation?.signal,
|
|
1537
|
+
what: "GC daemon shutdown"
|
|
1538
|
+
});
|
|
1412
1539
|
}
|
|
1413
1540
|
/** Stops the Gradle daemons first, then re-checks the Kotlin daemons, whose clients those Gradle daemons were. */
|
|
1414
|
-
async function reclaimDaemons(before) {
|
|
1541
|
+
async function reclaimDaemons(before, output, operation) {
|
|
1415
1542
|
const scope = {
|
|
1416
1543
|
watchman: false,
|
|
1417
1544
|
gradle: true
|
|
1418
1545
|
};
|
|
1419
|
-
const gradle = stillPlanned(await collectMemoryReport(scope, true), before, "gradleDaemon");
|
|
1420
|
-
for (const entry of gradle.filter((e) => !e.reclaimable)) keep(entry, "gradleDaemon", entry.detail ?? "it could not be proven idle");
|
|
1546
|
+
const gradle = stillPlanned(await collectMemoryReport(scope, true, operation), before, "gradleDaemon");
|
|
1547
|
+
for (const entry of gradle.filter((e) => !e.reclaimable)) keep(output, entry, "gradleDaemon", entry.detail ?? "it could not be proven idle");
|
|
1421
1548
|
const stopping = gradle.filter((entry) => entry.reclaimable);
|
|
1422
|
-
let failures = await stopGradle(stopping, hostRows());
|
|
1549
|
+
let failures = await stopGradle(stopping, await hostRows(operation), output, operation);
|
|
1423
1550
|
if (!before.processes.some((entry) => entry.kind === "kotlinDaemon")) return failures;
|
|
1424
|
-
await waitForExit(stopping.map((entry) => entry.pid));
|
|
1425
|
-
const kotlin = stillPlanned(await collectMemoryReport(scope, true), before, "kotlinDaemon");
|
|
1551
|
+
await waitForExit(stopping.map((entry) => entry.pid), operation);
|
|
1552
|
+
const kotlin = stillPlanned(await collectMemoryReport(scope, true, operation), before, "kotlinDaemon");
|
|
1426
1553
|
for (const entry of before.processes.filter((e) => e.kind === "kotlinDaemon" && e.reclaimable)) {
|
|
1427
1554
|
if (kotlin.some((current) => current.pid === entry.pid) || alive(entry.pid)) continue;
|
|
1428
|
-
|
|
1429
|
-
|
|
1555
|
+
output.log(chalk.green(`${processLabel(entry)} exited with its Gradle daemon, freeing ${formatBytes(entry.bytes)}`));
|
|
1556
|
+
output.record("kotlinDaemon", "done", processLabel(entry), {
|
|
1430
1557
|
id: String(entry.pid),
|
|
1431
1558
|
bytes: entry.bytes
|
|
1432
1559
|
});
|
|
1433
1560
|
}
|
|
1434
|
-
const rows = hostRows();
|
|
1435
|
-
for (const entry of kotlin) if (entry.reclaimable) failures += stopKotlin(entry, rows);
|
|
1436
|
-
else keep(entry, "kotlinDaemon", entry.detail ?? "it could not be proven idle");
|
|
1561
|
+
const rows = await hostRows(operation);
|
|
1562
|
+
for (const entry of kotlin) if (entry.reclaimable) failures += stopKotlin(entry, rows, output, operation);
|
|
1563
|
+
else keep(output, entry, "kotlinDaemon", entry.detail ?? "it could not be proven idle");
|
|
1437
1564
|
return failures;
|
|
1438
1565
|
}
|
|
1439
1566
|
/**
|
|
@@ -1442,7 +1569,7 @@ async function reclaimDaemons(before) {
|
|
|
1442
1569
|
* Gradle daemon is in `pids` too, since one left running may still be its client. Nothing stops while STIM_HOME scopes
|
|
1443
1570
|
* the config.
|
|
1444
1571
|
*/
|
|
1445
|
-
async function stopIdleGradleDaemons(pids) {
|
|
1572
|
+
async function stopIdleGradleDaemons(pids, output = GC_OUTPUT) {
|
|
1446
1573
|
if (memorySweepIsScoped()) return 0;
|
|
1447
1574
|
const report = await collectMemoryReport({
|
|
1448
1575
|
watchman: false,
|
|
@@ -1454,7 +1581,7 @@ async function stopIdleGradleDaemons(pids) {
|
|
|
1454
1581
|
return processes.length ? reclaimDaemons({
|
|
1455
1582
|
...report,
|
|
1456
1583
|
processes
|
|
1457
|
-
}) : 0;
|
|
1584
|
+
}, output) : 0;
|
|
1458
1585
|
}
|
|
1459
1586
|
/** Whether `gc --delete --cache <cache>` would act on a memory kind: stop a process or remove a watchman root. */
|
|
1460
1587
|
function memoryWorkPending(cache, report) {
|
|
@@ -1462,10 +1589,11 @@ function memoryWorkPending(cache, report) {
|
|
|
1462
1589
|
return reclaimableBytes(report) > 0 || report.watchmanRoots.some((root) => root.removable);
|
|
1463
1590
|
}
|
|
1464
1591
|
/** Acts on the one memory kind `--cache` names: never on an unscoped run or `--cache all`. */
|
|
1465
|
-
async function reclaimMemory(cache, report) {
|
|
1592
|
+
async function reclaimMemory(cache, report, output = GC_OUTPUT, operation) {
|
|
1593
|
+
operation?.signal.throwIfAborted();
|
|
1466
1594
|
const kind = memoryCacheKind(cache);
|
|
1467
1595
|
if (!kind || !report || report.processes.length === 0) return 0;
|
|
1468
|
-
return kind === "watchman" ? reclaimWatchman(report) : reclaimDaemons(report);
|
|
1596
|
+
return kind === "watchman" ? reclaimWatchman(report, output, operation) : reclaimDaemons(report, output, operation);
|
|
1469
1597
|
}
|
|
1470
1598
|
/** A doctor note when watchman's footprint is over 2 GiB: only a restart shrinks it, and gc says when one is safe. */
|
|
1471
1599
|
function watchmanFinding(report) {
|
|
@@ -2024,7 +2152,6 @@ const DYNAMIC_CONFIG_FILES = [
|
|
|
2024
2152
|
"app.config.mjs",
|
|
2025
2153
|
"app.config.cjs"
|
|
2026
2154
|
];
|
|
2027
|
-
const TIMED_OUT = Symbol("timed-out");
|
|
2028
2155
|
function dynamicConfigFile(root) {
|
|
2029
2156
|
for (const name of DYNAMIC_CONFIG_FILES) if (existsSync(join(root, name))) return name;
|
|
2030
2157
|
return null;
|
|
@@ -2045,8 +2172,7 @@ function normalizeProvider(raw) {
|
|
|
2045
2172
|
if (raw === null || raw === void 0 || raw === "") return null;
|
|
2046
2173
|
if (raw === "eas") return {
|
|
2047
2174
|
name: "eas",
|
|
2048
|
-
reference: EAS_PROVIDER_PACKAGE
|
|
2049
|
-
options: {}
|
|
2175
|
+
reference: EAS_PROVIDER_PACKAGE
|
|
2050
2176
|
};
|
|
2051
2177
|
if (typeof raw === "object") {
|
|
2052
2178
|
const obj = raw;
|
|
@@ -2054,8 +2180,7 @@ function normalizeProvider(raw) {
|
|
|
2054
2180
|
const reference = obj.plugin.trim();
|
|
2055
2181
|
return {
|
|
2056
2182
|
name: reference,
|
|
2057
|
-
reference
|
|
2058
|
-
options: obj.options && typeof obj.options === "object" ? obj.options : {}
|
|
2183
|
+
reference
|
|
2059
2184
|
};
|
|
2060
2185
|
}
|
|
2061
2186
|
}
|
|
@@ -2064,13 +2189,6 @@ function normalizeProvider(raw) {
|
|
|
2064
2189
|
function runOptionsFor(platform) {
|
|
2065
2190
|
return platform === "android" ? { variant: "debug" } : { configuration: "Debug" };
|
|
2066
2191
|
}
|
|
2067
|
-
function isProviderPlugin(plugin) {
|
|
2068
|
-
if (!plugin || typeof plugin !== "object") return false;
|
|
2069
|
-
const p = plugin;
|
|
2070
|
-
const resolves = typeof p.resolveBuildCache === "function" || typeof p.resolveRemoteBuildCache === "function";
|
|
2071
|
-
const uploads = typeof p.uploadBuildCache === "function" || typeof p.uploadRemoteBuildCache === "function";
|
|
2072
|
-
return resolves && uploads;
|
|
2073
|
-
}
|
|
2074
2192
|
function cacheLevel(value) {
|
|
2075
2193
|
return value === "local" || value === "remote" ? value : false;
|
|
2076
2194
|
}
|
|
@@ -2108,6 +2226,8 @@ async function readProjectConfig(root, { context, run = null, timeoutMs = CONFIG
|
|
|
2108
2226
|
root
|
|
2109
2227
|
], { timeoutMs });
|
|
2110
2228
|
} catch (err) {
|
|
2229
|
+
if (err instanceof Error && "code" in err && err.code === "STIM_CLAIM_UNAVAILABLE") throw err;
|
|
2230
|
+
context?.signal.throwIfAborted();
|
|
2111
2231
|
return { unavailable: `\`expo config --json\` failed: ${firstLine(err)}` };
|
|
2112
2232
|
}
|
|
2113
2233
|
try {
|
|
@@ -2119,20 +2239,6 @@ async function readProjectConfig(root, { context, run = null, timeoutMs = CONFIG
|
|
|
2119
2239
|
return { unavailable: `\`expo config --json\` did not print JSON: ${firstLine(err)}` };
|
|
2120
2240
|
}
|
|
2121
2241
|
}
|
|
2122
|
-
async function loadPlugin(projectRoot, reference, { requireFrom = null } = {}) {
|
|
2123
|
-
const localRequire = requireFrom ? requireFrom(projectRoot) : createRequire(join(projectRoot, "package.json"));
|
|
2124
|
-
const file = localRequire.resolve(reference);
|
|
2125
|
-
let mod;
|
|
2126
|
-
try {
|
|
2127
|
-
mod = localRequire(file);
|
|
2128
|
-
} catch (err) {
|
|
2129
|
-
if (err?.code !== "ERR_REQUIRE_ESM") throw err;
|
|
2130
|
-
mod = await import(pathToFileURL(file).href);
|
|
2131
|
-
}
|
|
2132
|
-
const plugin = mod?.default ?? mod;
|
|
2133
|
-
if (!isProviderPlugin(plugin)) throw new Error(`"${reference}" does not export resolveBuildCache and uploadBuildCache functions`);
|
|
2134
|
-
return plugin;
|
|
2135
|
-
}
|
|
2136
2242
|
async function loadProjectProvider(projectRoot, { context, isExpo = true, run = null, requireFrom = null, timeoutMs = CONFIG_TIMEOUT_MS } = {}) {
|
|
2137
2243
|
if (!isExpo) return { none: true };
|
|
2138
2244
|
const read = await readProjectConfig(projectRoot, {
|
|
@@ -2145,41 +2251,33 @@ async function loadProjectProvider(projectRoot, { context, isExpo = true, run =
|
|
|
2145
2251
|
if (!normalized) return { none: true };
|
|
2146
2252
|
if (normalized.invalid) return { unavailable: `buildCacheProvider is ${normalized.invalid}, which is not "eas" or { plugin: <module> }` };
|
|
2147
2253
|
if (normalized.reference === "@stim-cli/expo-build-cache") return { none: true };
|
|
2148
|
-
|
|
2254
|
+
if (normalized.reference !== "eas-build-cache-provider") return { unavailable: "Custom Expo buildCacheProvider plugins are not supported. Use EAS or a Stim cache provider." };
|
|
2149
2255
|
try {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
};
|
|
2169
|
-
}
|
|
2170
|
-
plugin = await loadPlugin(projectRoot, normalized.reference, { requireFrom });
|
|
2256
|
+
(requireFrom ? requireFrom(projectRoot) : createRequire(join(projectRoot, "package.json"))).resolve(EAS_PROVIDER_PACKAGE);
|
|
2257
|
+
const bin = resolveEasCliBin(projectRoot);
|
|
2258
|
+
if (!bin) return { unavailable: "the EAS build cache needs an installed eas-cli executable" };
|
|
2259
|
+
const entry = expoBinFromPackage(resolvePackageJson(projectRoot, "eas-cli"), "eas") ?? expoBinFromPackage(resolvePackageJson(dirname(realpathSync(bin.file)), "eas-cli"), "eas");
|
|
2260
|
+
if (entry && !/\.(cmd|bat)$/i.test(entry)) return {
|
|
2261
|
+
provider: {
|
|
2262
|
+
easCli: entry,
|
|
2263
|
+
nodeEntry: true
|
|
2264
|
+
},
|
|
2265
|
+
name: normalized.name,
|
|
2266
|
+
owner: ownerFromConfig(read.config)
|
|
2267
|
+
};
|
|
2268
|
+
if (process.platform === "win32") return { unavailable: "the EAS build cache needs an installed eas-cli package to cancel its Node process" };
|
|
2269
|
+
return {
|
|
2270
|
+
provider: { easCli: bin.file },
|
|
2271
|
+
name: normalized.name,
|
|
2272
|
+
owner: ownerFromConfig(read.config)
|
|
2273
|
+
};
|
|
2171
2274
|
} catch (err) {
|
|
2275
|
+
if (err instanceof WorkspaceProcessCleanupError || err instanceof Error && "reportCleanup" in err) throw err;
|
|
2276
|
+
context?.signal.throwIfAborted();
|
|
2277
|
+
operationState(context).runSignal?.throwIfAborted();
|
|
2172
2278
|
const missing = err?.code === "MODULE_NOT_FOUND";
|
|
2173
2279
|
return { unavailable: normalized.name === "eas" && missing ? `the EAS build cache needs the \`${EAS_PROVIDER_PACKAGE}\` package, which is not installed in this project` : `${normalized.name} could not be loaded: ${firstLine(err)}` };
|
|
2174
2280
|
}
|
|
2175
|
-
return {
|
|
2176
|
-
provider: {
|
|
2177
|
-
plugin,
|
|
2178
|
-
options: normalized.options ?? {}
|
|
2179
|
-
},
|
|
2180
|
-
name: normalized.name,
|
|
2181
|
-
owner: ownerFromConfig(read.config)
|
|
2182
|
-
};
|
|
2183
2281
|
}
|
|
2184
2282
|
const WHOAMI_TIMEOUT_MS = 15e3;
|
|
2185
2283
|
const EAS_CLI_PACKAGE = "eas-cli";
|
|
@@ -2318,275 +2416,39 @@ async function probeEasAuth({ context, projectRoot, owner, run, resolveBin, time
|
|
|
2318
2416
|
source: bin.source
|
|
2319
2417
|
};
|
|
2320
2418
|
}
|
|
2321
|
-
const capturing = [];
|
|
2322
|
-
const abandoned = /* @__PURE__ */ new Set();
|
|
2323
|
-
let patched = null;
|
|
2324
|
-
const PROVIDER_CONTEXT = new AsyncLocalStorage();
|
|
2325
|
-
function uploadDestination(lines) {
|
|
2326
|
-
const text = (lines || []).map(stripAnsi);
|
|
2327
|
-
for (const line of text) {
|
|
2328
|
-
const url = /(https?:\/\/[^\s'"]+)/.exec(line)?.[1];
|
|
2329
|
-
if (url !== void 0) return url.replace(/[.,)\]]+$/, "");
|
|
2330
|
-
}
|
|
2331
|
-
for (const line of text) {
|
|
2332
|
-
const slug = /\bto\s+(@?[\w.-]+\/[\w.-]+)/i.exec(line)?.[1];
|
|
2333
|
-
if (slug !== void 0) return slug.replace(/[.,)\]]+$/, "");
|
|
2334
|
-
}
|
|
2335
|
-
return null;
|
|
2336
|
-
}
|
|
2337
|
-
function providerNote(text) {
|
|
2338
|
-
return `${"cache".padEnd(11)} ${text}`;
|
|
2339
|
-
}
|
|
2340
|
-
function currentFrame() {
|
|
2341
|
-
const store = PROVIDER_CONTEXT.getStore();
|
|
2342
|
-
return store && (capturing.includes(store) || abandoned.has(store)) ? store : null;
|
|
2343
|
-
}
|
|
2344
|
-
function install() {
|
|
2345
|
-
if (patched && process.stdout.write === patched.patchedWrite) return;
|
|
2346
|
-
const stdout = process.stdout;
|
|
2347
|
-
const write = stdout.write.bind(stdout);
|
|
2348
|
-
const log = console.log;
|
|
2349
|
-
const passthrough = write;
|
|
2350
|
-
const patchedWrite = ((chunk, encoding, callback) => {
|
|
2351
|
-
const frame = currentFrame();
|
|
2352
|
-
if (!frame) return passthrough(chunk, encoding, callback);
|
|
2353
|
-
const cb = typeof encoding === "function" ? encoding : callback;
|
|
2354
|
-
absorb(frame, typeof chunk === "string" ? chunk : String(chunk));
|
|
2355
|
-
if (typeof cb === "function") cb();
|
|
2356
|
-
return true;
|
|
2357
|
-
});
|
|
2358
|
-
const patchedLog = (...args) => {
|
|
2359
|
-
const frame = currentFrame();
|
|
2360
|
-
if (!frame) return log(...args);
|
|
2361
|
-
absorb(frame, `${format(...args)}\n`);
|
|
2362
|
-
};
|
|
2363
|
-
stdout.write = patchedWrite;
|
|
2364
|
-
console.log = patchedLog;
|
|
2365
|
-
patched = {
|
|
2366
|
-
stdout,
|
|
2367
|
-
write,
|
|
2368
|
-
log,
|
|
2369
|
-
patchedWrite,
|
|
2370
|
-
patchedLog
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2373
|
-
function uninstall() {
|
|
2374
|
-
if (!patched || capturing.length || abandoned.size) return;
|
|
2375
|
-
if (patched.stdout.write === patched.patchedWrite) patched.stdout.write = patched.write;
|
|
2376
|
-
if (console.log === patched.patchedLog) console.log = patched.log;
|
|
2377
|
-
patched = null;
|
|
2378
|
-
}
|
|
2379
|
-
function absorb(frame, chunk) {
|
|
2380
|
-
try {
|
|
2381
|
-
process.stderr.write(chunk);
|
|
2382
|
-
} catch {}
|
|
2383
|
-
frame.pending += chunk;
|
|
2384
|
-
let index = frame.pending.indexOf("\n");
|
|
2385
|
-
while (index !== -1) {
|
|
2386
|
-
emitLine(frame, frame.pending.slice(0, index));
|
|
2387
|
-
frame.pending = frame.pending.slice(index + 1);
|
|
2388
|
-
index = frame.pending.indexOf("\n");
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
function emitLine(frame, raw) {
|
|
2392
|
-
const line = stripAnsi(raw).trim();
|
|
2393
|
-
if (line === "") return;
|
|
2394
|
-
frame.lines.push(line);
|
|
2395
|
-
const writer = frame.writer();
|
|
2396
|
-
try {
|
|
2397
|
-
writer?.write({
|
|
2398
|
-
src: "build",
|
|
2399
|
-
level: "debug",
|
|
2400
|
-
event: "provider",
|
|
2401
|
-
msg: line
|
|
2402
|
-
});
|
|
2403
|
-
} catch {}
|
|
2404
|
-
frame.onLine?.(line);
|
|
2405
|
-
}
|
|
2406
|
-
function beginCapture({ logWriter = null, projectRoot = null, platform = null, onLine = null } = {}) {
|
|
2407
|
-
let own = null;
|
|
2408
|
-
const frame = {
|
|
2409
|
-
lines: [],
|
|
2410
|
-
pending: "",
|
|
2411
|
-
onLine,
|
|
2412
|
-
writer: () => {
|
|
2413
|
-
if (logWriter) return logWriter;
|
|
2414
|
-
if (own) return own;
|
|
2415
|
-
if (!projectRoot || !platform) return null;
|
|
2416
|
-
own = createNdjsonWriter(join(workspaceLogsDir$1(projectRoot), `build-${platform}.ndjson`));
|
|
2417
|
-
return own;
|
|
2418
|
-
},
|
|
2419
|
-
close: () => {
|
|
2420
|
-
own?.close?.();
|
|
2421
|
-
own = null;
|
|
2422
|
-
}
|
|
2423
|
-
};
|
|
2424
|
-
capturing.push(frame);
|
|
2425
|
-
install();
|
|
2426
|
-
return frame;
|
|
2427
|
-
}
|
|
2428
|
-
function endCapture(frame) {
|
|
2429
|
-
if (!frame) return;
|
|
2430
|
-
const at = capturing.lastIndexOf(frame);
|
|
2431
|
-
if (at !== -1) capturing.splice(at, 1);
|
|
2432
|
-
abandoned.delete(frame);
|
|
2433
|
-
if (frame.pending !== "") {
|
|
2434
|
-
const rest = frame.pending;
|
|
2435
|
-
frame.pending = "";
|
|
2436
|
-
emitLine(frame, rest);
|
|
2437
|
-
}
|
|
2438
|
-
frame.close();
|
|
2439
|
-
uninstall();
|
|
2440
|
-
}
|
|
2441
|
-
function abandonCapture(frame, work) {
|
|
2442
|
-
if (!frame) return;
|
|
2443
|
-
const at = capturing.lastIndexOf(frame);
|
|
2444
|
-
if (at !== -1) capturing.splice(at, 1);
|
|
2445
|
-
abandoned.add(frame);
|
|
2446
|
-
Promise.resolve(work).then(() => endCapture(frame), () => endCapture(frame));
|
|
2447
|
-
}
|
|
2448
2419
|
async function resolveRemote({ context, provider, platform, projectRoot, fingerprintHash, runOptions = null, timeoutMs = RESOLVE_TIMEOUT_MS, logWriter = null } = {}) {
|
|
2449
2420
|
if (!provider || !fingerprintHash) return null;
|
|
2450
2421
|
const opts = runOptions || runOptionsFor(platform);
|
|
2451
|
-
if (
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
nodeEntry: provider.nodeEntry,
|
|
2456
|
-
projectRoot,
|
|
2457
|
-
platform,
|
|
2458
|
-
fingerprintHash,
|
|
2459
|
-
runOptions: opts,
|
|
2460
|
-
timeoutMs,
|
|
2461
|
-
context,
|
|
2462
|
-
logWriter
|
|
2463
|
-
});
|
|
2464
|
-
}
|
|
2465
|
-
const outcome = await withBudget(async () => {
|
|
2466
|
-
const props = {
|
|
2467
|
-
fingerprintHash: await providerFingerprint({
|
|
2468
|
-
provider,
|
|
2469
|
-
platform,
|
|
2470
|
-
projectRoot,
|
|
2471
|
-
runOptions: opts
|
|
2472
|
-
}) ?? fingerprintHash,
|
|
2473
|
-
platform,
|
|
2474
|
-
runOptions: opts,
|
|
2475
|
-
projectRoot
|
|
2476
|
-
};
|
|
2477
|
-
return typeof provider.plugin.resolveBuildCache === "function" ? provider.plugin.resolveBuildCache(props, provider.options) : provider.plugin.resolveRemoteBuildCache(props, provider.options);
|
|
2478
|
-
}, timeoutMs, {
|
|
2479
|
-
logWriter,
|
|
2422
|
+
if (!projectRoot) return null;
|
|
2423
|
+
return easCache("resolve", {
|
|
2424
|
+
file: provider.easCli,
|
|
2425
|
+
nodeEntry: provider.nodeEntry,
|
|
2480
2426
|
projectRoot,
|
|
2481
|
-
platform
|
|
2427
|
+
platform,
|
|
2428
|
+
fingerprintHash,
|
|
2429
|
+
runOptions: opts,
|
|
2430
|
+
timeoutMs,
|
|
2431
|
+
context,
|
|
2432
|
+
logWriter
|
|
2482
2433
|
});
|
|
2483
|
-
if (outcome.timedOut) return { timedOut: true };
|
|
2484
|
-
if (outcome.error) return { failed: firstLine(outcome.error) };
|
|
2485
|
-
const appPath = typeof outcome.value === "string" ? outcome.value.trim() : "";
|
|
2486
|
-
if (!appPath) return null;
|
|
2487
|
-
if (!existsSync(appPath)) return { failed: `returned ${appPath}, which does not exist` };
|
|
2488
|
-
return { appPath };
|
|
2489
2434
|
}
|
|
2490
2435
|
async function uploadRemote({ context, provider, platform, projectRoot, fingerprintHash, buildPath, runOptions = null, timeoutMs = UPLOAD_TIMEOUT_MS, logWriter = null, note = (line) => context ? context.output("stderr", `${line}\n`) : console.error(line) } = {}) {
|
|
2491
2436
|
if (!provider || !fingerprintHash || !buildPath) return { skipped: true };
|
|
2492
2437
|
const opts = runOptions || runOptionsFor(platform);
|
|
2493
|
-
if (
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
nodeEntry: provider.nodeEntry,
|
|
2498
|
-
projectRoot,
|
|
2499
|
-
platform,
|
|
2500
|
-
fingerprintHash,
|
|
2501
|
-
runOptions: opts,
|
|
2502
|
-
buildPath,
|
|
2503
|
-
timeoutMs,
|
|
2504
|
-
context,
|
|
2505
|
-
logWriter,
|
|
2506
|
-
note
|
|
2507
|
-
}) ?? { skipped: true };
|
|
2508
|
-
}
|
|
2509
|
-
let announced = false;
|
|
2510
|
-
const onLine = (line) => {
|
|
2511
|
-
if (announced) return;
|
|
2512
|
-
const dest = uploadDestination([line]);
|
|
2513
|
-
if (!dest) return;
|
|
2514
|
-
announced = true;
|
|
2515
|
-
note(providerNote(`uploading to ${dest}`));
|
|
2516
|
-
};
|
|
2517
|
-
const outcome = await withBudget(async () => {
|
|
2518
|
-
const props = {
|
|
2519
|
-
projectRoot,
|
|
2520
|
-
platform,
|
|
2521
|
-
fingerprintHash: await providerFingerprint({
|
|
2522
|
-
provider,
|
|
2523
|
-
platform,
|
|
2524
|
-
projectRoot,
|
|
2525
|
-
runOptions: opts
|
|
2526
|
-
}) ?? fingerprintHash,
|
|
2527
|
-
buildPath,
|
|
2528
|
-
runOptions: opts
|
|
2529
|
-
};
|
|
2530
|
-
return typeof provider.plugin.uploadBuildCache === "function" ? provider.plugin.uploadBuildCache(props, provider.options) : provider.plugin.uploadRemoteBuildCache(props, provider.options);
|
|
2531
|
-
}, timeoutMs, {
|
|
2532
|
-
logWriter,
|
|
2438
|
+
if (!projectRoot) return { skipped: true };
|
|
2439
|
+
return await easCache("upload", {
|
|
2440
|
+
file: provider.easCli,
|
|
2441
|
+
nodeEntry: provider.nodeEntry,
|
|
2533
2442
|
projectRoot,
|
|
2534
2443
|
platform,
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
}
|
|
2543
|
-
const destination = uploadDestination(outcome.lines);
|
|
2544
|
-
return destination ? {
|
|
2545
|
-
uploaded: true,
|
|
2546
|
-
destination
|
|
2547
|
-
} : { uploaded: true };
|
|
2548
|
-
}
|
|
2549
|
-
async function providerFingerprint({ provider, platform, projectRoot, runOptions }) {
|
|
2550
|
-
if (typeof provider.plugin.calculateFingerprintHash !== "function") return null;
|
|
2551
|
-
try {
|
|
2552
|
-
const hash = await provider.plugin.calculateFingerprintHash({
|
|
2553
|
-
projectRoot,
|
|
2554
|
-
platform,
|
|
2555
|
-
runOptions
|
|
2556
|
-
}, provider.options);
|
|
2557
|
-
return typeof hash === "string" && hash.trim() !== "" ? hash.trim() : null;
|
|
2558
|
-
} catch {
|
|
2559
|
-
return null;
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
async function withBudget(factory, ms, capture) {
|
|
2563
|
-
let timer = null;
|
|
2564
|
-
const timeout = new Promise((resolve) => {
|
|
2565
|
-
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
2566
|
-
timer?.unref?.();
|
|
2567
|
-
});
|
|
2568
|
-
const frame = beginCapture(capture || {});
|
|
2569
|
-
const work = Promise.resolve().then(() => PROVIDER_CONTEXT.run(frame, factory)).then((value) => ({ value }), (error) => ({ error }));
|
|
2570
|
-
try {
|
|
2571
|
-
const settled = await Promise.race([work, timeout]);
|
|
2572
|
-
if (settled === TIMED_OUT) {
|
|
2573
|
-
abandonCapture(frame, work);
|
|
2574
|
-
return {
|
|
2575
|
-
timedOut: true,
|
|
2576
|
-
lines: frame.lines
|
|
2577
|
-
};
|
|
2578
|
-
}
|
|
2579
|
-
endCapture(frame);
|
|
2580
|
-
return {
|
|
2581
|
-
...settled,
|
|
2582
|
-
lines: frame.lines
|
|
2583
|
-
};
|
|
2584
|
-
} catch (err) {
|
|
2585
|
-
endCapture(frame);
|
|
2586
|
-
throw err;
|
|
2587
|
-
} finally {
|
|
2588
|
-
if (timer) clearTimeout(timer);
|
|
2589
|
-
}
|
|
2444
|
+
fingerprintHash,
|
|
2445
|
+
runOptions: opts,
|
|
2446
|
+
buildPath,
|
|
2447
|
+
timeoutMs,
|
|
2448
|
+
context,
|
|
2449
|
+
logWriter,
|
|
2450
|
+
note
|
|
2451
|
+
}) ?? { skipped: true };
|
|
2590
2452
|
}
|
|
2591
2453
|
function firstLine(err) {
|
|
2592
2454
|
const line = (String(err?.message || err || "unknown error").trim().split("\n")[0] ?? "").trim();
|
|
@@ -5111,7 +4973,7 @@ async function collectEasSessionSweep(config, deps) {
|
|
|
5111
4973
|
const recorded = readRecordedRemoteSessionIds([...Object.keys(config?.projects ?? {}), projectRoot]);
|
|
5112
4974
|
const ledgerRoot = deps.easLedgerRoot ?? easMachineStateRoot$1();
|
|
5113
4975
|
const ledger = readEasSessionLedger$1(ledgerRoot);
|
|
5114
|
-
const run = deps.runEasFile ?? ((file, args, options) => getExecutor()
|
|
4976
|
+
const run = deps.runEasFile ?? ((file, args, options) => runOperationFile(deps.operation, getExecutor(), file, args, {
|
|
5115
4977
|
...options,
|
|
5116
4978
|
killSignal: "SIGKILL"
|
|
5117
4979
|
}));
|
|
@@ -5124,6 +4986,7 @@ async function collectEasSessionSweep(config, deps) {
|
|
|
5124
4986
|
let pageCount = 0;
|
|
5125
4987
|
let after = null;
|
|
5126
4988
|
while (true) {
|
|
4989
|
+
deps.operation?.signal.throwIfAborted();
|
|
5127
4990
|
if (pageCount >= maxPages) return empty([...recorded.notices, `EAS session list exceeded the maximum page limit of ${maxPages} for ${projectRoot}.`]);
|
|
5128
4991
|
const remainingMs = deadline - now();
|
|
5129
4992
|
if (remainingMs <= 0) return empty([...recorded.notices, `EAS session list exceeded its total time limit for ${projectRoot}.`]);
|
|
@@ -5135,6 +4998,8 @@ async function collectEasSessionSweep(config, deps) {
|
|
|
5135
4998
|
omitEnv: PROXY_CREDENTIAL_ENV
|
|
5136
4999
|
});
|
|
5137
5000
|
} catch (error) {
|
|
5001
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
5002
|
+
deps.operation?.signal.throwIfAborted();
|
|
5138
5003
|
return empty([...recorded.notices, `EAS session list failed for ${projectRoot}: ${describeError(error)}`]);
|
|
5139
5004
|
}
|
|
5140
5005
|
pageCount++;
|
|
@@ -5207,6 +5072,7 @@ async function withRemoteSessionGcLocks(projectRoot, deps, fn) {
|
|
|
5207
5072
|
const waitMs = deps.easLockWaitMs ?? EAS_LOCK_WAIT_MS;
|
|
5208
5073
|
const maxSnapshots = deps.easLockSnapshotRetries ?? EAS_LOCK_SNAPSHOT_RETRIES;
|
|
5209
5074
|
for (let attempt = 0; attempt < maxSnapshots; attempt++) {
|
|
5075
|
+
deps.operation?.signal.throwIfAborted();
|
|
5210
5076
|
const roots = easLockRoots(projectRoot, loadConfig$1());
|
|
5211
5077
|
const unavailable = unavailableLockRootNotice(roots);
|
|
5212
5078
|
if (unavailable) return fn({
|
|
@@ -5231,11 +5097,19 @@ async function withRemoteSessionGcLocks(projectRoot, deps, fn) {
|
|
|
5231
5097
|
easSweepBlockedNotice: void 0,
|
|
5232
5098
|
easLockedRoots: roots
|
|
5233
5099
|
});
|
|
5234
|
-
})() : withLock(roots[index], () => acquire(index + 1), {
|
|
5100
|
+
})() : withLock(roots[index], () => acquire(index + 1), {
|
|
5101
|
+
waitMs,
|
|
5102
|
+
...deps.operation ? {
|
|
5103
|
+
signal: deps.operation.signal,
|
|
5104
|
+
declareSpawnsFor: deps.operation
|
|
5105
|
+
} : {}
|
|
5106
|
+
});
|
|
5235
5107
|
try {
|
|
5236
5108
|
const result = await acquire(0);
|
|
5237
5109
|
if (!expanded) return result;
|
|
5238
5110
|
} catch (error) {
|
|
5111
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
5112
|
+
deps.operation?.signal.throwIfAborted();
|
|
5239
5113
|
if (coreStarted) throw error;
|
|
5240
5114
|
return fn({
|
|
5241
5115
|
...deps,
|
|
@@ -5248,11 +5122,12 @@ async function withRemoteSessionGcLocks(projectRoot, deps, fn) {
|
|
|
5248
5122
|
easSweepBlockedNotice: `EAS session sweep skipped: registered workspace roots kept changing during remote-session lock acquisition.`
|
|
5249
5123
|
});
|
|
5250
5124
|
}
|
|
5251
|
-
async function deleteEasSessions(easSessionSweep, deps) {
|
|
5125
|
+
async function deleteEasSessions(easSessionSweep, deps, output = GC_OUTPUT) {
|
|
5126
|
+
deps.operation?.signal.throwIfAborted();
|
|
5252
5127
|
let deleteFailures = 0;
|
|
5253
5128
|
const refuseAll = (detail) => {
|
|
5254
5129
|
deleteFailures += easSessionSweep.orphaned.length;
|
|
5255
|
-
for (const session of easSessionSweep.orphaned)
|
|
5130
|
+
for (const session of easSessionSweep.orphaned) output.record("easSession", "failed", session.name, {
|
|
5256
5131
|
id: session.id,
|
|
5257
5132
|
detail
|
|
5258
5133
|
});
|
|
@@ -5267,7 +5142,7 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5267
5142
|
return withRemoteSessionGcLocks(projectScope, deps, async (lockedDeps) => {
|
|
5268
5143
|
if (lockedDeps.easSweepBlockedNotice) {
|
|
5269
5144
|
refuseAll(`EAS session deletion refused: ${lockedDeps.easSweepBlockedNotice}`);
|
|
5270
|
-
|
|
5145
|
+
output.log(chalk.red(`EAS session deletion refused: ${lockedDeps.easSweepBlockedNotice}`));
|
|
5271
5146
|
return;
|
|
5272
5147
|
}
|
|
5273
5148
|
const coordinatedDeps = lockedDeps;
|
|
@@ -5279,29 +5154,35 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5279
5154
|
const ledger = readEasSessionLedger$1(ledgerRoot);
|
|
5280
5155
|
const removeClaim = coordinatedDeps.removeEasSessionClaim ?? removeEasSessionClaim;
|
|
5281
5156
|
const reconcileClaim = (sessionId) => {
|
|
5157
|
+
deps.operation?.signal.throwIfAborted();
|
|
5282
5158
|
let detail = "the claim store did not remove it";
|
|
5283
5159
|
try {
|
|
5284
5160
|
if (removeClaim(sessionId, ledgerRoot)) return;
|
|
5285
5161
|
} catch (error) {
|
|
5162
|
+
deps.operation?.signal.throwIfAborted();
|
|
5286
5163
|
detail = describeError(error);
|
|
5287
5164
|
}
|
|
5288
5165
|
deleteFailures++;
|
|
5289
|
-
|
|
5166
|
+
output.log(chalk.red(`EAS session ${sessionId} is resolved, but its ownership claim could not be removed: ${detail}. The claim was retained for reconciliation.`));
|
|
5290
5167
|
};
|
|
5291
5168
|
if (registryExpanded) {
|
|
5292
5169
|
refuseAll("EAS session deletion refused: registered workspace roots changed after classification.");
|
|
5293
|
-
|
|
5170
|
+
output.log(chalk.red("EAS session deletion refused: registered workspace roots changed after classification."));
|
|
5294
5171
|
} else if (!currentRecords?.safe || !ledger.safe) {
|
|
5295
5172
|
refuseAll("EAS session deletion refused: the session records could not be read safely.");
|
|
5296
|
-
for (const notice of [...currentRecords?.notices ?? [], ...ledger.notice ? [ledger.notice] : []])
|
|
5173
|
+
for (const notice of [...currentRecords?.notices ?? [], ...ledger.notice ? [ledger.notice] : []]) output.log(chalk.red(`EAS session deletion refused: ${notice}`));
|
|
5297
5174
|
} else {
|
|
5298
5175
|
const recorded = new Set(currentRecords.ids);
|
|
5299
5176
|
const eas = (coordinatedDeps.resolveEasCliBin ?? resolveEasCliBin)(projectScope);
|
|
5300
|
-
const run = coordinatedDeps.runEasFile ?? ((file, args, options) => getExecutor()
|
|
5177
|
+
const run = coordinatedDeps.runEasFile ?? ((file, args, options) => runOperationFile(coordinatedDeps.operation, getExecutor(), file, args, {
|
|
5178
|
+
...options,
|
|
5179
|
+
killSignal: "SIGKILL"
|
|
5180
|
+
}));
|
|
5301
5181
|
for (const session of easSessionSweep.orphaned) {
|
|
5182
|
+
deps.operation?.signal.throwIfAborted();
|
|
5302
5183
|
if (recorded.has(session.id)) {
|
|
5303
|
-
|
|
5304
|
-
|
|
5184
|
+
output.log(chalk.dim(`EAS session ${session.id} has a workspace record and was left running.`));
|
|
5185
|
+
output.record("easSession", "kept", session.name, {
|
|
5305
5186
|
id: session.id,
|
|
5306
5187
|
detail: "it has a workspace record and was left running"
|
|
5307
5188
|
});
|
|
@@ -5311,8 +5192,8 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5311
5192
|
const claimState = claim ? claimStateAbsence(claim) : null;
|
|
5312
5193
|
if (!claim || claim.name !== session.name || claim.platform !== session.platform || !claimState?.absent || claimState.notice) {
|
|
5313
5194
|
deleteFailures++;
|
|
5314
|
-
|
|
5315
|
-
|
|
5195
|
+
output.log(chalk.red(`Could not verify fixed ownership for EAS session ${session.id}: ${claimState?.notice ?? "the claim is missing, mismatched, or still recorded in workspace state"}.`));
|
|
5196
|
+
output.record("easSession", "failed", session.name, {
|
|
5316
5197
|
id: session.id,
|
|
5317
5198
|
detail: `Could not verify fixed ownership for EAS session ${session.id}: ${claimState?.notice ?? "the claim is missing, mismatched, or still recorded in workspace state"}.`
|
|
5318
5199
|
});
|
|
@@ -5320,8 +5201,8 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5320
5201
|
}
|
|
5321
5202
|
if (!eas) {
|
|
5322
5203
|
deleteFailures++;
|
|
5323
|
-
|
|
5324
|
-
|
|
5204
|
+
output.log(chalk.red(`Could not verify EAS session ${session.id}: eas-cli is not available.`));
|
|
5205
|
+
output.record("easSession", "failed", session.name, {
|
|
5325
5206
|
id: session.id,
|
|
5326
5207
|
detail: `Could not verify EAS session ${session.id}: eas-cli is not available.`
|
|
5327
5208
|
});
|
|
@@ -5335,14 +5216,17 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5335
5216
|
let live;
|
|
5336
5217
|
try {
|
|
5337
5218
|
live = await run(eas.file, getSessionArgs(session.id), options);
|
|
5219
|
+
deps.operation?.signal.throwIfAborted();
|
|
5338
5220
|
} catch (error) {
|
|
5221
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
5222
|
+
deps.operation?.signal.throwIfAborted();
|
|
5339
5223
|
if (isDefinitiveMissingSessionError(error, session.id)) {
|
|
5340
|
-
|
|
5224
|
+
output.log(chalk.dim(`EAS session ${session.id} is already gone.`));
|
|
5341
5225
|
reconcileClaim(session.id);
|
|
5342
5226
|
} else {
|
|
5343
5227
|
deleteFailures++;
|
|
5344
|
-
|
|
5345
|
-
|
|
5228
|
+
output.log(chalk.red(`Could not verify EAS session ${session.id}: ${describeError(error)}`));
|
|
5229
|
+
output.record("easSession", "failed", session.name, {
|
|
5346
5230
|
id: session.id,
|
|
5347
5231
|
detail: `Could not verify EAS session ${session.id}: ${describeError(error)}`
|
|
5348
5232
|
});
|
|
@@ -5351,14 +5235,14 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5351
5235
|
}
|
|
5352
5236
|
const inspection = inspectSessionForTeardown(live, session.id);
|
|
5353
5237
|
if (inspection.action === "already-stopped") {
|
|
5354
|
-
|
|
5238
|
+
output.log(chalk.dim(`EAS session ${session.id} is already stopped (${inspection.status}).`));
|
|
5355
5239
|
reconcileClaim(session.id);
|
|
5356
5240
|
continue;
|
|
5357
5241
|
}
|
|
5358
5242
|
if (inspection.action === "refused") {
|
|
5359
5243
|
deleteFailures++;
|
|
5360
|
-
|
|
5361
|
-
|
|
5244
|
+
output.log(chalk.red(`Could not stop EAS session ${session.id}: ${inspection.reason}`));
|
|
5245
|
+
output.record("easSession", "failed", session.name, {
|
|
5362
5246
|
id: session.id,
|
|
5363
5247
|
detail: `Could not stop EAS session ${session.id}: ${inspection.reason}`
|
|
5364
5248
|
});
|
|
@@ -5366,11 +5250,15 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5366
5250
|
}
|
|
5367
5251
|
let stopped;
|
|
5368
5252
|
try {
|
|
5253
|
+
deps.operation?.signal.throwIfAborted();
|
|
5369
5254
|
stopped = await run(eas.file, stopSessionArgs(session.id), options);
|
|
5255
|
+
deps.operation?.signal.throwIfAborted();
|
|
5370
5256
|
} catch (error) {
|
|
5257
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
5258
|
+
deps.operation?.signal.throwIfAborted();
|
|
5371
5259
|
deleteFailures++;
|
|
5372
|
-
|
|
5373
|
-
|
|
5260
|
+
output.log(chalk.red(`Could not stop EAS session ${session.id}: ${describeError(error)}`));
|
|
5261
|
+
output.record("easSession", "failed", session.name, {
|
|
5374
5262
|
id: session.id,
|
|
5375
5263
|
detail: `Could not stop EAS session ${session.id}: ${describeError(error)}`
|
|
5376
5264
|
});
|
|
@@ -5379,15 +5267,15 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5379
5267
|
const verified = verifyStoppedSession(stopped, session.id);
|
|
5380
5268
|
if (!verified.ok) {
|
|
5381
5269
|
deleteFailures++;
|
|
5382
|
-
|
|
5383
|
-
|
|
5270
|
+
output.log(chalk.red(`Could not verify EAS session ${session.id} stopped: ${verified.reason}`));
|
|
5271
|
+
output.record("easSession", "failed", session.name, {
|
|
5384
5272
|
id: session.id,
|
|
5385
5273
|
detail: `Could not verify EAS session ${session.id} stopped: ${verified.reason}`
|
|
5386
5274
|
});
|
|
5387
5275
|
continue;
|
|
5388
5276
|
}
|
|
5389
|
-
|
|
5390
|
-
|
|
5277
|
+
output.log(chalk.green(`Stopped EAS session ${session.id} (${session.name})`));
|
|
5278
|
+
output.record("easSession", "done", session.name, { id: session.id });
|
|
5391
5279
|
reconcileClaim(session.id);
|
|
5392
5280
|
}
|
|
5393
5281
|
}
|
|
@@ -5395,12 +5283,18 @@ async function deleteEasSessions(easSessionSweep, deps) {
|
|
|
5395
5283
|
}, {
|
|
5396
5284
|
waitMs: 0,
|
|
5397
5285
|
ownerPurpose: "EAS orphan deletion",
|
|
5398
|
-
machineRoot: deps.easLedgerRoot
|
|
5286
|
+
machineRoot: deps.easLedgerRoot,
|
|
5287
|
+
...deps.operation ? {
|
|
5288
|
+
signal: deps.operation.signal,
|
|
5289
|
+
declareSpawnsFor: deps.operation
|
|
5290
|
+
} : {}
|
|
5399
5291
|
});
|
|
5400
5292
|
} catch (error) {
|
|
5293
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
5294
|
+
deps.operation?.signal.throwIfAborted();
|
|
5401
5295
|
const phase = finalSweepStarted ? "failed" : "lock acquisition failed";
|
|
5402
5296
|
refuseAll(`EAS session deletion ${phase}: ${describeError(error)}`);
|
|
5403
|
-
|
|
5297
|
+
output.log(chalk.red(`EAS session deletion ${phase}: ${describeError(error)}`));
|
|
5404
5298
|
}
|
|
5405
5299
|
}
|
|
5406
5300
|
return deleteFailures;
|
|
@@ -5569,9 +5463,10 @@ function describeUnverifiableDevices(simNames = [], avdNames = [], { reason = "n
|
|
|
5569
5463
|
if (ours.length === 0) return [`${reason}; device sweep skipped`];
|
|
5570
5464
|
return [`${reason}, so ${ours.length} stim-created device(s) cannot be verified as orphaned: ${ours.join(", ")}`, "they were NOT touched. If they are stale, delete them with `xcrun simctl delete <udid>` or `avdmanager delete avd -n <name>`"];
|
|
5571
5465
|
}
|
|
5572
|
-
async function withAndroidAvdSizes(devices, { avdDirectory = ownedAvdDirectory, size = directorySizeAsync } = {}) {
|
|
5466
|
+
async function withAndroidAvdSizes(devices, { avdDirectory = ownedAvdDirectory, size = directorySizeAsync, operation } = {}) {
|
|
5573
5467
|
const sized = [];
|
|
5574
5468
|
for (const device of devices) {
|
|
5469
|
+
operation?.signal.throwIfAborted();
|
|
5575
5470
|
if (device.kind !== "android") {
|
|
5576
5471
|
sized.push(device);
|
|
5577
5472
|
continue;
|
|
@@ -5582,12 +5477,18 @@ async function withAndroidAvdSizes(devices, { avdDirectory = ownedAvdDirectory,
|
|
|
5582
5477
|
continue;
|
|
5583
5478
|
}
|
|
5584
5479
|
try {
|
|
5585
|
-
const bytes = await size(dir, {
|
|
5480
|
+
const bytes = await size(dir, {
|
|
5481
|
+
timeoutMs: AVD_SIZE_TIMEOUT_MS,
|
|
5482
|
+
operation,
|
|
5483
|
+
signal: operation?.signal
|
|
5484
|
+
});
|
|
5586
5485
|
sized.push(bytes > 0 ? {
|
|
5587
5486
|
...device,
|
|
5588
5487
|
bytes
|
|
5589
5488
|
} : device);
|
|
5590
|
-
} catch {
|
|
5489
|
+
} catch (error) {
|
|
5490
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
5491
|
+
operation?.signal.throwIfAborted();
|
|
5591
5492
|
sized.push(device);
|
|
5592
5493
|
}
|
|
5593
5494
|
}
|
|
@@ -5615,6 +5516,8 @@ function parkedAtLeast(records, { olderThanDays = null, now = Date.now() }) {
|
|
|
5615
5516
|
return records.filter((record) => Date.parse(record.parkedAt) <= cutoff);
|
|
5616
5517
|
}
|
|
5617
5518
|
async function collectParkedSims(deps, age = {}) {
|
|
5519
|
+
const operation = deps.operation;
|
|
5520
|
+
operation?.signal.throwIfAborted();
|
|
5618
5521
|
const records = parkedAtLeast(readParked("ios"), age);
|
|
5619
5522
|
if (records.length === 0) return [];
|
|
5620
5523
|
let sims;
|
|
@@ -5622,14 +5525,24 @@ async function collectParkedSims(deps, age = {}) {
|
|
|
5622
5525
|
try {
|
|
5623
5526
|
sims = await (deps.listAllIosSims ?? listAllIosSimsAsync)({
|
|
5624
5527
|
timeoutMs: DEVICE_LIST_TIMEOUT_MS$1,
|
|
5625
|
-
includeUnavailable: true
|
|
5528
|
+
includeUnavailable: true,
|
|
5529
|
+
operation,
|
|
5530
|
+
signal: operation?.signal
|
|
5626
5531
|
});
|
|
5627
|
-
} catch {
|
|
5532
|
+
} catch (error) {
|
|
5533
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
5534
|
+
operation?.signal.throwIfAborted();
|
|
5628
5535
|
return describeParkedSims(records, [], [], { simsChecked: false });
|
|
5629
5536
|
}
|
|
5630
5537
|
try {
|
|
5631
|
-
deviceTypes = await (deps.listIosDeviceTypes ?? listIosDeviceTypesAsync)(
|
|
5632
|
-
|
|
5538
|
+
deviceTypes = await (deps.listIosDeviceTypes ?? listIosDeviceTypesAsync)({
|
|
5539
|
+
operation,
|
|
5540
|
+
signal: operation?.signal
|
|
5541
|
+
});
|
|
5542
|
+
} catch (error) {
|
|
5543
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
5544
|
+
operation?.signal.throwIfAborted();
|
|
5545
|
+
}
|
|
5633
5546
|
return describeParkedSims(records, sims, deviceTypes);
|
|
5634
5547
|
}
|
|
5635
5548
|
function poolConfigurationDeviceProfile(configuration) {
|
|
@@ -5642,19 +5555,35 @@ function poolConfigurationDeviceProfile(configuration) {
|
|
|
5642
5555
|
}
|
|
5643
5556
|
}
|
|
5644
5557
|
async function collectParkedAvds(deps, age = {}) {
|
|
5558
|
+
const operation = deps.operation;
|
|
5559
|
+
operation?.signal.throwIfAborted();
|
|
5645
5560
|
const records = parkedAtLeast(readParked("android"), age);
|
|
5646
5561
|
if (!records.length) return [];
|
|
5647
5562
|
let avds = null;
|
|
5648
5563
|
try {
|
|
5649
|
-
avds = await (deps.listAvds ?? listAvdsAsync)(
|
|
5650
|
-
|
|
5564
|
+
avds = await (deps.listAvds ?? listAvdsAsync)({
|
|
5565
|
+
operation,
|
|
5566
|
+
signal: operation?.signal
|
|
5567
|
+
});
|
|
5568
|
+
} catch (error) {
|
|
5569
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
5570
|
+
operation?.signal.throwIfAborted();
|
|
5571
|
+
}
|
|
5651
5572
|
const reports = [];
|
|
5652
5573
|
for (const record of records) {
|
|
5574
|
+
operation?.signal.throwIfAborted();
|
|
5653
5575
|
let bytes = null;
|
|
5654
5576
|
try {
|
|
5655
5577
|
const directory = (deps.avdDirectory ?? ownedAvdDirectory)(record.name);
|
|
5656
|
-
if (directory) bytes = await (deps.directorySize ?? directorySizeAsync)(directory, {
|
|
5657
|
-
|
|
5578
|
+
if (directory) bytes = await (deps.directorySize ?? directorySizeAsync)(directory, {
|
|
5579
|
+
timeoutMs: AVD_SIZE_TIMEOUT_MS,
|
|
5580
|
+
operation,
|
|
5581
|
+
signal: operation?.signal
|
|
5582
|
+
});
|
|
5583
|
+
} catch (error) {
|
|
5584
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
5585
|
+
operation?.signal.throwIfAborted();
|
|
5586
|
+
}
|
|
5658
5587
|
reports.push({
|
|
5659
5588
|
name: record.name,
|
|
5660
5589
|
systemImage: record.systemImage,
|
|
@@ -5667,7 +5596,7 @@ async function collectParkedAvds(deps, age = {}) {
|
|
|
5667
5596
|
}
|
|
5668
5597
|
return reports;
|
|
5669
5598
|
}
|
|
5670
|
-
async function eraseParkedDevices(sims, avds) {
|
|
5599
|
+
async function eraseParkedDevices(sims, avds, output = GC_OUTPUT, operation) {
|
|
5671
5600
|
let failures = 0;
|
|
5672
5601
|
const devices = [...sims.map((sim) => ({
|
|
5673
5602
|
platform: "ios",
|
|
@@ -5683,74 +5612,82 @@ async function eraseParkedDevices(sims, avds) {
|
|
|
5683
5612
|
bytes: avd.bytes
|
|
5684
5613
|
}))];
|
|
5685
5614
|
for (const device of devices) {
|
|
5615
|
+
operation?.signal.throwIfAborted();
|
|
5686
5616
|
const what = `parked ${device.platform === "ios" ? "ios sim" : "android emulator"} ${device.name}`;
|
|
5687
5617
|
if (device.listed !== true) {
|
|
5688
5618
|
const unverified = device.listed === null;
|
|
5689
5619
|
const detail = unverified ? "Stim could not list devices to verify it; it was kept. Retry stim gc --cache parked --delete." : "it is not on this machine; stim gc --delete drops its pool record.";
|
|
5690
5620
|
if (unverified) failures++;
|
|
5691
|
-
|
|
5692
|
-
|
|
5621
|
+
output.log((unverified ? chalk.red : chalk.dim)(`Did not erase ${what}: ${detail}`));
|
|
5622
|
+
output.record("parkedDevice", unverified ? "failed" : "kept", device.name, {
|
|
5693
5623
|
id: device.id,
|
|
5694
5624
|
detail
|
|
5695
5625
|
});
|
|
5696
5626
|
continue;
|
|
5697
5627
|
}
|
|
5698
|
-
const result = device.platform === "ios" ? await eraseParkedIosSim(device.id, {
|
|
5628
|
+
const result = device.platform === "ios" ? await eraseParkedIosSim(device.id, {
|
|
5629
|
+
label: device.name,
|
|
5630
|
+
operation
|
|
5631
|
+
}) : await eraseParkedAvd(device.id, { operation });
|
|
5632
|
+
operation?.signal.throwIfAborted();
|
|
5699
5633
|
if (result.status === "torn-down") {
|
|
5700
|
-
|
|
5701
|
-
|
|
5634
|
+
output.log(chalk.green(`Erased ${what}; it stays parked`));
|
|
5635
|
+
output.record("parkedDevice", "done", device.name, {
|
|
5702
5636
|
id: device.id,
|
|
5703
5637
|
bytes: device.bytes
|
|
5704
5638
|
});
|
|
5705
5639
|
} else if (result.status === "failed") {
|
|
5706
5640
|
failures++;
|
|
5707
|
-
|
|
5708
|
-
|
|
5641
|
+
output.log(chalk.red(`Failed to erase ${what}: ${result.reason}`));
|
|
5642
|
+
output.record("parkedDevice", "failed", device.name, {
|
|
5709
5643
|
id: device.id,
|
|
5710
5644
|
detail: result.reason
|
|
5711
5645
|
});
|
|
5712
|
-
} else
|
|
5646
|
+
} else output.log(chalk.dim(`Skipped ${device.name}; it is no longer parked.`));
|
|
5713
5647
|
}
|
|
5714
5648
|
return failures;
|
|
5715
5649
|
}
|
|
5716
|
-
async function deleteParkedAvds(avds) {
|
|
5650
|
+
async function deleteParkedAvds(avds, output = GC_OUTPUT, operation) {
|
|
5717
5651
|
let failures = 0;
|
|
5718
5652
|
for (const avd of avds) {
|
|
5653
|
+
operation?.signal.throwIfAborted();
|
|
5719
5654
|
if (avd.listed === null) {
|
|
5720
5655
|
failures++;
|
|
5721
|
-
|
|
5722
|
-
|
|
5656
|
+
output.log(chalk.red(`Could not verify parked android emulator ${avd.name}; its pool record was kept.`));
|
|
5657
|
+
output.record("parkedDevice", "failed", avd.name, {
|
|
5723
5658
|
id: avd.name,
|
|
5724
5659
|
detail: "Stim could not list emulators to verify it; its pool record was kept. Retry stim gc --delete."
|
|
5725
5660
|
});
|
|
5726
5661
|
continue;
|
|
5727
5662
|
}
|
|
5728
|
-
const result = await teardownParkedAvd(avd.name);
|
|
5663
|
+
const result = await teardownParkedAvd(avd.name, { operation });
|
|
5664
|
+
operation?.signal.throwIfAborted();
|
|
5729
5665
|
if (result.status === "failed") {
|
|
5730
5666
|
failures++;
|
|
5731
|
-
|
|
5732
|
-
|
|
5667
|
+
output.log(chalk.red(`Failed to delete parked android emulator ${avd.name}: ${result.reason}`));
|
|
5668
|
+
output.record("parkedDevice", "failed", avd.name, {
|
|
5733
5669
|
id: avd.name,
|
|
5734
5670
|
detail: result.reason
|
|
5735
5671
|
});
|
|
5736
5672
|
} else if (result.status === "torn-down") {
|
|
5737
|
-
|
|
5738
|
-
|
|
5673
|
+
output.log(chalk.green(`Deleted parked android emulator ${avd.name}`));
|
|
5674
|
+
output.record("parkedDevice", "done", avd.name, {
|
|
5739
5675
|
id: avd.name,
|
|
5740
5676
|
bytes: avd.bytes
|
|
5741
5677
|
});
|
|
5742
|
-
} else
|
|
5678
|
+
} else output.log(chalk.dim(`Skipped ${avd.name}; it is no longer parked.`));
|
|
5743
5679
|
}
|
|
5744
5680
|
return failures;
|
|
5745
5681
|
}
|
|
5746
|
-
async function deleteParkedSims(parkedSims, deps = {}) {
|
|
5682
|
+
async function deleteParkedSims(parkedSims, deps = {}, output = GC_OUTPUT) {
|
|
5747
5683
|
let failures = 0;
|
|
5748
5684
|
let emptied = 0;
|
|
5749
5685
|
for (const sim of parkedSims) {
|
|
5686
|
+
deps.operation?.signal.throwIfAborted();
|
|
5750
5687
|
if (sim.listed === null) {
|
|
5751
5688
|
failures++;
|
|
5752
|
-
|
|
5753
|
-
|
|
5689
|
+
output.log(chalk.red(`Could not verify parked ios sim ${sim.name} (${sim.udid}); its pool record was kept.`));
|
|
5690
|
+
output.record("parkedDevice", "failed", sim.name, {
|
|
5754
5691
|
id: sim.udid,
|
|
5755
5692
|
detail: "Stim could not list simulators to verify it; its pool record was kept. Retry stim gc --delete."
|
|
5756
5693
|
});
|
|
@@ -5759,35 +5696,38 @@ async function deleteParkedSims(parkedSims, deps = {}) {
|
|
|
5759
5696
|
try {
|
|
5760
5697
|
const teardown = sim.listed ? await teardownParkedIosSim(sim.udid, {
|
|
5761
5698
|
label: sim.name,
|
|
5762
|
-
deleteSim: deps.deleteParkedIosSim
|
|
5699
|
+
deleteSim: deps.deleteParkedIosSim,
|
|
5700
|
+
operation: deps.operation
|
|
5763
5701
|
}) : null;
|
|
5764
|
-
if (!(sim.listed ? teardown?.status === "torn-down" : await dropParked("ios", sim.udid))) {
|
|
5702
|
+
if (!(sim.listed ? teardown?.status === "torn-down" : await dropParked("ios", sim.udid, deps.operation))) {
|
|
5765
5703
|
if (teardown?.status === "failed") {
|
|
5766
5704
|
failures++;
|
|
5767
|
-
|
|
5768
|
-
|
|
5705
|
+
output.log(chalk.red(`Failed to delete parked ios sim ${sim.name}: ${teardown.reason}`));
|
|
5706
|
+
output.record("parkedDevice", "failed", sim.name, {
|
|
5769
5707
|
id: sim.udid,
|
|
5770
5708
|
detail: teardown.reason
|
|
5771
5709
|
});
|
|
5772
|
-
} else
|
|
5710
|
+
} else output.log(chalk.dim(`Skipped ${sim.name} (${sim.udid}); it is no longer parked.`));
|
|
5773
5711
|
continue;
|
|
5774
5712
|
}
|
|
5775
5713
|
emptied++;
|
|
5776
|
-
|
|
5714
|
+
output.record("parkedDevice", "done", sim.name, {
|
|
5777
5715
|
id: sim.udid,
|
|
5778
5716
|
bytes: sim.listed ? sim.bytes : null
|
|
5779
5717
|
});
|
|
5780
|
-
|
|
5718
|
+
output.log(chalk.green(sim.listed ? `Deleted parked ios sim ${sim.name} (${sim.udid})` : `Dropped the parked record for ${sim.name} (${sim.udid}); it is not on this machine.`));
|
|
5781
5719
|
} catch (err) {
|
|
5720
|
+
if (err instanceof WorkspaceProcessCleanupError) throw err;
|
|
5721
|
+
deps.operation?.signal.throwIfAborted();
|
|
5782
5722
|
failures++;
|
|
5783
|
-
|
|
5784
|
-
|
|
5723
|
+
output.log(chalk.red(`Failed to delete parked ios sim ${sim.name}: ${err?.message || err}`));
|
|
5724
|
+
output.record("parkedDevice", "failed", sim.name, {
|
|
5785
5725
|
id: sim.udid,
|
|
5786
5726
|
detail: err?.message || String(err)
|
|
5787
5727
|
});
|
|
5788
5728
|
}
|
|
5789
5729
|
}
|
|
5790
|
-
if (emptied)
|
|
5730
|
+
if (emptied) output.log(chalk.dim(` emptied ${plural(emptied, "parked simulator")} from the pool`));
|
|
5791
5731
|
return failures;
|
|
5792
5732
|
}
|
|
5793
5733
|
function deviceSweepIsScoped(env = process.env) {
|
|
@@ -5821,13 +5761,16 @@ function collectDeviceLeases(now) {
|
|
|
5821
5761
|
kept
|
|
5822
5762
|
};
|
|
5823
5763
|
}
|
|
5824
|
-
async function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords) {
|
|
5764
|
+
async function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords, output = GC_OUTPUT, operation) {
|
|
5825
5765
|
let deleteFailures = 0;
|
|
5826
5766
|
async function reap(d, onlyIfMissing = false) {
|
|
5767
|
+
operation?.signal.throwIfAborted();
|
|
5827
5768
|
const r = d.kind === "ios" ? await teardownOwnedIosSim(d.id, {
|
|
5828
5769
|
del: true,
|
|
5829
|
-
label: d.name
|
|
5770
|
+
label: d.name,
|
|
5771
|
+
operation
|
|
5830
5772
|
}) : await teardownOwnedAvd(d.name, {
|
|
5773
|
+
operation,
|
|
5831
5774
|
del: true,
|
|
5832
5775
|
onlyIfMissing,
|
|
5833
5776
|
..."project" in d ? { owner: {
|
|
@@ -5835,7 +5778,7 @@ async function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRe
|
|
|
5835
5778
|
slot: d.slot
|
|
5836
5779
|
} } : {},
|
|
5837
5780
|
..."project" in d ? { onRemoved: () => {
|
|
5838
|
-
if (clearDevice(d.project, d.kind, d.slot, d.id))
|
|
5781
|
+
if (clearDevice(d.project, d.kind, d.slot, d.id)) output.log(chalk.dim(` cleared the ${d.kind} record for ${d.project}`));
|
|
5839
5782
|
} } : {},
|
|
5840
5783
|
..."orphanedDirectory" in d ? { orphanedDirectory: d.orphanedDirectory } : {}
|
|
5841
5784
|
});
|
|
@@ -5845,19 +5788,19 @@ async function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRe
|
|
|
5845
5788
|
bytes: d.bytes ?? null
|
|
5846
5789
|
};
|
|
5847
5790
|
if (r.status === "torn-down") {
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
} else if (r.status === "missing")
|
|
5791
|
+
output.log(chalk.green(`Deleted ${what}`));
|
|
5792
|
+
output.record("device", "done", d.name, result);
|
|
5793
|
+
} else if (r.status === "missing") output.log(chalk.dim(`${what} is already gone; nothing to delete.`));
|
|
5851
5794
|
else if (r.status === "skipped") {
|
|
5852
|
-
|
|
5853
|
-
|
|
5795
|
+
output.log(chalk.yellow(`Skipped ${what}: ${r.reason} -- left for a later gc`));
|
|
5796
|
+
output.record("device", "kept", d.name, {
|
|
5854
5797
|
...result,
|
|
5855
5798
|
detail: `${r.reason}; left for a later gc`
|
|
5856
5799
|
});
|
|
5857
5800
|
} else {
|
|
5858
5801
|
deleteFailures++;
|
|
5859
|
-
|
|
5860
|
-
|
|
5802
|
+
output.log(chalk.red(`Failed to delete ${d.kind} device ${d.name}: ${r.reason}`));
|
|
5803
|
+
output.record("device", "failed", d.name, {
|
|
5861
5804
|
...result,
|
|
5862
5805
|
detail: r.reason
|
|
5863
5806
|
});
|
|
@@ -5869,20 +5812,24 @@ async function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRe
|
|
|
5869
5812
|
const status = await reap(d);
|
|
5870
5813
|
if (d.kind === "ios" && (status === "torn-down" || status === "missing")) {
|
|
5871
5814
|
clearDevice(d.project, d.kind, d.slot, d.id);
|
|
5872
|
-
|
|
5815
|
+
output.log(chalk.dim(` cleared the ${d.kind} record for ${d.project}`));
|
|
5873
5816
|
}
|
|
5874
5817
|
}
|
|
5875
|
-
for (const r of staleDeviceRecords)
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5818
|
+
for (const r of staleDeviceRecords) {
|
|
5819
|
+
operation?.signal.throwIfAborted();
|
|
5820
|
+
if (r.kind === "android" && r.owned) await reap({
|
|
5821
|
+
...r,
|
|
5822
|
+
name: r.id,
|
|
5823
|
+
idleDays: 0
|
|
5824
|
+
}, true);
|
|
5825
|
+
else if (await withConfigLockAsync$1(() => {
|
|
5826
|
+
operation?.signal.throwIfAborted();
|
|
5827
|
+
if (r.kind === "android" && deviceSlotPlatforms(getProject(r.project), r.slot)?.android?.owned) return false;
|
|
5828
|
+
return clearDevice(r.project, r.kind, r.slot, r.id);
|
|
5829
|
+
}, operation?.signal)) {
|
|
5830
|
+
output.log(chalk.green(`Cleared the ${r.kind} record for ${r.project} (${r.id} is not on this machine)`));
|
|
5831
|
+
output.record("deviceRecord", "done", r.project, { id: r.id });
|
|
5832
|
+
}
|
|
5886
5833
|
}
|
|
5887
5834
|
return deleteFailures;
|
|
5888
5835
|
}
|
|
@@ -6094,14 +6041,16 @@ function listAvdRecords(notices, env) {
|
|
|
6094
6041
|
}
|
|
6095
6042
|
return [...records.values()];
|
|
6096
6043
|
}
|
|
6097
|
-
function simctl(args, env) {
|
|
6098
|
-
return getExecutor()
|
|
6044
|
+
function simctl(args, env, operation) {
|
|
6045
|
+
return runOperationFile(operation, getExecutor(), "xcrun", ["simctl", ...args], {
|
|
6099
6046
|
timeoutMs: DEVICE_LIST_TIMEOUT_MS$1,
|
|
6100
6047
|
killSignal: "SIGKILL",
|
|
6101
|
-
env
|
|
6048
|
+
env,
|
|
6049
|
+
cwd: operation?.cwd
|
|
6102
6050
|
});
|
|
6103
6051
|
}
|
|
6104
|
-
async function collectInventory(config, deadProjects, env = process.env) {
|
|
6052
|
+
async function collectInventory(config, deadProjects, env = process.env, operation) {
|
|
6053
|
+
operation?.signal.throwIfAborted();
|
|
6105
6054
|
const childEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
|
|
6106
6055
|
const notices = [];
|
|
6107
6056
|
let sims = [];
|
|
@@ -6112,8 +6061,10 @@ async function collectInventory(config, deadProjects, env = process.env) {
|
|
|
6112
6061
|
"list",
|
|
6113
6062
|
"devices",
|
|
6114
6063
|
"--json"
|
|
6115
|
-
], childEnv));
|
|
6064
|
+
], childEnv, operation));
|
|
6116
6065
|
} catch (error) {
|
|
6066
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6067
|
+
operation?.signal.throwIfAborted();
|
|
6117
6068
|
notices.push(`simulators not listed: ${describeError(error)}`);
|
|
6118
6069
|
}
|
|
6119
6070
|
try {
|
|
@@ -6121,8 +6072,10 @@ async function collectInventory(config, deadProjects, env = process.env) {
|
|
|
6121
6072
|
"runtime",
|
|
6122
6073
|
"list",
|
|
6123
6074
|
"-j"
|
|
6124
|
-
], childEnv));
|
|
6075
|
+
], childEnv, operation));
|
|
6125
6076
|
} catch (error) {
|
|
6077
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6078
|
+
operation?.signal.throwIfAborted();
|
|
6126
6079
|
notices.push(`simulator runtime images not listed: ${describeError(error)}`);
|
|
6127
6080
|
}
|
|
6128
6081
|
try {
|
|
@@ -6130,8 +6083,10 @@ async function collectInventory(config, deadProjects, env = process.env) {
|
|
|
6130
6083
|
"list",
|
|
6131
6084
|
"runtimes",
|
|
6132
6085
|
"-j"
|
|
6133
|
-
], childEnv), runtimes));
|
|
6086
|
+
], childEnv, operation), runtimes));
|
|
6134
6087
|
} catch (error) {
|
|
6088
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6089
|
+
operation?.signal.throwIfAborted();
|
|
6135
6090
|
notices.push(`simulator runtimes not listed: ${describeError(error)}`);
|
|
6136
6091
|
}
|
|
6137
6092
|
}
|
|
@@ -6225,29 +6180,37 @@ function readWrapper(path) {
|
|
|
6225
6180
|
return missing$1(error) ? null : "unreadable";
|
|
6226
6181
|
}
|
|
6227
6182
|
}
|
|
6228
|
-
async function projectWrapperRoots(project) {
|
|
6183
|
+
async function projectWrapperRoots(project, operation) {
|
|
6229
6184
|
try {
|
|
6230
|
-
const { context, settings } = await resolveProjectSettingsAsync(project
|
|
6185
|
+
const { context, settings } = await resolveProjectSettingsAsync(project, {
|
|
6186
|
+
env: operation?.env,
|
|
6187
|
+
signal: operation?.signal,
|
|
6188
|
+
declareFor: operation
|
|
6189
|
+
});
|
|
6231
6190
|
return {
|
|
6232
6191
|
roots: [project, androidLayoutSetting(settings, project, context.repoRoot ?? project).gradleRoot],
|
|
6233
6192
|
resolved: true
|
|
6234
6193
|
};
|
|
6235
|
-
} catch {
|
|
6194
|
+
} catch (error) {
|
|
6195
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6196
|
+
operation?.signal.throwIfAborted();
|
|
6236
6197
|
return {
|
|
6237
6198
|
roots: [project],
|
|
6238
6199
|
resolved: false
|
|
6239
6200
|
};
|
|
6240
6201
|
}
|
|
6241
6202
|
}
|
|
6242
|
-
async function projectPins(project) {
|
|
6203
|
+
async function projectPins(project, operation) {
|
|
6204
|
+
operation?.signal.throwIfAborted();
|
|
6243
6205
|
if (!isDirectory(project)) return {
|
|
6244
6206
|
versions: [],
|
|
6245
6207
|
unreadable: true
|
|
6246
6208
|
};
|
|
6247
|
-
const { roots, resolved } = await projectWrapperRoots(project);
|
|
6209
|
+
const { roots, resolved } = await projectWrapperRoots(project, operation);
|
|
6248
6210
|
let unreadable = !resolved;
|
|
6249
6211
|
const versions = [];
|
|
6250
6212
|
for (const root of roots) {
|
|
6213
|
+
operation?.signal.throwIfAborted();
|
|
6251
6214
|
const text = readWrapper(join(root, "gradle", "wrapper", "gradle-wrapper.properties"));
|
|
6252
6215
|
if (text === null) continue;
|
|
6253
6216
|
const version = text === "unreadable" ? null : parseWrapperVersion(text);
|
|
@@ -6260,13 +6223,16 @@ async function projectPins(project) {
|
|
|
6260
6223
|
};
|
|
6261
6224
|
}
|
|
6262
6225
|
/** The Gradle versions registered projects pin in their wrapper, and the projects whose pin cannot be read. */
|
|
6263
|
-
async function projectGradleVersions(projects) {
|
|
6226
|
+
async function projectGradleVersions(projects, operation) {
|
|
6264
6227
|
const used = /* @__PURE__ */ new Map();
|
|
6265
6228
|
const unreadable = [];
|
|
6266
|
-
const
|
|
6229
|
+
const read = async (project) => ({
|
|
6267
6230
|
project,
|
|
6268
|
-
...await projectPins(project)
|
|
6269
|
-
})
|
|
6231
|
+
...await projectPins(project, operation)
|
|
6232
|
+
});
|
|
6233
|
+
const pins = [];
|
|
6234
|
+
if (operation) for (const project of projects) pins.push(await read(project));
|
|
6235
|
+
else pins.push(...await Promise.all(projects.map(read)));
|
|
6270
6236
|
for (const { project, versions, unreadable: partial } of pins) {
|
|
6271
6237
|
if (partial) unreadable.push(project);
|
|
6272
6238
|
for (const version of new Set(versions)) used.set(version, [...used.get(version) ?? [], project]);
|
|
@@ -6293,40 +6259,61 @@ function parseSimRuntimes(runtimeList, deviceList) {
|
|
|
6293
6259
|
function parseXcodeBuildVersion(output) {
|
|
6294
6260
|
return output ? /^Build version (\S+)/m.exec(output)?.[1] ?? null : null;
|
|
6295
6261
|
}
|
|
6296
|
-
async function quiet(file, args) {
|
|
6262
|
+
async function quiet(file, args, operation) {
|
|
6297
6263
|
try {
|
|
6298
|
-
return await getExecutor()
|
|
6264
|
+
return await runOperationFile(operation, getExecutor(), file, args, {
|
|
6299
6265
|
timeoutMs: TOOL_TIMEOUT_MS,
|
|
6300
|
-
killSignal: "SIGKILL"
|
|
6266
|
+
killSignal: "SIGKILL",
|
|
6267
|
+
cwd: operation?.cwd,
|
|
6268
|
+
signal: operation?.signal
|
|
6301
6269
|
});
|
|
6302
|
-
} catch {
|
|
6270
|
+
} catch (error) {
|
|
6271
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6272
|
+
operation?.signal.throwIfAborted();
|
|
6303
6273
|
return null;
|
|
6304
6274
|
}
|
|
6305
6275
|
}
|
|
6306
|
-
async function measure(dirs) {
|
|
6276
|
+
async function measure(dirs, operation) {
|
|
6307
6277
|
const darwin = process.platform === "darwin";
|
|
6308
|
-
const
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6278
|
+
const size = async (dir) => {
|
|
6279
|
+
const result = await measureDu(["-sk", dir], operation);
|
|
6280
|
+
return {
|
|
6281
|
+
dir,
|
|
6282
|
+
bytes: result.complete ? result.sizes.get(dir) ?? null : null
|
|
6283
|
+
};
|
|
6284
|
+
};
|
|
6285
|
+
const measureSizes = async () => {
|
|
6286
|
+
if (!operation) return Promise.all(dirs.map(size));
|
|
6287
|
+
const sizes = [];
|
|
6288
|
+
for (const dir of dirs) sizes.push(await size(dir));
|
|
6289
|
+
return sizes;
|
|
6290
|
+
};
|
|
6291
|
+
const runtimes = () => darwin ? quiet("xcrun", [
|
|
6292
|
+
"simctl",
|
|
6293
|
+
"runtime",
|
|
6294
|
+
"list",
|
|
6295
|
+
"-j"
|
|
6296
|
+
], operation) : null;
|
|
6297
|
+
const devices = () => darwin ? quiet("xcrun", [
|
|
6298
|
+
"simctl",
|
|
6299
|
+
"list",
|
|
6300
|
+
"devices",
|
|
6301
|
+
"--json"
|
|
6302
|
+
], operation) : null;
|
|
6303
|
+
const agent = () => quiet("agent-device", ["--version"], operation);
|
|
6304
|
+
const version = () => darwin ? quiet("xcodebuild", ["-version"], operation) : null;
|
|
6305
|
+
const [sizes, runtimeList, deviceList, agentDevice, xcode] = operation ? [
|
|
6306
|
+
await measureSizes(),
|
|
6307
|
+
await runtimes(),
|
|
6308
|
+
await devices(),
|
|
6309
|
+
await agent(),
|
|
6310
|
+
await version()
|
|
6311
|
+
] : await Promise.all([
|
|
6312
|
+
measureSizes(),
|
|
6313
|
+
runtimes(),
|
|
6314
|
+
devices(),
|
|
6315
|
+
agent(),
|
|
6316
|
+
version()
|
|
6330
6317
|
]);
|
|
6331
6318
|
return {
|
|
6332
6319
|
version: 1,
|
|
@@ -6426,7 +6413,7 @@ function assembleMachineCacheReport(usage, discovery, gradleUse, agentDeviceUsag
|
|
|
6426
6413
|
}
|
|
6427
6414
|
async function getMachineCacheReport(agentDeviceUsage, { maxAgeMs = MAX_AGE_MS, operation } = {}) {
|
|
6428
6415
|
const discovery = discover(operation?.env, operation?.cwd);
|
|
6429
|
-
const gradleUse = await projectGradleVersions(Object.keys(loadConfig$1()?.projects ?? {}));
|
|
6416
|
+
const gradleUse = await projectGradleVersions(Object.keys(loadConfig$1()?.projects ?? {}), operation);
|
|
6430
6417
|
const dirs = [
|
|
6431
6418
|
...[...discovery.versionDirs].filter(([version]) => !gradleUse.used.has(version)).flatMap(([, list]) => list),
|
|
6432
6419
|
...discovery.modulesDir ? [discovery.modulesDir] : [],
|
|
@@ -6436,7 +6423,7 @@ async function getMachineCacheReport(agentDeviceUsage, { maxAgeMs = MAX_AGE_MS,
|
|
|
6436
6423
|
const age = cached ? Date.now() - Date.parse(cached.measuredAt) : Infinity;
|
|
6437
6424
|
let usage = cached;
|
|
6438
6425
|
if (!usage || age < 0 || age >= maxAgeMs || JSON.stringify(usage.roots) !== JSON.stringify(dirs)) {
|
|
6439
|
-
usage = await measure(dirs);
|
|
6426
|
+
usage = await measure(dirs, operation);
|
|
6440
6427
|
writeUsageCache(machineCacheUsageFile(), usage, operation?.signal);
|
|
6441
6428
|
}
|
|
6442
6429
|
return assembleMachineCacheReport(usage, discovery, gradleUse, agentDeviceUsage, operation?.env);
|
|
@@ -6554,15 +6541,24 @@ function mayUseHome(command, homes) {
|
|
|
6554
6541
|
return real !== null && !homes.includes(real);
|
|
6555
6542
|
}));
|
|
6556
6543
|
}
|
|
6557
|
-
async function gradleProcesses(home, env = process.env, cwd = process.cwd()) {
|
|
6544
|
+
async function gradleProcesses(home, env = process.env, cwd = process.cwd(), signal, operation) {
|
|
6545
|
+
signal?.throwIfAborted();
|
|
6558
6546
|
let table;
|
|
6559
6547
|
try {
|
|
6560
|
-
table = await getExecutor()
|
|
6548
|
+
table = await runOperationFile(operation, getExecutor(), "ps", [
|
|
6561
6549
|
"-axww",
|
|
6562
6550
|
"-o",
|
|
6563
6551
|
"pid=,command="
|
|
6564
|
-
], {
|
|
6552
|
+
], {
|
|
6553
|
+
timeoutMs: PS_TIMEOUT_MS,
|
|
6554
|
+
cwd,
|
|
6555
|
+
...childEnvironment(env),
|
|
6556
|
+
signal
|
|
6557
|
+
});
|
|
6558
|
+
signal?.throwIfAborted();
|
|
6565
6559
|
} catch (error) {
|
|
6560
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6561
|
+
signal?.throwIfAborted();
|
|
6566
6562
|
return describeError(error);
|
|
6567
6563
|
}
|
|
6568
6564
|
const homes = [.../* @__PURE__ */ new Set([home, gradleUserHomeSetting(env, cwd)])];
|
|
@@ -6576,8 +6572,8 @@ function insideConfigDir(dir) {
|
|
|
6576
6572
|
const rel = relative(realpathOrNull(config) ?? config, dir);
|
|
6577
6573
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
6578
6574
|
}
|
|
6579
|
-
async function liveBlock(home, { pastRunning = false, env = process.env, cwd = process.cwd() } = {}) {
|
|
6580
|
-
const processes = await gradleProcesses(home, env, cwd);
|
|
6575
|
+
async function liveBlock(home, { pastRunning = false, env = process.env, cwd = process.cwd(), operation } = {}) {
|
|
6576
|
+
const processes = await gradleProcesses(home, env, cwd, operation?.signal, operation);
|
|
6581
6577
|
const config = loadConfig$1();
|
|
6582
6578
|
if (config === null) return {
|
|
6583
6579
|
blocked: {
|
|
@@ -6588,7 +6584,8 @@ async function liveBlock(home, { pastRunning = false, env = process.env, cwd = p
|
|
|
6588
6584
|
running: null,
|
|
6589
6585
|
runningPids: []
|
|
6590
6586
|
};
|
|
6591
|
-
const { used, unreadable } = await projectGradleVersions(Object.keys(config.projects ?? {}));
|
|
6587
|
+
const { used, unreadable } = await projectGradleVersions(Object.keys(config.projects ?? {}), operation);
|
|
6588
|
+
operation?.signal.throwIfAborted();
|
|
6592
6589
|
const pinned = new Set(used.keys());
|
|
6593
6590
|
if (typeof processes === "string") return {
|
|
6594
6591
|
blocked: {
|
|
@@ -6635,7 +6632,8 @@ function stagedDirs(home) {
|
|
|
6635
6632
|
}
|
|
6636
6633
|
});
|
|
6637
6634
|
}
|
|
6638
|
-
async function sweepState(pastRunning = false, env = process.env, cwd = process.cwd()) {
|
|
6635
|
+
async function sweepState(pastRunning = false, env = process.env, cwd = process.cwd(), operation) {
|
|
6636
|
+
operation?.signal.throwIfAborted();
|
|
6639
6637
|
const home = gradleUserHome(env, cwd);
|
|
6640
6638
|
const empty = {
|
|
6641
6639
|
home,
|
|
@@ -6679,7 +6677,8 @@ async function sweepState(pastRunning = false, env = process.env, cwd = process.
|
|
|
6679
6677
|
...await liveBlock(home, {
|
|
6680
6678
|
pastRunning,
|
|
6681
6679
|
env,
|
|
6682
|
-
cwd
|
|
6680
|
+
cwd,
|
|
6681
|
+
operation
|
|
6683
6682
|
})
|
|
6684
6683
|
};
|
|
6685
6684
|
}
|
|
@@ -6713,12 +6712,15 @@ function judgeVersion(version, dirs, state, olderThanDays, now, maxEntries = MAX
|
|
|
6713
6712
|
detail: null
|
|
6714
6713
|
};
|
|
6715
6714
|
}
|
|
6716
|
-
async function directoryBytes(dirs) {
|
|
6717
|
-
const
|
|
6718
|
-
const result = await measureDu(["-sk", dir]);
|
|
6715
|
+
async function directoryBytes(dirs, operation) {
|
|
6716
|
+
const size = async (dir) => {
|
|
6717
|
+
const result = await measureDu(["-sk", dir], operation);
|
|
6719
6718
|
return result.complete ? result.sizes.get(dir) ?? null : null;
|
|
6720
|
-
}
|
|
6721
|
-
|
|
6719
|
+
};
|
|
6720
|
+
const sizes = [];
|
|
6721
|
+
if (operation) for (const dir of dirs) sizes.push(await size(dir));
|
|
6722
|
+
else sizes.push(...await Promise.all(dirs.map(size)));
|
|
6723
|
+
return sizes.includes(null) ? null : sizes.reduce((sum, bytes) => sum + (bytes ?? 0), 0);
|
|
6722
6724
|
}
|
|
6723
6725
|
/**
|
|
6724
6726
|
* The Gradle versions under GRADLE_USER_HOME that no registered project pins, each with whether `--delete` removes
|
|
@@ -6726,8 +6728,8 @@ async function directoryBytes(dirs) {
|
|
|
6726
6728
|
* removal left. Shared caches such as `modules-2` are never candidates.
|
|
6727
6729
|
*/
|
|
6728
6730
|
async function collectGradleVersions({ olderThanDays = null, now = Date.now(), measure = true, maxWalkEntries = MAX_WALK_ENTRIES, pastRunningGradle = false, operation } = {}) {
|
|
6729
|
-
const state = await sweepState(pastRunningGradle, operation?.env ?? process.env, operation?.cwd);
|
|
6730
|
-
const
|
|
6731
|
+
const state = await sweepState(pastRunningGradle, operation?.env ?? process.env, operation?.cwd, operation);
|
|
6732
|
+
const read = async ([version, dirs]) => {
|
|
6731
6733
|
let tree;
|
|
6732
6734
|
if (operation && !state.blocked && dirs.every((dir) => plainChild(dir, state.home))) {
|
|
6733
6735
|
tree = {
|
|
@@ -6737,8 +6739,12 @@ async function collectGradleVersions({ olderThanDays = null, now = Date.now(), m
|
|
|
6737
6739
|
};
|
|
6738
6740
|
for (const dir of dirs) await walkAsync(dir, tree, maxWalkEntries, operation.signal);
|
|
6739
6741
|
}
|
|
6740
|
-
return Object.assign(judgeVersion(version, dirs, state, olderThanDays, now, maxWalkEntries, tree), { bytes: measure ? await directoryBytes(dirs) : null });
|
|
6741
|
-
}
|
|
6742
|
+
return Object.assign(judgeVersion(version, dirs, state, olderThanDays, now, maxWalkEntries, tree), { bytes: measure ? await directoryBytes(dirs, operation) : null });
|
|
6743
|
+
};
|
|
6744
|
+
const entries = [...state.versionDirs].filter(([version]) => !state.pinned.has(version));
|
|
6745
|
+
const versions = [];
|
|
6746
|
+
if (operation) for (const entry of entries) versions.push(await read(entry));
|
|
6747
|
+
else versions.push(...await Promise.all(entries.map(read)));
|
|
6742
6748
|
return {
|
|
6743
6749
|
home: state.home,
|
|
6744
6750
|
olderThanDays,
|
|
@@ -6769,19 +6775,32 @@ function stage(dir) {
|
|
|
6769
6775
|
* Checks one version again and removes its directories when it is still unpinned and unused: each directory is
|
|
6770
6776
|
* renamed to a `.stim-delete-*` sibling right after the last process and pin check, then deleted.
|
|
6771
6777
|
*/
|
|
6772
|
-
async function deleteGradleVersion(version, { olderThanDays = null, now = Date.now() } = {}) {
|
|
6773
|
-
const state = await sweepState();
|
|
6778
|
+
async function deleteGradleVersion(version, { olderThanDays = null, now = Date.now(), operation } = {}) {
|
|
6779
|
+
const state = await sweepState(false, operation?.env, operation?.cwd, operation);
|
|
6774
6780
|
const dirs = state.versionDirs.get(version) ?? [];
|
|
6775
6781
|
if (dirs.length === 0 && !state.blocked) return {
|
|
6776
6782
|
deleted: false,
|
|
6777
6783
|
detail: "its directories are already gone"
|
|
6778
6784
|
};
|
|
6779
|
-
|
|
6785
|
+
let tree;
|
|
6786
|
+
if (operation && !state.blocked && dirs.every((dir) => plainChild(dir, state.home))) {
|
|
6787
|
+
tree = {
|
|
6788
|
+
newestMs: 0,
|
|
6789
|
+
entries: 0,
|
|
6790
|
+
error: null
|
|
6791
|
+
};
|
|
6792
|
+
for (const dir of dirs) await walkAsync(dir, tree, MAX_WALK_ENTRIES, operation.signal);
|
|
6793
|
+
}
|
|
6794
|
+
const judged = judgeVersion(version, dirs, state, olderThanDays, now, MAX_WALK_ENTRIES, tree);
|
|
6780
6795
|
if (!judged.willDelete) return {
|
|
6781
6796
|
deleted: false,
|
|
6782
6797
|
detail: judged.detail ?? "it is no longer eligible"
|
|
6783
6798
|
};
|
|
6784
|
-
const live = await liveBlock(state.home
|
|
6799
|
+
const live = await liveBlock(state.home, {
|
|
6800
|
+
env: operation?.env,
|
|
6801
|
+
cwd: operation?.cwd,
|
|
6802
|
+
operation
|
|
6803
|
+
});
|
|
6785
6804
|
if (live.blocked) return {
|
|
6786
6805
|
deleted: false,
|
|
6787
6806
|
detail: live.blocked.detail
|
|
@@ -6794,56 +6813,71 @@ async function deleteGradleVersion(version, { olderThanDays = null, now = Date.n
|
|
|
6794
6813
|
deleted: false,
|
|
6795
6814
|
detail: `one of its paths is no longer a plain directory inside ${state.home}`
|
|
6796
6815
|
};
|
|
6816
|
+
operation?.signal.throwIfAborted();
|
|
6797
6817
|
const staged = dirs.map(stage);
|
|
6798
|
-
for (const dir of staged)
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6818
|
+
for (const dir of staged) {
|
|
6819
|
+
operation?.signal.throwIfAborted();
|
|
6820
|
+
await rm(dir, {
|
|
6821
|
+
recursive: true,
|
|
6822
|
+
force: true
|
|
6823
|
+
});
|
|
6824
|
+
}
|
|
6802
6825
|
return {
|
|
6803
6826
|
deleted: true,
|
|
6804
6827
|
detail: dirs.join(", ")
|
|
6805
6828
|
};
|
|
6806
6829
|
}
|
|
6807
|
-
function deleteLeftovers(sweep) {
|
|
6830
|
+
async function deleteLeftovers(sweep, output, operation) {
|
|
6808
6831
|
let failures = 0;
|
|
6809
|
-
for (const dir of sweep.leftovers)
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6832
|
+
for (const dir of sweep.leftovers) {
|
|
6833
|
+
operation?.signal.throwIfAborted();
|
|
6834
|
+
try {
|
|
6835
|
+
await rm(dir, {
|
|
6836
|
+
recursive: true,
|
|
6837
|
+
force: true
|
|
6838
|
+
});
|
|
6839
|
+
output.log(chalk.green(`Finished deleting ${dir}`));
|
|
6840
|
+
output.record("gradleVersion", "done", basename(dir), { id: dir });
|
|
6841
|
+
} catch (error) {
|
|
6842
|
+
operation?.signal.throwIfAborted();
|
|
6843
|
+
failures += 1;
|
|
6844
|
+
output.log(chalk.red(`Failed to finish deleting ${dir}: ${describeError(error)}`));
|
|
6845
|
+
output.record("gradleVersion", "failed", basename(dir), {
|
|
6846
|
+
id: dir,
|
|
6847
|
+
detail: describeError(error)
|
|
6848
|
+
});
|
|
6849
|
+
}
|
|
6823
6850
|
}
|
|
6824
6851
|
return failures;
|
|
6825
6852
|
}
|
|
6826
|
-
async function deleteGradleVersions(sweep) {
|
|
6853
|
+
async function deleteGradleVersions(sweep, output = GC_OUTPUT, operation) {
|
|
6854
|
+
operation?.signal.throwIfAborted();
|
|
6827
6855
|
if (!sweep) return 0;
|
|
6828
|
-
let failures = deleteLeftovers(sweep);
|
|
6856
|
+
let failures = await deleteLeftovers(sweep, output, operation);
|
|
6829
6857
|
for (const entry of sweep.versions) {
|
|
6858
|
+
operation?.signal.throwIfAborted();
|
|
6830
6859
|
if (!entry.willDelete) continue;
|
|
6831
6860
|
try {
|
|
6832
|
-
const result = await deleteGradleVersion(entry.version, {
|
|
6861
|
+
const result = await deleteGradleVersion(entry.version, {
|
|
6862
|
+
olderThanDays: sweep.olderThanDays,
|
|
6863
|
+
operation
|
|
6864
|
+
});
|
|
6833
6865
|
if (result.deleted) {
|
|
6834
|
-
|
|
6835
|
-
|
|
6866
|
+
output.log(chalk.green(`Deleted Gradle ${entry.version}: ${result.detail}`));
|
|
6867
|
+
output.record("gradleVersion", "done", `Gradle ${entry.version}`, {
|
|
6836
6868
|
bytes: entry.bytes,
|
|
6837
6869
|
id: entry.dirs[0]
|
|
6838
6870
|
});
|
|
6839
6871
|
} else {
|
|
6840
|
-
|
|
6841
|
-
|
|
6872
|
+
output.log(chalk.yellow(`Kept Gradle ${entry.version}: ${result.detail}`));
|
|
6873
|
+
output.record("gradleVersion", "kept", `Gradle ${entry.version}`, { detail: result.detail });
|
|
6842
6874
|
}
|
|
6843
6875
|
} catch (error) {
|
|
6876
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
6877
|
+
operation?.signal.throwIfAborted();
|
|
6844
6878
|
failures += 1;
|
|
6845
|
-
|
|
6846
|
-
|
|
6879
|
+
output.log(chalk.red(`Failed to delete Gradle ${entry.version}: ${describeError(error)}`));
|
|
6880
|
+
output.record("gradleVersion", "failed", `Gradle ${entry.version}`, { detail: describeError(error) });
|
|
6847
6881
|
}
|
|
6848
6882
|
}
|
|
6849
6883
|
return failures;
|
|
@@ -6950,15 +6984,17 @@ function previewScopedEmptying(caches, env = process.env) {
|
|
|
6950
6984
|
function ownsItsDirectory(cache) {
|
|
6951
6985
|
return !Array.isArray(cache.files) && !cacheDirRefusal(cache.dir);
|
|
6952
6986
|
}
|
|
6953
|
-
function emptyCache(cache) {
|
|
6987
|
+
async function emptyCache(cache, signal, operation) {
|
|
6954
6988
|
if (cache.prune === "report-only") return {
|
|
6955
6989
|
removed: 0,
|
|
6956
6990
|
bytes: 0,
|
|
6957
6991
|
skipped: "report-only shared cache; Stim never deletes it"
|
|
6958
6992
|
};
|
|
6959
|
-
if (cache.prune !== "atomic") return
|
|
6993
|
+
if (cache.prune !== "atomic") return pruneCacheAsync(cache, {
|
|
6960
6994
|
olderThanDays: 0,
|
|
6961
|
-
now: Date.now() + DAY_MS
|
|
6995
|
+
now: Date.now() + DAY_MS,
|
|
6996
|
+
signal,
|
|
6997
|
+
operation
|
|
6962
6998
|
});
|
|
6963
6999
|
const claim = claimForEmptying(cache.dir);
|
|
6964
7000
|
if (typeof claim === "string") return {
|
|
@@ -6969,8 +7005,9 @@ function emptyCache(cache) {
|
|
|
6969
7005
|
try {
|
|
6970
7006
|
let names;
|
|
6971
7007
|
try {
|
|
6972
|
-
names =
|
|
7008
|
+
names = await readdir(cache.dir);
|
|
6973
7009
|
} catch (e) {
|
|
7010
|
+
signal?.throwIfAborted();
|
|
6974
7011
|
return {
|
|
6975
7012
|
removed: 0,
|
|
6976
7013
|
bytes: 0,
|
|
@@ -6979,15 +7016,19 @@ function emptyCache(cache) {
|
|
|
6979
7016
|
}
|
|
6980
7017
|
let removed = 0;
|
|
6981
7018
|
let failed = 0;
|
|
6982
|
-
for (const name of names)
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7019
|
+
for (const name of names) {
|
|
7020
|
+
signal?.throwIfAborted();
|
|
7021
|
+
try {
|
|
7022
|
+
await rm(join$1(cache.dir, name), {
|
|
7023
|
+
recursive: true,
|
|
7024
|
+
force: true
|
|
7025
|
+
});
|
|
7026
|
+
removed++;
|
|
7027
|
+
} catch {
|
|
7028
|
+
failed++;
|
|
7029
|
+
}
|
|
6990
7030
|
}
|
|
7031
|
+
signal?.throwIfAborted();
|
|
6991
7032
|
return {
|
|
6992
7033
|
removed,
|
|
6993
7034
|
bytes: failed ? 0 : cache.bytes ?? 0,
|
|
@@ -7009,12 +7050,13 @@ function reportFailures(cache, failed, { log, record, fail }) {
|
|
|
7009
7050
|
fail();
|
|
7010
7051
|
return true;
|
|
7011
7052
|
}
|
|
7012
|
-
function trimCaches(caches, olderThan, output = GC_OUTPUT) {
|
|
7053
|
+
async function trimCaches(caches, olderThan, output = GC_OUTPUT, signal, operation) {
|
|
7013
7054
|
const { log, record } = output;
|
|
7014
7055
|
let cacheBytes = 0;
|
|
7015
7056
|
let failures = 0;
|
|
7016
7057
|
const trimmed = [];
|
|
7017
7058
|
for (const c of caches) {
|
|
7059
|
+
signal?.throwIfAborted();
|
|
7018
7060
|
if (c.machineGlobal) {
|
|
7019
7061
|
log(chalk.yellow(`Left ${c.name} alone: ${c.machineGlobal}`));
|
|
7020
7062
|
record("cache", "kept", c.name, {
|
|
@@ -7023,7 +7065,11 @@ function trimCaches(caches, olderThan, output = GC_OUTPUT) {
|
|
|
7023
7065
|
});
|
|
7024
7066
|
continue;
|
|
7025
7067
|
}
|
|
7026
|
-
const r =
|
|
7068
|
+
const r = await pruneCacheAsync(c, {
|
|
7069
|
+
olderThanDays: olderThan,
|
|
7070
|
+
signal,
|
|
7071
|
+
operation
|
|
7072
|
+
});
|
|
7027
7073
|
if (r.skipped) {
|
|
7028
7074
|
log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
|
|
7029
7075
|
record("cache", "kept", c.name, {
|
|
@@ -7047,10 +7093,11 @@ function trimCaches(caches, olderThan, output = GC_OUTPUT) {
|
|
|
7047
7093
|
failures
|
|
7048
7094
|
};
|
|
7049
7095
|
}
|
|
7050
|
-
function emptyCaches(caches, output = GC_OUTPUT) {
|
|
7096
|
+
async function emptyCaches(caches, output = GC_OUTPUT, signal, operation) {
|
|
7051
7097
|
const { log, record } = output;
|
|
7052
7098
|
let cacheBytes = 0;
|
|
7053
7099
|
for (const c of caches) {
|
|
7100
|
+
signal?.throwIfAborted();
|
|
7054
7101
|
if (!c.willEmpty) {
|
|
7055
7102
|
log(chalk.yellow(`Left ${c.name} alone: ${c.emptySkipped}`));
|
|
7056
7103
|
record("cache", "kept", c.name, {
|
|
@@ -7059,7 +7106,7 @@ function emptyCaches(caches, output = GC_OUTPUT) {
|
|
|
7059
7106
|
});
|
|
7060
7107
|
continue;
|
|
7061
7108
|
}
|
|
7062
|
-
const r = emptyCache(c);
|
|
7109
|
+
const r = await emptyCache(c, signal, operation);
|
|
7063
7110
|
if (r.skipped) {
|
|
7064
7111
|
log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
|
|
7065
7112
|
record("cache", "kept", c.name, {
|
|
@@ -8285,17 +8332,24 @@ function collectIdleDevices(config, sims, deadProjects, now = Date.now(), env) {
|
|
|
8285
8332
|
buildInProgress: workspaceBuildInProgress
|
|
8286
8333
|
});
|
|
8287
8334
|
}
|
|
8288
|
-
async function deviceActivityInputsAsync(config, sims, now, env, deadProjects = []) {
|
|
8289
|
-
const resolve = ownedAvdSerialResolverAsync({
|
|
8335
|
+
async function deviceActivityInputsAsync(config, sims, now, env, deadProjects = [], signal, operation) {
|
|
8336
|
+
const resolve = ownedAvdSerialResolverAsync({
|
|
8337
|
+
timeoutMs: 5e3,
|
|
8338
|
+
signal,
|
|
8339
|
+
operation
|
|
8340
|
+
});
|
|
8290
8341
|
const serials = /* @__PURE__ */ new Map();
|
|
8291
8342
|
for (const [project, record] of Object.entries(config?.projects ?? {})) {
|
|
8343
|
+
signal?.throwIfAborted();
|
|
8292
8344
|
if (deadProjects.includes(project)) continue;
|
|
8293
8345
|
for (const { platforms } of projectDeviceSlots(record)) {
|
|
8294
8346
|
const android = platforms.android;
|
|
8295
8347
|
if (!android?.owned || !android.avdName || serials.has(android.avdName)) continue;
|
|
8296
8348
|
try {
|
|
8297
8349
|
serials.set(android.avdName, (await resolve(android.avdName)).serial ?? null);
|
|
8298
|
-
} catch {
|
|
8350
|
+
} catch (error) {
|
|
8351
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
8352
|
+
signal?.throwIfAborted();
|
|
8299
8353
|
serials.set(android.avdName, null);
|
|
8300
8354
|
}
|
|
8301
8355
|
}
|
|
@@ -8305,18 +8359,20 @@ async function deviceActivityInputsAsync(config, sims, now, env, deadProjects =
|
|
|
8305
8359
|
sims,
|
|
8306
8360
|
readActivity: createActivityReaderAsync({
|
|
8307
8361
|
now,
|
|
8308
|
-
env
|
|
8362
|
+
env,
|
|
8363
|
+
signal,
|
|
8364
|
+
operation
|
|
8309
8365
|
}),
|
|
8310
8366
|
androidSerial: (name) => serials.get(name) ?? null
|
|
8311
8367
|
};
|
|
8312
8368
|
}
|
|
8313
|
-
async function collectOwnedDeviceActivityAsync(config, sims, now = Date.now(), env) {
|
|
8314
|
-
const devices = findOwnedDeviceActivity(await deviceActivityInputsAsync(config, sims, now, env));
|
|
8369
|
+
async function collectOwnedDeviceActivityAsync(config, sims, now = Date.now(), env, signal, operation) {
|
|
8370
|
+
const devices = findOwnedDeviceActivity(await deviceActivityInputsAsync(config, sims, now, env, [], signal, operation));
|
|
8315
8371
|
return Promise.all(devices.map(async (device) => Object.assign({}, device, { activity: await device.activity })));
|
|
8316
8372
|
}
|
|
8317
|
-
async function collectIdleDevicesAsync(config, sims, deadProjects, now = Date.now(), env) {
|
|
8373
|
+
async function collectIdleDevicesAsync(config, sims, deadProjects, now = Date.now(), env, signal, operation) {
|
|
8318
8374
|
const pending = findOwnedDeviceActivity({
|
|
8319
|
-
...await deviceActivityInputsAsync(config, sims, now, env, deadProjects),
|
|
8375
|
+
...await deviceActivityInputsAsync(config, sims, now, env, deadProjects, signal, operation),
|
|
8320
8376
|
deadProjects
|
|
8321
8377
|
});
|
|
8322
8378
|
return idleDevicesFromActivity(await Promise.all(pending.map(async (device) => Object.assign({}, device, { activity: await device.activity }))), workspaceBuildInProgress, now);
|
|
@@ -8330,7 +8386,8 @@ function idleDeviceLines(devices) {
|
|
|
8330
8386
|
return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
|
|
8331
8387
|
})];
|
|
8332
8388
|
}
|
|
8333
|
-
async function shutDownIdleDevices(devices, idleMs, recollect, { log, record } = GC_OUTPUT) {
|
|
8389
|
+
async function shutDownIdleDevices(devices, idleMs, recollect, { log, record } = GC_OUTPUT, signal, operation) {
|
|
8390
|
+
signal?.throwIfAborted();
|
|
8334
8391
|
const candidates = idleShutdownCandidates(devices, idleMs);
|
|
8335
8392
|
const shutDown = [];
|
|
8336
8393
|
if (candidates.length === 0) {
|
|
@@ -8341,8 +8398,10 @@ async function shutDownIdleDevices(devices, idleMs, recollect, { log, record } =
|
|
|
8341
8398
|
};
|
|
8342
8399
|
}
|
|
8343
8400
|
const fresh = idleShutdownCandidates(await recollect(), idleMs);
|
|
8401
|
+
signal?.throwIfAborted();
|
|
8344
8402
|
let failures = 0;
|
|
8345
8403
|
for (const device of candidates) {
|
|
8404
|
+
signal?.throwIfAborted();
|
|
8346
8405
|
const what = `${device.kind} ${device.name}`;
|
|
8347
8406
|
const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
|
|
8348
8407
|
if (!current) {
|
|
@@ -8353,10 +8412,16 @@ async function shutDownIdleDevices(devices, idleMs, recollect, { log, record } =
|
|
|
8353
8412
|
});
|
|
8354
8413
|
continue;
|
|
8355
8414
|
}
|
|
8356
|
-
const outcome = device.kind === "ios" ? await teardownOwnedIosSim(device.id, {
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
}
|
|
8415
|
+
const outcome = device.kind === "ios" ? await teardownOwnedIosSim(device.id, {
|
|
8416
|
+
label: device.name,
|
|
8417
|
+
operation
|
|
8418
|
+
}) : await teardownOwnedAvd(device.id, {
|
|
8419
|
+
owner: {
|
|
8420
|
+
projectPath: device.project,
|
|
8421
|
+
slot: device.slot
|
|
8422
|
+
},
|
|
8423
|
+
operation
|
|
8424
|
+
});
|
|
8360
8425
|
if (outcome.status === "torn-down") {
|
|
8361
8426
|
shutDown.push(device);
|
|
8362
8427
|
log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
|
|
@@ -8477,7 +8542,18 @@ function describeMetroLastStop(stop) {
|
|
|
8477
8542
|
}
|
|
8478
8543
|
//#endregion
|
|
8479
8544
|
//#region src/supervisor/cache-reset.ts
|
|
8480
|
-
async function stopOwnedMetro(root, requester) {
|
|
8545
|
+
async function stopOwnedMetro(root, requester, signal) {
|
|
8546
|
+
const resolve = async (port, probeSignal) => {
|
|
8547
|
+
throwIfCancelled(probeSignal, "Metro reset");
|
|
8548
|
+
const result = await resolveProjectMetro(port, root, {
|
|
8549
|
+
pidsOf: (candidate) => listeningPidsAsync(candidate, process.platform, probeSignal),
|
|
8550
|
+
cwdOf: async (pid) => (await processCwds([pid], probeSignal)).get(pid) ?? null,
|
|
8551
|
+
probe: (candidate) => isMetroRunning(candidate, probeSignal)
|
|
8552
|
+
});
|
|
8553
|
+
throwIfCancelled(probeSignal, "Metro reset");
|
|
8554
|
+
return result;
|
|
8555
|
+
};
|
|
8556
|
+
throwIfCancelled(signal, "Metro reset");
|
|
8481
8557
|
const project = getProject(root);
|
|
8482
8558
|
const port = project?.metroPort;
|
|
8483
8559
|
const target = resolveSupervisorTarget({
|
|
@@ -8489,7 +8565,7 @@ async function stopOwnedMetro(root, requester) {
|
|
|
8489
8565
|
status: "unverified",
|
|
8490
8566
|
reason: target.reason ?? "unknown identity"
|
|
8491
8567
|
};
|
|
8492
|
-
if (port && target.status !== "ours" && !(await
|
|
8568
|
+
if (port && target.status !== "ours" && !(await resolve(port, signal)).missing) return {
|
|
8493
8569
|
status: "external",
|
|
8494
8570
|
reason: `a dev server Stim did not start answers on port ${port}`
|
|
8495
8571
|
};
|
|
@@ -8498,23 +8574,24 @@ async function stopOwnedMetro(root, requester) {
|
|
|
8498
8574
|
pid: target.pid,
|
|
8499
8575
|
processToken: target.processToken
|
|
8500
8576
|
};
|
|
8501
|
-
requestDevServerStop(root, target.processToken, requester);
|
|
8502
|
-
const signalled =
|
|
8503
|
-
if (!signalled) withdrawDevServerStopRequest(root, target.processToken);
|
|
8577
|
+
await withWorkspaceStateLockAsync(root, () => requestDevServerStop(root, target.processToken, requester), signal);
|
|
8578
|
+
const signalled = await killMetroTreeAsync(target.pid, target.processToken);
|
|
8579
|
+
if (!signalled) await withWorkspaceStateLockAsync(root, () => withdrawDevServerStopRequest(root, target.processToken));
|
|
8504
8580
|
if (!signalled || !await (0, process_identity_exports.waitForProcessExit)(expected, 1e4)) return {
|
|
8505
8581
|
status: "stuck",
|
|
8506
8582
|
reason: `supervisor pid ${target.pid} did not exit`
|
|
8507
8583
|
};
|
|
8508
|
-
if (port && !(await
|
|
8584
|
+
if (port && !(await resolve(port)).missing) return {
|
|
8509
8585
|
status: "port-occupied",
|
|
8510
8586
|
reason: `port ${port} is still occupied after stopping Metro`
|
|
8511
8587
|
};
|
|
8512
|
-
clearWorkspaceSupervisor(root, expected);
|
|
8513
|
-
clearSupervisor(root, expected);
|
|
8588
|
+
await withWorkspaceStateLockAsync(root, () => clearWorkspaceSupervisor(root, expected));
|
|
8589
|
+
await withConfigLockAsync$1(() => clearSupervisor(root, expected));
|
|
8590
|
+
throwIfCancelled(signal, "Metro reset");
|
|
8514
8591
|
return { status: "stopped" };
|
|
8515
8592
|
}
|
|
8516
|
-
async function stopOwnedMetroForReset(root) {
|
|
8517
|
-
const stopped = await stopOwnedMetro(root, { by: "stim start --reset-cache" });
|
|
8593
|
+
async function stopOwnedMetroForReset(root, signal) {
|
|
8594
|
+
const stopped = await stopOwnedMetro(root, { by: "stim start --reset-cache" }, signal);
|
|
8518
8595
|
switch (stopped.status) {
|
|
8519
8596
|
case "unverified": throw supervisorError("STIM_SUPERVISOR_EXITED", `Cannot reset Metro: ${stopped.reason}.`, "Stop it with the tool that started it, then retry. Stim leaves unverified processes alone.");
|
|
8520
8597
|
case "external": throw supervisorError("STIM_SUPERVISOR_EXITED", "Cannot reset an externally started dev server.", "Stop it with the tool that started it, then retry `stim start --reset-cache`.");
|
|
@@ -8667,10 +8744,16 @@ function estimateMemoryUse({ config, sims, androidSerial, supervisorRunning }) {
|
|
|
8667
8744
|
liveWorkspaces: liveCount
|
|
8668
8745
|
};
|
|
8669
8746
|
}
|
|
8670
|
-
async function listSims() {
|
|
8747
|
+
async function listSims(signal, operation) {
|
|
8671
8748
|
try {
|
|
8672
|
-
return await listAllIosSimsAsync({
|
|
8673
|
-
|
|
8749
|
+
return await listAllIosSimsAsync({
|
|
8750
|
+
timeoutMs: DEVICE_LIST_TIMEOUT_MS,
|
|
8751
|
+
signal,
|
|
8752
|
+
operation
|
|
8753
|
+
});
|
|
8754
|
+
} catch (error) {
|
|
8755
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
8756
|
+
signal?.throwIfAborted();
|
|
8674
8757
|
return null;
|
|
8675
8758
|
}
|
|
8676
8759
|
}
|
|
@@ -8689,20 +8772,30 @@ function runningSupervisorPid(path, project) {
|
|
|
8689
8772
|
});
|
|
8690
8773
|
return target.status === "ours" ? target.pid ?? null : null;
|
|
8691
8774
|
}
|
|
8692
|
-
async function readMemoryUse(simSnapshot) {
|
|
8693
|
-
|
|
8775
|
+
async function readMemoryUse(simSnapshot, signal, operation) {
|
|
8776
|
+
signal?.throwIfAborted();
|
|
8777
|
+
const resolve = ownedAvdSerialResolverAsync({
|
|
8778
|
+
timeoutMs: 5e3,
|
|
8779
|
+
signal,
|
|
8780
|
+
operation
|
|
8781
|
+
});
|
|
8694
8782
|
const config = loadConfig$1();
|
|
8695
|
-
const sims = simSnapshot ? await snapshotSims(simSnapshot) : await listSims();
|
|
8783
|
+
const sims = simSnapshot && !signal ? await snapshotSims(simSnapshot) : await listSims(signal, operation);
|
|
8784
|
+
signal?.throwIfAborted();
|
|
8696
8785
|
const serials = /* @__PURE__ */ new Map();
|
|
8697
8786
|
for (const record of Object.values(config?.projects ?? {})) for (const { platforms } of projectDeviceSlots(record)) {
|
|
8787
|
+
signal?.throwIfAborted();
|
|
8698
8788
|
const android = platforms.android;
|
|
8699
8789
|
if (!android?.owned || !android.avdName || serials.has(android.avdName)) continue;
|
|
8700
8790
|
try {
|
|
8701
8791
|
serials.set(android.avdName, (await resolve(android.avdName)).serial ?? null);
|
|
8702
|
-
} catch {
|
|
8792
|
+
} catch (error) {
|
|
8793
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
8794
|
+
signal?.throwIfAborted();
|
|
8703
8795
|
serials.set(android.avdName, null);
|
|
8704
8796
|
}
|
|
8705
8797
|
}
|
|
8798
|
+
signal?.throwIfAborted();
|
|
8706
8799
|
return estimateMemoryUse({
|
|
8707
8800
|
config,
|
|
8708
8801
|
sims,
|
|
@@ -8734,7 +8827,7 @@ function isOtherWorkspace(root) {
|
|
|
8734
8827
|
function deviceLabel(device) {
|
|
8735
8828
|
return `${device.kind} ${device.name} in ${device.project}`;
|
|
8736
8829
|
}
|
|
8737
|
-
async function reclaimIdleDevices({ root, dryRun, note, now, env }) {
|
|
8830
|
+
async function reclaimIdleDevices({ root, dryRun, note, now, env, signal, operation }) {
|
|
8738
8831
|
if (deviceSweepIsScoped(env)) {
|
|
8739
8832
|
note(chalk.dim(phaseLine("budget", "idle devices left alone: STIM_HOME scopes this config, devices are global")));
|
|
8740
8833
|
return {
|
|
@@ -8743,13 +8836,18 @@ async function reclaimIdleDevices({ root, dryRun, note, now, env }) {
|
|
|
8743
8836
|
};
|
|
8744
8837
|
}
|
|
8745
8838
|
const other = isOtherWorkspace(root);
|
|
8746
|
-
const collect = async (at) =>
|
|
8839
|
+
const collect = async (at) => {
|
|
8840
|
+
signal?.throwIfAborted();
|
|
8841
|
+
const devices = await collectIdleDevicesAsync(loadConfig$1(), await listSims(signal, operation) ?? [], [], at, env, signal, operation);
|
|
8842
|
+
signal?.throwIfAborted();
|
|
8843
|
+
return idleShutdownCandidates(devices.filter((device) => other(device.project)), 0);
|
|
8844
|
+
};
|
|
8747
8845
|
const idle = await collect(now);
|
|
8748
8846
|
if (dryRun || idle.length === 0) return {
|
|
8749
8847
|
targets: idle.map(deviceLabel),
|
|
8750
8848
|
failures: 0
|
|
8751
8849
|
};
|
|
8752
|
-
const { failures, shutDown } = await shutDownIdleDevices(idle, 0, () => collect(Date.now()), stepOutput(note));
|
|
8850
|
+
const { failures, shutDown } = await shutDownIdleDevices(idle, 0, () => collect(Date.now()), stepOutput(note), signal, operation);
|
|
8753
8851
|
return {
|
|
8754
8852
|
targets: shutDown.map(deviceLabel),
|
|
8755
8853
|
failures
|
|
@@ -8760,7 +8858,7 @@ function idleSince(path, now) {
|
|
|
8760
8858
|
const at = Date.parse(activity.lastActivityAt ?? "");
|
|
8761
8859
|
return activity.state === "idle" && Number.isFinite(at) ? at : null;
|
|
8762
8860
|
}
|
|
8763
|
-
async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
8861
|
+
async function reclaimIdleDevServers({ root, dryRun, note, now, env, signal, operation }) {
|
|
8764
8862
|
const config = loadConfig$1();
|
|
8765
8863
|
const other = isOtherWorkspace(root);
|
|
8766
8864
|
const serving = Object.entries(config?.projects ?? {}).flatMap(([path, project]) => {
|
|
@@ -8774,7 +8872,8 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
|
8774
8872
|
targets: [],
|
|
8775
8873
|
failures: 0
|
|
8776
8874
|
};
|
|
8777
|
-
const sims = await listSims();
|
|
8875
|
+
const sims = await listSims(signal, operation);
|
|
8876
|
+
signal?.throwIfAborted();
|
|
8778
8877
|
if (sims === null) {
|
|
8779
8878
|
note(chalk.dim(phaseLine("budget", "dev servers left alone: simulators could not be listed to prove them idle")));
|
|
8780
8879
|
return {
|
|
@@ -8782,7 +8881,8 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
|
8782
8881
|
failures: 0
|
|
8783
8882
|
};
|
|
8784
8883
|
}
|
|
8785
|
-
const busy = new Set((await collectOwnedDeviceActivityAsync(config, sims, now, env)).filter((device) => device.activity.state !== "idle").map((device) => device.project));
|
|
8884
|
+
const busy = new Set((await collectOwnedDeviceActivityAsync(config, sims, now, env, signal, operation)).filter((device) => device.activity.state !== "idle").map((device) => device.project));
|
|
8885
|
+
signal?.throwIfAborted();
|
|
8786
8886
|
const idle = serving.filter(({ path }) => !busy.has(path));
|
|
8787
8887
|
if (dryRun) return {
|
|
8788
8888
|
targets: idle.map(({ path }) => path),
|
|
@@ -8795,6 +8895,7 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
|
8795
8895
|
return Boolean(project && runningSupervisorPid(path, project) === pid && !workspaceBuildInProgress(path) && idleSince(path, Date.now()) !== null);
|
|
8796
8896
|
};
|
|
8797
8897
|
for (const { path, pid } of idle) {
|
|
8898
|
+
signal?.throwIfAborted();
|
|
8798
8899
|
let run;
|
|
8799
8900
|
try {
|
|
8800
8901
|
run = await withIdleWorkspace(path, async () => {
|
|
@@ -8802,20 +8903,28 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
|
8802
8903
|
return await withWorkspaceProcessLock(workspaceDir(path), "metro-start", async () => stillIdle(path, pid) ? stopOwnedMetro(path, {
|
|
8803
8904
|
by: "budget reclaim",
|
|
8804
8905
|
workspace: root
|
|
8805
|
-
}) : "busy", {
|
|
8906
|
+
}, signal) : "busy", {
|
|
8806
8907
|
external: true,
|
|
8807
8908
|
waitMs: 0,
|
|
8808
|
-
ownerPurpose: "budget reclaim"
|
|
8909
|
+
ownerPurpose: "budget reclaim",
|
|
8910
|
+
signal,
|
|
8911
|
+
declareSpawnsFor: operation
|
|
8809
8912
|
});
|
|
8810
8913
|
} catch (error) {
|
|
8914
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
8915
|
+
signal?.throwIfAborted();
|
|
8811
8916
|
if (workspaceProcessLockError(error)) return "starting";
|
|
8812
8917
|
throw error;
|
|
8813
8918
|
}
|
|
8814
8919
|
}, {
|
|
8815
8920
|
purpose: "budget reclaim",
|
|
8816
|
-
supervisor: false
|
|
8921
|
+
supervisor: false,
|
|
8922
|
+
signal,
|
|
8923
|
+
operation
|
|
8817
8924
|
});
|
|
8818
8925
|
} catch (error) {
|
|
8926
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
8927
|
+
signal?.throwIfAborted();
|
|
8819
8928
|
failures++;
|
|
8820
8929
|
note(chalk.red(`Could not stop the dev server of ${path}: ${error?.message || error}`));
|
|
8821
8930
|
continue;
|
|
@@ -8841,12 +8950,14 @@ async function reclaimIdleDevServers({ root, dryRun, note, now, env }) {
|
|
|
8841
8950
|
failures
|
|
8842
8951
|
};
|
|
8843
8952
|
}
|
|
8844
|
-
async function reclaimWorkspaceOutputs({ root, dryRun, now, note, diskRecovered }) {
|
|
8953
|
+
async function reclaimWorkspaceOutputs({ root, dryRun, now, note, diskRecovered, signal, operation }) {
|
|
8845
8954
|
const other = isOtherWorkspace(root);
|
|
8846
|
-
const report = collectWorkspaceOutputs({
|
|
8955
|
+
const report = await collectWorkspaceOutputs({
|
|
8847
8956
|
olderThan: null,
|
|
8848
8957
|
now,
|
|
8849
|
-
measure: false
|
|
8958
|
+
measure: false,
|
|
8959
|
+
signal,
|
|
8960
|
+
operation
|
|
8850
8961
|
});
|
|
8851
8962
|
const clearable = report.workspaces.flatMap((entry) => {
|
|
8852
8963
|
const project = entry.projectRoot;
|
|
@@ -8868,11 +8979,13 @@ async function reclaimWorkspaceOutputs({ root, dryRun, now, note, diskRecovered
|
|
|
8868
8979
|
workspaces: [entry]
|
|
8869
8980
|
}, {
|
|
8870
8981
|
olderThan: null,
|
|
8871
|
-
now
|
|
8982
|
+
now,
|
|
8983
|
+
signal,
|
|
8984
|
+
operation
|
|
8872
8985
|
}, stepOutput(note));
|
|
8873
8986
|
failures += result.failures;
|
|
8874
8987
|
targets.push(...result.cleared);
|
|
8875
|
-
if (diskRecovered()) break;
|
|
8988
|
+
if (await diskRecovered()) break;
|
|
8876
8989
|
}
|
|
8877
8990
|
return {
|
|
8878
8991
|
targets,
|
|
@@ -8885,13 +8998,13 @@ function cacheLabel(cache) {
|
|
|
8885
8998
|
function trimmableCaches(env = process.env, cwd) {
|
|
8886
8999
|
return planCacheEmptying(discoverCaches(env, cwd), false, env).filter((cache) => cache.prune !== "report-only" && cache.prune !== "atomic" && !cache.machineGlobal);
|
|
8887
9000
|
}
|
|
8888
|
-
function reclaimStaleCacheEntries({ dryRun, note, env, cwd }) {
|
|
9001
|
+
async function reclaimStaleCacheEntries({ dryRun, note, env, cwd, signal }) {
|
|
8889
9002
|
const caches = trimmableCaches(env, cwd);
|
|
8890
9003
|
if (dryRun) return {
|
|
8891
9004
|
targets: caches.map(cacheLabel),
|
|
8892
9005
|
failures: 0
|
|
8893
9006
|
};
|
|
8894
|
-
const { trimmed, failures } = trimCaches(caches, 14, stepOutput(note));
|
|
9007
|
+
const { trimmed, failures } = await trimCaches(caches, 14, stepOutput(note), signal);
|
|
8895
9008
|
return {
|
|
8896
9009
|
targets: trimmed.map(cacheLabel),
|
|
8897
9010
|
failures
|
|
@@ -8900,6 +9013,7 @@ function reclaimStaleCacheEntries({ dryRun, note, env, cwd }) {
|
|
|
8900
9013
|
function stepOutput(note) {
|
|
8901
9014
|
return {
|
|
8902
9015
|
log: note,
|
|
9016
|
+
error: note,
|
|
8903
9017
|
record: () => {},
|
|
8904
9018
|
fail: () => {}
|
|
8905
9019
|
};
|
|
@@ -8920,6 +9034,8 @@ async function runStep(step, context) {
|
|
|
8920
9034
|
try {
|
|
8921
9035
|
return await STEPS[step](context);
|
|
8922
9036
|
} catch (error) {
|
|
9037
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
9038
|
+
context.signal?.throwIfAborted();
|
|
8923
9039
|
context.note(chalk.red(phaseLine("budget", `${STEP_ACTIONS[step]} failed: ${error?.message || error}`)));
|
|
8924
9040
|
return {
|
|
8925
9041
|
targets: [],
|
|
@@ -8927,8 +9043,8 @@ async function runStep(step, context) {
|
|
|
8927
9043
|
};
|
|
8928
9044
|
}
|
|
8929
9045
|
}
|
|
8930
|
-
function spaceUsers() {
|
|
8931
|
-
const home = homedir();
|
|
9046
|
+
async function spaceUsers(env = process.env, signal, operation) {
|
|
9047
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
8932
9048
|
const candidates = [
|
|
8933
9049
|
["Stim state", getConfigDir()],
|
|
8934
9050
|
["shared build cache", sharedBuildCache()],
|
|
@@ -8942,7 +9058,11 @@ function spaceUsers() {
|
|
|
8942
9058
|
if (measured.has(canonical) || [...measured].some((dir) => canonical.startsWith(`${dir}${sep}`))) continue;
|
|
8943
9059
|
measured.add(canonical);
|
|
8944
9060
|
if (!existsSync(path)) continue;
|
|
8945
|
-
const bytes =
|
|
9061
|
+
const bytes = await measuredDirectorySizeAsync(path, {
|
|
9062
|
+
timeoutMs: USAGE_TIMEOUT_MS,
|
|
9063
|
+
signal,
|
|
9064
|
+
operation
|
|
9065
|
+
});
|
|
8946
9066
|
if (bytes) users.push({
|
|
8947
9067
|
label: `${label} ${path}`,
|
|
8948
9068
|
bytes
|
|
@@ -8966,7 +9086,7 @@ function describeShortfall(budget, measure, short) {
|
|
|
8966
9086
|
...short.workspaces && measure.memory ? [`${measure.memory.liveWorkspaces} active workspaces, over the limit of ${budget.maxLiveWorkspaces}`] : []
|
|
8967
9087
|
];
|
|
8968
9088
|
}
|
|
8969
|
-
async function enforceBudget({ root, note, dryRun = false, budget: resolved, env = process.env, simSnapshot, cwd }, overrides = {}) {
|
|
9089
|
+
async function enforceBudget({ root, note, dryRun = false, budget: resolved, env = process.env, simSnapshot, cwd, signal, operation }, overrides = {}) {
|
|
8970
9090
|
const deps = {
|
|
8971
9091
|
...DEFAULT_DEPS,
|
|
8972
9092
|
...overrides
|
|
@@ -8987,7 +9107,7 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
|
|
|
8987
9107
|
const paths = [root, getConfigDir()];
|
|
8988
9108
|
const measureNow = async () => ({
|
|
8989
9109
|
volumes: budget.minFreeDiskMb > 0 || budget.hardFloorDiskMb > 0 ? await (deps.volumesAsync?.(paths) ?? deps.volumes(paths)) : [],
|
|
8990
|
-
memory: budget.maxCommittedMemoryMb > 0 || budget.maxLiveWorkspaces > 0 ? await deps.memory(simSnapshot) : null
|
|
9110
|
+
memory: budget.maxCommittedMemoryMb > 0 || budget.maxLiveWorkspaces > 0 ? await deps.memory(simSnapshot, signal, operation) : null
|
|
8991
9111
|
});
|
|
8992
9112
|
let measure = await measureNow();
|
|
8993
9113
|
let short = budgetShortfalls(budget, measure);
|
|
@@ -8995,6 +9115,7 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
|
|
|
8995
9115
|
const plan = reclaimPlan(short);
|
|
8996
9116
|
if (plan.length) note(chalk.yellow(phaseLine("budget", `${describeShortfall(budget, measure, short).join("; ")}; ${dryRun ? "would reclaim" : "reclaiming"}`)));
|
|
8997
9117
|
for (const step of plan) {
|
|
9118
|
+
signal?.throwIfAborted();
|
|
8998
9119
|
if (!dryRun && (!overBudget(short) || DISK_ONLY.includes(step) && short.disk.length === 0)) break;
|
|
8999
9120
|
const freeBefore = measure.volumes.reduce((sum, v) => sum + v.freeMb, 0);
|
|
9000
9121
|
const result = await deps.step(step, {
|
|
@@ -9004,11 +9125,14 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
|
|
|
9004
9125
|
dryRun,
|
|
9005
9126
|
note,
|
|
9006
9127
|
now: deps.now(),
|
|
9007
|
-
diskRecovered: () => budgetShortfalls(budget, {
|
|
9008
|
-
volumes: deps.volumes(paths),
|
|
9128
|
+
diskRecovered: async () => budgetShortfalls(budget, {
|
|
9129
|
+
volumes: await (deps.volumesAsync?.(paths) ?? deps.volumes(paths)),
|
|
9009
9130
|
memory: null
|
|
9010
|
-
}).disk.length === 0
|
|
9131
|
+
}).disk.length === 0,
|
|
9132
|
+
signal,
|
|
9133
|
+
operation
|
|
9011
9134
|
});
|
|
9135
|
+
signal?.throwIfAborted();
|
|
9012
9136
|
if (!dryRun) {
|
|
9013
9137
|
simSnapshot?.invalidate();
|
|
9014
9138
|
measure = await measureNow();
|
|
@@ -9026,7 +9150,7 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
|
|
|
9026
9150
|
}
|
|
9027
9151
|
if (!dryRun && short.hardFloor.length) {
|
|
9028
9152
|
const tight = short.hardFloor.map((v) => `${formatSpace(v.freeMb)} on ${v.volume}`).join(" and ");
|
|
9029
|
-
const users = deps.usage();
|
|
9153
|
+
const users = await deps.usage(env, signal, operation);
|
|
9030
9154
|
return {
|
|
9031
9155
|
status: "refused",
|
|
9032
9156
|
reclaimed,
|
|
@@ -9046,13 +9170,15 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved, env
|
|
|
9046
9170
|
short
|
|
9047
9171
|
};
|
|
9048
9172
|
}
|
|
9049
|
-
async function peekBudget(root, overrides = {}, env = process.env) {
|
|
9173
|
+
async function peekBudget(root, overrides = {}, env = process.env, signal) {
|
|
9050
9174
|
const outcome = await enforceBudget({
|
|
9051
9175
|
root,
|
|
9052
9176
|
note: () => {},
|
|
9053
9177
|
dryRun: true,
|
|
9054
|
-
env
|
|
9178
|
+
env,
|
|
9179
|
+
signal
|
|
9055
9180
|
}, {
|
|
9181
|
+
...signal ? { volumesAsync: (paths) => readVolumeSpaceAsync(paths, signal) } : {},
|
|
9056
9182
|
...overrides,
|
|
9057
9183
|
step: async () => ({
|
|
9058
9184
|
targets: [],
|
|
@@ -9064,7 +9190,10 @@ async function peekBudget(root, overrides = {}, env = process.env) {
|
|
|
9064
9190
|
return shortfall.length ? shortfall.join("; ") : null;
|
|
9065
9191
|
}
|
|
9066
9192
|
async function budgetGate(args, overrides = {}) {
|
|
9067
|
-
const outcome = await enforceBudget(args,
|
|
9193
|
+
const outcome = await enforceBudget(args, {
|
|
9194
|
+
...args.signal ? { volumesAsync: (paths) => readVolumeSpaceAsync(paths, args.signal) } : {},
|
|
9195
|
+
...overrides
|
|
9196
|
+
});
|
|
9068
9197
|
return {
|
|
9069
9198
|
reclaimed: "reclaimed" in outcome ? outcome.reclaimed : [],
|
|
9070
9199
|
refusal: outcome.status === "ok" ? null : outcome.refusal
|
|
@@ -9115,7 +9244,9 @@ async function inspectBudget(root, deps = {}, env = process.env, operation) {
|
|
|
9115
9244
|
dryRun: true,
|
|
9116
9245
|
budget: read.budget,
|
|
9117
9246
|
env,
|
|
9118
|
-
cwd: operation?.cwd
|
|
9247
|
+
cwd: operation?.cwd,
|
|
9248
|
+
signal: operation?.signal,
|
|
9249
|
+
operation
|
|
9119
9250
|
}, {
|
|
9120
9251
|
...operation ? { volumesAsync: (paths) => readVolumeSpaceAsync(paths, operation.signal) } : {},
|
|
9121
9252
|
...deps
|