stim 1.16.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.
Files changed (107) hide show
  1. package/dist/{activity-BxwdD81S.mjs → activity-IRvJY_ru.mjs} +23 -3
  2. package/dist/{agent-device-usage-output-Dpih1Tfa.mjs → agent-device-usage-output-BK41vnzN.mjs} +3 -3
  3. package/dist/{android-Ift4kVvt.mjs → android--GorydsK.mjs} +679 -317
  4. package/dist/{android-B_NumVyQ.mjs → android-B_YNZy61.mjs} +7 -43
  5. package/dist/{android-r2rBL8kV.mjs → android-DE4v59Mb.mjs} +12 -3
  6. package/dist/{android-cas-8U4DPqeR.mjs → android-cas-ZFpjJBL_.mjs} +6 -5
  7. package/dist/{app-install-CKWdbXIX.mjs → app-install-VPkI5WEg.mjs} +6 -5
  8. package/dist/{budget-DjrURfQn.mjs → budget-DM984mnd.mjs} +34 -18
  9. package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-CmQCoyT-.mjs} +345 -86
  10. package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
  11. package/dist/{cli-BkYZ_6hy.mjs → cli-Daxug5-K.mjs} +17 -17
  12. package/dist/cli.mjs +1 -1
  13. package/dist/collector-run.mjs +3 -3
  14. package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
  15. package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
  16. package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  17. package/dist/{deps-urbmfXig.mjs → deps-DNWaQs4V.mjs} +1 -1
  18. package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
  19. package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
  20. package/dist/device-capacity-D0GWHnTM.mjs +580 -0
  21. package/dist/device-host-worker.mjs +436 -154
  22. package/dist/{device-ios-BadmeL2V.mjs → device-ios-Bc7vbGwl.mjs} +75 -44
  23. package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
  24. package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
  25. package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
  26. package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
  27. package/dist/{doctor-Cimf-gGB.mjs → doctor-Cnk9d7Hm.mjs} +32 -51
  28. package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
  29. package/dist/{gc-Bqy4bjEu.mjs → gc-DxCL2A46.mjs} +308 -26
  30. package/dist/{gradle-BsO1MdsH.mjs → gradle-D7-L2Th3.mjs} +3 -3
  31. package/dist/{guide-B_uyKTxW.mjs → guide-DjrTQGik.mjs} +1662 -565
  32. package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  33. package/dist/hosted-android-DwLS4Mse.mjs +32 -0
  34. package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-wk1W_CNW.mjs} +2 -3
  35. package/dist/hosted-ios-aMo7Cljf.mjs +15 -0
  36. package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-DqYmWtXc.mjs} +16 -16
  37. package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-dlHXEeF5.mjs} +4 -4
  38. package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-C9xJ9rpA.mjs} +77 -42
  39. package/dist/{idle-DYVymG92.mjs → idle-B2m-Ovod.mjs} +122 -58
  40. package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-_3oOV1V_.mjs} +25 -17
  41. package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
  42. package/dist/{ios-aq3QoGI3.mjs → ios-DE6duQoz.mjs} +170 -88
  43. package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
  44. package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
  45. package/dist/ios-state-8L3lgum6.mjs +89 -0
  46. package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
  47. package/dist/{logs-CHEVK8zD.mjs → logs-DUoKlDoS.mjs} +36 -23
  48. package/dist/{client-BTY686R6.mjs → machines-C2XiNwh9.mjs} +278 -20
  49. package/dist/{macos-CSYEAFmF.mjs → macos-32po3kUq.mjs} +14 -14
  50. package/dist/macos-run.mjs +1 -1
  51. package/dist/maintenance-run.mjs +2 -2
  52. package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
  53. package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-DmslUM30.mjs} +22 -3
  54. package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  55. package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
  56. package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-DhJI3IVQ.mjs} +6 -6
  57. package/dist/offload-worker.mjs +8 -8
  58. package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
  59. package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
  60. package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
  61. package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
  62. package/dist/{preview-DhVbvPAZ.mjs → preview-CxPRIEht.mjs} +7 -7
  63. package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
  64. package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  65. package/dist/{reload-rM9jV1fz.mjs → reload-qZei6iWn.mjs} +62 -35
  66. package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
  67. package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
  68. package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  69. package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
  70. package/dist/server-expo-CeakhI8P.mjs +2 -0
  71. package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
  72. package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
  73. package/dist/settings.schema.json +42 -13
  74. package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
  75. package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
  76. package/dist/{start-CX8Ig-U2.mjs → start-BopZcr4n.mjs} +1 -1
  77. package/dist/{start-ClDfwXa4.mjs → start-uee1wWjY.mjs} +11 -11
  78. package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
  79. package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
  80. package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
  81. package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
  82. package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
  83. package/dist/{status-Cs7VLg9D.mjs → status-dXX2JX1C.mjs} +125 -35
  84. package/dist/{stop-Dt_dM16A.mjs → stop-BCwvvs7F.mjs} +2 -2
  85. package/dist/{stop-Bffozsac.mjs → stop-BjizrQwv.mjs} +48 -27
  86. package/dist/{stop-B0Yxtmiy.mjs → stop-Cr1d8DSE.mjs} +4 -4
  87. package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
  88. package/dist/supervisor-run.d.mts +7 -4
  89. package/dist/supervisor-run.mjs +26 -28
  90. package/dist/{support-D4aYJniO.mjs → support-BJPgYCu-.mjs} +44 -19
  91. package/dist/{support-CAP8qDUF.mjs → support-F9-sr0Oi.mjs} +6 -6
  92. package/dist/{toolchain-DkUflduf.mjs → toolchain-CNIBTIOv.mjs} +7 -7
  93. package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
  94. package/dist/watchman-APYjXSQa.mjs +48 -0
  95. package/dist/{web-DWUaMp8P.mjs → web-exQtGkoc.mjs} +12 -13
  96. package/dist/web-run.mjs +4 -4
  97. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  98. package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
  99. package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
  100. package/dist/{worktree-jnG8Iph5.mjs → worktree-Dc98cE6_.mjs} +1 -1
  101. package/dist/{worktree-K-xj9Db0.mjs → worktree-ZpZR3S1k.mjs} +141 -40
  102. package/package.json +5 -5
  103. package/dist/device-capacity-BHwxNBlu.mjs +0 -136
  104. package/dist/ios-state-DS5UpYTH.mjs +0 -48
  105. package/dist/machines-BIBUdhpj.mjs +0 -265
  106. package/dist/ownership-hCkPzGmr.mjs +0 -571
  107. package/dist/server-expo-CVqO2c35.mjs +0 -2
@@ -1,62 +1,15 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
1
  import { D as withConfigLock$1, H as projectDeviceSlots } from "./config-D0DbpZtM.mjs";
3
2
  import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
4
- import { a as formatElapsed, o as formatLongDuration, u as plural } from "./command-output-DnJMdEY-.mjs";
5
- import { a as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-B8-aJ1_5.mjs";
6
- import { i as recordGcResult } from "./in-use-DvBw0Kxo.mjs";
7
- import { o as idleForMs, r as createActivityReader } from "./activity-BxwdD81S.mjs";
8
- import { z as ownedAvdSerialResolver } from "./android-B_NumVyQ.mjs";
9
- import { i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-CL6vDs2Y.mjs";
3
+ import { a as formatElapsed, o as formatLongDuration, u as plural } from "./command-output-CDAIFD-8.mjs";
4
+ import { a as updateWorkspaceState, r as readWorkspaceState$1, t as clearWorkspaceStateKey } from "./workspace-state-DqcCbpE7.mjs";
5
+ import { i as recordGcResult } from "./in-use-CvGOUn-x.mjs";
6
+ import { o as idleForMs, r as createActivityReader } from "./activity-IRvJY_ru.mjs";
7
+ import { z as ownedAvdSerialResolver } from "./android-B_YNZy61.mjs";
8
+ import { g as teardownOwnedIosSim, m as teardownOwnedAvd } from "./ownership-BMLqyBgJ.mjs";
10
9
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
10
  import { dirname } from "node:path";
12
- import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, LAST_BUILD_KEYS, STATS_VERSION, decodeStats, parseMissReason, readRunEstimates, readStats as readStats$1, statsFile, statsProjectKey as statsProjectKey$1, trimSamples, wholePhases, workspaceBuildDetailFile } from "@stim-cli/core/state";
11
+ import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, LAST_BUILD_KEYS, STATS_VERSION, decodeStats, isJsonObject, parseMissReason, readRunEstimates, readStats as readStats$1, statsFile, statsProjectKey as statsProjectKey$1, trimCapacityRefusals, trimCapacityWaits, trimSamples, wholePhases, workspaceBuildDetailFile } from "@stim-cli/core/state";
13
12
  import chalk from "chalk";
14
- //#region src/watchman.ts
15
- /** Runs one watchman command with `--no-spawn`, so Stim never starts a daemon, and parses its JSON answer. */
16
- const runWatchman = async (args, timeoutMs) => JSON.parse(await getExecutor().runFileAsync("watchman", ["--no-spawn", ...args], { timeoutMs }));
17
- function parseWatchRoots(payload) {
18
- const roots = payload?.roots;
19
- if (!Array.isArray(roots)) return null;
20
- return roots.filter((root) => typeof root === "string");
21
- }
22
- function parseSubscriberNames(payload) {
23
- if (!payload || typeof payload !== "object" || "error" in payload) return null;
24
- const subscribers = payload.subscribers;
25
- if (subscribers === void 0) return [];
26
- if (!Array.isArray(subscribers)) return null;
27
- const names = [];
28
- for (const subscriber of subscribers) {
29
- const name = subscriber?.info?.name;
30
- if (typeof name !== "string") return null;
31
- names.push(name);
32
- }
33
- return names;
34
- }
35
- function parseTriggerCount(payload) {
36
- const triggers = payload?.triggers;
37
- return Array.isArray(triggers) ? triggers.length : null;
38
- }
39
- function parseWatchmanPid(payload) {
40
- const pid = payload?.pid;
41
- return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : null;
42
- }
43
- /** The connected clients in a `debug-status` answer, or null when the answer does not list them. */
44
- function parseWatchmanClients(payload) {
45
- if (!payload || typeof payload !== "object" || "error" in payload) return null;
46
- const clients = payload.clients;
47
- if (!Array.isArray(clients)) return null;
48
- const parsed = [];
49
- for (const client of clients) {
50
- const peer = client?.peer;
51
- if (typeof peer?.pid !== "number") return null;
52
- parsed.push({
53
- pid: peer.pid,
54
- name: typeof peer.name === "string" ? peer.name : null
55
- });
56
- }
57
- return parsed;
58
- }
59
- //#endregion
60
13
  //#region src/engine/stats.ts
61
14
  const PLACEMENT_MAX_AGE_MS = 6048e5;
62
15
  function emptyStats() {
@@ -116,12 +69,16 @@ function updateStats(record, run, now) {
116
69
  [placement.machine]: applyPlacement(buildMachines?.[placement.machine], placement)
117
70
  };
118
71
  }
72
+ const capacityRefusals = trimCapacityRefusals(record.capacityRefusals ?? [], now);
73
+ const capacityWaits = trimCapacityWaits(record.capacityWaits ?? [], now);
119
74
  return {
120
75
  version: STATS_VERSION,
121
76
  machine,
122
77
  projects,
123
78
  ...Object.keys(history).length ? { history } : {},
124
79
  ...placements?.length ? { placements } : {},
80
+ ...capacityRefusals.length ? { capacityRefusals } : {},
81
+ ...capacityWaits.length ? { capacityWaits } : {},
125
82
  ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
126
83
  };
127
84
  }
@@ -130,6 +87,7 @@ function buildPlacement(run, before, at) {
130
87
  const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
131
88
  const localEstimateMs = wholeMs(before?.lastColdBuildMs);
132
89
  const slotWaitMs = wholeMs(placement.slotWaitMs);
90
+ const deviceSlotWaitMs = wholeMs(placement.deviceSlotWaitMs);
133
91
  return {
134
92
  at,
135
93
  project: run.projectKey,
@@ -139,6 +97,7 @@ function buildPlacement(run, before, at) {
139
97
  ...placement.machine ? { machine: placement.machine } : {},
140
98
  ...buildMs > 0 ? { buildMs } : {},
141
99
  ...slotWaitMs > 0 ? { slotWaitMs } : {},
100
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {},
142
101
  ...localEstimateMs > 0 ? { localEstimateMs } : {},
143
102
  ...run.failed ? { failed: true } : {}
144
103
  };
@@ -179,12 +138,53 @@ function recordRunStats(run, now) {
179
138
  };
180
139
  });
181
140
  }
141
+ function recordCapacityRefusal(event, now) {
142
+ try {
143
+ withConfigLock$1(() => {
144
+ const path = statsFile();
145
+ const loaded = loadForUpdate(path, now, false);
146
+ if (!loaded.record || loaded.newerVersion !== null) return;
147
+ const capacityRefusals = trimCapacityRefusals([...loaded.record.capacityRefusals ?? [], {
148
+ ...event,
149
+ at: new Date(now).toISOString(),
150
+ kind: "device"
151
+ }], now);
152
+ writeStats(path, {
153
+ ...loaded.record,
154
+ capacityRefusals
155
+ });
156
+ });
157
+ } catch {}
158
+ }
159
+ function recordCapacityWait(event, now) {
160
+ try {
161
+ withConfigLock$1(() => {
162
+ const path = statsFile();
163
+ const loaded = loadForUpdate(path, now, false);
164
+ if (!loaded.record || loaded.newerVersion !== null) return;
165
+ const capacityWaits = trimCapacityWaits([...loaded.record.capacityWaits ?? [], {
166
+ platform: event.platform,
167
+ max: event.max,
168
+ workspace: event.workspace,
169
+ ms: wholeMs(event.ms),
170
+ ...event.reclaimed && Number.isInteger(event.reclaimed) && event.reclaimed > 0 ? { reclaimed: event.reclaimed } : {},
171
+ at: new Date(now).toISOString(),
172
+ kind: "device-wait"
173
+ }], now);
174
+ writeStats(path, {
175
+ ...loaded.record,
176
+ capacityWaits
177
+ });
178
+ });
179
+ } catch {}
180
+ }
182
181
  function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
183
182
  let projectKey = null;
184
183
  let cacheKey = null;
185
184
  let coldBuildMs = 0;
186
185
  let podsMs = 0;
187
186
  let placement = null;
187
+ let deviceSlotWaitMs = 0;
188
188
  let recorded = false;
189
189
  return {
190
190
  setProject(key) {
@@ -199,6 +199,12 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
199
199
  setPodsMs(ms) {
200
200
  podsMs = wholeMs(ms);
201
201
  },
202
+ addDeviceSlotWaitMs(ms) {
203
+ deviceSlotWaitMs += wholeMs(ms);
204
+ },
205
+ deviceSlotWaitMs() {
206
+ return deviceSlotWaitMs || void 0;
207
+ },
202
208
  setPlacement(next) {
203
209
  placement = next;
204
210
  },
@@ -219,7 +225,10 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
219
225
  ...podsMs > 0 ? { podsMs } : {},
220
226
  ...Object.keys(ran).length ? { phases: ran } : {},
221
227
  ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
222
- ...placement ? { placement } : {}
228
+ ...placement ? { placement: {
229
+ ...placement,
230
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {}
231
+ } } : {}
223
232
  }, now());
224
233
  if (outcome?.note) note(outcome.note);
225
234
  } catch (error) {
@@ -228,7 +237,7 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
228
237
  }
229
238
  };
230
239
  }
231
- function loadForUpdate(path, now) {
240
+ function loadForUpdate(path, now, recoverCorrupt = true) {
232
241
  let text;
233
242
  try {
234
243
  text = readFileSync(path, "utf-8");
@@ -241,6 +250,11 @@ function loadForUpdate(path, now) {
241
250
  }
242
251
  const parsed = decodeStats(text);
243
252
  if (!parsed.record && parsed.newerVersion === null) {
253
+ if (!recoverCorrupt) return {
254
+ record: null,
255
+ note: null,
256
+ newerVersion: null
257
+ };
244
258
  const aside = `${path}.corrupt-${now}`;
245
259
  renameSync(path, aside);
246
260
  return {
@@ -469,6 +483,7 @@ const NO_BUILD_PROGRESS = {
469
483
  deviceSetupKnown: () => void 0,
470
484
  place: () => {},
471
485
  waitingOn: () => {},
486
+ waitingFor: () => {},
472
487
  output: () => {},
473
488
  durations: () => ({}),
474
489
  clear: () => {}
@@ -578,6 +593,7 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
578
593
  record.outcome = outcome;
579
594
  if (projectKey) record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
580
595
  };
596
+ const waits = /* @__PURE__ */ new Map();
581
597
  return {
582
598
  step(phase) {
583
599
  if (phase === record.phase) return;
@@ -636,6 +652,15 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
636
652
  };
637
653
  write();
638
654
  },
655
+ waitingFor(info, kind) {
656
+ if (info) waits.set(info.kind, info);
657
+ else if (kind) waits.delete(kind);
658
+ else waits.clear();
659
+ const first = [...waits.values()].toSorted((a, b) => Date.parse(a.since) - Date.parse(b.since))[0];
660
+ if (first) record.waitingFor = first;
661
+ else delete record.waitingFor;
662
+ write();
663
+ },
639
664
  waitingOn(holder) {
640
665
  if (holder === null) {
641
666
  if (!record.waitingOn) return;
@@ -684,6 +709,7 @@ function completedPhaseDurations(phases) {
684
709
  return Object.keys(durations).length ? durations : void 0;
685
710
  }
686
711
  const HISTORY_FIELDS = [
712
+ "devicePlacement",
687
713
  "platform",
688
714
  "status",
689
715
  "configuration",
@@ -728,6 +754,28 @@ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now
728
754
  slot: own?.slot ?? "default",
729
755
  phases: own ? phaseDurations(own.phases, now()) : {}
730
756
  };
757
+ const slot = own?.slot ?? "default";
758
+ const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
759
+ const savedSlot = isJsonObject(slots[slot]) ? slots[slot] : {};
760
+ const savedDevice = slot === "default" ? state[platform] : savedSlot[platform];
761
+ if (record.devicePlacement || isJsonObject(savedDevice)) {
762
+ const device = isJsonObject(savedDevice) ? { ...savedDevice } : {};
763
+ if (record.devicePlacement) device.devicePlacement = record.devicePlacement;
764
+ else delete device.devicePlacement;
765
+ state = slot === "default" ? {
766
+ ...state,
767
+ [platform]: device
768
+ } : {
769
+ ...state,
770
+ deviceSlots: {
771
+ ...slots,
772
+ [slot]: {
773
+ ...savedSlot,
774
+ [platform]: device
775
+ }
776
+ }
777
+ };
778
+ }
731
779
  return withHistoryEntry({
732
780
  ...state,
733
781
  lastBuild: record,
@@ -770,6 +818,7 @@ function parseActiveBuild(value) {
770
818
  const missReason = parseMissReason(record.missReason);
771
819
  const placement = parsePlacement(record.placement);
772
820
  const waitingOn = parseWaitingOn(record.waitingOn);
821
+ const waitingFor = parseWaitingFor(record.waitingFor);
773
822
  const estimate = parseEstimate(record.estimate);
774
823
  return {
775
824
  platform: record.platform,
@@ -782,6 +831,7 @@ function parseActiveBuild(value) {
782
831
  ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
783
832
  ...placement ? { placement } : {},
784
833
  ...waitingOn ? { waitingOn } : {},
834
+ ...waitingFor ? { waitingFor } : {},
785
835
  ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
786
836
  ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
787
837
  ...estimate ? { estimate } : {},
@@ -809,6 +859,19 @@ function parsePlacement(value) {
809
859
  phaseStartedAt
810
860
  };
811
861
  }
862
+ function parseWaitingFor(value) {
863
+ if (!value || typeof value !== "object") return null;
864
+ const { kind, inUse, max, since } = value;
865
+ if (kind !== "build-slot" && kind !== "device-slot") return null;
866
+ if (!Number.isInteger(inUse) || inUse < 0 || !Number.isInteger(max) || max <= 0) return null;
867
+ if (typeof since !== "string" || !Number.isFinite(Date.parse(since))) return null;
868
+ return {
869
+ kind,
870
+ inUse,
871
+ max,
872
+ since
873
+ };
874
+ }
812
875
  function parseWaitingOn(value) {
813
876
  const path = value?.path;
814
877
  return typeof path === "string" && path ? { path } : null;
@@ -910,7 +973,8 @@ function buildReport(record, { state, history }) {
910
973
  ...record.missReason ? { missReason: record.missReason } : {},
911
974
  ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
912
975
  placement: record.placement ?? "local",
913
- ...record.waitingOn ? { waitingOn: record.waitingOn } : {}
976
+ ...record.waitingOn ? { waitingOn: record.waitingOn } : {},
977
+ ...record.waitingFor ? { waitingFor: record.waitingFor } : {}
914
978
  };
915
979
  }
916
980
  function remainingText(remainingMs) {
@@ -1096,4 +1160,4 @@ function shutDownIdleDevices(devices, idleMs, recollect) {
1096
1160
  };
1097
1161
  }
1098
1162
  //#endregion
1099
- export { parseSubscriberNames as C, parseWatchmanPid as D, parseWatchmanClients as E, runWatchman as O, statsProjectKey$1 as S, parseWatchRoots as T, tapBuildLog as _, parseIdleDuration as a, readStats$1 as b, ACTIVE_BUILD_KEY as c, buildReport as d, buildStatusLine as f, startBuildProgress as g, recordFinishedBuild as h, idleShutdownCandidates as i, NO_BUILD_PROGRESS as l, parseActiveBuild as m, collectOwnedDeviceActivity as n, shutDownIdleDevices as o, estimateBuild as p, idleDeviceLines as r, workspaceBuildInProgress as s, collectIdleDevices as t, activeBuildState as u, createRunRecorder as v, parseTriggerCount as w, recordRunStats as x, readRunEstimates as y };
1163
+ export { recordRunStats as C, recordCapacityWait as S, tapBuildLog as _, parseIdleDuration as a, readStats$1 as b, ACTIVE_BUILD_KEY as c, buildReport as d, buildStatusLine as f, startBuildProgress as g, recordFinishedBuild as h, idleShutdownCandidates as i, NO_BUILD_PROGRESS as l, parseActiveBuild as m, collectOwnedDeviceActivity as n, shutDownIdleDevices as o, estimateBuild as p, idleDeviceLines as r, workspaceBuildInProgress as s, collectIdleDevices as t, activeBuildState as u, createRunRecorder as v, statsProjectKey$1 as w, recordCapacityRefusal as x, readRunEstimates as y };
@@ -1,11 +1,12 @@
1
1
  import { R as deviceSlotKey, _ as loadConfig$1 } from "./config-D0DbpZtM.mjs";
2
- import { o as formatLongDuration } from "./command-output-DnJMdEY-.mjs";
3
- import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-B8-aJ1_5.mjs";
4
- import { o as canonicalPath } from "./in-use-DvBw0Kxo.mjs";
5
- import { n as collectOwnedDeviceActivity, s as workspaceBuildInProgress } from "./idle-DYVymG92.mjs";
6
- import { u as listAllIosSims } from "./ios-BaInn3sW.mjs";
7
- import { i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-CL6vDs2Y.mjs";
8
- import { DEVICE_IDLE_SHUTDOWN_KEY, readDeviceIdleShutdowns } from "@stim-cli/core/state";
2
+ import { o as formatLongDuration } from "./command-output-CDAIFD-8.mjs";
3
+ import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-DqcCbpE7.mjs";
4
+ import { o as canonicalPath } from "./in-use-CvGOUn-x.mjs";
5
+ import { m as workspaceDeviceLastUseAt } from "./activity-IRvJY_ru.mjs";
6
+ import { u as listAllIosSims } from "./ios-DSfnPfse.mjs";
7
+ import { g as teardownOwnedIosSim, m as teardownOwnedAvd } from "./ownership-BMLqyBgJ.mjs";
8
+ import { n as collectOwnedDeviceActivity, s as workspaceBuildInProgress } from "./idle-B2m-Ovod.mjs";
9
+ import { DEVICE_IDLE_SHUTDOWN_KEY, DEVICE_RECLAIM_REASON, readDeviceIdleShutdowns } from "@stim-cli/core/state";
9
10
  //#region src/devices/idle-shutdown.ts
10
11
  const SIM_LIST_TIMEOUT_MS = 1e4;
11
12
  const MINUTE_MS = 6e4;
@@ -30,21 +31,26 @@ function listSims() {
30
31
  return [];
31
32
  }
32
33
  }
33
- /** The workspace's booted owned simulators and emulators that have been idle for `idleMs` or more. */
34
+ /** Booted owned devices idle for `idleMs`, in one workspace or all registered workspaces when root is absent. */
34
35
  function dueIdleDevices(root, idleMs, now = Date.now()) {
35
36
  const config = loadConfig$1();
36
- const self = canonicalPath(root);
37
- const entry = Object.entries(config?.projects ?? {}).find(([path]) => canonicalPath(path) === self);
38
- if (!config || !entry) return [];
39
- const buildInProgress = workspaceBuildInProgress(entry[0]);
37
+ if (!config) return [];
38
+ const self = root === void 0 ? void 0 : canonicalPath(root);
39
+ const projects = Object.fromEntries(Object.entries(config.projects).filter(([path]) => {
40
+ if (self !== void 0 && canonicalPath(path) !== self) return false;
41
+ const usedAt = workspaceDeviceLastUseAt(path);
42
+ return !Number.isFinite(usedAt) || now - usedAt >= idleMs;
43
+ }));
44
+ if (!Object.keys(projects).length) return [];
45
+ const builds = new Map(Object.keys(projects).map((path) => [path, workspaceBuildInProgress(path)]));
40
46
  return collectOwnedDeviceActivity({
41
47
  ...config,
42
- projects: { [entry[0]]: entry[1] }
48
+ projects
43
49
  }, listSims(), now).flatMap((device) => {
44
50
  const idleForMs = idleShutdownDueMs(device.activity, {
45
51
  idleMs,
46
52
  now,
47
- buildInProgress
53
+ buildInProgress: builds.get(device.project) ?? true
48
54
  });
49
55
  return idleForMs === null ? [] : [{
50
56
  device,
@@ -57,9 +63,10 @@ function dueIdleDevices(root, idleMs, now = Date.now()) {
57
63
  * and records why. The caller holds the workspace's native-run lock, so no `ios` or `android` run is booting or
58
64
  * installing on them.
59
65
  */
60
- function shutDownIdleDevices(root, idleMs, log, now = Date.now()) {
66
+ function shutDownIdleDevices(root, idleMs, log, now = Date.now(), { only, reason } = {}) {
61
67
  let shutDown = 0;
62
68
  for (const { device, idleForMs } of dueIdleDevices(root, idleMs, now)) {
69
+ if (only && (device.kind !== only.kind || device.id !== only.id || device.project !== only.project || device.slot !== only.slot)) continue;
63
70
  const what = `${device.kind === "ios" ? "simulator" : "emulator"} ${device.name}`;
64
71
  const outcome = device.kind === "ios" ? teardownOwnedIosSim(device.id, {
65
72
  label: device.name,
@@ -81,7 +88,8 @@ function shutDownIdleDevices(root, idleMs, log, now = Date.now()) {
81
88
  ...readDeviceIdleShutdowns(state),
82
89
  [deviceSlotKey(device.kind, device.slot)]: {
83
90
  at: new Date(now).toISOString(),
84
- idleMinutes
91
+ idleMinutes,
92
+ ...reason ? { reason } : {}
85
93
  }
86
94
  }
87
95
  }));
@@ -95,7 +103,7 @@ function shutDownIdleDevices(root, idleMs, log, now = Date.now()) {
95
103
  log({
96
104
  level: "info",
97
105
  event: "device_idle_shutdown",
98
- msg: `shut down ${what}, idle ${formatLongDuration(idleForMs)}`
106
+ msg: `shut down ${what}, idle ${formatLongDuration(idleForMs)}${reason === DEVICE_RECLAIM_REASON ? ", reclaimed for a waiting run" : ""}`
99
107
  });
100
108
  } else if (outcome.status !== "missing") log({
101
109
  level: "warn",
@@ -1,17 +1,17 @@
1
1
  import { p as getProject } from "./config-D0DbpZtM.mjs";
2
2
  import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
3
- import { r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-B8-aJ1_5.mjs";
4
- import { o as readWebRecord, u as webFacts } from "./state-C1jB7Q8q.mjs";
5
- import { h as listBuildLocks } from "./device-lease-D-hLqx1b.mjs";
6
- import { n as listBuildSlots } from "./build-slots-rUk77xqh.mjs";
7
- import { n as workspaceProcessLockError, r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
8
- import { i as macosRuntimeClaim } from "./state-3DwSQIBK.mjs";
9
- import { n as managedLockHolders, t as resolveSupervisorTarget } from "./ownership-hCkPzGmr.mjs";
3
+ import { r as readWorkspaceState$1, v as workspaceDir } from "./workspace-state-DqcCbpE7.mjs";
4
+ import { o as readWebRecord, u as webFacts } from "./state-PeQiJuIu.mjs";
5
+ import { h as listBuildLocks } from "./device-lease-DpoS36_u.mjs";
6
+ import { n as listBuildSlots } from "./build-slots-41v_d2x9.mjs";
7
+ import { n as managedLockHolders, t as resolveSupervisorTarget } from "./ownership-BMLqyBgJ.mjs";
8
+ import { n as workspaceProcessLockError, r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-2mNzOlaB.mjs";
9
+ import { i as macosRuntimeClaim } from "./state-DyaNTgfR.mjs";
10
10
  import { lstatSync, readdirSync, rmSync, rmdirSync, unlinkSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { realpathSync as realpathSync$1 } from "fs";
13
13
  import { resolve as resolve$1 } from "path";
14
- import { hostedIosRecords, macosAppState, parseHostedIosPlacement, readMacosRecord, unreadableHostedIos } from "@stim-cli/core/state";
14
+ import { hostedNativeRecords, macosAppState, parseHostedNativePlacement, readMacosRecord, unreadableHostedNative } from "@stim-cli/core/state";
15
15
  //#region src/commands/gc/paths.ts
16
16
  function canonicalPath(p) {
17
17
  try {
@@ -67,9 +67,9 @@ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted =
67
67
  if (claims.live.length) reasons.push(NATIVE_RUN_HELD);
68
68
  else if (claims.unresolved[0]) reasons.push(`its native-run.lock cannot be resolved: ${claims.unresolved[0].reason}`);
69
69
  }
70
- for (const [slot, record] of Object.entries(hosted ? hostedIosRecords(readWorkspaceState$1(root)) : {})) {
71
- const placement = parseHostedIosPlacement(record);
72
- reasons.push(placement ? `its iOS simulator runs on ${placement.machine}` : unreadableHostedIos(slot));
70
+ for (const platform of ["ios", "android"]) for (const [slot, record] of Object.entries(hosted ? hostedNativeRecords(readWorkspaceState$1(root), platform) : {})) {
71
+ const placement = parseHostedNativePlacement(record, platform);
72
+ reasons.push(placement ? `its ${platform === "ios" ? "iOS simulator" : "Android emulator"} runs on ${placement.machine}` : unreadableHostedNative(slot, platform));
73
73
  }
74
74
  const self = canonicalPath(root);
75
75
  for (const [kind, entries] of [["build lock", listBuildLocks()], ["build slot", listBuildSlots()]]) for (const entry of entries) {