stim 1.15.0 → 1.17.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/dist/{activity-B2-kb7k0.mjs → activity-IRvJY_ru.mjs} +49 -16
- package/dist/agent-device-usage-output-BK41vnzN.mjs +411 -0
- package/dist/{android-Cu_L4RpL.mjs → android--GorydsK.mjs} +692 -321
- package/dist/{android-Db2eEMaf.mjs → android-B_YNZy61.mjs} +25 -60
- package/dist/{android-CYmq0jNi.mjs → android-DE4v59Mb.mjs} +12 -3
- package/dist/{android-cas-PnKfAdB2.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
- package/dist/{app-install-CAAplXAt.mjs → app-install-VPkI5WEg.mjs} +52 -8
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/{budget-CMiqghig.mjs → budget-DM984mnd.mjs} +372 -366
- package/dist/{build-plan-aeGMFX7e.mjs → build-plan-CmQCoyT-.mjs} +346 -88
- package/dist/{build-slots-Df1ApSuH.mjs → build-slots-41v_d2x9.mjs} +36 -21
- package/dist/{cli-0cX5s4NM.mjs → cli-Daxug5-K.mjs} +23 -17
- package/dist/cli.mjs +1 -1
- package/dist/collector-run.mjs +5 -192
- package/dist/{command-output-CliHHn7a.mjs → command-output-CDAIFD-8.mjs} +3 -3
- package/dist/{created-devices-BwVb3liH.mjs → created-devices-CpvzBL25.mjs} +1 -1
- package/dist/{dependency-state-BmsjKZ2H.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
- package/dist/{deps-BFlKbOIp.mjs → deps-DNWaQs4V.mjs} +2 -2
- package/dist/{dev-client-CjzLoNs0.mjs → dev-client-B-XC4uRH.mjs} +3 -40
- package/dist/{device-BZ1IyS5A.mjs → device-C8IOoJPv.mjs} +7 -6
- package/dist/device-capacity-D0GWHnTM.mjs +580 -0
- package/dist/device-host-worker.mjs +587 -110
- package/dist/{device-ios-DMQBUPus.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
- package/dist/{device-lease-W5Pi1q38.mjs → device-lease-DpoS36_u.mjs} +2 -2
- package/dist/{device-pool-B2r38YjS.mjs → device-lease-run-B8YltIH_.mjs} +5 -125
- package/dist/device-pool-3o7s5lo4.mjs +125 -0
- package/dist/{device-remote-9ulPsdhY.mjs → device-remote-hFSkpfQu.mjs} +13 -13
- package/dist/{doctor-BcE5xsBn.mjs → doctor-Cnk9d7Hm.mjs} +86 -59
- package/dist/{error-diagnostics-Cb0P1WLS.mjs → error-diagnostics-C-NMnhrk.mjs} +11 -15
- package/dist/{gc-BuL1Genr.mjs → gc-DxCL2A46.mjs} +478 -33
- package/dist/{gradle-CmdOBq1d.mjs → gradle-D7-L2Th3.mjs} +4 -4
- package/dist/{guide-cCSGRFd3.mjs → guide-DjrTQGik.mjs} +2198 -566
- package/dist/{guide-status-CIoWK6mG.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
- package/dist/hosted-android-DwLS4Mse.mjs +32 -0
- package/dist/hosted-client-wk1W_CNW.mjs +196 -0
- package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
- package/dist/hosted-logs-DqYmWtXc.mjs +187 -0
- package/dist/hosted-macos-dlHXEeF5.mjs +135 -0
- package/dist/hosted-native-C9xJ9rpA.mjs +255 -0
- package/dist/{build-progress-C2Jv1AtA.mjs → idle-B2m-Ovod.mjs} +300 -9
- package/dist/{idle-shutdown-B5LPaNyY.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
- package/dist/{in-use-CndPMilN.mjs → in-use-CvGOUn-x.mjs} +30 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-Chob8ny7.mjs → ios-DE6duQoz.mjs} +819 -460
- package/dist/{ios-3CtCRhwr.mjs → ios-DSfnPfse.mjs} +4 -4
- package/dist/{ios-device-DERmMmk-.mjs → ios-device-pAN4t54-.mjs} +1 -1
- package/dist/ios-state-8L3lgum6.mjs +89 -0
- package/dist/{launch-verify-CLlofxRt.mjs → launch-verify-lTnWZikR.mjs} +3 -3
- package/dist/logs-DUoKlDoS.mjs +384 -0
- package/dist/{client-C2IS36Qf.mjs → machines-C2XiNwh9.mjs} +330 -37
- package/dist/{macos-BwtbUtTN.mjs → macos-32po3kUq.mjs} +23 -18
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-Di8EN2Tb.mjs → metro-g8NqiJp-.mjs} +1 -1
- package/dist/metro-gateway-DmslUM30.mjs +103 -0
- package/dist/{named-ports-Dj_XBNaL.mjs → named-ports-C8m8EQs2.mjs} +2 -2
- package/dist/{native-run-DFDoIRbY.mjs → native-run-D1Q56rAD.mjs} +17 -6
- package/dist/{native-runtime-i6OkVg-w.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +1 -0
- package/dist/offload-worker.mjs +13 -8
- package/dist/{teardown-BYuQafiA.mjs → ownership-BMLqyBgJ.mjs} +639 -53
- package/dist/{ownership-BkYxUtuj.mjs → ownership-DyRQru0q.mjs} +2 -2
- package/dist/{ports-DddwttKM.mjs → ports-4nYqxPWC.mjs} +2 -2
- package/dist/{prebuild-BvxDlD6C.mjs → prebuild-D7RUMkwN.mjs} +4 -4
- package/dist/preview-CxPRIEht.mjs +318 -0
- package/dist/{project-CV3tDFHl.mjs → project-D47Rh0ly.mjs} +72 -3
- package/dist/{recordings-GEXp4iBT.mjs → recordings-BNgsD1Tr.mjs} +1 -1
- package/dist/{reload-ChPiwvot.mjs → reload-qZei6iWn.mjs} +77 -13
- package/dist/{remote-cache-CO2elm_b.mjs → remote-cache-Ci4DQYYj.mjs} +4 -4
- package/dist/{run-B-xqRrsg.mjs → run-CkE9y0nc.mjs} +2 -2
- package/dist/{server-bare-X7PUgizL.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
- package/dist/{server-expo-DhtX6bom.mjs → server-expo-CHTyXD0G.mjs} +4 -4
- package/dist/server-expo-CeakhI8P.mjs +2 -0
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/settings-Cfd-0r2d.mjs +1440 -0
- package/dist/{settings-DbL6uhH8.mjs → settings-SR9OmW9h.mjs} +10 -4
- package/dist/settings.schema.json +472 -50
- package/dist/{simslim-Ciluxlq4.mjs → simslim-BWNrc3M3.mjs} +3 -3
- package/dist/{slot-launch-BY0BrOLh.mjs → slot-launch-DmjBmpQV.mjs} +10 -7
- package/dist/{spawn-entry-DncpJvWL.mjs → spawn-entry-jw19m8Nl.mjs} +1 -0
- package/dist/{start-CPO4wRg7.mjs → start-BopZcr4n.mjs} +1 -1
- package/dist/{start-CUD9nasA.mjs → start-uee1wWjY.mjs} +21 -17
- package/dist/{state-CaeaHbDA.mjs → state-BKa8wNN5.mjs} +1 -1
- package/dist/{state-BxCgeTQu.mjs → state-DyaNTgfR.mjs} +2 -2
- package/dist/{state-DFm77wRn.mjs → state-PeQiJuIu.mjs} +5 -5
- package/dist/{stats-DBLLFmKd.mjs → stats-Bq4fJOdB.mjs} +19 -5
- package/dist/{status-l84Gbvp2.mjs → status-CzS4bPO9.mjs} +5 -3
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/{status-DYXxNOT0.mjs → status-dXX2JX1C.mjs} +244 -50
- package/dist/{stop-CceYh1yG.mjs → stop-BCwvvs7F.mjs} +2 -2
- package/dist/{stop-Bad61z_2.mjs → stop-BjizrQwv.mjs} +83 -21
- package/dist/{stop-Bvf1MflS.mjs → stop-Cr1d8DSE.mjs} +4 -4
- package/dist/stop-cause-rhmGfwvt.mjs +97 -0
- package/dist/supervisor-run.d.mts +7 -4
- package/dist/supervisor-run.mjs +156 -27
- package/dist/{support-WLOzVKUv.mjs → support-BJPgYCu-.mjs} +61 -11
- package/dist/{support-CDQbDkL5.mjs → support-F9-sr0Oi.mjs} +6 -5
- package/dist/{toolchain-DmeYjXD9.mjs → toolchain-CNIBTIOv.mjs} +11 -11
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/{warm-progress-DVHLN4uC.mjs → warm-progress-DF4JRvYI.mjs} +274 -5
- package/dist/watchman-APYjXSQa.mjs +48 -0
- package/dist/{web-k6w8-HnE.mjs → web-exQtGkoc.mjs} +14 -16
- package/dist/web-run.mjs +5 -5
- package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
- package/dist/{workspace-state-CL2V2Ons.mjs → workspace-state-DqcCbpE7.mjs} +34 -34
- package/dist/workspaces-Bcib97-6.mjs +306 -0
- package/dist/{worktree-BT_uULFx.mjs → worktree-Dc98cE6_.mjs} +1 -1
- package/dist/{worktree-qXKzADSC.mjs → worktree-ZpZR3S1k.mjs} +601 -33
- package/package.json +5 -5
- package/dist/device-capacity-DMuKC7nx.mjs +0 -136
- package/dist/hosted-macos-DwowNU7W.mjs +0 -447
- package/dist/logs-h9h6MUvE.mjs +0 -568
- package/dist/machine-usage-D_YYMzfR.mjs +0 -283
- package/dist/machines-CYtOCqcj.mjs +0 -259
- package/dist/merge-state-BnxLNQiM.mjs +0 -272
- package/dist/server-expo-KJeD109I.mjs +0 -2
- package/dist/settings-CduVcoxj.mjs +0 -680
- package/dist/stop-cause-CZ_Mr-v9.mjs +0 -665
- package/dist/watchman-Bx0wceoF.mjs +0 -238
- package/dist/worktree-C0c5Udwa.mjs +0 -753
|
@@ -1,80 +1,35 @@
|
|
|
1
1
|
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
-
import { A as directorySize, D as withConfigLock$1, F as volumeRootFor, H as projectDeviceSlots, M as isOnMountedVolume,
|
|
2
|
+
import { A as directorySize, D as withConfigLock$1, F as volumeRootFor, H as projectDeviceSlots, M as isOnMountedVolume, P as measuredDirectorySize, _ as loadConfig$1, a as clearDevice, d as getConfigDir, j as formatBytes, o as clearSupervisor, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
|
|
3
3
|
import { t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
4
4
|
import { i as registeredCaches } from "./cache-manifest-DnUHBJaU.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { l as phaseLine, o as formatLongDuration, u as plural } from "./command-output-CDAIFD-8.mjs";
|
|
6
|
+
import { l as resolveProjectMetro, r as killMetroTree } from "./metro-g8NqiJp-.mjs";
|
|
7
|
+
import { d as sharedBuildCache, r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
|
|
8
|
+
import { a as capacity, l as environmentState, u as formatSpace } from "./status-CzS4bPO9.mjs";
|
|
9
|
+
import { i as recordGcResult, n as withIdleWorkspace, o as canonicalPath } from "./in-use-CvGOUn-x.mjs";
|
|
10
|
+
import { i as createDeviceProcessTables, p as workspaceActivity } from "./activity-IRvJY_ru.mjs";
|
|
11
|
+
import { h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-DpoS36_u.mjs";
|
|
12
|
+
import { n as listBuildSlots } from "./build-slots-41v_d2x9.mjs";
|
|
13
|
+
import { l as readPidFile, s as clearWorkspaceSupervisor } from "./state-BKa8wNN5.mjs";
|
|
14
|
+
import { a as parseWatchmanPid, i as parseWatchmanClients, n as parseTriggerCount, r as parseWatchRoots, t as parseSubscriberNames } from "./watchman-APYjXSQa.mjs";
|
|
15
|
+
import { A as listAvds, I as ownedAvdDirectory, at as isStimOwnedSim, ct as dropParked, it as isStimOwnedAvd, pt as readParked, z as ownedAvdSerialResolver } from "./android-B_YNZy61.mjs";
|
|
16
|
+
import { _ as parseRuntimeVersion, f as listIosDeviceTypes, u as listAllIosSims } from "./ios-DSfnPfse.mjs";
|
|
17
|
+
import { _ as teardownParkedAvd, d as eraseParkedAvd, f as eraseParkedIosSim, g as teardownOwnedIosSim, m as teardownOwnedAvd, t as resolveSupervisorTarget, v as teardownParkedIosSim } from "./ownership-BMLqyBgJ.mjs";
|
|
18
|
+
import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
|
|
19
|
+
import { i as idleShutdownCandidates, n as collectOwnedDeviceActivity, o as shutDownIdleDevices, s as workspaceBuildInProgress, t as collectIdleDevices } from "./idle-B2m-Ovod.mjs";
|
|
20
|
+
import { a as collectWorkspaceOutputs, r as clearWorkspaceOutputs } from "./workspaces-Bcib97-6.mjs";
|
|
6
21
|
import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
|
|
7
|
-
import { l as
|
|
8
|
-
import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
|
|
9
|
-
import { l as workspaceActivity, r as createDeviceProcessTables } from "./activity-B2-kb7k0.mjs";
|
|
10
|
-
import { l as phaseLine, o as formatLongDuration, u as plural } from "./command-output-CliHHn7a.mjs";
|
|
11
|
-
import { h as listBuildLocks, i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-W5Pi1q38.mjs";
|
|
12
|
-
import { A as listAvds, I as ownedAvdDirectory, ft as readParked, it as isStimOwnedSim, rt as isStimOwnedAvd, st as dropParked, z as ownedAvdSerialResolver } from "./android-Db2eEMaf.mjs";
|
|
13
|
-
import { l as resolveProjectMetro, r as killMetroTree } from "./metro-Di8EN2Tb.mjs";
|
|
14
|
-
import { a as teardownOwnedBrowser, c as teardownParkedIosSim, i as teardownOwnedAvd, n as eraseParkedIosSim, o as teardownOwnedIosSim, s as teardownParkedAvd, t as eraseParkedAvd } from "./teardown-BYuQafiA.mjs";
|
|
15
|
-
import { _ as parseRuntimeVersion, f as listIosDeviceTypes, u as listAllIosSims } from "./ios-3CtCRhwr.mjs";
|
|
16
|
-
import { a as parseWatchmanPid, c as collectOwnedDeviceActivity, f as shutDownIdleDevices, i as parseWatchmanClients, m as recordGcResult, n as parseTriggerCount, p as workspaceBuildInProgress, r as parseWatchRoots, s as collectIdleDevices, t as parseSubscriberNames, u as idleShutdownCandidates } from "./watchman-Bx0wceoF.mjs";
|
|
17
|
-
import { i as canonicalPath, n as withIdleWorkspace, r as workspaceInUse, t as emptyWorkspaceDir } from "./in-use-CndPMilN.mjs";
|
|
18
|
-
import { n as listBuildSlots } from "./build-slots-Df1ApSuH.mjs";
|
|
19
|
-
import { l as withdrawDevServerStopRequest, s as requestDevServerStop, u as resolveSupervisorTarget } from "./stop-cause-CZ_Mr-v9.mjs";
|
|
20
|
-
import { a as capacity, l as environmentState, u as formatSpace } from "./status-l84Gbvp2.mjs";
|
|
21
|
-
import { r as readFootprints, t as STIM_SERVER_COMMAND } from "./machine-usage-D_YYMzfR.mjs";
|
|
22
|
+
import { l as withdrawDevServerStopRequest, s as requestDevServerStop } from "./stop-cause-rhmGfwvt.mjs";
|
|
22
23
|
import { existsSync, readFileSync, statSync, statfsSync } from "node:fs";
|
|
23
|
-
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
24
|
-
import { existsSync as existsSync$1,
|
|
24
|
+
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";
|
|
25
26
|
import { homedir, tmpdir } from "os";
|
|
26
27
|
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";
|
|
27
|
-
import { METRO_NAMED_CACHE_LAYOUT } from "@stim-cli/core";
|
|
28
|
-
import {
|
|
28
|
+
import { METRO_NAMED_CACHE_LAYOUT, compiledHelper, configDir } from "@stim-cli/core";
|
|
29
|
+
import { MACHINE_OWNER_KINDS, buildWorkerRoot, loadConfig, settingDefinition, settingValueError } from "@stim-cli/core/state";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
29
31
|
import { homedir as homedir$1, platform, totalmem as totalmem$1 } from "node:os";
|
|
30
32
|
import chalk from "chalk";
|
|
31
|
-
//#region src/supervisor/cache-reset.ts
|
|
32
|
-
async function stopOwnedMetro(root, requester) {
|
|
33
|
-
const project = getProject(root);
|
|
34
|
-
const port = project?.metroPort;
|
|
35
|
-
const target = resolveSupervisorTarget({
|
|
36
|
-
state: readWorkspaceState$1(root)?.supervisor,
|
|
37
|
-
record: project?.supervisor,
|
|
38
|
-
reservedPort: port
|
|
39
|
-
});
|
|
40
|
-
if (target.status === "unverified") return {
|
|
41
|
-
status: "unverified",
|
|
42
|
-
reason: target.reason ?? "unknown identity"
|
|
43
|
-
};
|
|
44
|
-
if (port && target.status !== "ours" && !(await resolveProjectMetro(port, root)).missing) return {
|
|
45
|
-
status: "external",
|
|
46
|
-
reason: `a dev server Stim did not start answers on port ${port}`
|
|
47
|
-
};
|
|
48
|
-
if (target.status !== "ours") return { status: "not-running" };
|
|
49
|
-
const expected = {
|
|
50
|
-
pid: target.pid,
|
|
51
|
-
processToken: target.processToken
|
|
52
|
-
};
|
|
53
|
-
requestDevServerStop(root, target.processToken, requester);
|
|
54
|
-
const signalled = killMetroTree(target.pid, target.processToken);
|
|
55
|
-
if (!signalled) withdrawDevServerStopRequest(root, target.processToken);
|
|
56
|
-
if (!signalled || !await (0, process_identity_exports.waitForProcessExit)(expected, 1e4)) return {
|
|
57
|
-
status: "stuck",
|
|
58
|
-
reason: `supervisor pid ${target.pid} did not exit`
|
|
59
|
-
};
|
|
60
|
-
if (port && !(await resolveProjectMetro(port, root)).missing) return {
|
|
61
|
-
status: "port-occupied",
|
|
62
|
-
reason: `port ${port} is still occupied after stopping Metro`
|
|
63
|
-
};
|
|
64
|
-
clearWorkspaceSupervisor(root, expected);
|
|
65
|
-
clearSupervisor(root, expected);
|
|
66
|
-
return { status: "stopped" };
|
|
67
|
-
}
|
|
68
|
-
async function stopOwnedMetroForReset(root) {
|
|
69
|
-
const stopped = await stopOwnedMetro(root, { by: "stim start --reset-cache" });
|
|
70
|
-
switch (stopped.status) {
|
|
71
|
-
case "unverified": throw supervisorError("STIM_SUPERVISOR_EXITED", `Cannot reset Metro: ${stopped.reason}.`, "Stop it with the tool that started it, then retry. Stim leaves unverified processes alone.");
|
|
72
|
-
case "external": throw supervisorError("STIM_SUPERVISOR_EXITED", "Cannot reset an externally started dev server.", "Stop it with the tool that started it, then retry `stim start --reset-cache`.");
|
|
73
|
-
case "stuck": throw supervisorError("STIM_SUPERVISOR_EXITED", "The owned Metro supervisor could not be stopped for a cache reset.", "Inspect `stim status` and `stim logs`, then retry. No cache reset was recorded.");
|
|
74
|
-
case "port-occupied": throw supervisorError("STIM_SUPERVISOR_EXITED", `Port ${getProject(root)?.metroPort} is still occupied after stopping Metro.`, "Inspect the listener before retrying. Stim does not terminate unrecorded processes.");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
//#endregion
|
|
78
33
|
//#region src/cache/caches.ts
|
|
79
34
|
function xcodeDerivedDataRoot() {
|
|
80
35
|
return join$1(homedir(), "Library", "Developer", "Xcode", "DerivedData");
|
|
@@ -309,6 +264,283 @@ function entriesAtDepth(dir, depth) {
|
|
|
309
264
|
return level;
|
|
310
265
|
}
|
|
311
266
|
//#endregion
|
|
267
|
+
//#region src/footprint.ts
|
|
268
|
+
const SOURCE = "stim-footprint.swift";
|
|
269
|
+
const COMPILE_TIMEOUT_MS = 12e4;
|
|
270
|
+
const READ_TIMEOUT_MS = 5e3;
|
|
271
|
+
function helperSource() {
|
|
272
|
+
const here = fileURLToPath(import.meta.url);
|
|
273
|
+
if (basename(dirname(here)) === "src") return fileURLToPath(new URL(`../helper/${SOURCE}`, import.meta.url));
|
|
274
|
+
return join(dirname(here), SOURCE);
|
|
275
|
+
}
|
|
276
|
+
async function buildHelper() {
|
|
277
|
+
const exec = getExecutor();
|
|
278
|
+
if (!exec.runFileQuiet("xcode-select", ["-p"], { timeoutMs: READ_TIMEOUT_MS })) return null;
|
|
279
|
+
const source = helperSource();
|
|
280
|
+
try {
|
|
281
|
+
return await compiledHelper({
|
|
282
|
+
dir: join(configDir(), "helpers"),
|
|
283
|
+
name: "stim-footprint",
|
|
284
|
+
inputs: [source],
|
|
285
|
+
version: "",
|
|
286
|
+
compile: async (output) => {
|
|
287
|
+
await exec.runFileAsync("xcrun", [
|
|
288
|
+
"swiftc",
|
|
289
|
+
"-O",
|
|
290
|
+
"-swift-version",
|
|
291
|
+
"5",
|
|
292
|
+
"-o",
|
|
293
|
+
output,
|
|
294
|
+
source
|
|
295
|
+
], { timeoutMs: COMPILE_TIMEOUT_MS });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const RETRY_BACKOFF_INITIAL_MS = 1e4;
|
|
303
|
+
const RETRY_BACKOFF_MAX_MS = 6e5;
|
|
304
|
+
let built = null;
|
|
305
|
+
let failure = null;
|
|
306
|
+
/** The `stim-footprint` helper, compiled on first use. A success is kept for the process's life; a failure retries after a doubling backoff instead of being pinned forever. */
|
|
307
|
+
function footprintHelper() {
|
|
308
|
+
const home = configDir();
|
|
309
|
+
if (built?.home === home) return built.promise;
|
|
310
|
+
if (failure?.home === home && Date.now() < failure.nextRetryAt) return Promise.resolve(null);
|
|
311
|
+
const promise = buildHelper().then((helper) => {
|
|
312
|
+
if (helper === null) {
|
|
313
|
+
const backoffMs = failure?.home === home ? Math.min(failure.backoffMs * 2, RETRY_BACKOFF_MAX_MS) : RETRY_BACKOFF_INITIAL_MS;
|
|
314
|
+
failure = {
|
|
315
|
+
home,
|
|
316
|
+
nextRetryAt: Date.now() + backoffMs,
|
|
317
|
+
backoffMs
|
|
318
|
+
};
|
|
319
|
+
if (built?.promise === promise) built = null;
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return helper;
|
|
323
|
+
});
|
|
324
|
+
built = {
|
|
325
|
+
home,
|
|
326
|
+
promise
|
|
327
|
+
};
|
|
328
|
+
return promise;
|
|
329
|
+
}
|
|
330
|
+
/** Parses the helper's `<pid> <bytes>` lines into footprint bytes by pid. */
|
|
331
|
+
function parseFootprints(output) {
|
|
332
|
+
const footprints = /* @__PURE__ */ new Map();
|
|
333
|
+
for (const line of output.split("\n")) {
|
|
334
|
+
const match = /^(\d+) (\d+)$/.exec(line.trim());
|
|
335
|
+
if (match) footprints.set(Number(match[1]), Number(match[2]));
|
|
336
|
+
}
|
|
337
|
+
return footprints;
|
|
338
|
+
}
|
|
339
|
+
/** Every readable process's physical footprint in bytes, or null when the helper is unavailable or fails. */
|
|
340
|
+
async function readFootprints() {
|
|
341
|
+
const helper = await footprintHelper();
|
|
342
|
+
const output = helper ? getExecutor().runFileQuiet(helper, [], { timeoutMs: READ_TIMEOUT_MS }) : null;
|
|
343
|
+
return output === null ? null : parseFootprints(output);
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/machine-usage.ts
|
|
347
|
+
const SHARED = [
|
|
348
|
+
{
|
|
349
|
+
name: "CoreSimulator services",
|
|
350
|
+
pattern: /\/CoreSimulator\.framework\/|\/Simulator\.app\/Contents\/MacOS\//
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "adb server",
|
|
354
|
+
pattern: /(^|\/)adb\s.*\bfork-server\b/
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: "Android emulator services",
|
|
358
|
+
pattern: /\/emulator\/netsimd(\s|$)/
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "Gradle daemon",
|
|
362
|
+
pattern: /\borg\.gradle\.launcher\.daemon\.bootstrap\.GradleDaemon\b/
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "Kotlin daemon",
|
|
366
|
+
pattern: /\bKotlinCompileDaemon\b/
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "Watchman",
|
|
370
|
+
pattern: /(^|\/)watchman(\s|$)/
|
|
371
|
+
}
|
|
372
|
+
];
|
|
373
|
+
const STIM_SERVER_COMMAND = /(^|[\s/])stim-server(\.mjs)?(\s|$)/;
|
|
374
|
+
const LAUNCHD_SIM = /^launchd_sim\s.*\/Devices\/([^/\s]+)\//;
|
|
375
|
+
const MAX_DEPTH = 64;
|
|
376
|
+
const EMULATOR = /(?:^|\/)(?:qemu-system-[^\s/]+|emulator)(?:\s.*)?\s(?:-avd\s+|@)(\S+)/;
|
|
377
|
+
function emulatorAvd(command) {
|
|
378
|
+
return EMULATOR.exec(command)?.[1] ?? null;
|
|
379
|
+
}
|
|
380
|
+
function workspaceOwner(kind, env, name, id) {
|
|
381
|
+
return {
|
|
382
|
+
kind,
|
|
383
|
+
name,
|
|
384
|
+
workspace: env.path,
|
|
385
|
+
id,
|
|
386
|
+
owned: true
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function rank(owner) {
|
|
390
|
+
return MACHINE_OWNER_KINDS.indexOf(owner.kind);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Attributes every process in `processes` to at most one owner: the owner of its nearest ancestor (itself included)
|
|
394
|
+
* that is a root. Roots are each booted simulator's `launchd_sim`, each emulator launcher and qemu process, and each
|
|
395
|
+
* workspace's verified supervisor and Metro, running build and Chrome. Only a process under none of those falls back
|
|
396
|
+
* to stim-server or a machine-wide service in `SHARED`, so a `simctl` a workspace's log collector runs stays with the
|
|
397
|
+
* workspace. An emulator counts in the workspace that records its AVD whenever its process runs, including while adb
|
|
398
|
+
* does not list it. A process with no root ancestor is left out. `footprints` holds physical footprint bytes by pid,
|
|
399
|
+
* or is null when they could not be read, and the owners' `memoryMb` then sums resident sizes. A process missing from
|
|
400
|
+
* `footprints`, another user's or one that exited between the two reads, counts its resident size.
|
|
401
|
+
*/
|
|
402
|
+
function attributeMachineUsage({ processes, footprints, environments, roots, simNames }) {
|
|
403
|
+
const owners = /* @__PURE__ */ new Map();
|
|
404
|
+
const rootOf = /* @__PURE__ */ new Map();
|
|
405
|
+
const fallbackOf = /* @__PURE__ */ new Map();
|
|
406
|
+
const claim = (pid, key, owner, into = rootOf) => {
|
|
407
|
+
if (!owners.has(key)) owners.set(key, owner);
|
|
408
|
+
into.set(pid, key);
|
|
409
|
+
};
|
|
410
|
+
const sims = /* @__PURE__ */ new Map();
|
|
411
|
+
const emulators = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const env of environments) for (const device of [{
|
|
413
|
+
slot: "default",
|
|
414
|
+
ios: env.ios,
|
|
415
|
+
android: env.android
|
|
416
|
+
}, ...env.slots ?? []]) {
|
|
417
|
+
const slot = device.slot === "default" ? {} : { slot: device.slot };
|
|
418
|
+
if (device.ios?.state === "Booted") sims.set(device.ios.udid.toUpperCase(), {
|
|
419
|
+
kind: "simulator",
|
|
420
|
+
name: device.ios.name ?? device.ios.udid,
|
|
421
|
+
workspace: env.path,
|
|
422
|
+
...slot,
|
|
423
|
+
id: device.ios.udid,
|
|
424
|
+
owned: device.ios.owned
|
|
425
|
+
});
|
|
426
|
+
if (device.android?.name && !device.android.physical) emulators.set(device.android.name, {
|
|
427
|
+
kind: "emulator",
|
|
428
|
+
name: device.android.name,
|
|
429
|
+
workspace: env.path,
|
|
430
|
+
...slot,
|
|
431
|
+
id: device.android.name,
|
|
432
|
+
owned: device.android.owned
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
for (const p of processes) {
|
|
436
|
+
const shared = SHARED.find(({ pattern }) => pattern.test(p.command));
|
|
437
|
+
if (shared) {
|
|
438
|
+
const owner = {
|
|
439
|
+
kind: "shared",
|
|
440
|
+
name: shared.name,
|
|
441
|
+
workspace: null,
|
|
442
|
+
id: null,
|
|
443
|
+
owned: false
|
|
444
|
+
};
|
|
445
|
+
claim(p.pid, `shared:${shared.name}`, owner, fallbackOf);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (STIM_SERVER_COMMAND.test(p.command)) {
|
|
449
|
+
claim(p.pid, "server", {
|
|
450
|
+
kind: "server",
|
|
451
|
+
name: "stim-server",
|
|
452
|
+
workspace: null,
|
|
453
|
+
id: null,
|
|
454
|
+
owned: false
|
|
455
|
+
}, fallbackOf);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const udid = LAUNCHD_SIM.exec(p.command)?.[1]?.toUpperCase();
|
|
459
|
+
if (udid) {
|
|
460
|
+
const owner = sims.get(udid) ?? {
|
|
461
|
+
kind: "simulator",
|
|
462
|
+
name: simNames[udid] ?? udid,
|
|
463
|
+
workspace: null,
|
|
464
|
+
id: udid,
|
|
465
|
+
owned: false
|
|
466
|
+
};
|
|
467
|
+
claim(p.pid, `simulator:${udid}`, owner);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const avd = emulatorAvd(p.command);
|
|
471
|
+
if (avd) {
|
|
472
|
+
const owner = emulators.get(avd) ?? {
|
|
473
|
+
kind: "emulator",
|
|
474
|
+
name: avd,
|
|
475
|
+
workspace: null,
|
|
476
|
+
id: avd,
|
|
477
|
+
owned: false
|
|
478
|
+
};
|
|
479
|
+
claim(p.pid, `emulator:${avd}`, owner);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const present = new Set(processes.map((p) => p.pid));
|
|
483
|
+
const byPath = new Map(environments.map((env) => [env.path, env]));
|
|
484
|
+
for (const root of roots) {
|
|
485
|
+
const env = byPath.get(root.path);
|
|
486
|
+
if (!env) continue;
|
|
487
|
+
const metroPids = [root.supervisorPid, env.metro?.running ? env.metro.pid : null];
|
|
488
|
+
for (const pid of metroPids) if (pid && present.has(pid)) {
|
|
489
|
+
const port = env.metro?.port ?? null;
|
|
490
|
+
claim(pid, `metro:${env.path}`, workspaceOwner("metro", env, port ? `Metro :${port}` : "Metro", port ? String(port) : null));
|
|
491
|
+
}
|
|
492
|
+
if (root.build && present.has(root.build.pid)) {
|
|
493
|
+
const label = root.build.platform === "ios" ? "iOS build" : "Android build";
|
|
494
|
+
claim(root.build.pid, `build:${env.path}`, workspaceOwner("build", env, label, root.build.platform));
|
|
495
|
+
}
|
|
496
|
+
for (const pid of root.macosPids ?? []) if (present.has(pid)) claim(pid, `macos:${env.path}`, workspaceOwner("macos", env, env.macos?.product ?? "macOS app", null));
|
|
497
|
+
for (const pid of root.browserPids) if (present.has(pid)) claim(pid, `browser:${env.path}`, workspaceOwner("browser", env, "Chrome", null));
|
|
498
|
+
}
|
|
499
|
+
const parentOf = new Map(processes.map((p) => [p.pid, p.ppid]));
|
|
500
|
+
const totals = /* @__PURE__ */ new Map();
|
|
501
|
+
for (const p of processes) {
|
|
502
|
+
let pid = p.pid;
|
|
503
|
+
let key;
|
|
504
|
+
let fallback;
|
|
505
|
+
for (let depth = 0; pid !== void 0 && pid > 1 && depth < MAX_DEPTH; depth++) {
|
|
506
|
+
key = rootOf.get(pid);
|
|
507
|
+
if (key) break;
|
|
508
|
+
fallback ??= fallbackOf.get(pid);
|
|
509
|
+
pid = parentOf.get(pid);
|
|
510
|
+
}
|
|
511
|
+
key ??= fallback;
|
|
512
|
+
if (!key) continue;
|
|
513
|
+
const total = totals.get(key) ?? {
|
|
514
|
+
rssKb: 0,
|
|
515
|
+
memoryKb: 0,
|
|
516
|
+
cpu: 0,
|
|
517
|
+
processes: 0
|
|
518
|
+
};
|
|
519
|
+
total.rssKb += p.rssKb;
|
|
520
|
+
const footprint = footprints?.get(p.pid);
|
|
521
|
+
total.memoryKb += footprint === void 0 ? p.rssKb : footprint / 1024;
|
|
522
|
+
total.cpu += p.cpuPercent;
|
|
523
|
+
total.processes += 1;
|
|
524
|
+
totals.set(key, total);
|
|
525
|
+
}
|
|
526
|
+
const list = [];
|
|
527
|
+
for (const [key, owner] of owners) {
|
|
528
|
+
const total = totals.get(key);
|
|
529
|
+
if (total) list.push({
|
|
530
|
+
...owner,
|
|
531
|
+
cpuPercent: Math.round(total.cpu),
|
|
532
|
+
residentMb: Math.round(total.rssKb / 1024),
|
|
533
|
+
memoryMb: Math.round(total.memoryKb / 1024),
|
|
534
|
+
processes: total.processes
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
list.sort((x, y) => rank(x) - rank(y) || (x.workspace ?? "~").localeCompare(y.workspace ?? "~") || x.name.localeCompare(y.name));
|
|
538
|
+
return {
|
|
539
|
+
memorySource: footprints ? "footprint" : "rss",
|
|
540
|
+
owners: list
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
//#endregion
|
|
312
544
|
//#region src/commands/gc/progress.ts
|
|
313
545
|
function phase(label, text) {
|
|
314
546
|
console.error(chalk.dim(phaseLine(label, text)));
|
|
@@ -1187,7 +1419,7 @@ async function inspectWatchmanMemory() {
|
|
|
1187
1419
|
}
|
|
1188
1420
|
//#endregion
|
|
1189
1421
|
//#region src/commands/gc/caches.ts
|
|
1190
|
-
const DAY_MS$
|
|
1422
|
+
const DAY_MS$1 = 864e5;
|
|
1191
1423
|
function cacheSweepIsScoped() {
|
|
1192
1424
|
return Boolean(process.env.STIM_HOME);
|
|
1193
1425
|
}
|
|
@@ -1207,6 +1439,14 @@ const EVERY_CACHE = "all";
|
|
|
1207
1439
|
const WORKSPACE_OUTPUTS = "workspaces";
|
|
1208
1440
|
const RECORDINGS = "recordings";
|
|
1209
1441
|
const PARKED_DEVICES = "parked";
|
|
1442
|
+
function includesArchives(name) {
|
|
1443
|
+
const wanted = name?.trim().toLowerCase();
|
|
1444
|
+
return Boolean(wanted && (wanted === "archived" || wanted.startsWith("archived:") || [
|
|
1445
|
+
"archived-logs",
|
|
1446
|
+
"archived-recordings",
|
|
1447
|
+
"archived-agent"
|
|
1448
|
+
].includes(wanted)));
|
|
1449
|
+
}
|
|
1210
1450
|
function includesParkedDevices(name) {
|
|
1211
1451
|
return name?.trim().toLowerCase() === PARKED_DEVICES;
|
|
1212
1452
|
}
|
|
@@ -1223,13 +1463,13 @@ function includesWorkspaceOutputs(name) {
|
|
|
1223
1463
|
}
|
|
1224
1464
|
/** Whether a --cache name selects something besides shared caches: recordings, parked devices or a memory kind. */
|
|
1225
1465
|
function selectsBeyondCaches(name) {
|
|
1226
|
-
return includesRecordings(name) || includesParkedDevices(name) || memoryCacheKind(name) !== null;
|
|
1466
|
+
return includesArchives(name) || includesRecordings(name) || includesParkedDevices(name) || memoryCacheKind(name) !== null;
|
|
1227
1467
|
}
|
|
1228
1468
|
function selectCaches(caches, name) {
|
|
1229
1469
|
if (!name) return caches;
|
|
1230
1470
|
const wanted = name.trim().toLowerCase();
|
|
1231
1471
|
if (wanted === EVERY_CACHE) return caches;
|
|
1232
|
-
if (wanted === WORKSPACE_OUTPUTS || wanted === RECORDINGS || wanted === PARKED_DEVICES || memoryCacheKind(wanted)) return [];
|
|
1472
|
+
if (includesArchives(wanted) || wanted === WORKSPACE_OUTPUTS || wanted === RECORDINGS || wanted === PARKED_DEVICES || memoryCacheKind(wanted)) return [];
|
|
1233
1473
|
return caches.filter((c) => c.name.toLowerCase().includes(wanted) || c.dir.toLowerCase().includes(wanted));
|
|
1234
1474
|
}
|
|
1235
1475
|
function emptyVerdict(c) {
|
|
@@ -1276,7 +1516,7 @@ function emptyCache(cache) {
|
|
|
1276
1516
|
};
|
|
1277
1517
|
if (cache.prune !== "atomic") return pruneCache(cache, {
|
|
1278
1518
|
olderThanDays: 0,
|
|
1279
|
-
now: Date.now() + DAY_MS$
|
|
1519
|
+
now: Date.now() + DAY_MS$1
|
|
1280
1520
|
});
|
|
1281
1521
|
let names;
|
|
1282
1522
|
try {
|
|
@@ -1383,7 +1623,7 @@ function emptyCaches(caches) {
|
|
|
1383
1623
|
//#region src/commands/gc/devices.ts
|
|
1384
1624
|
const DEVICE_LIST_TIMEOUT_MS$1 = 3e4;
|
|
1385
1625
|
const AVD_SIZE_TIMEOUT_MS = 5e3;
|
|
1386
|
-
const DAY_MS
|
|
1626
|
+
const DAY_MS = 864e5;
|
|
1387
1627
|
function describeKept(ref) {
|
|
1388
1628
|
return ref.mounted ? `referenced by ${ref.path}` : `referenced by ${ref.path} (volume not mounted; kept just in case)`;
|
|
1389
1629
|
}
|
|
@@ -1475,7 +1715,7 @@ function unverifiedDeviceCommand(device) {
|
|
|
1475
1715
|
}
|
|
1476
1716
|
function findStaleProjectDevices({ config, sims = [], avds = [], olderThanDays, now = Date.now(), lastTouched, deadProjects = [] }) {
|
|
1477
1717
|
if (!Number.isFinite(olderThanDays) || typeof lastTouched !== "function") return [];
|
|
1478
|
-
const cutoff = now - olderThanDays * DAY_MS
|
|
1718
|
+
const cutoff = now - olderThanDays * DAY_MS;
|
|
1479
1719
|
const dead = new Set(deadProjects);
|
|
1480
1720
|
const liveSims = new Map(sims.filter((s) => s?.name?.startsWith("stim-")).map((s) => [s.udid, s]));
|
|
1481
1721
|
const liveAvds = new Set(avds.filter((a) => typeof a === "string" && a.startsWith("stim-")));
|
|
@@ -1484,7 +1724,7 @@ function findStaleProjectDevices({ config, sims = [], avds = [], olderThanDays,
|
|
|
1484
1724
|
if (dead.has(path) || !isAbsolute$1(path)) continue;
|
|
1485
1725
|
const touched = lastTouched(path);
|
|
1486
1726
|
if (!Number.isFinite(touched) || touched >= cutoff) continue;
|
|
1487
|
-
const idleDays = Math.floor((now - touched) / DAY_MS
|
|
1727
|
+
const idleDays = Math.floor((now - touched) / DAY_MS);
|
|
1488
1728
|
for (const { slot, platforms } of projectDeviceSlots(proj)) {
|
|
1489
1729
|
const ios = platforms.ios;
|
|
1490
1730
|
const sim = ios?.owned && ios.deviceUdid ? liveSims.get(ios.deviceUdid) : void 0;
|
|
@@ -1577,7 +1817,7 @@ function describeParkedSims(records, sims, deviceTypes, { simsChecked = true } =
|
|
|
1577
1817
|
}
|
|
1578
1818
|
function parkedAtLeast(records, { olderThanDays = null, now = Date.now() }) {
|
|
1579
1819
|
if (olderThanDays === null) return [...records];
|
|
1580
|
-
const cutoff = now - olderThanDays * DAY_MS
|
|
1820
|
+
const cutoff = now - olderThanDays * DAY_MS;
|
|
1581
1821
|
return records.filter((record) => Date.parse(record.parkedAt) <= cutoff);
|
|
1582
1822
|
}
|
|
1583
1823
|
function collectParkedSims(deps, age = {}) {
|
|
@@ -1851,301 +2091,51 @@ function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords)
|
|
|
1851
2091
|
return deleteFailures;
|
|
1852
2092
|
}
|
|
1853
2093
|
//#endregion
|
|
1854
|
-
//#region src/
|
|
1855
|
-
function
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
} catch (error) {
|
|
1867
|
-
return {
|
|
1868
|
-
projectRoot: null,
|
|
1869
|
-
problem: error?.code === "ENOENT" ? "it has no workspace.json" : "its workspace.json does not parse"
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
const projectRoot = isJsonObject(parsed) ? parsed.projectRoot : void 0;
|
|
1873
|
-
if (typeof projectRoot !== "string" || !isAbsolute$1(projectRoot)) return {
|
|
1874
|
-
projectRoot: null,
|
|
1875
|
-
problem: "its workspace.json records no absolute project root"
|
|
1876
|
-
};
|
|
1877
|
-
if (workspaceName$1(projectRoot) !== name) return {
|
|
1878
|
-
projectRoot: null,
|
|
1879
|
-
problem: `its workspace.json records ${projectRoot}, whose workspace directory has another name`
|
|
1880
|
-
};
|
|
1881
|
-
return {
|
|
1882
|
-
projectRoot,
|
|
1883
|
-
problem: null
|
|
2094
|
+
//#region src/supervisor/cache-reset.ts
|
|
2095
|
+
async function stopOwnedMetro(root, requester) {
|
|
2096
|
+
const project = getProject(root);
|
|
2097
|
+
const port = project?.metroPort;
|
|
2098
|
+
const target = resolveSupervisorTarget({
|
|
2099
|
+
state: readWorkspaceState$1(root)?.supervisor,
|
|
2100
|
+
record: project?.supervisor,
|
|
2101
|
+
reservedPort: port
|
|
2102
|
+
});
|
|
2103
|
+
if (target.status === "unverified") return {
|
|
2104
|
+
status: "unverified",
|
|
2105
|
+
reason: target.reason ?? "unknown identity"
|
|
1884
2106
|
};
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
let names;
|
|
1889
|
-
try {
|
|
1890
|
-
names = readdirSync$1(root).toSorted();
|
|
1891
|
-
} catch {
|
|
1892
|
-
return [];
|
|
1893
|
-
}
|
|
1894
|
-
const entries = [];
|
|
1895
|
-
for (const name of names) {
|
|
1896
|
-
const dir = join$1(root, name);
|
|
1897
|
-
try {
|
|
1898
|
-
if (!statSync$1(dir).isDirectory()) continue;
|
|
1899
|
-
} catch {
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
entries.push({
|
|
1903
|
-
dir,
|
|
1904
|
-
...readRecordedRoot(dir, name)
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
return entries;
|
|
1908
|
-
}
|
|
1909
|
-
function classifyWorkspaceDirs(entries, { registryKeys, exists, isMounted, inUse }) {
|
|
1910
|
-
const orphaned = [];
|
|
1911
|
-
const skipped = [];
|
|
1912
|
-
for (const { dir, projectRoot, problem } of entries) {
|
|
1913
|
-
if (projectRoot === null) {
|
|
1914
|
-
skipped.push({
|
|
1915
|
-
dir,
|
|
1916
|
-
reason: `workspace directory not resolved: ${problem ?? "unknown project root"}`
|
|
1917
|
-
});
|
|
1918
|
-
continue;
|
|
1919
|
-
}
|
|
1920
|
-
const present = exists(projectRoot);
|
|
1921
|
-
if (present === null) {
|
|
1922
|
-
skipped.push({
|
|
1923
|
-
dir,
|
|
1924
|
-
reason: `cannot tell whether its project root ${projectRoot} still exists`
|
|
1925
|
-
});
|
|
1926
|
-
continue;
|
|
1927
|
-
}
|
|
1928
|
-
if (present) continue;
|
|
1929
|
-
if (registryKeys.some((key) => isPathPrefix(projectRoot, key))) continue;
|
|
1930
|
-
if (!isMounted(projectRoot)) {
|
|
1931
|
-
skipped.push({
|
|
1932
|
-
dir,
|
|
1933
|
-
reason: `the volume of its project root ${projectRoot} is not mounted`
|
|
1934
|
-
});
|
|
1935
|
-
continue;
|
|
1936
|
-
}
|
|
1937
|
-
const reasons = inUse(projectRoot);
|
|
1938
|
-
if (reasons.length) {
|
|
1939
|
-
skipped.push({
|
|
1940
|
-
dir,
|
|
1941
|
-
reason: `its project root ${projectRoot} is gone, but ${reasons.join("; ")}`
|
|
1942
|
-
});
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
orphaned.push({
|
|
1946
|
-
dir,
|
|
1947
|
-
projectRoot
|
|
1948
|
-
});
|
|
1949
|
-
}
|
|
1950
|
-
return {
|
|
1951
|
-
orphaned,
|
|
1952
|
-
skipped
|
|
2107
|
+
if (port && target.status !== "ours" && !(await resolveProjectMetro(port, root)).missing) return {
|
|
2108
|
+
status: "external",
|
|
2109
|
+
reason: `a dev server Stim did not start answers on port ${port}`
|
|
1953
2110
|
};
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
return true;
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
const code = error?.code;
|
|
1961
|
-
return code === "ENOENT" || code === "ENOTDIR" ? false : null;
|
|
1962
|
-
}
|
|
1963
|
-
}
|
|
1964
|
-
function collectOrphanedWorkspaces(registryKeys, mountedVolumes, { measure = true } = {}) {
|
|
1965
|
-
const classified = classifyWorkspaceDirs(listWorkspaceDirs(), {
|
|
1966
|
-
registryKeys,
|
|
1967
|
-
exists: rootPresence,
|
|
1968
|
-
isMounted: (path) => isOnMountedVolume(path, mountedVolumes),
|
|
1969
|
-
inUse: (root) => workspaceInUse(root)
|
|
1970
|
-
});
|
|
1971
|
-
return {
|
|
1972
|
-
orphaned: measure ? classified.orphaned.map((entry) => Object.assign({}, entry, { bytes: sizeOf(entry.dir) })) : classified.orphaned,
|
|
1973
|
-
skipped: classified.skipped
|
|
2111
|
+
if (target.status !== "ours") return { status: "not-running" };
|
|
2112
|
+
const expected = {
|
|
2113
|
+
pid: target.pid,
|
|
2114
|
+
processToken: target.processToken
|
|
1974
2115
|
};
|
|
1975
|
-
|
|
1976
|
-
const
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
"cache-provider"
|
|
1982
|
-
];
|
|
1983
|
-
const DAY_MS = 864e5;
|
|
1984
|
-
function planWorkspaceOutputs(entries, { olderThan, now }) {
|
|
1985
|
-
return entries.map(({ dir, projectRoot, problem, bytes, lastUsed, inUse }) => {
|
|
1986
|
-
const idleDays = Number.isFinite(lastUsed) ? Math.max(0, Math.floor((now - lastUsed) / DAY_MS)) : null;
|
|
1987
|
-
const [keptCode, keptReason] = projectRoot === null ? ["unresolved", `workspace directory not resolved: ${problem ?? "unknown project root"}`] : inUse.length ? ["in-use", `in use: ${inUse.join("; ")}`] : olderThan === null ? [null, null] : idleDays === null ? ["last-use-unknown", "its last use is unknown"] : idleDays < olderThan ? ["recently-used", `used ${idleDays}d ago, within --older-than ${olderThan}`] : [null, null];
|
|
1988
|
-
return {
|
|
1989
|
-
dir,
|
|
1990
|
-
projectRoot,
|
|
1991
|
-
bytes,
|
|
1992
|
-
idleDays,
|
|
1993
|
-
willClear: keptCode === null,
|
|
1994
|
-
keptCode,
|
|
1995
|
-
keptReason
|
|
1996
|
-
};
|
|
1997
|
-
});
|
|
1998
|
-
}
|
|
1999
|
-
const SIZE_TIMEOUT_MS = 6e4;
|
|
2000
|
-
function sizeOf(dir) {
|
|
2001
|
-
return measuredDirectorySize(dir, { timeoutMs: SIZE_TIMEOUT_MS });
|
|
2002
|
-
}
|
|
2003
|
-
function outputPaths(dir) {
|
|
2004
|
-
const paths = WORKSPACE_OUTPUT_DIRS.map((name) => join$1(dir, name)).filter((path) => existsSync$1(path));
|
|
2005
|
-
const macos = join$1(dir, "macos");
|
|
2006
|
-
try {
|
|
2007
|
-
if (!lstatSync$1(macos).isDirectory()) return paths;
|
|
2008
|
-
for (const entry of readdirSync$1(macos, { withFileTypes: true })) if (entry.name === "build" || entry.name.endsWith(".app") || entry.name.startsWith("staging-") && entry.isDirectory()) paths.push(join$1(macos, entry.name));
|
|
2009
|
-
} catch {}
|
|
2010
|
-
return paths;
|
|
2011
|
-
}
|
|
2012
|
-
function outputBytes(paths) {
|
|
2013
|
-
let total = 0;
|
|
2014
|
-
for (const path of paths) {
|
|
2015
|
-
const size = sizeOf(path);
|
|
2016
|
-
if (size === null) return null;
|
|
2017
|
-
total += size;
|
|
2018
|
-
}
|
|
2019
|
-
return total;
|
|
2020
|
-
}
|
|
2021
|
-
function collectWorkspaceOutputs({ olderThan, now, exclude = [], measure = true }) {
|
|
2022
|
-
const mountedVolumes = listMountedVolumes();
|
|
2023
|
-
const entries = listWorkspaceDirs().filter((entry) => !exclude.includes(entry.dir)).filter((entry) => entry.projectRoot === null || isOnMountedVolume(entry.projectRoot, mountedVolumes)).map((entry) => Object.assign({}, entry, { paths: outputPaths(entry.dir) })).filter((entry) => entry.paths.length > 0).map(({ paths, ...entry }) => Object.assign({}, entry, { bytes: measure ? outputBytes(paths) : null })).map((entry) => Object.assign({}, entry, {
|
|
2024
|
-
lastUsed: entry.projectRoot === null ? NaN : workspaceLastUsed(entry.projectRoot),
|
|
2025
|
-
inUse: entry.projectRoot === null ? [] : workspaceInUse(entry.projectRoot)
|
|
2026
|
-
}));
|
|
2027
|
-
return {
|
|
2028
|
-
root: workspacesRoot(),
|
|
2029
|
-
workspaces: planWorkspaceOutputs(entries, {
|
|
2030
|
-
olderThan,
|
|
2031
|
-
now
|
|
2032
|
-
})
|
|
2116
|
+
requestDevServerStop(root, target.processToken, requester);
|
|
2117
|
+
const signalled = killMetroTree(target.pid, target.processToken);
|
|
2118
|
+
if (!signalled) withdrawDevServerStopRequest(root, target.processToken);
|
|
2119
|
+
if (!signalled || !await (0, process_identity_exports.waitForProcessExit)(expected, 1e4)) return {
|
|
2120
|
+
status: "stuck",
|
|
2121
|
+
reason: `supervisor pid ${target.pid} did not exit`
|
|
2033
2122
|
};
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
let cleared = 0;
|
|
2038
|
-
const clearedRoots = [];
|
|
2039
|
-
for (const entry of report.workspaces) {
|
|
2040
|
-
const root = entry.projectRoot;
|
|
2041
|
-
if (!entry.willClear || root === null) {
|
|
2042
|
-
if (entry.keptReason) {
|
|
2043
|
-
console.log(chalk.dim(`Kept the build outputs of ${root ?? entry.dir}: ${entry.keptReason}`));
|
|
2044
|
-
recordGcResult("workspaceOutputs", "kept", root ?? entry.dir, {
|
|
2045
|
-
bytes: entry.bytes,
|
|
2046
|
-
detail: entry.keptReason
|
|
2047
|
-
});
|
|
2048
|
-
}
|
|
2049
|
-
continue;
|
|
2050
|
-
}
|
|
2051
|
-
let run;
|
|
2052
|
-
try {
|
|
2053
|
-
run = await withIdleWorkspace(root, () => {
|
|
2054
|
-
const [again] = planWorkspaceOutputs([{
|
|
2055
|
-
...entry,
|
|
2056
|
-
problem: null,
|
|
2057
|
-
lastUsed: workspaceLastUsed(root),
|
|
2058
|
-
inUse: []
|
|
2059
|
-
}], {
|
|
2060
|
-
olderThan,
|
|
2061
|
-
now
|
|
2062
|
-
});
|
|
2063
|
-
if (again?.keptReason) return again.keptReason;
|
|
2064
|
-
for (const path of outputPaths(entry.dir)) rmSync$1(path, {
|
|
2065
|
-
recursive: true,
|
|
2066
|
-
force: true
|
|
2067
|
-
});
|
|
2068
|
-
return null;
|
|
2069
|
-
}, { purpose: "gc" });
|
|
2070
|
-
} catch (error) {
|
|
2071
|
-
failures++;
|
|
2072
|
-
console.log(chalk.red(`Could not clear the build outputs of ${root}: ${error?.message || String(error)}`));
|
|
2073
|
-
recordGcResult("workspaceOutputs", "failed", root, {
|
|
2074
|
-
bytes: entry.bytes,
|
|
2075
|
-
detail: error?.message || String(error)
|
|
2076
|
-
});
|
|
2077
|
-
continue;
|
|
2078
|
-
}
|
|
2079
|
-
const kept = run.ran ? run.value : `in use: ${run.reasons.join("; ")}`;
|
|
2080
|
-
if (kept) {
|
|
2081
|
-
console.log(chalk.yellow(`Kept the build outputs of ${root}: ${kept}`));
|
|
2082
|
-
recordGcResult("workspaceOutputs", "kept", root, {
|
|
2083
|
-
bytes: entry.bytes,
|
|
2084
|
-
detail: kept
|
|
2085
|
-
});
|
|
2086
|
-
continue;
|
|
2087
|
-
}
|
|
2088
|
-
cleared += entry.bytes ?? 0;
|
|
2089
|
-
clearedRoots.push(root);
|
|
2090
|
-
console.log(chalk.green(`Cleared the build outputs of ${root} (${sizeText(entry.bytes)})`));
|
|
2091
|
-
recordGcResult("workspaceOutputs", "done", root, { bytes: entry.bytes });
|
|
2092
|
-
}
|
|
2093
|
-
if (cleared) console.log(chalk.dim(`Cleared ${formatBytes(cleared)} of workspace build outputs. ${REBUILD_COST}`));
|
|
2094
|
-
return {
|
|
2095
|
-
failures,
|
|
2096
|
-
cleared: clearedRoots
|
|
2123
|
+
if (port && !(await resolveProjectMetro(port, root)).missing) return {
|
|
2124
|
+
status: "port-occupied",
|
|
2125
|
+
reason: `port ${port} is still occupied after stopping Metro`
|
|
2097
2126
|
};
|
|
2127
|
+
clearWorkspaceSupervisor(root, expected);
|
|
2128
|
+
clearSupervisor(root, expected);
|
|
2129
|
+
return { status: "stopped" };
|
|
2098
2130
|
}
|
|
2099
|
-
function
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
if (!existsSync$1(entry.dir)) continue;
|
|
2107
|
-
let run;
|
|
2108
|
-
try {
|
|
2109
|
-
run = await withIdleWorkspace(entry.projectRoot, async () => {
|
|
2110
|
-
if (!stillOrphaned(entry)) return false;
|
|
2111
|
-
const browser = await teardownOwnedBrowser(entry.projectRoot, { deleteProfile: true });
|
|
2112
|
-
if (browser.status === "failed" || browser.status === "skipped") throw new Error(`its owned Chrome profile was kept: ${browser.reason}`);
|
|
2113
|
-
emptyWorkspaceDir(entry.dir);
|
|
2114
|
-
return true;
|
|
2115
|
-
}, { purpose: "gc" });
|
|
2116
|
-
} catch (error) {
|
|
2117
|
-
failures++;
|
|
2118
|
-
console.log(chalk.red(`Could not remove ${entry.dir}: ${error?.message || error}`));
|
|
2119
|
-
recordGcResult("workspaceDirectory", "failed", entry.dir, {
|
|
2120
|
-
bytes: entry.bytes ?? null,
|
|
2121
|
-
detail: error?.message || String(error)
|
|
2122
|
-
});
|
|
2123
|
-
continue;
|
|
2124
|
-
}
|
|
2125
|
-
if (!run.ran) {
|
|
2126
|
-
console.log(chalk.yellow(`Kept ${entry.dir}: ${run.reasons.join("; ")}`));
|
|
2127
|
-
recordGcResult("workspaceDirectory", "kept", entry.dir, {
|
|
2128
|
-
bytes: entry.bytes ?? null,
|
|
2129
|
-
detail: run.reasons.join("; ")
|
|
2130
|
-
});
|
|
2131
|
-
continue;
|
|
2132
|
-
}
|
|
2133
|
-
if (!run.value) {
|
|
2134
|
-
console.log(chalk.yellow(`Kept ${entry.dir}: it can no longer be confirmed orphaned.`));
|
|
2135
|
-
recordGcResult("workspaceDirectory", "kept", entry.dir, {
|
|
2136
|
-
bytes: entry.bytes ?? null,
|
|
2137
|
-
detail: "it can no longer be confirmed orphaned"
|
|
2138
|
-
});
|
|
2139
|
-
continue;
|
|
2140
|
-
}
|
|
2141
|
-
const size = entry.bytes === void 0 ? "" : ` (${sizeText(entry.bytes)})`;
|
|
2142
|
-
console.log(chalk.green(`Removed the orphaned workspace directory ${entry.dir}${size}`));
|
|
2143
|
-
recordGcResult("workspaceDirectory", "done", entry.dir, { bytes: entry.bytes ?? null });
|
|
2131
|
+
async function stopOwnedMetroForReset(root) {
|
|
2132
|
+
const stopped = await stopOwnedMetro(root, { by: "stim start --reset-cache" });
|
|
2133
|
+
switch (stopped.status) {
|
|
2134
|
+
case "unverified": throw supervisorError("STIM_SUPERVISOR_EXITED", `Cannot reset Metro: ${stopped.reason}.`, "Stop it with the tool that started it, then retry. Stim leaves unverified processes alone.");
|
|
2135
|
+
case "external": throw supervisorError("STIM_SUPERVISOR_EXITED", "Cannot reset an externally started dev server.", "Stop it with the tool that started it, then retry `stim start --reset-cache`.");
|
|
2136
|
+
case "stuck": throw supervisorError("STIM_SUPERVISOR_EXITED", "The owned Metro supervisor could not be stopped for a cache reset.", "Inspect `stim status` and `stim logs`, then retry. No cache reset was recorded.");
|
|
2137
|
+
case "port-occupied": throw supervisorError("STIM_SUPERVISOR_EXITED", `Port ${getProject(root)?.metroPort} is still occupied after stopping Metro.`, "Inspect the listener before retrying. Stim does not terminate unrecorded processes.");
|
|
2144
2138
|
}
|
|
2145
|
-
return failures;
|
|
2146
|
-
}
|
|
2147
|
-
function sizeText(bytes) {
|
|
2148
|
-
return bytes === null ? "size unknown" : formatBytes(bytes);
|
|
2149
2139
|
}
|
|
2150
2140
|
//#endregion
|
|
2151
2141
|
//#region src/budget.ts
|
|
@@ -2178,7 +2168,7 @@ function machineNumber(key, config, env) {
|
|
|
2178
2168
|
error: null
|
|
2179
2169
|
};
|
|
2180
2170
|
}
|
|
2181
|
-
if (env.STIM_HOME && setting.scopedHomeValue
|
|
2171
|
+
if (env.STIM_HOME && typeof setting.scopedHomeValue === "number") return {
|
|
2182
2172
|
value: setting.scopedHomeValue,
|
|
2183
2173
|
error: null
|
|
2184
2174
|
};
|
|
@@ -2624,6 +2614,22 @@ async function enforceBudget({ root, note, dryRun = false, budget: resolved }, o
|
|
|
2624
2614
|
short
|
|
2625
2615
|
};
|
|
2626
2616
|
}
|
|
2617
|
+
async function peekBudget(root, overrides = {}) {
|
|
2618
|
+
const outcome = await enforceBudget({
|
|
2619
|
+
root,
|
|
2620
|
+
note: () => {},
|
|
2621
|
+
dryRun: true
|
|
2622
|
+
}, {
|
|
2623
|
+
...overrides,
|
|
2624
|
+
step: async () => ({
|
|
2625
|
+
targets: [],
|
|
2626
|
+
failures: 0
|
|
2627
|
+
})
|
|
2628
|
+
});
|
|
2629
|
+
if (outcome.status !== "ok") return outcome.refusal.message;
|
|
2630
|
+
const shortfall = describeShortfall(outcome.budget, outcome.measure, outcome.short);
|
|
2631
|
+
return shortfall.length ? shortfall.join("; ") : null;
|
|
2632
|
+
}
|
|
2627
2633
|
async function budgetGate(args, overrides = {}) {
|
|
2628
2634
|
const outcome = await enforceBudget(args, overrides);
|
|
2629
2635
|
return {
|
|
@@ -2707,4 +2713,4 @@ function budgetLine(report) {
|
|
|
2707
2713
|
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"}`;
|
|
2708
2714
|
}
|
|
2709
2715
|
//#endregion
|
|
2710
|
-
export {
|
|
2716
|
+
export { includesWorkspaceOutputs as A, inspectWatchmanMemory as B, findStaleProjectDevices as C, includesArchives as D, emptyCaches as E, selectsBeyondCaches as F, memoryWorkPending as G, memoryLines as H, trimCaches as I, phase as J, reclaimMemory as K, GRADLE_DAEMONS_KIND as L, planCacheEmptying as M, previewScopedEmptying as N, includesParkedDevices as O, selectCaches as P, sizeCaches as Q, WATCHMAN_KIND as R, findStaleDeviceRecords as S, withAndroidAvdSizes as T, memoryScope as U, memoryCacheKind as V, memorySweepIsScoped as W, readFootprints as X, attributeMachineUsage as Y, discoverCaches as Z, describeParkedSims as _, machineNumber as a, eraseParkedDevices as b, resolveBudget as c, collectDeviceLeases as d, collectParkedAvds as f, deleteProjectDevices as g, deleteParkedSims as h, inspectBudget as i, isWorkspacesScope as j, includesRecordings as k, stopOwnedMetroForReset as l, deleteParkedAvds as m, budgetLine as n, peekBudget as o, collectParkedSims as p, scopedMemoryReport as q, enforceBudget as r, readVolumeSpace as s, budgetGate as t, DEVICE_LIST_TIMEOUT_MS$1 as u, describeUnverifiableDevices as v, unverifiedDeviceCommand as w, findOrphanedDevices as x, deviceSweepIsScoped as y, collectMemoryReport as z };
|