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