stim 1.12.0 → 1.13.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/README.md +3 -1
  2. package/dist/{activity-CCZJs__P.mjs → activity-DfVKZTEB.mjs} +42 -25
  3. package/dist/{android-BQiKJYK0.mjs → android-BHfAu2mf.mjs} +1 -1
  4. package/dist/{android-B7X-8H7h.mjs → android-CD8pr8Q2.mjs} +62 -47
  5. package/dist/{android-cas-DzLPXiEn.mjs → android-cas-B9Kv9aIy.mjs} +9 -7
  6. package/dist/{android-75Sk2rIR.mjs → android-qx_31wJo.mjs} +395 -116
  7. package/dist/{app-install-CsZi0SAT.mjs → app-install-CDFY2nUL.mjs} +1 -1
  8. package/dist/{budget-DbzW5VTD.mjs → budget-E5o91NR1.mjs} +154 -37
  9. package/dist/{build-plan-CqntbR7b.mjs → build-plan-BTa93o_r.mjs} +21 -88
  10. package/dist/{build-progress-h60GPV27.mjs → build-progress-l7d-X-ZP.mjs} +75 -7
  11. package/dist/cdp-Dq_ypAlJ.mjs +115 -0
  12. package/dist/chrome-B5hWgQJS.mjs +61 -0
  13. package/dist/cli.mjs +16 -15
  14. package/dist/collector-run.mjs +14 -8
  15. package/dist/{command-output-BWCj5Vh-.mjs → command-output-BIL7rlX7.mjs} +1 -1
  16. package/dist/created-devices-Cd558h31.mjs +37 -0
  17. package/dist/{deps-CO-_7UOm.mjs → deps-2Wo81np9.mjs} +1 -1
  18. package/dist/{device-9r9xRYDa.mjs → device-Cf7vl-so.mjs} +6 -6
  19. package/dist/{device-lease-B9LAXIUH.mjs → device-lease-CL9dZf73.mjs} +52 -3
  20. package/dist/{device-pool-BoezJd-c.mjs → device-pool-MuoiyRcu.mjs} +3 -3
  21. package/dist/{device-remote-C0_o-tJH.mjs → device-remote-DT9ngZiD.mjs} +10 -10
  22. package/dist/{doctor-LfDlbR-N.mjs → doctor-D2iysNNH.mjs} +47 -18
  23. package/dist/{error-diagnostics-CCxjn31V.mjs → error-diagnostics-BoiOhck8.mjs} +6 -6
  24. package/dist/{gc-C4nQFcTX.mjs → gc-eA-PRBX8.mjs} +853 -126
  25. package/dist/{guide-BPBlh9ML.mjs → guide-BfZ0OmiP.mjs} +736 -110
  26. package/dist/{guide-status-i9ogHt4v.mjs → guide-status-BaMYu55M.mjs} +1 -1
  27. package/dist/{idle-S3tFZ53_.mjs → idle-DxLi5OkN.mjs} +42 -9
  28. package/dist/{in-use-D9XYEgFD.mjs → in-use-wYEaKQM9.mjs} +9 -5
  29. package/dist/{ios-CDzWL1mj.mjs → ios-CcfCeZ19.mjs} +98 -54
  30. package/dist/{ios-CZWOHTTc.mjs → ios-CmEzZiOj.mjs} +10 -5
  31. package/dist/{ios-device-D9cLxVr5.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
  32. package/dist/{launch-verify-B2oR_kFN.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
  33. package/dist/{logs-SzuYudOj.mjs → logs-DcSRbC2t.mjs} +11 -10
  34. package/dist/{metro-Dzo30hvJ.mjs → metro-BPeY1YMh.mjs} +4 -4
  35. package/dist/{named-ports-Dn1o61D9.mjs → named-ports-n7tnJsI8.mjs} +78 -6
  36. package/dist/native-runtime-CiiFHt0e.mjs +74 -0
  37. package/dist/{ownership-Dqnrjrio.mjs → ownership-Bz2kETAS.mjs} +1 -1
  38. package/dist/{ownership-B8d4kXBx.mjs → ownership-PUP4Dwai.mjs} +134 -209
  39. package/dist/page-BTu7I60K.mjs +29 -0
  40. package/dist/{ports-Dp_QTwTC.mjs → ports-C5UutqpJ.mjs} +4 -4
  41. package/dist/{project-B8rbutVC.mjs → project-DGeclhvO.mjs} +53 -17
  42. package/dist/{reload-nocmx8KB.mjs → reload-C-cV5yy_.mjs} +79 -20
  43. package/dist/{remote-cache-BRsRZru5.mjs → remote-cache--aehr4Pr.mjs} +3 -3
  44. package/dist/{server-bare-Gn3ssSdK.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
  45. package/dist/server-expo-BeYwP_Xy.mjs +2 -0
  46. package/dist/{server-expo-CeT1nIIk.mjs → server-expo-DUCg52-K.mjs} +2 -2
  47. package/dist/{settings-bE-Gu3Ti.mjs → settings-ssVBToDR.mjs} +10 -10
  48. package/dist/{settings-BlyDn2Oz.mjs → settings-xB0Frkg6.mjs} +11 -2
  49. package/dist/settings.schema.json +53 -1
  50. package/dist/{simslim-COtEEP7x.mjs → simslim-Bbg9UWrt.mjs} +12 -6
  51. package/dist/{slot-launch-DIACGEfH.mjs → slot-launch-BIWEW-wI.mjs} +3 -3
  52. package/dist/{start-BboE1HIS.mjs → start-BZhItkiL.mjs} +11 -10
  53. package/dist/{start-BFstVBlx.mjs → start-Rw2GD3nV.mjs} +1 -1
  54. package/dist/{state-DcDgzfu3.mjs → state-DwAU2_-D.mjs} +1 -1
  55. package/dist/state-l1pYwOre.mjs +148 -0
  56. package/dist/{stats-DiCtmluY.mjs → stats-CaSwmsSZ.mjs} +2 -2
  57. package/dist/{status-DI9Tjo7T.mjs → status-TJlL0HWo.mjs} +624 -103
  58. package/dist/{status-BUASYyy_.mjs → status-sszP4XKU.mjs} +59 -2
  59. package/dist/{stim-desktop-D0xhpZt8.mjs → stim-desktop-CXt8-Z3J.mjs} +13 -2
  60. package/dist/{stop-Bn6v-GvS.mjs → stop-BZ2qMiBP.mjs} +37 -12
  61. package/dist/{stop-BI7QY76M.mjs → stop-DskDKNTU.mjs} +2 -2
  62. package/dist/supervisor-run.mjs +10 -10
  63. package/dist/web-run.d.mts +22 -0
  64. package/dist/web-run.mjs +481 -0
  65. package/dist/web-sWXd5iek.mjs +394 -0
  66. package/dist/{worktree-B5sTRskL.mjs → worktree-3cBa-bQt.mjs} +1 -1
  67. package/dist/{worktree-ChDL5fO1.mjs → worktree-D4Do_XiQ.mjs} +226 -19
  68. package/dist/{xcode-BzplpQzc.mjs → xcode-LM6A8cNZ.mjs} +5 -5
  69. package/package.json +5 -5
  70. package/dist/server-expo-eWi9Coof.mjs +0 -2
  71. package/dist/workspace-process-lock-BvvJTfo_.mjs +0 -57
@@ -1,5 +1,5 @@
1
1
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
- import { T as uninstallIosApp, c as iosSimulatorFailureAdvice, p as listUserApps } from "./ios-CZWOHTTc.mjs";
2
+ import { T as uninstallIosApp, c as iosSimulatorFailureAdvice, p as listUserApps } from "./ios-CmEzZiOj.mjs";
3
3
  import { closeSync, lstatSync, openSync, readSync, readdirSync } from "node:fs";
4
4
  import { isAbsolute, join } from "node:path";
5
5
  import { createHash } from "node:crypto";
@@ -1,18 +1,18 @@
1
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
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";
3
+ import { C as sharedBuildCache, N as workspaceName$1, c as resolveProjectMetro, h as readWorkspaceState$1, k as workspaceDir, n as killMetroTree, y as workspaceLastUsed } from "./metro-BPeY1YMh.mjs";
4
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";
5
+ import { s as clearWorkspaceSupervisor } from "./state-DwAU2_-D.mjs";
6
+ import { a as withWorkspaceProcessLock, o as workspaceProcessLockError } from "./named-ports-n7tnJsI8.mjs";
7
+ import { l as phaseLine, u as plural } from "./command-output-BIL7rlX7.mjs";
8
+ import { i as listLeaseFiles, r as leaseIsExpired } from "./device-lease-CL9dZf73.mjs";
9
+ import { o as workspaceActivity } from "./activity-DfVKZTEB.mjs";
10
+ import { F as ownedAvdDirectory, R as ownedAvdSerialResolver, at as dropParked, k as listAvds, lt as readParked, nt as isStimOwnedSim, tt as isStimOwnedAvd } from "./android-qx_31wJo.mjs";
11
+ import { f as teardownOwnedAvd, g as teardownParkedIosSim, h as teardownParkedAvd, m as teardownOwnedIosSim, p as teardownOwnedBrowser, t as resolveSupervisorTarget } from "./ownership-PUP4Dwai.mjs";
12
+ import { d as listIosDeviceTypes, g as parseRuntimeVersion, l as listAllIosSims } from "./ios-CmEzZiOj.mjs";
13
+ import { c as recordGcResult, i as idleShutdownCandidates, n as collectOwnedDeviceActivity, o as shutDownIdleDevices, s as workspaceBuildInProgress, t as collectIdleDevices } from "./idle-DxLi5OkN.mjs";
14
+ import { i as canonicalPath, n as withIdleWorkspace, r as workspaceInUse, t as emptyWorkspaceDir } from "./in-use-wYEaKQM9.mjs";
15
+ import { c as formatSpace, r as capacity, s as environmentState } from "./status-sszP4XKU.mjs";
16
16
  import { existsSync, statSync, statfsSync } from "node:fs";
17
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";
18
18
  import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
@@ -394,7 +394,12 @@ function emptyCache(cache) {
394
394
  }
395
395
  function reportFailures(cache, failed) {
396
396
  if (!failed) return;
397
- console.log(chalk.red(`${cache.name}: ${failed} entr${failed === 1 ? "y" : "ies"} in ${cache.dir} could not be removed`));
397
+ const message = `${failed} entr${failed === 1 ? "y" : "ies"} in ${cache.dir} could not be removed`;
398
+ console.log(chalk.red(`${cache.name}: ${message}`));
399
+ recordGcResult("cache", "failed", cache.name, {
400
+ id: cache.dir,
401
+ detail: message
402
+ });
398
403
  process.exitCode = 1;
399
404
  }
400
405
  function trimCaches(caches, olderThan) {
@@ -403,13 +408,26 @@ function trimCaches(caches, olderThan) {
403
408
  for (const c of caches) {
404
409
  if (c.machineGlobal) {
405
410
  console.log(chalk.yellow(`Left ${c.name} alone: ${c.machineGlobal}`));
411
+ recordGcResult("cache", "kept", c.name, {
412
+ id: c.dir,
413
+ detail: c.machineGlobal
414
+ });
406
415
  continue;
407
416
  }
408
417
  const r = pruneCache(c, { olderThanDays: olderThan });
409
- if (r.skipped) console.log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
410
- else if (r.removed) {
418
+ if (r.skipped) {
419
+ console.log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
420
+ recordGcResult("cache", "kept", c.name, {
421
+ id: c.dir,
422
+ detail: r.skipped
423
+ });
424
+ } else if (r.removed) {
411
425
  cacheBytes += r.bytes;
412
426
  trimmed.push(c);
427
+ recordGcResult("cache", "done", c.name, {
428
+ id: c.dir,
429
+ bytes: r.bytes
430
+ });
413
431
  console.log(chalk.green(`Trimmed ${c.name}: ${r.removed} entr${r.removed === 1 ? "y" : "ies"} (${formatBytes(r.bytes)})`));
414
432
  } else if (!r.failed) console.log(chalk.dim(`${c.name}: nothing older than ${olderThan}d`));
415
433
  reportFailures(c, r.failed);
@@ -422,12 +440,25 @@ function emptyCaches(caches) {
422
440
  for (const c of caches) {
423
441
  if (!c.willEmpty) {
424
442
  console.log(chalk.yellow(`Left ${c.name} alone: ${c.emptySkipped}`));
443
+ recordGcResult("cache", "kept", c.name, {
444
+ id: c.dir,
445
+ detail: c.emptySkipped ?? null
446
+ });
425
447
  continue;
426
448
  }
427
449
  const r = emptyCache(c);
428
- if (r.skipped) console.log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
429
- else if (r.removed) {
450
+ if (r.skipped) {
451
+ console.log(chalk.yellow(`Left ${c.name} alone: ${r.skipped}`));
452
+ recordGcResult("cache", "kept", c.name, {
453
+ id: c.dir,
454
+ detail: r.skipped
455
+ });
456
+ } else if (r.removed) {
430
457
  cacheBytes += r.bytes;
458
+ recordGcResult("cache", "done", c.name, {
459
+ id: c.dir,
460
+ bytes: r.bytes
461
+ });
431
462
  console.log(chalk.green(`Emptied ${c.name}: ${r.removed} entr${r.removed === 1 ? "y" : "ies"} (${formatBytes(r.bytes)})`));
432
463
  } else if (!r.failed) console.log(chalk.dim(`${c.name}: already empty`));
433
464
  reportFailures(c, r.failed);
@@ -442,7 +473,7 @@ const DAY_MS$1 = 864e5;
442
473
  function describeKept(ref) {
443
474
  return ref.mounted ? `referenced by ${ref.path}` : `referenced by ${ref.path} (volume not mounted; kept just in case)`;
444
475
  }
445
- function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProjects = [], isOwned = isStimCreated }) {
476
+ function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProjects = [] }) {
446
477
  const dead = new Set(deadProjects);
447
478
  const referenced = /* @__PURE__ */ new Map();
448
479
  for (const [path, proj] of Object.entries(config?.projects || {})) {
@@ -480,8 +511,11 @@ function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProj
480
511
  id: sim.udid,
481
512
  name: sim.name
482
513
  };
483
- if (!ref && !isOwned(device)) unverified.push(device);
484
- else if (!ref) orphaned.push(device);
514
+ if (!ref && !isStimCreated(device)) unverified.push(device);
515
+ else if (!ref) orphaned.push({
516
+ ...device,
517
+ ...simBytes(sim)
518
+ });
485
519
  else kept.push({
486
520
  kind: "ios",
487
521
  id: sim.udid,
@@ -497,7 +531,7 @@ function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProj
497
531
  id: avdName,
498
532
  name: avdName
499
533
  };
500
- if (!ref && !isOwned(device)) unverified.push(device);
534
+ if (!ref && !isStimCreated(device)) unverified.push(device);
501
535
  else if (!ref) orphaned.push(device);
502
536
  else kept.push({
503
537
  kind: "android",
@@ -512,20 +546,24 @@ function findOrphanedDevices({ sims = [], avds = [], config, isMounted, deadProj
512
546
  unverified
513
547
  };
514
548
  }
549
+ function simBytes(sim) {
550
+ return typeof sim.dataPathSize === "number" ? { bytes: sim.dataPathSize } : {};
551
+ }
515
552
  function isStimCreated(device) {
516
553
  return device.kind === "ios" ? isStimOwnedSim({
517
554
  udid: device.id,
518
555
  name: device.name
519
- }) : isStimOwnedAvdName(device.id);
556
+ }) : isStimOwnedAvd(device.id);
520
557
  }
521
558
  function unverifiedDeviceCommand(device) {
522
- return device.kind === "ios" ? `xcrun simctl delete ${device.id}` : `avdmanager delete avd -n ${JSON.stringify(device.id)}`;
559
+ if (device.kind === "ios") return `xcrun simctl delete ${device.id}`;
560
+ return device.directory ? `rm -rf ${JSON.stringify(device.directory)}` : `avdmanager delete avd -n ${JSON.stringify(device.id)}`;
523
561
  }
524
562
  function findStaleProjectDevices({ config, sims = [], avds = [], olderThanDays, now = Date.now(), lastTouched, deadProjects = [] }) {
525
563
  if (!Number.isFinite(olderThanDays) || typeof lastTouched !== "function") return [];
526
564
  const cutoff = now - olderThanDays * DAY_MS$1;
527
565
  const dead = new Set(deadProjects);
528
- const liveSims = new Map(sims.filter((s) => s?.name?.startsWith("stim-")).map((s) => [s.udid, s.name]));
566
+ const liveSims = new Map(sims.filter((s) => s?.name?.startsWith("stim-")).map((s) => [s.udid, s]));
529
567
  const liveAvds = new Set(avds.filter((a) => typeof a === "string" && a.startsWith("stim-")));
530
568
  const stale = [];
531
569
  for (const [path, proj] of Object.entries(config?.projects || {})) {
@@ -535,13 +573,15 @@ function findStaleProjectDevices({ config, sims = [], avds = [], olderThanDays,
535
573
  const idleDays = Math.floor((now - touched) / DAY_MS$1);
536
574
  for (const { slot, platforms } of projectDeviceSlots(proj)) {
537
575
  const ios = platforms.ios;
538
- if (ios?.owned && ios.deviceUdid && liveSims.has(ios.deviceUdid)) stale.push({
576
+ const sim = ios?.owned && ios.deviceUdid ? liveSims.get(ios.deviceUdid) : void 0;
577
+ if (sim) stale.push({
539
578
  kind: "ios",
540
- id: ios.deviceUdid,
541
- name: liveSims.get(ios.deviceUdid),
579
+ id: sim.udid,
580
+ name: sim.name,
542
581
  project: path,
543
582
  ...slot === "default" ? {} : { slot },
544
- idleDays
583
+ idleDays,
584
+ ...simBytes(sim)
545
585
  });
546
586
  const android = platforms.android;
547
587
  if (android?.owned && android.avdName && liveAvds.has(android.avdName)) stale.push({
@@ -681,14 +721,27 @@ function deleteParkedAvds(avds) {
681
721
  if (avd.listed === null) {
682
722
  failures++;
683
723
  console.log(chalk.red(`Could not verify parked android emulator ${avd.name}; its pool record was kept.`));
724
+ recordGcResult("parkedDevice", "failed", avd.name, {
725
+ id: avd.name,
726
+ detail: "Stim could not list emulators to verify it; its pool record was kept. Retry stim gc --delete."
727
+ });
684
728
  continue;
685
729
  }
686
730
  const result = teardownParkedAvd(avd.name);
687
731
  if (result.status === "failed") {
688
732
  failures++;
689
733
  console.log(chalk.red(`Failed to delete parked android emulator ${avd.name}: ${result.reason}`));
690
- } else if (result.status === "torn-down") console.log(chalk.green(`Deleted parked android emulator ${avd.name}`));
691
- else console.log(chalk.dim(`Skipped ${avd.name}; it is no longer parked.`));
734
+ recordGcResult("parkedDevice", "failed", avd.name, {
735
+ id: avd.name,
736
+ detail: result.reason
737
+ });
738
+ } else if (result.status === "torn-down") {
739
+ console.log(chalk.green(`Deleted parked android emulator ${avd.name}`));
740
+ recordGcResult("parkedDevice", "done", avd.name, {
741
+ id: avd.name,
742
+ bytes: avd.bytes
743
+ });
744
+ } else console.log(chalk.dim(`Skipped ${avd.name}; it is no longer parked.`));
692
745
  }
693
746
  return failures;
694
747
  }
@@ -699,6 +752,10 @@ function deleteParkedSims(parkedSims, deps = {}) {
699
752
  if (sim.listed === null) {
700
753
  failures++;
701
754
  console.log(chalk.red(`Could not verify parked ios sim ${sim.name} (${sim.udid}); its pool record was kept.`));
755
+ recordGcResult("parkedDevice", "failed", sim.name, {
756
+ id: sim.udid,
757
+ detail: "Stim could not list simulators to verify it; its pool record was kept. Retry stim gc --delete."
758
+ });
702
759
  continue;
703
760
  }
704
761
  try {
@@ -710,14 +767,26 @@ function deleteParkedSims(parkedSims, deps = {}) {
710
767
  if (teardown?.status === "failed") {
711
768
  failures++;
712
769
  console.log(chalk.red(`Failed to delete parked ios sim ${sim.name}: ${teardown.reason}`));
770
+ recordGcResult("parkedDevice", "failed", sim.name, {
771
+ id: sim.udid,
772
+ detail: teardown.reason
773
+ });
713
774
  } else console.log(chalk.dim(`Skipped ${sim.name} (${sim.udid}); it is no longer parked.`));
714
775
  continue;
715
776
  }
716
777
  emptied++;
778
+ recordGcResult("parkedDevice", "done", sim.name, {
779
+ id: sim.udid,
780
+ bytes: sim.listed ? sim.bytes : null
781
+ });
717
782
  console.log(chalk.green(sim.listed ? `Deleted parked ios sim ${sim.name} (${sim.udid})` : `Dropped the parked record for ${sim.name} (${sim.udid}); it is not on this machine.`));
718
783
  } catch (err) {
719
784
  failures++;
720
785
  console.log(chalk.red(`Failed to delete parked ios sim ${sim.name}: ${err?.message || err}`));
786
+ recordGcResult("parkedDevice", "failed", sim.name, {
787
+ id: sim.udid,
788
+ detail: err?.message || String(err)
789
+ });
721
790
  }
722
791
  }
723
792
  if (emptied) console.log(chalk.dim(` emptied ${plural(emptied, "parked simulator")} from the pool`));
@@ -773,12 +842,27 @@ function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords)
773
842
  ..."orphanedDirectory" in d ? { orphanedDirectory: d.orphanedDirectory } : {}
774
843
  });
775
844
  const what = d.kind === "ios" ? `ios sim ${d.name} (${d.id})` : `android avd ${d.name}`;
776
- if (r.status === "torn-down") console.log(chalk.green(`Deleted ${what}`));
777
- else if (r.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to delete.`));
778
- else if (r.status === "skipped") console.log(chalk.yellow(`Skipped ${what}: ${r.reason} -- left for a later gc`));
779
- else {
845
+ const result = {
846
+ id: d.id,
847
+ bytes: d.bytes ?? null
848
+ };
849
+ if (r.status === "torn-down") {
850
+ console.log(chalk.green(`Deleted ${what}`));
851
+ recordGcResult("device", "done", d.name, result);
852
+ } else if (r.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to delete.`));
853
+ else if (r.status === "skipped") {
854
+ console.log(chalk.yellow(`Skipped ${what}: ${r.reason} -- left for a later gc`));
855
+ recordGcResult("device", "kept", d.name, {
856
+ ...result,
857
+ detail: `${r.reason}; left for a later gc`
858
+ });
859
+ } else {
780
860
  deleteFailures++;
781
861
  console.log(chalk.red(`Failed to delete ${d.kind} device ${d.name}: ${r.reason}`));
862
+ recordGcResult("device", "failed", d.name, {
863
+ ...result,
864
+ detail: r.reason
865
+ });
782
866
  }
783
867
  return r.status;
784
868
  }
@@ -798,7 +882,10 @@ function deleteProjectDevices(orphanedDevices, staleDevices, staleDeviceRecords)
798
882
  else if (withConfigLock$1(() => {
799
883
  if (r.kind === "android" && deviceSlotPlatforms(getProject(r.project), r.slot)?.android?.owned) return false;
800
884
  return clearDevice(r.project, r.kind, r.slot, r.id);
801
- })) console.log(chalk.green(`Cleared the ${r.kind} record for ${r.project} (${r.id} is not on this machine)`));
885
+ })) {
886
+ console.log(chalk.green(`Cleared the ${r.kind} record for ${r.project} (${r.id} is not on this machine)`));
887
+ recordGcResult("deviceRecord", "done", r.project, { id: r.id });
888
+ }
802
889
  return deleteFailures;
803
890
  }
804
891
  //#endregion
@@ -984,7 +1071,13 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }) {
984
1071
  for (const entry of report.workspaces) {
985
1072
  const root = entry.projectRoot;
986
1073
  if (!entry.willClear || root === null) {
987
- if (entry.keptReason) console.log(chalk.dim(`Kept the build outputs of ${root ?? entry.dir}: ${entry.keptReason}`));
1074
+ if (entry.keptReason) {
1075
+ console.log(chalk.dim(`Kept the build outputs of ${root ?? entry.dir}: ${entry.keptReason}`));
1076
+ recordGcResult("workspaceOutputs", "kept", root ?? entry.dir, {
1077
+ bytes: entry.bytes,
1078
+ detail: entry.keptReason
1079
+ });
1080
+ }
988
1081
  continue;
989
1082
  }
990
1083
  let run;
@@ -1009,16 +1102,25 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }) {
1009
1102
  } catch (error) {
1010
1103
  failures++;
1011
1104
  console.log(chalk.red(`Could not clear the build outputs of ${root}: ${error?.message || String(error)}`));
1105
+ recordGcResult("workspaceOutputs", "failed", root, {
1106
+ bytes: entry.bytes,
1107
+ detail: error?.message || String(error)
1108
+ });
1012
1109
  continue;
1013
1110
  }
1014
1111
  const kept = run.ran ? run.value : `in use: ${run.reasons.join("; ")}`;
1015
1112
  if (kept) {
1016
1113
  console.log(chalk.yellow(`Kept the build outputs of ${root}: ${kept}`));
1114
+ recordGcResult("workspaceOutputs", "kept", root, {
1115
+ bytes: entry.bytes,
1116
+ detail: kept
1117
+ });
1017
1118
  continue;
1018
1119
  }
1019
1120
  cleared += entry.bytes ?? 0;
1020
1121
  clearedRoots.push(root);
1021
1122
  console.log(chalk.green(`Cleared the build outputs of ${root} (${sizeText(entry.bytes)})`));
1123
+ recordGcResult("workspaceOutputs", "done", root, { bytes: entry.bytes });
1022
1124
  }
1023
1125
  if (cleared) console.log(chalk.dim(`Cleared ${formatBytes(cleared)} of workspace build outputs. ${REBUILD_COST}`));
1024
1126
  return {
@@ -1036,26 +1138,41 @@ async function deleteOrphanedWorkspaces(orphaned) {
1036
1138
  if (!existsSync$1(entry.dir)) continue;
1037
1139
  let run;
1038
1140
  try {
1039
- run = await withIdleWorkspace(entry.projectRoot, () => {
1141
+ run = await withIdleWorkspace(entry.projectRoot, async () => {
1040
1142
  if (!stillOrphaned(entry)) return false;
1143
+ const browser = await teardownOwnedBrowser(entry.projectRoot, { deleteProfile: true });
1144
+ if (browser.status === "failed" || browser.status === "skipped") throw new Error(`its owned Chrome profile was kept: ${browser.reason}`);
1041
1145
  emptyWorkspaceDir(entry.dir);
1042
1146
  return true;
1043
1147
  }, { purpose: "gc" });
1044
1148
  } catch (error) {
1045
1149
  failures++;
1046
1150
  console.log(chalk.red(`Could not remove ${entry.dir}: ${error?.message || error}`));
1151
+ recordGcResult("workspaceDirectory", "failed", entry.dir, {
1152
+ bytes: entry.bytes ?? null,
1153
+ detail: error?.message || String(error)
1154
+ });
1047
1155
  continue;
1048
1156
  }
1049
1157
  if (!run.ran) {
1050
1158
  console.log(chalk.yellow(`Kept ${entry.dir}: ${run.reasons.join("; ")}`));
1159
+ recordGcResult("workspaceDirectory", "kept", entry.dir, {
1160
+ bytes: entry.bytes ?? null,
1161
+ detail: run.reasons.join("; ")
1162
+ });
1051
1163
  continue;
1052
1164
  }
1053
1165
  if (!run.value) {
1054
1166
  console.log(chalk.yellow(`Kept ${entry.dir}: it can no longer be confirmed orphaned.`));
1167
+ recordGcResult("workspaceDirectory", "kept", entry.dir, {
1168
+ bytes: entry.bytes ?? null,
1169
+ detail: "it can no longer be confirmed orphaned"
1170
+ });
1055
1171
  continue;
1056
1172
  }
1057
1173
  const size = entry.bytes === void 0 ? "" : ` (${sizeText(entry.bytes)})`;
1058
1174
  console.log(chalk.green(`Removed the orphaned workspace directory ${entry.dir}${size}`));
1175
+ recordGcResult("workspaceDirectory", "done", entry.dir, { bytes: entry.bytes ?? null });
1059
1176
  }
1060
1177
  return failures;
1061
1178
  }
@@ -1,27 +1,25 @@
1
1
  import { B as deviceSlotPlatforms, C as setDevice, E as withConfigLock$1, S as saveConfig, U as projectDeviceSlots, _ as loadConfig$1, a as clearDevice, d as getConfigDir, i as clearAndroidBootPending, k as acquireAvdClaim, t as allConsolePortsAndSerials, y as releaseAndroidConsolePort } from "./config-0jCcMM0E.mjs";
2
2
  import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { S as sharedBuildCache, g as updateWorkspaceState, i as pidExists, k as workspaceId, m as readWorkspaceState$1, s as resolveProjectMetro, x as ensureWorkspaceStorage } from "./metro-Dzo30hvJ.mjs";
3
+ import { A as workspaceId, C as sharedBuildCache, _ as updateWorkspaceState, a as pidExists, h as readWorkspaceState$1 } from "./metro-BPeY1YMh.mjs";
4
4
  import { D as makeTemporaryDirectory, O as removeTemporaryEntry, c as gitCommonDirOnDisk } from "./worktree-DeBQSVtR.mjs";
5
- import { a as isPackageResolvable, o as ownedDeviceLabel, r as detectIsExpo } from "./project-B8rbutVC.mjs";
5
+ import { c as ownedDeviceLabel, r as detectIsExpo, s as isPackageResolvable } from "./project-DGeclhvO.mjs";
6
6
  import { a as spawnDeclared } from "./spawn-claims-PT8aQpCa.mjs";
7
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BvvJTfo_.mjs";
7
+ import { a as withWorkspaceProcessLock } from "./named-ports-n7tnJsI8.mjs";
8
8
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
9
- import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-BWCj5Vh-.mjs";
10
- import { N as storedSources, S as entryDir, x as diffFingerprintSources, y as artifactIn } from "./device-lease-B9LAXIUH.mjs";
11
- import { B as pickDefaultSystemImage, C as getAvdNameForSerial, E as listAdbDevices, F as ownedAvdMatchesConfiguration, I as ownedAvdName, M as nextConsolePort, N as ownedAvdDeviceProfile, O as listAvds, R as ownedAvdSystemImage, W as resolveOwnedAvdSerial, X as waitForBoot, _ as createOwnedAvd, d as assertOwnedAvdStopped, g as configureNewOwnedAvd, h as bootAndroidEmulator, k as listInstalledSystemImages, n as OWNED_AVD_CONFIG_DEFAULTS, p as avdPoolConfiguration, t as DEFAULT_AVD_DEVICE_PROFILE } from "./android-75Sk2rIR.mjs";
12
- import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-BlyDn2Oz.mjs";
13
- import { _ as dropParked, b as readParked, d as teardownOwnedAvd, g as adoptParked, m as teardownParkedIosSim, p as teardownParkedAvd, v as isLegacyDeletionClaim, x as selectParked, y as parkedMaxSetting } from "./ownership-B8d4kXBx.mjs";
14
- import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-CZWOHTTc.mjs";
15
- import { o as estimateBuild } from "./build-progress-h60GPV27.mjs";
16
- import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-CeT1nIIk.mjs";
17
- import { a as easAuthNote } from "./remote-cache-BRsRZru5.mjs";
18
- import { r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING } from "./launch-verify-B2oR_kFN.mjs";
19
- import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, t as reconcileSimSlim } from "./simslim-COtEEP7x.mjs";
20
- import { s as startDevServer } from "./start-BboE1HIS.mjs";
9
+ import { i as formatDuration, l as phaseLine, m as shortHash, u as plural } from "./command-output-BIL7rlX7.mjs";
10
+ import { C as diffFingerprintSources, I as storedSources, w as entryDir, y as artifactIn } from "./device-lease-CL9dZf73.mjs";
11
+ import { $ as waitForBoot, A as listInstalledSystemImages, D as listAdbDevices, I as ownedAvdMatchesConfiguration, L as ownedAvdName, N as nextConsolePort, P as ownedAvdDeviceProfile, T as getAvdNameForSerial, U as pickDefaultSystemImage, _ as bootAndroidEmulator, at as dropParked, ct as parkedMaxSetting, d as assertOwnedAvdStopped, dt as selectParked, h as avdPoolConfiguration, it as adoptParked, k as listAvds, lt as readParked, n as OWNED_AVD_CONFIG_DEFAULTS, ot as isLegacyDeletionClaim, q as resolveOwnedAvdSerial, t as DEFAULT_AVD_DEVICE_PROFILE, v as configureNewOwnedAvd, y as createOwnedAvd, z as ownedAvdSystemImage } from "./android-qx_31wJo.mjs";
12
+ import { l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-xB0Frkg6.mjs";
13
+ import { f as teardownOwnedAvd, g as teardownParkedIosSim, h as teardownParkedAvd } from "./ownership-PUP4Dwai.mjs";
14
+ import { E as configuredIosSimulatorViewer, O as hostMemoryPressureAdvice, S as resolveOwnedIosSim, b as resetIosPrivacy, c as iosSimulatorFailureAdvice, d as listIosDeviceTypes, f as listIosRuntimes, g as parseRuntimeVersion, k as readHostMemoryPressure, l as listAllIosSims, m as ownedSimName, n as bootIosSim, r as createOwnedIosSim, v as renameIosSim, x as resolveIosCreation, y as resetIosKeychain } from "./ios-CmEzZiOj.mjs";
15
+ import { o as estimateBuild } from "./build-progress-l7d-X-ZP.mjs";
16
+ import { i as expoBinRefusal, r as expoBinPath, t as cleanLine } from "./server-expo-DUCg52-K.mjs";
17
+ import { a as easAuthNote } from "./remote-cache--aehr4Pr.mjs";
18
+ import { a as liveOwnedDeviceCount, i as deviceTypeMismatch, o as runtimeMismatch, t as reconcileSimSlim } from "./simslim-Bbg9UWrt.mjs";
21
19
  import { r as readStats } from "./stats-D8rUTPHd.mjs";
22
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-BzplpQzc.mjs";
20
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-LM6A8cNZ.mjs";
23
21
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync } from "node:fs";
24
- import { LAST_BUILD_KEYS, isJsonObject, loadConfig, readIdleStop, readJsonObject, readWorkspaceState } from "@stim-cli/core/state";
22
+ import { LAST_BUILD_KEYS, isJsonObject, loadConfig, readJsonObject, readWorkspaceState } from "@stim-cli/core/state";
25
23
  import { basename, dirname, join, relative } from "node:path";
26
24
  import { claimRemoveCommand, clearClaimChild, markClaimChildPending, processGroupAlive, releaseClaim, setClaimChild } from "@stim-cli/core/ownership-claim";
27
25
  import { captureProcessIdentity } from "@stim-cli/core/process-identity";
@@ -93,73 +91,6 @@ async function warmMetro({ port, platform, isExpo, appId, bundleUrl }) {
93
91
  } catch {}
94
92
  }
95
93
  //#endregion
96
- //#region src/commands/native-runtime.ts
97
- const sleep$1 = (ms) => new Promise((r) => setTimeout(r, ms));
98
- async function ensureDevServer({ root, port, settings, remote, note, resolve = resolveProjectMetro, start = startDevServer, readState = readWorkspaceState$1 }) {
99
- const held = port === null ? null : (await resolve(port, root)).metro;
100
- if (port !== null && held) return {
101
- ok: true,
102
- port,
103
- pid: held.pid ?? null,
104
- devServer: null,
105
- reclaimed: []
106
- };
107
- const reason = readIdleStop(readState(root)) ? "stopped (idle)" : "not running";
108
- note(chalk.dim(phaseLine("metro", `dev server ${reason}; starting it${remote ? " for a remote device" : ""}`)));
109
- const result = await start({
110
- root,
111
- settings,
112
- remote,
113
- out: note,
114
- note
115
- });
116
- if (!result.ok) return {
117
- ok: false,
118
- code: result.error.code,
119
- message: `Could not start this workspace's dev server: ${result.error.message}`,
120
- remedy: result.error.remedy,
121
- lines: result.lines,
122
- reclaimed: result.reclaimed
123
- };
124
- const { facts } = result;
125
- return {
126
- ok: true,
127
- port: facts.port,
128
- pid: facts.supervisorPid,
129
- devServer: facts.alreadyRunning ? null : {
130
- started: true,
131
- reason
132
- },
133
- reclaimed: result.reclaimed
134
- };
135
- }
136
- async function ensureWorkspaceStorageSafely(root, { note = (_line) => {} } = {}) {
137
- try {
138
- return ensureWorkspaceStorage(root);
139
- } catch (err) {
140
- note(chalk.dim(`Could not prepare this workspace's Stim state: ${err?.message || err}`));
141
- throw err;
142
- }
143
- }
144
- function launchOutcomeRecord({ launchState, release, bundleId, configuration, metroPort }) {
145
- const unverified = launchState === LAUNCH_UNVERIFIED;
146
- const bundling = launchState === LAUNCH_BUNDLING;
147
- let msg;
148
- if (release) msg = unverified ? `${bundleId} could not be verified as running after its ${configuration} launch` : `${bundleId} is running its embedded ${configuration} bundle`;
149
- else if (unverified) msg = `no bundle request from ${bundleId} reached this workspace's Metro on port ${metroPort}`;
150
- else if (bundling) msg = `${bundleId} requested a bundle from this workspace's Metro on port ${metroPort}; it was still being built when the launch check ended`;
151
- else msg = `${bundleId} fetched a bundle from this workspace's Metro on port ${metroPort}`;
152
- return {
153
- src: "build",
154
- level: unverified ? "warn" : "info",
155
- event: unverified ? "launch_unverified" : bundling ? "launch_bundling" : "launch_verified",
156
- msg
157
- };
158
- }
159
- function isPhysicalDeviceRequest(flag) {
160
- return flag !== null && flag !== void 0 && flag !== false;
161
- }
162
- //#endregion
163
94
  //#region src/engine/device-ios.ts
164
95
  function startIosBoot(udid, configure, label, out, simulatorApp) {
165
96
  const done = (async () => {
@@ -183,13 +114,15 @@ async function ensureOwnedIosDevice({ record, projectPath, slot, settingsRoot, l
183
114
  if (record?.deviceUdid) {
184
115
  if (record.owned) {
185
116
  const resolved = resolveOwnedIosSim(record.deviceUdid);
186
- if (resolved.notOwned) note(chalk.yellow(`Note: recorded sim is now named "${resolved.notOwned}", not Stim-owned by name -- creating a fresh owned sim instead of booting it.`));
117
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded sim is now named "${resolved.notOwned}", not Stim-owned -- creating a fresh owned sim instead of booting it.`));
187
118
  else if (resolved.missing) {} else {
188
119
  const sim = resolved.sim;
189
120
  const wantedType = flags.deviceType || settings?.ios?.deviceType;
190
121
  const deviceTypes = listIosDeviceTypes();
191
122
  const mismatch = deviceTypeMismatch(sim.deviceTypeIdentifier, wantedType, deviceTypes);
192
123
  if (mismatch) throw new Error(`${mismatch}. Stim will not silently boot a different model. Run \`stim worktree remove\` (or \`stim gc --delete\`) to reap the current sim, then \`stim ios\` again to create the requested one.`);
124
+ const versionMismatch = flags.runtimeFlag ? runtimeMismatch(sim.runtime, flags.runtimeFlag, listIosRuntimes()) : null;
125
+ if (versionMismatch) throw new Error(`${versionMismatch}. Stim will not silently boot a different iOS version. Run \`stim worktree remove\` (or \`stim gc --delete\`) to reap the current sim, then \`stim ios\` again to create the requested one, or pass \`--slot <name>\` to create it beside the current one.`);
193
126
  const model = {
194
127
  model: deviceTypes.find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null,
195
128
  runtime: parseRuntimeVersion(sim.runtime)
@@ -731,7 +664,7 @@ async function ensureOwnedAndroidDevice({ record, projectPath, slot, settingsRoo
731
664
  const resolved = resolveOwnedAvdSerial(record.avdName);
732
665
  const currentProfile = resolved.notOwned || resolved.missing ? null : ownedAvdDeviceProfile(record.avdName);
733
666
  if (requestedProfile && currentProfile && currentProfile !== requestedProfile) throw new AvdRecoveryError(`this project's emulator uses device profile ${currentProfile}, but ${requestedProfile} was requested. Stim will not silently boot a different model.`, "Run `stim worktree remove` (or `stim gc --delete`) to reap the current emulator, then `stim android` again to create the requested one, or pass `--slot <name>` to create it beside the current one.");
734
- if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
667
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned -- creating a fresh owned AVD instead of reusing it.`));
735
668
  else if (resolved.serial) {
736
669
  const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
737
670
  if (record.consolePort && record.consolePort !== consolePort) out(phaseLine("device", `${record.avdName} changed serial (emulator-${record.consolePort} -> ${resolved.serial}); reconnecting this run, reopen agent-device on ${resolved.serial}`));
@@ -1003,7 +936,7 @@ async function ensureAndroidBooted({ device, projectPath, slot, timeoutMs, out,
1003
936
  };
1004
937
  if (resolved.notOwned) return {
1005
938
  failed: true,
1006
- reason: `AVD ${device.avdName} is not Stim-owned by name; refusing to boot it.`
939
+ reason: `AVD ${device.avdName} is not Stim-owned; refusing to boot it.`
1007
940
  };
1008
941
  const avdName = device.avdName;
1009
942
  const booted = (result) => {
@@ -2048,4 +1981,4 @@ function planFlagRefusal(flag) {
2048
1981
  };
2049
1982
  }
2050
1983
  //#endregion
2051
- export { ensureDevServer as C, sleep$1 as D, launchOutcomeRecord as E, warmMetro as O, clearIosAdoptionPending as S, isPhysicalDeviceRequest as T, staleNativeDirRefusal as _, refusePlan as a, AvdBootError as b, skippedMissReason as c, schemesFromInfoPlist as d, detectEntryFile as f, runPrebuild as g, recordPrebuild as h, printPlan as i, devClientScheme as l, planPrebuild as m, planFlagRefusal as n, explainBuildMiss as o, swapJsBundle as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, pickDevClientScheme as u, ensureBooted as v, ensureWorkspaceStorageSafely as w, AvdRecoveryError as x, ensureOwnedDevice as y };
1984
+ export { warmMetro as C, clearIosAdoptionPending as S, staleNativeDirRefusal as _, refusePlan as a, AvdBootError as b, skippedMissReason as c, schemesFromInfoPlist as d, detectEntryFile as f, runPrebuild as g, recordPrebuild as h, printPlan as i, devClientScheme as l, planPrebuild as m, planFlagRefusal as n, explainBuildMiss as o, swapJsBundle as p, planPayload as r, fingerprintErrorMissReason as s, planCachedBuild as t, pickDevClientScheme as u, ensureBooted as v, AvdRecoveryError as x, ensureOwnedDevice as y };
@@ -1,7 +1,7 @@
1
1
  import { A as ownership_claim_exports } from "./config-0jCcMM0E.mjs";
2
- import { f as clearWorkspaceStateKey, g as updateWorkspaceState } from "./metro-Dzo30hvJ.mjs";
3
- import { a as formatElapsed, u as plural } from "./command-output-BWCj5Vh-.mjs";
4
- import { BUILD_PHASES } from "@stim-cli/core/state";
2
+ import { _ as updateWorkspaceState, p as clearWorkspaceStateKey } from "./metro-BPeY1YMh.mjs";
3
+ import { a as formatElapsed, u as plural } from "./command-output-BIL7rlX7.mjs";
4
+ import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, LAST_BUILD_KEYS } from "@stim-cli/core/state";
5
5
  //#region src/engine/build-progress.ts
6
6
  const ACTIVE_BUILD_KEY = "activeBuild";
7
7
  const NO_BUILD_PROGRESS = {
@@ -44,11 +44,11 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
44
44
  note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
45
45
  }
46
46
  };
47
- const write = () => guard(() => updateWorkspaceState(root, (state) => ({
48
- ...state,
47
+ const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
48
+ ...before(state),
49
49
  [ACTIVE_BUILD_KEY]: record
50
50
  })));
51
- write();
51
+ write(withInterruptedBuild);
52
52
  return {
53
53
  step(phase) {
54
54
  if (phase === record.phase) return;
@@ -82,6 +82,74 @@ function phaseDurations(phases, now) {
82
82
  });
83
83
  return out;
84
84
  }
85
+ const HISTORY_FIELDS = [
86
+ "platform",
87
+ "status",
88
+ "configuration",
89
+ "fingerprint",
90
+ "cacheKey",
91
+ "cacheHit",
92
+ "cacheSkipped",
93
+ "durationMs",
94
+ "startedAt",
95
+ "errorCode",
96
+ "missReason",
97
+ "diagnostics"
98
+ ];
99
+ function withHistoryEntry(state, platform, entry) {
100
+ const saved = state[BUILD_HISTORY_KEY];
101
+ const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
102
+ const list = Array.isArray(history[platform]) ? history[platform] : [];
103
+ return {
104
+ ...state,
105
+ [BUILD_HISTORY_KEY]: {
106
+ ...history,
107
+ [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
108
+ }
109
+ };
110
+ }
111
+ function finishedResult(record) {
112
+ if (record.status === "ok") return "succeeded";
113
+ return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
114
+ }
115
+ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now } = {}) {
116
+ const platform = record.platform;
117
+ update(root, (state) => {
118
+ const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
119
+ const own = active?.platform === platform ? active : null;
120
+ const entry = {
121
+ ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
122
+ result: finishedResult(record),
123
+ slot: own?.slot ?? "default",
124
+ phases: own ? phaseDurations(own.phases, now()) : {}
125
+ };
126
+ return withHistoryEntry({
127
+ ...state,
128
+ lastBuild: record,
129
+ [LAST_BUILD_KEYS[platform]]: record
130
+ }, platform, entry);
131
+ });
132
+ }
133
+ function withInterruptedBuild(state) {
134
+ const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
135
+ if (!left) return state;
136
+ const last = state[LAST_BUILD_KEYS[left.platform]];
137
+ if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
138
+ return withHistoryEntry(state, left.platform, {
139
+ platform: left.platform,
140
+ status: "failed",
141
+ result: "interrupted",
142
+ slot: left.slot,
143
+ configuration: null,
144
+ fingerprint: null,
145
+ cacheKey: null,
146
+ cacheHit: false,
147
+ cacheSkipped: false,
148
+ durationMs: null,
149
+ startedAt: left.startedAt,
150
+ phases: phaseDurations(left.phases, Date.parse(left.phaseStartedAt))
151
+ });
152
+ }
85
153
  function parseActiveBuild(value) {
86
154
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
87
155
  const record = value;
@@ -176,4 +244,4 @@ function buildStatusLine(report, now) {
176
244
  return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
177
245
  }
178
246
  //#endregion
179
- export { buildStatusLine as a, startBuildProgress as c, buildReport as i, NO_BUILD_PROGRESS as n, estimateBuild as o, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t };
247
+ export { buildStatusLine as a, recordFinishedBuild as c, buildReport as i, startBuildProgress as l, NO_BUILD_PROGRESS as n, estimateBuild as o, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t };