stim 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{activity-BtG4Va9l.mjs → activity-CCZJs__P.mjs} +4 -3
  2. package/dist/{android-C4zG2Tuc.mjs → android-75Sk2rIR.mjs} +25 -8
  3. package/dist/{android-C1q4l523.mjs → android-B7X-8H7h.mjs} +75 -32
  4. package/dist/{android-BA2i_KAV.mjs → android-BQiKJYK0.mjs} +1 -1
  5. package/dist/{android-cas-DfJkwCXW.mjs → android-cas-DzLPXiEn.mjs} +7 -7
  6. package/dist/{app-install-DHut6JnV.mjs → app-install-CsZi0SAT.mjs} +1 -1
  7. package/dist/{workspaces-COD2xCz-.mjs → budget-DbzW5VTD.mjs} +641 -32
  8. package/dist/{build-plan-CeAYgKAx.mjs → build-plan-CqntbR7b.mjs} +120 -35
  9. package/dist/{build-progress-CDwogz6R.mjs → build-progress-h60GPV27.mjs} +3 -3
  10. package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +16 -16
  13. package/dist/collector-run.mjs +4 -4
  14. package/dist/{command-output-D5z2_bcF.mjs → command-output-BWCj5Vh-.mjs} +1 -1
  15. package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
  16. package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
  17. package/dist/{deps-OF7CD5Dx.mjs → deps-CO-_7UOm.mjs} +2 -2
  18. package/dist/{device-BwH2iI3O.mjs → device-9r9xRYDa.mjs} +7 -7
  19. package/dist/{device-lease-D83nqeMX.mjs → device-lease-B9LAXIUH.mjs} +5 -5
  20. package/dist/{device-pool-DHj7cdPy.mjs → device-pool-BoezJd-c.mjs} +4 -4
  21. package/dist/{device-remote-BQZSnnmE.mjs → device-remote-C0_o-tJH.mjs} +254 -579
  22. package/dist/{doctor-D8QdMRMf.mjs → doctor-LfDlbR-N.mjs} +19 -18
  23. package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-CCxjn31V.mjs} +7 -7
  24. package/dist/{gc-COZteyiq.mjs → gc-C4nQFcTX.mjs} +203 -39
  25. package/dist/{guide-NDvTmMi-.mjs → guide-BPBlh9ML.mjs} +189 -38
  26. package/dist/{guide-status-CWvti2Lg.mjs → guide-status-i9ogHt4v.mjs} +2 -2
  27. package/dist/{idle-NETbeNiQ.mjs → idle-S3tFZ53_.mjs} +7 -7
  28. package/dist/{in-use-DVhlSoyn.mjs → in-use-D9XYEgFD.mjs} +7 -7
  29. package/dist/{ios-CQLKpNfr.mjs → ios-CDzWL1mj.mjs} +30 -29
  30. package/dist/{ios-DArbmwer.mjs → ios-CZWOHTTc.mjs} +23 -19
  31. package/dist/{ios-device-CwDZo0X4.mjs → ios-device-D9cLxVr5.mjs} +1 -1
  32. package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-B2oR_kFN.mjs} +2 -2
  33. package/dist/{logs-B44a1UeU.mjs → logs-SzuYudOj.mjs} +6 -6
  34. package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
  35. package/dist/{metro-BWLOJ_Wr.mjs → metro-Dzo30hvJ.mjs} +1 -1
  36. package/dist/{named-ports-Dc7neAlc.mjs → named-ports-Dn1o61D9.mjs} +3 -3
  37. package/dist/{ownership-BiojdtAp.mjs → ownership-B8d4kXBx.mjs} +49 -16
  38. package/dist/{ownership-D2xbLVoz.mjs → ownership-Dqnrjrio.mjs} +2 -2
  39. package/dist/{ports-DwBaRFJs.mjs → ports-Dp_QTwTC.mjs} +3 -3
  40. package/dist/{project-BXZzOABt.mjs → project-B8rbutVC.mjs} +4 -72
  41. package/dist/{reload-By9Dwsrt.mjs → reload-nocmx8KB.mjs} +9 -9
  42. package/dist/remote-cache-BRsRZru5.mjs +526 -0
  43. package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Gn3ssSdK.mjs} +1 -1
  44. package/dist/{server-expo-o6H5gcBc.mjs → server-expo-CeT1nIIk.mjs} +2 -2
  45. package/dist/server-expo-eWi9Coof.mjs +2 -0
  46. package/dist/{settings-B5oFZEri.mjs → settings-BlyDn2Oz.mjs} +2 -2
  47. package/dist/{settings-C0ugALSC.mjs → settings-bE-Gu3Ti.mjs} +23 -11
  48. package/dist/settings.schema.json +22 -2
  49. package/dist/{simslim-CIR3IYJO.mjs → simslim-COtEEP7x.mjs} +6 -5
  50. package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-DIACGEfH.mjs} +4 -4
  51. package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
  52. package/dist/{start-i8zc9VmS.mjs → start-BFstVBlx.mjs} +1 -1
  53. package/dist/{start-BSsf2tUs.mjs → start-BboE1HIS.mjs} +108 -17
  54. package/dist/{state-WGdIbi_d.mjs → state-DcDgzfu3.mjs} +15 -3
  55. package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
  56. package/dist/{stats-DBYJ5bxL.mjs → stats-DiCtmluY.mjs} +4 -4
  57. package/dist/{status-Q-WDEGCB.mjs → status-BUASYyy_.mjs} +73 -28
  58. package/dist/{status-CitcBRsC.mjs → status-DI9Tjo7T.mjs} +41 -29
  59. package/dist/stim-desktop-D0xhpZt8.mjs +29 -0
  60. package/dist/{stop-rst5mL14.mjs → stop-BI7QY76M.mjs} +2 -2
  61. package/dist/{stop-DIhrvu0B.mjs → stop-Bn6v-GvS.mjs} +23 -16
  62. package/dist/supervisor-run.mjs +11 -11
  63. package/dist/{workspace-process-lock-Cx74RSDZ.mjs → workspace-process-lock-BvvJTfo_.mjs} +2 -2
  64. package/dist/{worktree-DhxDTK44.mjs → worktree-B5sTRskL.mjs} +1 -1
  65. package/dist/{worktree-KOjMX3zA.mjs → worktree-ChDL5fO1.mjs} +17 -16
  66. package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
  67. package/dist/{xcode-B5SPD--o.mjs → xcode-BzplpQzc.mjs} +7 -7
  68. package/package.json +5 -5
  69. package/dist/budget-De3P2rq_.mjs +0 -620
  70. package/dist/native-run-D7yB_AsB.mjs +0 -143
  71. package/dist/server-expo-B-CCUJZj.mjs +0 -2
@@ -1,25 +1,78 @@
1
- import { A as directorySize, H as projectDeviceSlots, M as isOnMountedVolume, N as listMountedVolumes, P as measuredDirectorySize, T as withConfigLock$1, f as getProject, g as loadConfig$1, h as isPathPrefix, i as clearDevice, j as formatBytes, u as getConfigDir, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
2
- import { i as registeredCaches } from "./cache-manifest-Pubfp8wV.mjs";
3
- import { M as workspaceName$1, v as workspaceLastUsed } from "./metro-BWLOJ_Wr.mjs";
4
- import { u as plural } from "./command-output-D5z2_bcF.mjs";
5
- import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-D83nqeMX.mjs";
6
- import { D as listAvds, N as ownedAvdDirectory, Y as isStimOwnedSim, w as isStimOwnedAvdName } from "./android-C4zG2Tuc.mjs";
7
- import { _ as dropParked, b as readParked, d as teardownOwnedAvd, f as teardownOwnedIosSim, m as teardownParkedIosSim, p as teardownParkedAvd } from "./ownership-BiojdtAp.mjs";
8
- import { d as listIosDeviceTypes, g as parseRuntimeVersion, l as listAllIosSims } from "./ios-DArbmwer.mjs";
9
- import { i as canonicalPath, n as withIdleWorkspace, r as workspaceInUse, t as emptyWorkspaceDir } from "./in-use-DVhlSoyn.mjs";
10
- import { existsSync, lstatSync, readdirSync, realpathSync, rmSync, statSync } from "fs";
1
+ import { B as deviceSlotPlatforms, E as withConfigLock$1, F as measuredDirectorySize, I as volumeRootFor, M as formatBytes, N as isOnMountedVolume, O as process_identity_exports, P as listMountedVolumes, U as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, d as getConfigDir, g as isPathPrefix, j as directorySize, o as clearSupervisor, p as getProject } from "./config-0jCcMM0E.mjs";
2
+ import { i as registeredCaches } from "./cache-manifest-DWPF4RKJ.mjs";
3
+ import { M as workspaceName$1, O as workspaceDir, S as sharedBuildCache, m as readWorkspaceState$1, s as resolveProjectMetro, t as killMetroTree, v as workspaceLastUsed } from "./metro-Dzo30hvJ.mjs";
4
+ import { n as supervisorError } from "./errors-B6W7FkxQ.mjs";
5
+ import { s as clearWorkspaceSupervisor } from "./state-DcDgzfu3.mjs";
6
+ import { n as workspaceProcessLockError, t as withWorkspaceProcessLock } from "./workspace-process-lock-BvvJTfo_.mjs";
7
+ import { l as phaseLine, u as plural } from "./command-output-BWCj5Vh-.mjs";
8
+ import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-B9LAXIUH.mjs";
9
+ import { a as workspaceActivity } from "./activity-CCZJs__P.mjs";
10
+ import { L as ownedAvdSerialResolver, O as listAvds, P as ownedAvdDirectory, Q as isStimOwnedSim, T as isStimOwnedAvdName } from "./android-75Sk2rIR.mjs";
11
+ import { _ as dropParked, b as readParked, d as teardownOwnedAvd, f as teardownOwnedIosSim, m as teardownParkedIosSim, p as teardownParkedAvd, t as resolveSupervisorTarget } from "./ownership-B8d4kXBx.mjs";
12
+ import { d as listIosDeviceTypes, g as parseRuntimeVersion, l as listAllIosSims } from "./ios-CZWOHTTc.mjs";
13
+ import { i as idleShutdownCandidates, n as collectOwnedDeviceActivity, o as shutDownIdleDevices, s as workspaceBuildInProgress, t as collectIdleDevices } from "./idle-S3tFZ53_.mjs";
14
+ import { i as canonicalPath, n as withIdleWorkspace, r as workspaceInUse, t as emptyWorkspaceDir } from "./in-use-D9XYEgFD.mjs";
15
+ import { c as formatSpace, r as capacity, s as environmentState } from "./status-BUASYyy_.mjs";
16
+ import { existsSync, statSync, statfsSync } from "node:fs";
17
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
11
18
  import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
12
19
  import { METRO_NAMED_CACHE_LAYOUT } from "@stim-cli/core";
13
- import { isJsonObject, readJsonFile } from "@stim-cli/core/state";
20
+ import { isJsonObject, readJsonFile, settingDefinition, settingValueError } from "@stim-cli/core/state";
21
+ import { dirname as dirname$1, join as join$1, sep as sep$1 } from "node:path";
14
22
  import { homedir, tmpdir } from "os";
23
+ import { homedir as homedir$1, platform, totalmem as totalmem$1 } from "node:os";
15
24
  import chalk from "chalk";
25
+ //#region src/supervisor/cache-reset.ts
26
+ async function stopOwnedMetro(root) {
27
+ const project = getProject(root);
28
+ const port = project?.metroPort;
29
+ const target = resolveSupervisorTarget({
30
+ state: readWorkspaceState$1(root)?.supervisor,
31
+ record: project?.supervisor,
32
+ reservedPort: port
33
+ });
34
+ if (target.status === "unverified") return {
35
+ status: "unverified",
36
+ reason: target.reason ?? "unknown identity"
37
+ };
38
+ if (port && target.status !== "ours" && !(await resolveProjectMetro(port, root)).missing) return {
39
+ status: "external",
40
+ reason: `a dev server Stim did not start answers on port ${port}`
41
+ };
42
+ if (target.status !== "ours") return { status: "not-running" };
43
+ const expected = {
44
+ pid: target.pid,
45
+ processToken: target.processToken
46
+ };
47
+ if (!killMetroTree(target.pid, target.processToken) || !await (0, process_identity_exports.waitForProcessExit)(expected, 1e4)) return {
48
+ status: "stuck",
49
+ reason: `supervisor pid ${target.pid} did not exit`
50
+ };
51
+ if (port && !(await resolveProjectMetro(port, root)).missing) return {
52
+ status: "port-occupied",
53
+ reason: `port ${port} is still occupied after stopping Metro`
54
+ };
55
+ clearWorkspaceSupervisor(root, expected);
56
+ clearSupervisor(root, expected);
57
+ return { status: "stopped" };
58
+ }
59
+ async function stopOwnedMetroForReset(root) {
60
+ const stopped = await stopOwnedMetro(root);
61
+ switch (stopped.status) {
62
+ 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.");
63
+ 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`.");
64
+ 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.");
65
+ 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.");
66
+ }
67
+ }
68
+ //#endregion
16
69
  //#region src/cache/caches.ts
17
70
  function xcodeDerivedDataRoot() {
18
71
  return join(homedir(), "Library", "Developer", "Xcode", "DerivedData");
19
72
  }
20
73
  function compilationCache() {
21
74
  const dir = join(xcodeDerivedDataRoot(), "CompilationCache.noindex");
22
- if (!existsSync(dir)) return null;
75
+ if (!existsSync$1(dir)) return null;
23
76
  return {
24
77
  name: "Xcode compilation cache",
25
78
  dir,
@@ -30,7 +83,7 @@ function compilationCache() {
30
83
  function gradleBuildCache() {
31
84
  const gradleHome = process.env.GRADLE_USER_HOME || join(homedir(), ".gradle");
32
85
  const dir = resolve(gradleHome, "caches", "build-cache-1");
33
- if (!existsSync(dir)) return null;
86
+ if (!existsSync$1(dir)) return null;
34
87
  return {
35
88
  name: "Gradle build cache",
36
89
  dir,
@@ -40,10 +93,10 @@ function gradleBuildCache() {
40
93
  }
41
94
  function metroFileMaps() {
42
95
  const root = tmpdir();
43
- if (!existsSync(root)) return null;
96
+ if (!existsSync$1(root)) return null;
44
97
  let names;
45
98
  try {
46
- names = readdirSync(root).filter((n) => n.startsWith("metro-file-map-"));
99
+ names = readdirSync$1(root).filter((n) => n.startsWith("metro-file-map-"));
47
100
  } catch {
48
101
  return null;
49
102
  }
@@ -51,7 +104,7 @@ function metroFileMaps() {
51
104
  const files = [];
52
105
  let bytes = 0;
53
106
  for (const n of names) try {
54
- const st = statSync(join(root, n));
107
+ const st = statSync$1(join(root, n));
55
108
  if (uid !== void 0 && st.uid !== uid) continue;
56
109
  files.push(join(root, n));
57
110
  bytes += st.size;
@@ -168,7 +221,7 @@ function cachePathContains(parent, child) {
168
221
  }
169
222
  function canonicalCacheDir(dir) {
170
223
  try {
171
- return realpathSync(dir);
224
+ return realpathSync$1(dir);
172
225
  } catch {
173
226
  return resolve(dir);
174
227
  }
@@ -196,7 +249,7 @@ function pruneCache(cache, { olderThanDays, now = Date.now() } = {}) {
196
249
  let used;
197
250
  let size;
198
251
  try {
199
- const st = statSync(entry);
252
+ const st = statSync$1(entry);
200
253
  used = Math.max(st.atimeMs, st.mtimeMs);
201
254
  size = st.isDirectory() ? directorySize(entry) : st.size;
202
255
  } catch {
@@ -204,7 +257,7 @@ function pruneCache(cache, { olderThanDays, now = Date.now() } = {}) {
204
257
  }
205
258
  if (used >= cutoff) continue;
206
259
  try {
207
- rmSync(entry, {
260
+ rmSync$1(entry, {
208
261
  recursive: true,
209
262
  force: true
210
263
  });
@@ -228,7 +281,7 @@ function entriesAtDepth(dir, depth) {
228
281
  for (const parent of level) {
229
282
  let names;
230
283
  try {
231
- names = readdirSync(parent);
284
+ names = readdirSync$1(parent);
232
285
  } catch {
233
286
  continue;
234
287
  }
@@ -313,7 +366,7 @@ function emptyCache(cache) {
313
366
  });
314
367
  let names;
315
368
  try {
316
- names = readdirSync(cache.dir);
369
+ names = readdirSync$1(cache.dir);
317
370
  } catch (e) {
318
371
  return {
319
372
  removed: 0,
@@ -324,7 +377,7 @@ function emptyCache(cache) {
324
377
  let removed = 0;
325
378
  let failed = 0;
326
379
  for (const name of names) try {
327
- rmSync(join(cache.dir, name), {
380
+ rmSync$1(join(cache.dir, name), {
328
381
  recursive: true,
329
382
  force: true
330
383
  });
@@ -383,7 +436,7 @@ function emptyCaches(caches) {
383
436
  }
384
437
  //#endregion
385
438
  //#region src/commands/gc/devices.ts
386
- const DEVICE_LIST_TIMEOUT_MS = 3e4;
439
+ const DEVICE_LIST_TIMEOUT_MS$1 = 3e4;
387
440
  const AVD_SIZE_TIMEOUT_MS = 5e3;
388
441
  const DAY_MS$1 = 864e5;
389
442
  function describeKept(ref) {
@@ -579,7 +632,7 @@ function collectParkedSims(deps, age = {}) {
579
632
  let deviceTypes = [];
580
633
  try {
581
634
  sims = (deps.listAllIosSims ?? listAllIosSims)({
582
- timeoutMs: DEVICE_LIST_TIMEOUT_MS,
635
+ timeoutMs: DEVICE_LIST_TIMEOUT_MS$1,
583
636
  includeUnavailable: true
584
637
  });
585
638
  } catch {
@@ -690,7 +743,7 @@ function collectDeviceLeases(now) {
690
743
  expired.push(entry);
691
744
  continue;
692
745
  }
693
- if (!existsSync(lease.holder)) kept.push({
746
+ if (!existsSync$1(lease.holder)) kept.push({
694
747
  name: entry.name,
695
748
  path: entry.path,
696
749
  reason: `${lease.holder} is not on this machine, but the lease runs until ${lease.expiresAt}`
@@ -785,7 +838,7 @@ function listWorkspaceDirs() {
785
838
  const root = workspacesRoot();
786
839
  let names;
787
840
  try {
788
- names = readdirSync(root).toSorted();
841
+ names = readdirSync$1(root).toSorted();
789
842
  } catch {
790
843
  return [];
791
844
  }
@@ -793,7 +846,7 @@ function listWorkspaceDirs() {
793
846
  for (const name of names) {
794
847
  const dir = join(root, name);
795
848
  try {
796
- if (!statSync(dir).isDirectory()) continue;
849
+ if (!statSync$1(dir).isDirectory()) continue;
797
850
  } catch {
798
851
  continue;
799
852
  }
@@ -852,7 +905,7 @@ function classifyWorkspaceDirs(entries, { registryKeys, exists, isMounted, inUse
852
905
  }
853
906
  function rootPresence(path) {
854
907
  try {
855
- lstatSync(path);
908
+ lstatSync$1(path);
856
909
  return true;
857
910
  } catch (error) {
858
911
  const code = error?.code;
@@ -899,7 +952,7 @@ function sizeOf(dir) {
899
952
  return measuredDirectorySize(dir, { timeoutMs: SIZE_TIMEOUT_MS });
900
953
  }
901
954
  function outputPaths(dir) {
902
- return WORKSPACE_OUTPUT_DIRS.map((name) => join(dir, name)).filter((path) => existsSync(path));
955
+ return WORKSPACE_OUTPUT_DIRS.map((name) => join(dir, name)).filter((path) => existsSync$1(path));
903
956
  }
904
957
  function outputBytes(paths) {
905
958
  let total = 0;
@@ -947,7 +1000,7 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }) {
947
1000
  now
948
1001
  });
949
1002
  if (again?.keptReason) return again.keptReason;
950
- for (const name of WORKSPACE_OUTPUT_DIRS) rmSync(join(entry.dir, name), {
1003
+ for (const name of WORKSPACE_OUTPUT_DIRS) rmSync$1(join(entry.dir, name), {
951
1004
  recursive: true,
952
1005
  force: true
953
1006
  });
@@ -980,7 +1033,7 @@ function stillOrphaned({ dir, projectRoot }) {
980
1033
  async function deleteOrphanedWorkspaces(orphaned) {
981
1034
  let failures = 0;
982
1035
  for (const entry of orphaned) {
983
- if (!existsSync(entry.dir)) continue;
1036
+ if (!existsSync$1(entry.dir)) continue;
984
1037
  let run;
985
1038
  try {
986
1039
  run = await withIdleWorkspace(entry.projectRoot, () => {
@@ -1010,4 +1063,560 @@ function sizeText(bytes) {
1010
1063
  return bytes === null ? "size unknown" : formatBytes(bytes);
1011
1064
  }
1012
1065
  //#endregion
1013
- export { discoverCaches as A, unverifiedDeviceCommand as C, planCacheEmptying as D, includesWorkspaceOutputs as E, selectCaches as O, findStaleProjectDevices as S, emptyCaches as T, describeParkedSims as _, collectWorkspaceOutputs as a, findOrphanedDevices as b, listWorkspaceDirs as c, collectDeviceLeases as d, collectParkedAvds as f, deleteProjectDevices as g, deleteParkedSims as h, collectOrphanedWorkspaces as i, sizeCaches as j, trimCaches as k, sizeText as l, deleteParkedAvds as m, WORKSPACE_OUTPUT_DIRS as n, deleteOrphanedWorkspaces as o, collectParkedSims as p, clearWorkspaceOutputs as r, isInsideWorkspaces as s, REBUILD_COST as t, DEVICE_LIST_TIMEOUT_MS as u, describeUnverifiableDevices as v, withAndroidAvdSizes as w, findStaleDeviceRecords as x, deviceSweepIsScoped as y };
1066
+ //#region src/budget.ts
1067
+ const MB = 1048576;
1068
+ const GB_IN_MB = 1024;
1069
+ const COMMITTED_MEMORY_SHARE = .6;
1070
+ const STALE_CACHE_DAYS = 14;
1071
+ const DEVICE_LIST_TIMEOUT_MS = 1e4;
1072
+ const USAGE_TIMEOUT_MS = 1e4;
1073
+ 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.";
1074
+ function valueAt(source, key) {
1075
+ let node = source;
1076
+ for (const part of key.split(".")) {
1077
+ if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
1078
+ node = node[part];
1079
+ }
1080
+ return node;
1081
+ }
1082
+ function machineNumber(key, config, env) {
1083
+ const setting = settingDefinition(key);
1084
+ const fromEnv = setting.env ? env[setting.env] : void 0;
1085
+ if (fromEnv !== void 0 && fromEnv !== "") {
1086
+ const value = fromEnv.trim() === "" ? NaN : Number(fromEnv);
1087
+ const problem = settingValueError(setting, value);
1088
+ return problem ? {
1089
+ value: void 0,
1090
+ error: `Invalid ${setting.env} value ${JSON.stringify(fromEnv)}. Expected ${problem}.`
1091
+ } : {
1092
+ value,
1093
+ error: null
1094
+ };
1095
+ }
1096
+ if (env.STIM_HOME && setting.scopedHomeValue !== void 0) return {
1097
+ value: setting.scopedHomeValue,
1098
+ error: null
1099
+ };
1100
+ const raw = valueAt(config, key);
1101
+ if (raw === void 0) return {
1102
+ value: typeof setting.default === "number" ? setting.default : void 0,
1103
+ error: null
1104
+ };
1105
+ const problem = settingValueError(setting, raw);
1106
+ return problem ? {
1107
+ value: void 0,
1108
+ error: `Invalid ${key} value ${JSON.stringify(raw)}. Expected ${problem}.`
1109
+ } : {
1110
+ value: raw,
1111
+ error: null
1112
+ };
1113
+ }
1114
+ function resolveBudget({ config = loadConfig$1(), env = process.env, totalMemoryMb = totalmem$1() / MB } = {}) {
1115
+ const minFree = machineNumber("budget.minFreeDiskGb", config, env);
1116
+ const hardFloor = machineNumber("budget.hardFloorDiskGb", config, env);
1117
+ const memory = machineNumber("budget.maxCommittedMemoryGb", config, env);
1118
+ const live = machineNumber("budget.maxLiveWorkspaces", config, env);
1119
+ return {
1120
+ budget: {
1121
+ minFreeDiskMb: (minFree.value ?? 0) * GB_IN_MB,
1122
+ hardFloorDiskMb: (hardFloor.value ?? 0) * GB_IN_MB,
1123
+ maxCommittedMemoryMb: memory.value === void 0 ? Math.round(totalMemoryMb * COMMITTED_MEMORY_SHARE) : memory.value * GB_IN_MB,
1124
+ maxLiveWorkspaces: live.value ?? 0
1125
+ },
1126
+ error: minFree.error ?? hardFloor.error ?? memory.error ?? live.error
1127
+ };
1128
+ }
1129
+ function existingAncestor(path) {
1130
+ let current = path;
1131
+ while (!existsSync(current) && dirname$1(current) !== current) current = dirname$1(current);
1132
+ return current;
1133
+ }
1134
+ function readVolumeSpace(paths) {
1135
+ const seen = /* @__PURE__ */ new Set();
1136
+ const volumes = [];
1137
+ for (const path of paths) try {
1138
+ const existing = existingAncestor(path);
1139
+ const device = statSync(existing).dev;
1140
+ if (seen.has(device)) continue;
1141
+ seen.add(device);
1142
+ const fs = statfsSync(existing);
1143
+ volumes.push({
1144
+ volume: volumeRootFor(path),
1145
+ freeMb: fs.bavail * fs.bsize / MB
1146
+ });
1147
+ } catch {}
1148
+ return volumes;
1149
+ }
1150
+ function estimateMemoryUse({ config, sims, androidSerial, supervisorRunning }) {
1151
+ const simsByUdid = Object.fromEntries((sims ?? []).map((sim) => [sim.udid, sim]));
1152
+ const runtime = (avdName) => {
1153
+ if (!avdName) return null;
1154
+ const serial = androidSerial(avdName);
1155
+ return {
1156
+ serial,
1157
+ state: serial ? "detected" : "not-detected"
1158
+ };
1159
+ };
1160
+ const states = Object.entries(config?.projects ?? {}).map(([path, project]) => {
1161
+ const pid = supervisorRunning(path, project);
1162
+ const slots = projectDeviceSlots(project);
1163
+ return environmentState({
1164
+ ...project,
1165
+ __path: path
1166
+ }, {
1167
+ simsByUdid,
1168
+ simsAvailable: sims !== null,
1169
+ metro: pid === null ? { missing: true } : { metro: { pid } },
1170
+ androidRuntime: project.platforms?.android?.owned ? runtime(project.platforms.android.avdName) : null,
1171
+ androidRuntimes: Object.fromEntries(slots.slice(1).map(({ slot, platforms }) => [slot, platforms.android?.owned ? runtime(platforms.android.avdName) : null]))
1172
+ });
1173
+ });
1174
+ const { committedMb, liveCount } = capacity(states, 0);
1175
+ return {
1176
+ committedMb,
1177
+ liveWorkspaces: liveCount
1178
+ };
1179
+ }
1180
+ function listSims() {
1181
+ try {
1182
+ return listAllIosSims({ timeoutMs: DEVICE_LIST_TIMEOUT_MS });
1183
+ } catch {
1184
+ return null;
1185
+ }
1186
+ }
1187
+ function runningSupervisorPid(path, project) {
1188
+ const target = resolveSupervisorTarget({
1189
+ state: readWorkspaceState$1(path)?.supervisor,
1190
+ record: project.supervisor,
1191
+ reservedPort: project.metroPort
1192
+ });
1193
+ return target.status === "ours" ? target.pid ?? null : null;
1194
+ }
1195
+ function readMemoryUse() {
1196
+ const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
1197
+ return estimateMemoryUse({
1198
+ config: loadConfig$1(),
1199
+ sims: listSims(),
1200
+ androidSerial: (avdName) => {
1201
+ try {
1202
+ return resolve(avdName).serial ?? null;
1203
+ } catch {
1204
+ return null;
1205
+ }
1206
+ },
1207
+ supervisorRunning: runningSupervisorPid
1208
+ });
1209
+ }
1210
+ function budgetShortfalls(budget, measure) {
1211
+ const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
1212
+ return {
1213
+ disk: floor > 0 ? measure.volumes.filter((v) => v.freeMb < floor) : [],
1214
+ hardFloor: budget.hardFloorDiskMb > 0 ? measure.volumes.filter((v) => v.freeMb < budget.hardFloorDiskMb) : [],
1215
+ memory: Boolean(budget.maxCommittedMemoryMb > 0 && measure.memory && measure.memory.committedMb > budget.maxCommittedMemoryMb),
1216
+ workspaces: Boolean(budget.maxLiveWorkspaces > 0 && measure.memory && measure.memory.liveWorkspaces > budget.maxLiveWorkspaces)
1217
+ };
1218
+ }
1219
+ const DISK_ONLY = ["workspace-outputs", "stale-cache-entries"];
1220
+ function reclaimPlan(short) {
1221
+ const disk = short.disk.length > 0;
1222
+ return [...disk || short.memory || short.workspaces ? ["idle-devices", "idle-dev-servers"] : [], ...disk ? DISK_ONLY : []];
1223
+ }
1224
+ function overBudget(short) {
1225
+ return short.disk.length > 0 || short.memory || short.workspaces;
1226
+ }
1227
+ function isOtherWorkspace(root) {
1228
+ const self = canonicalPath(root);
1229
+ return (path) => canonicalPath(path) !== self;
1230
+ }
1231
+ function deviceLabel(device) {
1232
+ return `${device.kind} ${device.name} in ${device.project}`;
1233
+ }
1234
+ function reclaimIdleDevices({ root, dryRun, note, now }) {
1235
+ if (deviceSweepIsScoped()) {
1236
+ note(chalk.dim(phaseLine("budget", "idle devices left alone: STIM_HOME scopes this config, devices are global")));
1237
+ return {
1238
+ targets: [],
1239
+ failures: 0
1240
+ };
1241
+ }
1242
+ const other = isOtherWorkspace(root);
1243
+ const collect = (at) => idleShutdownCandidates(collectIdleDevices(loadConfig$1(), listSims() ?? [], [], at).filter((device) => other(device.project)), 0);
1244
+ const idle = collect(now);
1245
+ if (dryRun || idle.length === 0) return {
1246
+ targets: idle.map(deviceLabel),
1247
+ failures: 0
1248
+ };
1249
+ const { failures, shutDown } = shutDownIdleDevices(idle, 0, () => collect(Date.now()));
1250
+ return {
1251
+ targets: shutDown.map(deviceLabel),
1252
+ failures
1253
+ };
1254
+ }
1255
+ function idleSince(path, now) {
1256
+ const activity = workspaceActivity(path, now);
1257
+ const at = Date.parse(activity.lastActivityAt ?? "");
1258
+ return activity.state === "idle" && Number.isFinite(at) ? at : null;
1259
+ }
1260
+ async function reclaimIdleDevServers({ root, dryRun, note, now }) {
1261
+ const config = loadConfig$1();
1262
+ const other = isOtherWorkspace(root);
1263
+ const serving = Object.entries(config?.projects ?? {}).flatMap(([path, project]) => {
1264
+ const pid = other(path) ? runningSupervisorPid(path, project) : null;
1265
+ return pid !== null && !workspaceBuildInProgress(path) && idleSince(path, now) !== null ? [{
1266
+ path,
1267
+ pid
1268
+ }] : [];
1269
+ });
1270
+ if (serving.length === 0) return {
1271
+ targets: [],
1272
+ failures: 0
1273
+ };
1274
+ const sims = listSims();
1275
+ if (sims === null) {
1276
+ note(chalk.dim(phaseLine("budget", "dev servers left alone: simulators could not be listed to prove them idle")));
1277
+ return {
1278
+ targets: [],
1279
+ failures: 0
1280
+ };
1281
+ }
1282
+ const busy = new Set(collectOwnedDeviceActivity(config, sims, now).filter((device) => device.activity.state !== "idle").map((device) => device.project));
1283
+ const idle = serving.filter(({ path }) => !busy.has(path));
1284
+ if (dryRun) return {
1285
+ targets: idle.map(({ path }) => path),
1286
+ failures: 0
1287
+ };
1288
+ const targets = [];
1289
+ let failures = 0;
1290
+ const stillIdle = (path, pid) => {
1291
+ const project = loadConfig$1()?.projects?.[path];
1292
+ return Boolean(project && runningSupervisorPid(path, project) === pid && !workspaceBuildInProgress(path) && idleSince(path, Date.now()) !== null);
1293
+ };
1294
+ for (const { path, pid } of idle) {
1295
+ let run;
1296
+ try {
1297
+ run = await withIdleWorkspace(path, async () => {
1298
+ try {
1299
+ return await withWorkspaceProcessLock(workspaceDir(path), "metro-start", async () => stillIdle(path, pid) ? stopOwnedMetro(path) : "busy", {
1300
+ external: true,
1301
+ waitMs: 0,
1302
+ ownerPurpose: "budget reclaim"
1303
+ });
1304
+ } catch (error) {
1305
+ if (workspaceProcessLockError(error)) return "starting";
1306
+ throw error;
1307
+ }
1308
+ }, {
1309
+ purpose: "budget reclaim",
1310
+ supervisor: false
1311
+ });
1312
+ } catch (error) {
1313
+ failures++;
1314
+ note(chalk.red(`Could not stop the dev server of ${path}: ${error?.message || error}`));
1315
+ continue;
1316
+ }
1317
+ if (!run.ran) {
1318
+ note(chalk.yellow(`Kept the dev server of ${path}: ${run.reasons.join("; ")}`));
1319
+ continue;
1320
+ }
1321
+ const stopped = run.value;
1322
+ if (stopped === "busy" || stopped === "starting") {
1323
+ const why = stopped === "busy" ? "it is no longer idle" : "a stim start holds its metro-start lock";
1324
+ note(chalk.dim(`Kept the dev server of ${path}: ${why}`));
1325
+ } else if (stopped.status === "stopped") {
1326
+ targets.push(path);
1327
+ note(chalk.green(`Stopped the idle dev server of ${path}`));
1328
+ } else if ("reason" in stopped) {
1329
+ if (stopped.status === "stuck") failures++;
1330
+ note(chalk.yellow(`Kept the dev server of ${path}: ${stopped.reason}`));
1331
+ }
1332
+ }
1333
+ return {
1334
+ targets,
1335
+ failures
1336
+ };
1337
+ }
1338
+ async function reclaimWorkspaceOutputs({ root, dryRun, now, diskRecovered }) {
1339
+ const other = isOtherWorkspace(root);
1340
+ const report = collectWorkspaceOutputs({
1341
+ olderThan: null,
1342
+ now,
1343
+ measure: false
1344
+ });
1345
+ const clearable = report.workspaces.flatMap((entry) => {
1346
+ const project = entry.projectRoot;
1347
+ const since = project !== null && entry.willClear && other(project) ? idleSince(project, now) : null;
1348
+ return since === null ? [] : [{
1349
+ entry,
1350
+ since
1351
+ }];
1352
+ }).toSorted((a, b) => a.since - b.since).map(({ entry }) => entry);
1353
+ if (dryRun) return {
1354
+ targets: clearable.map((entry) => entry.projectRoot),
1355
+ failures: 0
1356
+ };
1357
+ const targets = [];
1358
+ let failures = 0;
1359
+ for (const entry of clearable) {
1360
+ const result = await clearWorkspaceOutputs({
1361
+ ...report,
1362
+ workspaces: [entry]
1363
+ }, {
1364
+ olderThan: null,
1365
+ now
1366
+ });
1367
+ failures += result.failures;
1368
+ targets.push(...result.cleared);
1369
+ if (diskRecovered()) break;
1370
+ }
1371
+ return {
1372
+ targets,
1373
+ failures
1374
+ };
1375
+ }
1376
+ function cacheLabel(cache) {
1377
+ return `${cache.name} ${cache.dir}`;
1378
+ }
1379
+ function reclaimStaleCacheEntries({ dryRun }) {
1380
+ const caches = planCacheEmptying(discoverCaches(), false).filter((cache) => cache.prune !== "report-only" && cache.prune !== "atomic" && !cache.machineGlobal);
1381
+ if (dryRun) return {
1382
+ targets: caches.map(cacheLabel),
1383
+ failures: 0
1384
+ };
1385
+ return {
1386
+ targets: trimCaches(caches, STALE_CACHE_DAYS).map(cacheLabel),
1387
+ failures: 0
1388
+ };
1389
+ }
1390
+ const STEPS = {
1391
+ "idle-devices": reclaimIdleDevices,
1392
+ "idle-dev-servers": reclaimIdleDevServers,
1393
+ "workspace-outputs": reclaimWorkspaceOutputs,
1394
+ "stale-cache-entries": reclaimStaleCacheEntries
1395
+ };
1396
+ const STEP_ACTIONS = {
1397
+ "idle-devices": "shut down idle owned devices",
1398
+ "idle-dev-servers": "stop idle dev servers",
1399
+ "workspace-outputs": "clear build outputs of idle workspaces",
1400
+ "stale-cache-entries": `trim cache entries unused for ${STALE_CACHE_DAYS} days`
1401
+ };
1402
+ async function runStep(step, context) {
1403
+ const log = console.log;
1404
+ const exitCode = process.exitCode;
1405
+ console.log = console.error;
1406
+ try {
1407
+ const result = await STEPS[step](context);
1408
+ return process.exitCode === exitCode ? result : {
1409
+ ...result,
1410
+ failures: result.failures + 1
1411
+ };
1412
+ } catch (error) {
1413
+ context.note(chalk.red(phaseLine("budget", `${STEP_ACTIONS[step]} failed: ${error?.message || error}`)));
1414
+ return {
1415
+ targets: [],
1416
+ failures: 1
1417
+ };
1418
+ } finally {
1419
+ console.log = log;
1420
+ process.exitCode = exitCode;
1421
+ }
1422
+ }
1423
+ function spaceUsers() {
1424
+ const home = homedir$1();
1425
+ const candidates = [
1426
+ ["Stim state", getConfigDir()],
1427
+ ["shared build cache", sharedBuildCache()],
1428
+ ...platform() === "darwin" ? [["simulators", join$1(home, "Library", "Developer", "CoreSimulator", "Devices")], ["Xcode DerivedData", join$1(home, "Library", "Developer", "Xcode", "DerivedData")]] : [],
1429
+ ["Android emulators", join$1(home, ".android", "avd")]
1430
+ ];
1431
+ const measured = /* @__PURE__ */ new Set();
1432
+ const users = [];
1433
+ for (const [label, path] of candidates) {
1434
+ const canonical = canonicalPath(path);
1435
+ if (measured.has(canonical) || [...measured].some((dir) => canonical.startsWith(`${dir}${sep$1}`))) continue;
1436
+ measured.add(canonical);
1437
+ if (!existsSync(path)) continue;
1438
+ const bytes = measuredDirectorySize(path, { timeoutMs: USAGE_TIMEOUT_MS });
1439
+ if (bytes) users.push({
1440
+ label: `${label} ${path}`,
1441
+ bytes
1442
+ });
1443
+ }
1444
+ return users.toSorted((a, b) => b.bytes - a.bytes).map(({ label, bytes }) => `${label}: ${formatBytes(bytes)}`);
1445
+ }
1446
+ const DEFAULT_DEPS = {
1447
+ volumes: readVolumeSpace,
1448
+ memory: readMemoryUse,
1449
+ step: runStep,
1450
+ usage: spaceUsers,
1451
+ now: Date.now
1452
+ };
1453
+ const LOW_DISK_REMEDY = "Run `stim gc` to see what else Stim can reclaim, then `stim gc --delete` (add `--cache all` to empty the shared caches, or `--worktrees` to remove finished worktrees). Free space outside Stim from the largest uses listed, or lower budget.hardFloorDiskGb.";
1454
+ function describeShortfall(budget, measure, short) {
1455
+ const floor = Math.max(budget.minFreeDiskMb, budget.hardFloorDiskMb);
1456
+ return [
1457
+ ...short.disk.map((v) => `${formatSpace(v.freeMb)} free on ${v.volume}, below the ${formatSpace(floor)} floor`),
1458
+ ...short.memory && measure.memory ? [`roughly ${formatSpace(measure.memory.committedMb)} of memory committed, over the ${formatSpace(budget.maxCommittedMemoryMb)} budget`] : [],
1459
+ ...short.workspaces && measure.memory ? [`${measure.memory.liveWorkspaces} live workspaces, over the limit of ${budget.maxLiveWorkspaces}`] : []
1460
+ ];
1461
+ }
1462
+ async function enforceBudget({ root, note, dryRun = false, budget: resolved }, overrides = {}) {
1463
+ const deps = {
1464
+ ...DEFAULT_DEPS,
1465
+ ...overrides
1466
+ };
1467
+ let budget = resolved;
1468
+ if (!budget) {
1469
+ const read = resolveBudget();
1470
+ if (read.error) return {
1471
+ status: "invalid",
1472
+ refusal: {
1473
+ code: "STIM_BAD_ARG",
1474
+ message: read.error,
1475
+ remedy: BUDGET_SETTING_REMEDY
1476
+ }
1477
+ };
1478
+ budget = read.budget;
1479
+ }
1480
+ const paths = [root, getConfigDir()];
1481
+ const measureNow = () => ({
1482
+ volumes: budget.minFreeDiskMb > 0 || budget.hardFloorDiskMb > 0 ? deps.volumes(paths) : [],
1483
+ memory: budget.maxCommittedMemoryMb > 0 || budget.maxLiveWorkspaces > 0 ? deps.memory() : null
1484
+ });
1485
+ let measure = measureNow();
1486
+ let short = budgetShortfalls(budget, measure);
1487
+ const reclaimed = [];
1488
+ const plan = reclaimPlan(short);
1489
+ if (plan.length) note(chalk.yellow(phaseLine("budget", `${describeShortfall(budget, measure, short).join("; ")}; ${dryRun ? "would reclaim" : "reclaiming"}`)));
1490
+ for (const step of plan) {
1491
+ if (!dryRun && (!overBudget(short) || DISK_ONLY.includes(step) && short.disk.length === 0)) break;
1492
+ const freeBefore = measure.volumes.reduce((sum, v) => sum + v.freeMb, 0);
1493
+ const result = await deps.step(step, {
1494
+ root,
1495
+ dryRun,
1496
+ note,
1497
+ now: deps.now(),
1498
+ diskRecovered: () => budgetShortfalls(budget, {
1499
+ volumes: deps.volumes(paths),
1500
+ memory: null
1501
+ }).disk.length === 0
1502
+ });
1503
+ if (!dryRun) {
1504
+ measure = measureNow();
1505
+ short = budgetShortfalls(budget, measure);
1506
+ }
1507
+ if (result.targets.length === 0 && result.failures === 0) continue;
1508
+ const freedMb = Math.max(0, Math.round(measure.volumes.reduce((sum, v) => sum + v.freeMb, 0) - freeBefore));
1509
+ reclaimed.push({
1510
+ step,
1511
+ ...result,
1512
+ ...dryRun ? {} : { freedMb }
1513
+ });
1514
+ const outcome = dryRun ? result.targets.join(", ") : `${result.targets.length}${result.failures ? `, ${result.failures} failed` : ""} (${formatSpace(freedMb)} freed)`;
1515
+ note(chalk.dim(phaseLine("budget", `${STEP_ACTIONS[step]}: ${outcome}`)));
1516
+ }
1517
+ if (!dryRun && short.hardFloor.length) {
1518
+ const tight = short.hardFloor.map((v) => `${formatSpace(v.freeMb)} on ${v.volume}`).join(" and ");
1519
+ const users = deps.usage();
1520
+ return {
1521
+ status: "refused",
1522
+ reclaimed,
1523
+ refusal: {
1524
+ code: "STIM_LOW_DISK",
1525
+ message: `Only ${tight} is free, below the ${formatSpace(budget.hardFloorDiskMb)} hard floor (budget.hardFloorDiskGb)${reclaimed.length ? " after reclaiming what Stim safely could" : ""}, so this run could fill the disk.` + (users.length ? ` Largest uses: ${users.join("; ")}.` : ""),
1526
+ remedy: LOW_DISK_REMEDY
1527
+ }
1528
+ };
1529
+ }
1530
+ if (!dryRun && overBudget(short)) note(chalk.yellow(phaseLine("budget", `still over budget: ${describeShortfall(budget, measure, short).join("; ")}`)));
1531
+ return {
1532
+ status: "ok",
1533
+ reclaimed,
1534
+ budget,
1535
+ measure,
1536
+ short
1537
+ };
1538
+ }
1539
+ async function budgetGate(args, overrides = {}) {
1540
+ const outcome = await enforceBudget(args, overrides);
1541
+ return {
1542
+ reclaimed: "reclaimed" in outcome ? outcome.reclaimed : [],
1543
+ refusal: outcome.status === "ok" ? null : outcome.refusal
1544
+ };
1545
+ }
1546
+ const gb = (mb) => Math.round(mb / GB_IN_MB * 10) / 10;
1547
+ function budgetFindings(budget, measure, short, plan) {
1548
+ const findings = [];
1549
+ const steps = plan.length ? plan.map((entry) => `${STEP_ACTIONS[entry.step]} (${entry.targets.join(", ")})`).join("; then ") : "nothing Stim can reclaim on its own right now";
1550
+ if (short.disk.length) {
1551
+ const refuses = short.hardFloor.length ? ` Below ${formatSpace(budget.hardFloorDiskMb)} it refuses with STIM_LOW_DISK when reclaiming does not free enough.` : "";
1552
+ findings.push({
1553
+ level: "cost",
1554
+ title: "Free disk is below the Stim budget",
1555
+ detail: `${describeShortfall(budget, measure, {
1556
+ ...short,
1557
+ memory: false,
1558
+ workspaces: false
1559
+ }).join("; ")}. The next ios, android or start will first ${steps}.${refuses}`,
1560
+ fix: "Run `stim gc` to see what Stim can reclaim, then `stim gc --delete`. Free space outside Stim, or lower budget.minFreeDiskGb."
1561
+ });
1562
+ }
1563
+ if (short.memory || short.workspaces) findings.push({
1564
+ level: "note",
1565
+ title: "Live environments are over the memory budget",
1566
+ detail: `${describeShortfall(budget, measure, {
1567
+ ...short,
1568
+ disk: []
1569
+ }).join("; ")}. The next ios, android or start shuts down idle owned devices and stops idle dev servers in other workspaces first.`,
1570
+ fix: "Run `stim status` to see what is live, and `stim stop` in workspaces you own that you no longer need."
1571
+ });
1572
+ return findings;
1573
+ }
1574
+ async function inspectBudget(root, deps = {}) {
1575
+ const read = resolveBudget();
1576
+ if (read.error) return {
1577
+ report: null,
1578
+ findings: [{
1579
+ level: "cost",
1580
+ title: "A budget setting is invalid",
1581
+ detail: read.error,
1582
+ fix: BUDGET_SETTING_REMEDY
1583
+ }]
1584
+ };
1585
+ const outcome = await enforceBudget({
1586
+ root,
1587
+ note: () => {},
1588
+ dryRun: true,
1589
+ budget: read.budget
1590
+ }, deps);
1591
+ if (outcome.status !== "ok") return {
1592
+ report: null,
1593
+ findings: []
1594
+ };
1595
+ const { budget, measure, short, reclaimed } = outcome;
1596
+ return {
1597
+ report: {
1598
+ budget: {
1599
+ minFreeDiskGb: gb(budget.minFreeDiskMb),
1600
+ hardFloorDiskGb: gb(budget.hardFloorDiskMb),
1601
+ maxCommittedMemoryGb: gb(budget.maxCommittedMemoryMb),
1602
+ maxLiveWorkspaces: budget.maxLiveWorkspaces
1603
+ },
1604
+ volumes: measure.volumes.map((v) => ({
1605
+ volume: v.volume,
1606
+ freeGb: gb(v.freeMb)
1607
+ })),
1608
+ memory: measure.memory ? {
1609
+ committedGb: gb(measure.memory.committedMb),
1610
+ liveWorkspaces: measure.memory.liveWorkspaces
1611
+ } : null,
1612
+ plan: reclaimed
1613
+ },
1614
+ findings: budgetFindings(budget, measure, short, reclaimed)
1615
+ };
1616
+ }
1617
+ function budgetLine(report) {
1618
+ const { budget } = report;
1619
+ 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"}`;
1620
+ }
1621
+ //#endregion
1622
+ export { includesWorkspaceOutputs as A, deviceSweepIsScoped as C, unverifiedDeviceCommand as D, findStaleProjectDevices as E, sizeCaches as F, stopOwnedMetroForReset as I, selectCaches as M, trimCaches as N, withAndroidAvdSizes as O, discoverCaches as P, describeUnverifiableDevices as S, findStaleDeviceRecords as T, collectParkedSims as _, REBUILD_COST as a, deleteProjectDevices as b, collectOrphanedWorkspaces as c, isInsideWorkspaces as d, listWorkspaceDirs as f, collectParkedAvds as g, collectDeviceLeases as h, machineNumber as i, planCacheEmptying as j, emptyCaches as k, collectWorkspaceOutputs as l, DEVICE_LIST_TIMEOUT_MS$1 as m, budgetLine as n, WORKSPACE_OUTPUT_DIRS as o, sizeText as p, inspectBudget as r, clearWorkspaceOutputs as s, budgetGate as t, deleteOrphanedWorkspaces as u, deleteParkedAvds as v, findOrphanedDevices as w, describeParkedSims as x, deleteParkedSims as y };