stim 1.16.0 → 1.17.1

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 (108) 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-B6rDWrTm.mjs} +681 -319
  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-CUDz870h.mjs} +62 -25
  9. package/dist/{build-plan-DjeSb2Xl.mjs → build-plan-BJSXzsCL.mjs} +345 -86
  10. package/dist/{idle-DYVymG92.mjs → build-progress-D0WkjCPd.mjs} +170 -233
  11. package/dist/{build-slots-rUk77xqh.mjs → build-slots-41v_d2x9.mjs} +35 -25
  12. package/dist/{cli-BkYZ_6hy.mjs → cli-BLQqDGlX.mjs} +17 -17
  13. package/dist/cli.mjs +1 -1
  14. package/dist/{client-BTY686R6.mjs → client-CxNotugx.mjs} +313 -44
  15. package/dist/collector-run.mjs +3 -3
  16. package/dist/{command-output-DnJMdEY-.mjs → command-output-CDAIFD-8.mjs} +3 -3
  17. package/dist/{created-devices-D5owjN4C.mjs → created-devices-CpvzBL25.mjs} +1 -1
  18. package/dist/{dependency-state-B6cMXZab.mjs → dependency-state-CLcBHH2M.mjs} +1 -1
  19. package/dist/{deps-urbmfXig.mjs → deps-CVxlsRFn.mjs} +1 -1
  20. package/dist/{dev-client-CroAIjjj.mjs → dev-client-B-XC4uRH.mjs} +2 -2
  21. package/dist/{device-DZQjF7H5.mjs → device-C8IOoJPv.mjs} +7 -7
  22. package/dist/device-capacity-ZC4XSZ4R.mjs +580 -0
  23. package/dist/device-host-worker.mjs +436 -154
  24. package/dist/{device-ios-BadmeL2V.mjs → device-ios-C2Um-XUR.mjs} +75 -44
  25. package/dist/{device-lease-D-hLqx1b.mjs → device-lease-DpoS36_u.mjs} +2 -2
  26. package/dist/{device-lease-run-CqjShqWW.mjs → device-lease-run-B8YltIH_.mjs} +3 -3
  27. package/dist/{device-pool-CUaEWMMm.mjs → device-pool-3o7s5lo4.mjs} +2 -2
  28. package/dist/{device-remote-D5WVWqOq.mjs → device-remote-hFSkpfQu.mjs} +12 -12
  29. package/dist/{doctor-Cimf-gGB.mjs → doctor-BF_435OC.mjs} +40 -58
  30. package/dist/{error-diagnostics-B9L4Ae0x.mjs → error-diagnostics-C-NMnhrk.mjs} +7 -7
  31. package/dist/{gc-Bqy4bjEu.mjs → gc-BsWKa_-d.mjs} +313 -30
  32. package/dist/{gradle-BsO1MdsH.mjs → gradle-bsBRy6i9.mjs} +3 -3
  33. package/dist/{guide-B_uyKTxW.mjs → guide-DC3n6-NH.mjs} +1776 -655
  34. package/dist/{guide-status-CjEZMAD4.mjs → guide-status-Du-ZQ3FD.mjs} +1 -1
  35. package/dist/hosted-android-DgU31wY3.mjs +32 -0
  36. package/dist/{hosted-client-a-WmqE2L.mjs → hosted-client-CdSoH-G6.mjs} +5 -6
  37. package/dist/hosted-ios-BTaVj4Jb.mjs +15 -0
  38. package/dist/{hosted-logs-D5g041Q3.mjs → hosted-logs-SxfBvSvc.mjs} +16 -16
  39. package/dist/{hosted-macos-BSZyIb3y.mjs → hosted-macos-CT9E3meL.mjs} +4 -4
  40. package/dist/{hosted-ios-CIWelIqf.mjs → hosted-native-CZLOrMrN.mjs} +77 -42
  41. package/dist/idle-BXG__1BA.mjs +176 -0
  42. package/dist/{idle-shutdown-CTsDnAde.mjs → idle-shutdown-IGyNV8Cr.mjs} +25 -17
  43. package/dist/{in-use-DvBw0Kxo.mjs → in-use-CvGOUn-x.mjs} +11 -11
  44. package/dist/{ios-aq3QoGI3.mjs → ios-C7JuT1bI.mjs} +173 -91
  45. package/dist/{ios-BaInn3sW.mjs → ios-DSfnPfse.mjs} +4 -4
  46. package/dist/{ios-device-FfCN_C8p.mjs → ios-device-pAN4t54-.mjs} +1 -1
  47. package/dist/ios-state-8L3lgum6.mjs +89 -0
  48. package/dist/{launch-verify-2DBRizjo.mjs → launch-verify-lTnWZikR.mjs} +2 -2
  49. package/dist/{logs-CHEVK8zD.mjs → logs-DNxKcok9.mjs} +36 -23
  50. package/dist/{macos-CSYEAFmF.mjs → macos-BEs8cwKx.mjs} +75 -34
  51. package/dist/macos-run.mjs +1 -1
  52. package/dist/maintenance-run.mjs +2 -2
  53. package/dist/{metro-BeK1ZPz1.mjs → metro-g8NqiJp-.mjs} +1 -1
  54. package/dist/{metro-gateway-BJ-rxQAB.mjs → metro-gateway-CHqK0p-a.mjs} +22 -3
  55. package/dist/{named-ports-Bk_YXEmE.mjs → named-ports-C8m8EQs2.mjs} +2 -2
  56. package/dist/{native-run-NaBrRFtQ.mjs → native-run-D1Q56rAD.mjs} +17 -6
  57. package/dist/{native-runtime-DBzRnEKS.mjs → native-runtime-YhaSxP4v.mjs} +6 -6
  58. package/dist/offload-worker.mjs +9 -9
  59. package/dist/{teardown-CL6vDs2Y.mjs → ownership-BMLqyBgJ.mjs} +603 -26
  60. package/dist/{ownership-DiA16AsN.mjs → ownership-DyRQru0q.mjs} +2 -2
  61. package/dist/{ports-R2k4Knz3.mjs → ports-4nYqxPWC.mjs} +2 -2
  62. package/dist/{prebuild-D2fsca_Z.mjs → prebuild-D7RUMkwN.mjs} +3 -3
  63. package/dist/{preview-DhVbvPAZ.mjs → preview-C_JjkEy4.mjs} +7 -7
  64. package/dist/{project-Du91Ilvc.mjs → project-D47Rh0ly.mjs} +7 -2
  65. package/dist/{recordings-BIXOzVnr.mjs → recordings-BNgsD1Tr.mjs} +1 -1
  66. package/dist/{reload-rM9jV1fz.mjs → reload-byOjE3P4.mjs} +62 -35
  67. package/dist/{remote-cache-C8-V13XN.mjs → remote-cache-Ci4DQYYj.mjs} +3 -3
  68. package/dist/{run-f4vI6EnK.mjs → run-CkE9y0nc.mjs} +1 -1
  69. package/dist/{server-bare-CpPGmnmO.mjs → server-bare-BkrSxSsZ.mjs} +1 -1
  70. package/dist/{server-expo-MC0rHlOC.mjs → server-expo-CHTyXD0G.mjs} +2 -2
  71. package/dist/server-expo-CeakhI8P.mjs +2 -0
  72. package/dist/{settings-oTSkkNrM.mjs → settings-Cfd-0r2d.mjs} +10 -9
  73. package/dist/{settings-ZkDzkzvB.mjs → settings-SR9OmW9h.mjs} +3 -3
  74. package/dist/settings.schema.json +123 -76
  75. package/dist/{simslim-B8-7AoTI.mjs → simslim-BWNrc3M3.mjs} +3 -3
  76. package/dist/{slot-launch-B8VrHxn1.mjs → slot-launch-DmjBmpQV.mjs} +8 -8
  77. package/dist/{start-CX8Ig-U2.mjs → start-BEP-XmEA.mjs} +1 -1
  78. package/dist/{start-ClDfwXa4.mjs → start-BHIersSm.mjs} +11 -11
  79. package/dist/{state-DcW6vOd_.mjs → state-BKa8wNN5.mjs} +1 -1
  80. package/dist/{state-3DwSQIBK.mjs → state-DyaNTgfR.mjs} +1 -1
  81. package/dist/{state-C1jB7Q8q.mjs → state-PeQiJuIu.mjs} +2 -2
  82. package/dist/{stats-CEZ_i5je.mjs → stats-Bq4fJOdB.mjs} +7 -4
  83. package/dist/{status-Cs7VLg9D.mjs → status-CqeWbIWW.mjs} +128 -38
  84. package/dist/{status-BuvBFeII.mjs → status-CzS4bPO9.mjs} +4 -4
  85. package/dist/{stop-Bffozsac.mjs → stop-Cj01je8D.mjs} +48 -27
  86. package/dist/{stop-B0Yxtmiy.mjs → stop-D03prQnW.mjs} +4 -4
  87. package/dist/{stop-Dt_dM16A.mjs → stop-DOxwkH4j.mjs} +2 -2
  88. package/dist/{stop-cause-CidxIqup.mjs → stop-cause-rhmGfwvt.mjs} +1 -1
  89. package/dist/supervisor-run.d.mts +7 -4
  90. package/dist/supervisor-run.mjs +26 -28
  91. package/dist/{support-D4aYJniO.mjs → support-BE-tbOP9.mjs} +45 -20
  92. package/dist/{support-CAP8qDUF.mjs → support-WvD__tSV.mjs} +7 -7
  93. package/dist/{toolchain-DkUflduf.mjs → toolchain-CdrxgUeA.mjs} +8 -8
  94. package/dist/{warm-progress-CpGv3Wb6.mjs → warm-progress-DF4JRvYI.mjs} +3 -3
  95. package/dist/watchman-APYjXSQa.mjs +48 -0
  96. package/dist/{web-DWUaMp8P.mjs → web-DddUdJzr.mjs} +12 -13
  97. package/dist/web-run.mjs +4 -4
  98. package/dist/{workspace-process-lock-BL7Q0HCO.mjs → workspace-process-lock-2mNzOlaB.mjs} +3 -2
  99. package/dist/{workspace-state-B8-aJ1_5.mjs → workspace-state-DqcCbpE7.mjs} +2 -2
  100. package/dist/{workspaces-BV4pdHFz.mjs → workspaces-Bcib97-6.mjs} +3 -3
  101. package/dist/{worktree-K-xj9Db0.mjs → worktree-AK_m-d_m.mjs} +141 -40
  102. package/dist/{worktree-jnG8Iph5.mjs → worktree-CsAjSmEo.mjs} +1 -1
  103. package/package.json +5 -5
  104. package/dist/device-capacity-BHwxNBlu.mjs +0 -136
  105. package/dist/ios-state-DS5UpYTH.mjs +0 -48
  106. package/dist/machines-BIBUdhpj.mjs +0 -265
  107. package/dist/ownership-hCkPzGmr.mjs +0 -571
  108. package/dist/server-expo-CVqO2c35.mjs +0 -2
@@ -1,62 +1,10 @@
1
- import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
- import { D as withConfigLock$1, H as projectDeviceSlots } from "./config-D0DbpZtM.mjs";
1
+ import { D as withConfigLock$1 } 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, u as plural } from "./command-output-CDAIFD-8.mjs";
4
+ import { a as updateWorkspaceState, t as clearWorkspaceStateKey } from "./workspace-state-DqcCbpE7.mjs";
10
5
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
6
  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";
13
- 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
7
+ 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";
60
8
  //#region src/engine/stats.ts
61
9
  const PLACEMENT_MAX_AGE_MS = 6048e5;
62
10
  function emptyStats() {
@@ -116,12 +64,16 @@ function updateStats(record, run, now) {
116
64
  [placement.machine]: applyPlacement(buildMachines?.[placement.machine], placement)
117
65
  };
118
66
  }
67
+ const capacityRefusals = trimCapacityRefusals(record.capacityRefusals ?? [], now);
68
+ const capacityWaits = trimCapacityWaits(record.capacityWaits ?? [], now);
119
69
  return {
120
70
  version: STATS_VERSION,
121
71
  machine,
122
72
  projects,
123
73
  ...Object.keys(history).length ? { history } : {},
124
74
  ...placements?.length ? { placements } : {},
75
+ ...capacityRefusals.length ? { capacityRefusals } : {},
76
+ ...capacityWaits.length ? { capacityWaits } : {},
125
77
  ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
126
78
  };
127
79
  }
@@ -130,6 +82,7 @@ function buildPlacement(run, before, at) {
130
82
  const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
131
83
  const localEstimateMs = wholeMs(before?.lastColdBuildMs);
132
84
  const slotWaitMs = wholeMs(placement.slotWaitMs);
85
+ const deviceSlotWaitMs = wholeMs(placement.deviceSlotWaitMs);
133
86
  return {
134
87
  at,
135
88
  project: run.projectKey,
@@ -139,6 +92,7 @@ function buildPlacement(run, before, at) {
139
92
  ...placement.machine ? { machine: placement.machine } : {},
140
93
  ...buildMs > 0 ? { buildMs } : {},
141
94
  ...slotWaitMs > 0 ? { slotWaitMs } : {},
95
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {},
142
96
  ...localEstimateMs > 0 ? { localEstimateMs } : {},
143
97
  ...run.failed ? { failed: true } : {}
144
98
  };
@@ -179,12 +133,53 @@ function recordRunStats(run, now) {
179
133
  };
180
134
  });
181
135
  }
136
+ function recordCapacityRefusal(event, now) {
137
+ try {
138
+ withConfigLock$1(() => {
139
+ const path = statsFile();
140
+ const loaded = loadForUpdate(path, now, false);
141
+ if (!loaded.record || loaded.newerVersion !== null) return;
142
+ const capacityRefusals = trimCapacityRefusals([...loaded.record.capacityRefusals ?? [], {
143
+ ...event,
144
+ at: new Date(now).toISOString(),
145
+ kind: "device"
146
+ }], now);
147
+ writeStats(path, {
148
+ ...loaded.record,
149
+ capacityRefusals
150
+ });
151
+ });
152
+ } catch {}
153
+ }
154
+ function recordCapacityWait(event, now) {
155
+ try {
156
+ withConfigLock$1(() => {
157
+ const path = statsFile();
158
+ const loaded = loadForUpdate(path, now, false);
159
+ if (!loaded.record || loaded.newerVersion !== null) return;
160
+ const capacityWaits = trimCapacityWaits([...loaded.record.capacityWaits ?? [], {
161
+ platform: event.platform,
162
+ max: event.max,
163
+ workspace: event.workspace,
164
+ ms: wholeMs(event.ms),
165
+ ...event.reclaimed && Number.isInteger(event.reclaimed) && event.reclaimed > 0 ? { reclaimed: event.reclaimed } : {},
166
+ at: new Date(now).toISOString(),
167
+ kind: "device-wait"
168
+ }], now);
169
+ writeStats(path, {
170
+ ...loaded.record,
171
+ capacityWaits
172
+ });
173
+ });
174
+ } catch {}
175
+ }
182
176
  function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
183
177
  let projectKey = null;
184
178
  let cacheKey = null;
185
179
  let coldBuildMs = 0;
186
180
  let podsMs = 0;
187
181
  let placement = null;
182
+ let deviceSlotWaitMs = 0;
188
183
  let recorded = false;
189
184
  return {
190
185
  setProject(key) {
@@ -199,6 +194,12 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
199
194
  setPodsMs(ms) {
200
195
  podsMs = wholeMs(ms);
201
196
  },
197
+ addDeviceSlotWaitMs(ms) {
198
+ deviceSlotWaitMs += wholeMs(ms);
199
+ },
200
+ deviceSlotWaitMs() {
201
+ return deviceSlotWaitMs || void 0;
202
+ },
202
203
  setPlacement(next) {
203
204
  placement = next;
204
205
  },
@@ -219,7 +220,10 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
219
220
  ...podsMs > 0 ? { podsMs } : {},
220
221
  ...Object.keys(ran).length ? { phases: ran } : {},
221
222
  ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
222
- ...placement ? { placement } : {}
223
+ ...placement ? { placement: {
224
+ ...placement,
225
+ ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {}
226
+ } } : {}
223
227
  }, now());
224
228
  if (outcome?.note) note(outcome.note);
225
229
  } catch (error) {
@@ -228,7 +232,7 @@ function createRunRecorder({ platform, write, now, note, phases, deviceSetup })
228
232
  }
229
233
  };
230
234
  }
231
- function loadForUpdate(path, now) {
235
+ function loadForUpdate(path, now, recoverCorrupt = true) {
232
236
  let text;
233
237
  try {
234
238
  text = readFileSync(path, "utf-8");
@@ -241,6 +245,11 @@ function loadForUpdate(path, now) {
241
245
  }
242
246
  const parsed = decodeStats(text);
243
247
  if (!parsed.record && parsed.newerVersion === null) {
248
+ if (!recoverCorrupt) return {
249
+ record: null,
250
+ note: null,
251
+ newerVersion: null
252
+ };
244
253
  const aside = `${path}.corrupt-${now}`;
245
254
  renameSync(path, aside);
246
255
  return {
@@ -337,6 +346,13 @@ const GRADLE_TASK = /^> Task (:\S+)/;
337
346
  */
338
347
  const XCODE_TARGET_DONE = /^(Touch|RegisterExecutionPolicyException|CodeSign)$/;
339
348
  const GRADLE_CONFIGURE = /^> Configure project /;
349
+ /**
350
+ * SwiftPM prints `[done / total] name` once it plans the build (with U+2009 thin spaces around the slash in Swift 6.4
351
+ * output), `[done/total] Compiling ...` in older toolchains; Swift 6.4 names the target being worked on, and
352
+ * `<product>-product` is the link of a product.
353
+ */
354
+ const SWIFTPM_PROGRESS = /^\[(\d+)\s*\/\s*(\d+)\](?:\s+(.*))?$/;
355
+ const SWIFTPM_CONFIGURE = /^(Fetching|Fetched|Computing version|Computed|Creating working copy|Working copy of)\b|^\[(Pre-planning|Planning|Using on-disk description)/;
340
356
  const XCODE_STEPS = [
341
357
  [/^(CpResource|CopyPNGFile|CopyStringsFile|CompileAssetCatalog\w*|CompileStoryboard|CompileXIB|LinkStoryboards|ProcessInfoPlistFile)$/, "resources"],
342
358
  [/^(Compile\w*|Swift\w*|ScanDependencies|Precompile\w*)$/, "compile"],
@@ -374,8 +390,23 @@ function shortXcodeAction(action, args, target) {
374
390
  const subject = action === "PhaseScriptExecution" ? unescape(tokens[0] ?? "") : baseName(unescape(tokens.findLast((token) => token.startsWith("/")) ?? ""));
375
391
  return clip(`${action}${subject ? ` ${subject}` : ""} (${target})`);
376
392
  }
377
- /** What one line of xcodebuild or Gradle output says about the build's progress, or null for any other line. */
393
+ function swiftpmStep(text) {
394
+ return /^Linking\b|-product$/.test(text) ? "link" : "compile";
395
+ }
396
+ /** What one line of xcodebuild, Gradle or SwiftPM output says about the build's progress, or null for any other line. */
378
397
  function parseBuildToolLine(msg) {
398
+ const counted = SWIFTPM_PROGRESS.exec(msg);
399
+ if (counted) {
400
+ const text = counted[3]?.trim() ?? "";
401
+ return {
402
+ kind: "count",
403
+ done: Number(counted[1]),
404
+ total: Number(counted[2]),
405
+ step: swiftpmStep(text),
406
+ line: text ? clip(text) : null
407
+ };
408
+ }
409
+ if (SWIFTPM_CONFIGURE.test(msg)) return { kind: "configure" };
379
410
  const graph = TARGET_GRAPH.exec(msg);
380
411
  if (graph) return {
381
412
  kind: "total",
@@ -412,7 +443,8 @@ function parseBuildToolLine(msg) {
412
443
  }
413
444
  /**
414
445
  * Accumulates a build's tool output into its detail: xcodebuild counts the distinct targets it finished against the
415
- * total its dependency graph note gives; Gradle counts the task lines it printed and has no total.
446
+ * total its dependency graph note gives; Gradle counts the task lines it printed and has no total; SwiftPM reports
447
+ * its own `[done / total]` counts, which the parser passes through.
416
448
  */
417
449
  function createBuildDetailParser() {
418
450
  let seen = false;
@@ -421,6 +453,7 @@ function createBuildDetailParser() {
421
453
  let total = null;
422
454
  let line = null;
423
455
  let tasks = 0;
456
+ let counted = null;
424
457
  const targets = /* @__PURE__ */ new Set();
425
458
  return {
426
459
  push(msg) {
@@ -436,6 +469,16 @@ function createBuildDetailParser() {
436
469
  step = "configure";
437
470
  return true;
438
471
  }
472
+ if (parsed.kind === "count") {
473
+ unit = "steps";
474
+ counted = {
475
+ done: parsed.done,
476
+ total: parsed.total
477
+ };
478
+ step = parsed.step;
479
+ if (parsed.line) line = parsed.line;
480
+ return true;
481
+ }
439
482
  unit = parsed.unit;
440
483
  if (parsed.unit === "tasks") tasks += 1;
441
484
  else if (parsed.finished) targets.add(parsed.id);
@@ -445,6 +488,14 @@ function createBuildDetailParser() {
445
488
  },
446
489
  detail(updatedAt) {
447
490
  if (!seen) return null;
491
+ if (unit === "steps" && counted) return {
492
+ step,
493
+ unit,
494
+ done: Math.min(counted.done, counted.total),
495
+ total: counted.total,
496
+ line,
497
+ updatedAt
498
+ };
448
499
  const done = unit === "tasks" ? tasks : unit === "targets" ? targets.size : null;
449
500
  return {
450
501
  step,
@@ -469,6 +520,7 @@ const NO_BUILD_PROGRESS = {
469
520
  deviceSetupKnown: () => void 0,
470
521
  place: () => {},
471
522
  waitingOn: () => {},
523
+ waitingFor: () => {},
472
524
  output: () => {},
473
525
  durations: () => ({}),
474
526
  clear: () => {}
@@ -576,8 +628,9 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
576
628
  };
577
629
  const settle = (outcome) => {
578
630
  record.outcome = outcome;
579
- if (projectKey) record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
631
+ if (projectKey && platform !== "macos") record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
580
632
  };
633
+ const waits = /* @__PURE__ */ new Map();
581
634
  return {
582
635
  step(phase) {
583
636
  if (phase === record.phase) return;
@@ -589,11 +642,12 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
589
642
  startedAt: at
590
643
  });
591
644
  if (phase !== "wait") delete record.waitingOn;
592
- const outcome = record.outcome ? null : settledOutcome(phase, record.phases);
645
+ const outcome = record.outcome || platform === "macos" ? null : settledOutcome(phase, record.phases);
593
646
  if (outcome) settle(outcome);
594
647
  write();
595
648
  },
596
649
  estimate(key) {
650
+ if (platform === "macos") return;
597
651
  projectKey = key;
598
652
  record.estimate = estimateBuild(projectHistory(key), platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
599
653
  write();
@@ -636,6 +690,15 @@ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note
636
690
  };
637
691
  write();
638
692
  },
693
+ waitingFor(info, kind) {
694
+ if (info) waits.set(info.kind, info);
695
+ else if (kind) waits.delete(kind);
696
+ else waits.clear();
697
+ const first = [...waits.values()].toSorted((a, b) => Date.parse(a.since) - Date.parse(b.since))[0];
698
+ if (first) record.waitingFor = first;
699
+ else delete record.waitingFor;
700
+ write();
701
+ },
639
702
  waitingOn(holder) {
640
703
  if (holder === null) {
641
704
  if (!record.waitingOn) return;
@@ -684,6 +747,7 @@ function completedPhaseDurations(phases) {
684
747
  return Object.keys(durations).length ? durations : void 0;
685
748
  }
686
749
  const HISTORY_FIELDS = [
750
+ "devicePlacement",
687
751
  "platform",
688
752
  "status",
689
753
  "configuration",
@@ -728,6 +792,29 @@ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now
728
792
  slot: own?.slot ?? "default",
729
793
  phases: own ? phaseDurations(own.phases, now()) : {}
730
794
  };
795
+ const slot = own?.slot ?? "default";
796
+ const slots = isJsonObject(state.deviceSlots) ? state.deviceSlots : {};
797
+ const savedSlot = isJsonObject(slots[slot]) ? slots[slot] : {};
798
+ const savedDevice = slot === "default" ? state[platform] : savedSlot[platform];
799
+ if (platform !== "macos" && (record.devicePlacement || isJsonObject(savedDevice))) {
800
+ const device = isJsonObject(savedDevice) ? { ...savedDevice } : {};
801
+ if (record.devicePlacement) device.devicePlacement = record.devicePlacement;
802
+ else delete device.devicePlacement;
803
+ state = slot === "default" ? {
804
+ ...state,
805
+ [platform]: device
806
+ } : {
807
+ ...state,
808
+ deviceSlots: {
809
+ ...slots,
810
+ [slot]: {
811
+ ...savedSlot,
812
+ [platform]: device
813
+ }
814
+ }
815
+ };
816
+ }
817
+ if (platform === "macos") return withHistoryEntry(state, platform, entry);
731
818
  return withHistoryEntry({
732
819
  ...state,
733
820
  lastBuild: record,
@@ -738,7 +825,7 @@ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now
738
825
  function withInterruptedBuild(state) {
739
826
  const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
740
827
  if (!left) return state;
741
- const last = state[LAST_BUILD_KEYS[left.platform]];
828
+ const last = left.platform === "macos" ? state[BUILD_HISTORY_KEY]?.macos?.[0] : state[LAST_BUILD_KEYS[left.platform]];
742
829
  if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
743
830
  return withHistoryEntry(state, left.platform, {
744
831
  platform: left.platform,
@@ -762,7 +849,7 @@ function parseActiveBuild(value) {
762
849
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
763
850
  const record = value;
764
851
  const claim = record.claim;
765
- if (record.platform !== "ios" && record.platform !== "android") return null;
852
+ if (record.platform !== "ios" && record.platform !== "android" && record.platform !== "macos") return null;
766
853
  if (!isPhase(record.phase)) return null;
767
854
  if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
768
855
  if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
@@ -770,6 +857,7 @@ function parseActiveBuild(value) {
770
857
  const missReason = parseMissReason(record.missReason);
771
858
  const placement = parsePlacement(record.placement);
772
859
  const waitingOn = parseWaitingOn(record.waitingOn);
860
+ const waitingFor = parseWaitingFor(record.waitingFor);
773
861
  const estimate = parseEstimate(record.estimate);
774
862
  return {
775
863
  platform: record.platform,
@@ -782,6 +870,7 @@ function parseActiveBuild(value) {
782
870
  ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
783
871
  ...placement ? { placement } : {},
784
872
  ...waitingOn ? { waitingOn } : {},
873
+ ...waitingFor ? { waitingFor } : {},
785
874
  ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
786
875
  ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
787
876
  ...estimate ? { estimate } : {},
@@ -809,6 +898,19 @@ function parsePlacement(value) {
809
898
  phaseStartedAt
810
899
  };
811
900
  }
901
+ function parseWaitingFor(value) {
902
+ if (!value || typeof value !== "object") return null;
903
+ const { kind, inUse, max, since } = value;
904
+ if (kind !== "build-slot" && kind !== "device-slot") return null;
905
+ if (!Number.isInteger(inUse) || inUse < 0 || !Number.isInteger(max) || max <= 0) return null;
906
+ if (typeof since !== "string" || !Number.isFinite(Date.parse(since))) return null;
907
+ return {
908
+ kind,
909
+ inUse,
910
+ max,
911
+ since
912
+ };
913
+ }
812
914
  function parseWaitingOn(value) {
813
915
  const path = value?.path;
814
916
  return typeof path === "string" && path ? { path } : null;
@@ -843,7 +945,7 @@ function median(values) {
843
945
  return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
844
946
  }
845
947
  function estimateBuild(history, platform, known, deviceSetup) {
846
- const lists = history?.[platform];
948
+ const lists = platform === "macos" ? void 0 : history?.[platform];
847
949
  const outcome = known ?? latestOutcome(lists);
848
950
  const samples = matchDeviceSetup(outcome && lists?.[outcome] || [], deviceSetup);
849
951
  const phaseMs = {};
@@ -910,7 +1012,8 @@ function buildReport(record, { state, history }) {
910
1012
  ...record.missReason ? { missReason: record.missReason } : {},
911
1013
  ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
912
1014
  placement: record.placement ?? "local",
913
- ...record.waitingOn ? { waitingOn: record.waitingOn } : {}
1015
+ ...record.waitingOn ? { waitingOn: record.waitingOn } : {},
1016
+ ...record.waitingFor ? { waitingFor: record.waitingFor } : {}
914
1017
  };
915
1018
  }
916
1019
  function remainingText(remainingMs) {
@@ -930,170 +1033,4 @@ function buildStatusLine(report, now) {
930
1033
  return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
931
1034
  }
932
1035
  //#endregion
933
- //#region src/commands/gc/idle.ts
934
- function findOwnedDeviceActivity({ config, sims, androidSerial, readActivity, deadProjects = [] }) {
935
- const booted = new Map(sims.filter((sim) => sim.state === "Booted").map((sim) => [sim.udid, sim.name]));
936
- const dead = new Set(deadProjects);
937
- const found = [];
938
- for (const [project, record] of Object.entries(config?.projects ?? {})) {
939
- if (dead.has(project)) continue;
940
- for (const { slot, platforms } of projectDeviceSlots(record)) {
941
- const ios = platforms.ios;
942
- if (ios?.owned && ios.deviceUdid && booted.has(ios.deviceUdid)) found.push({
943
- kind: "ios",
944
- id: ios.deviceUdid,
945
- name: booted.get(ios.deviceUdid) ?? ios.deviceUdid,
946
- project,
947
- slot,
948
- activity: readActivity({
949
- platform: "ios",
950
- id: ios.deviceUdid,
951
- slot,
952
- workspace: project
953
- })
954
- });
955
- const android = platforms.android;
956
- const serial = android?.owned && android.avdName ? androidSerial(android.avdName) : null;
957
- if (android?.avdName && serial) found.push({
958
- kind: "android",
959
- id: android.avdName,
960
- name: android.avdName,
961
- project,
962
- slot,
963
- activity: readActivity({
964
- platform: "android",
965
- id: serial,
966
- slot,
967
- workspace: project
968
- })
969
- });
970
- }
971
- }
972
- return found;
973
- }
974
- function findIdleDevices({ buildInProgress, now, ...inputs }) {
975
- return findOwnedDeviceActivity(inputs).filter((device) => device.activity.state === "idle").map(({ kind, id, name, project, slot, activity }) => ({
976
- kind,
977
- id,
978
- name,
979
- project,
980
- slot,
981
- lastActivityAt: activity.lastActivityAt ?? null,
982
- idleForMs: idleForMs(activity, now),
983
- buildInProgress: buildInProgress(project)
984
- }));
985
- }
986
- function idleShutdownCandidates(devices, idleMs) {
987
- return devices.filter((d) => !d.buildInProgress && d.idleForMs !== null && d.idleForMs >= idleMs);
988
- }
989
- function parseIdleDuration(value) {
990
- const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value.trim());
991
- if (!match) return null;
992
- const unit = {
993
- m: 6e4,
994
- h: 36e5,
995
- d: 864e5
996
- }[match[2]];
997
- return Number(match[1]) * unit;
998
- }
999
- function workspaceBuildInProgress(project) {
1000
- const record = parseActiveBuild(readWorkspaceState$1(project)?.[ACTIVE_BUILD_KEY]);
1001
- if (!record) return false;
1002
- try {
1003
- return activeBuildState(record.claim) !== "stale";
1004
- } catch {
1005
- return true;
1006
- }
1007
- }
1008
- function deviceActivityInputs(config, sims, now) {
1009
- const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
1010
- return {
1011
- config,
1012
- sims,
1013
- readActivity: createActivityReader({ now }),
1014
- androidSerial: (avdName) => {
1015
- try {
1016
- return resolve(avdName).serial ?? null;
1017
- } catch {
1018
- return null;
1019
- }
1020
- }
1021
- };
1022
- }
1023
- function collectOwnedDeviceActivity(config, sims, now = Date.now()) {
1024
- return findOwnedDeviceActivity(deviceActivityInputs(config, sims, now));
1025
- }
1026
- function collectIdleDevices(config, sims, deadProjects, now = Date.now()) {
1027
- return findIdleDevices({
1028
- ...deviceActivityInputs(config, sims, now),
1029
- deadProjects,
1030
- now,
1031
- buildInProgress: workspaceBuildInProgress
1032
- });
1033
- }
1034
- function idleDeviceLines(devices) {
1035
- if (devices.length === 0) return [];
1036
- return [`Idle owned devices (${devices.length}) - booted with no driver, claim, or recent activity:`, ...devices.map((d) => {
1037
- const slot = d.slot === "default" ? "" : ` [${d.slot}]`;
1038
- const idle = d.idleForMs === null ? "idle for an unknown time" : `idle ${formatLongDuration(d.idleForMs)}`;
1039
- const build = d.buildInProgress ? ", build in progress" : "";
1040
- return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
1041
- })];
1042
- }
1043
- function shutDownIdleDevices(devices, idleMs, recollect) {
1044
- const candidates = idleShutdownCandidates(devices, idleMs);
1045
- const shutDown = [];
1046
- if (candidates.length === 0) {
1047
- console.log(chalk.dim(`No owned device has been idle for ${formatLongDuration(idleMs)} or more.`));
1048
- return {
1049
- failures: 0,
1050
- shutDown
1051
- };
1052
- }
1053
- const fresh = idleShutdownCandidates(recollect(), idleMs);
1054
- let failures = 0;
1055
- for (const device of candidates) {
1056
- const what = `${device.kind} ${device.name}`;
1057
- const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
1058
- if (!current) {
1059
- console.log(chalk.dim(`Kept ${what}: it is no longer idle for ${formatLongDuration(idleMs)}.`));
1060
- recordGcResult("idleDevice", "kept", device.name, {
1061
- id: device.id,
1062
- detail: `it is no longer idle for ${formatLongDuration(idleMs)}`
1063
- });
1064
- continue;
1065
- }
1066
- const outcome = device.kind === "ios" ? teardownOwnedIosSim(device.id, { label: device.name }) : teardownOwnedAvd(device.id, { owner: {
1067
- projectPath: device.project,
1068
- slot: device.slot
1069
- } });
1070
- if (outcome.status === "torn-down") {
1071
- shutDown.push(device);
1072
- console.log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
1073
- recordGcResult("idleDevice", "done", device.name, {
1074
- id: device.id,
1075
- detail: `idle ${formatLongDuration(current.idleForMs)}`
1076
- });
1077
- } else if (outcome.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to shut down.`));
1078
- else if (outcome.status === "skipped") {
1079
- console.log(chalk.yellow(`Skipped ${what}: ${outcome.reason}`));
1080
- recordGcResult("idleDevice", "kept", device.name, {
1081
- id: device.id,
1082
- detail: outcome.reason
1083
- });
1084
- } else {
1085
- failures++;
1086
- console.log(chalk.red(`Failed to shut down ${what}: ${outcome.reason}`));
1087
- recordGcResult("idleDevice", "failed", device.name, {
1088
- id: device.id,
1089
- detail: outcome.reason
1090
- });
1091
- }
1092
- }
1093
- return {
1094
- failures,
1095
- shutDown
1096
- };
1097
- }
1098
- //#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 };
1036
+ export { statsProjectKey$1 as _, buildStatusLine as a, recordFinishedBuild as c, createRunRecorder as d, readRunEstimates as f, recordRunStats as g, recordCapacityWait as h, buildReport as i, startBuildProgress as l, recordCapacityRefusal as m, NO_BUILD_PROGRESS as n, estimateBuild as o, readStats$1 as p, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t, tapBuildLog as u };
@@ -1,5 +1,5 @@
1
1
  import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
2
- import { a as formatElapsed } from "./command-output-DnJMdEY-.mjs";
2
+ import { a as formatElapsed } from "./command-output-CDAIFD-8.mjs";
3
3
  import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-D0XfSe8u.mjs";
4
4
  import { readdirSync, statSync } from "node:fs";
5
5
  import { join } from "node:path";
@@ -53,7 +53,7 @@ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
53
53
  function slotWaitingLine({ max, elapsedMs }) {
54
54
  return `${"build".padEnd(11)} waiting for a build slot (all ${max} in use, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle concurrency`;
55
55
  }
56
- async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.now, out = () => {}, sleep = sleepAsync, intervalMs = SLOT_POLL_MS, progressMs = SLOT_PROGRESS_MS, ceilingMs = SLOT_CEILING_MS }) {
56
+ async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.now, out = () => {}, waitingFor = () => {}, sleep = sleepAsync, intervalMs = SLOT_POLL_MS, progressMs = SLOT_PROGRESS_MS, ceilingMs = SLOT_CEILING_MS }) {
57
57
  if (!max || max <= 0) return {
58
58
  acquired: true,
59
59
  unlimited: true
@@ -61,31 +61,41 @@ async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.n
61
61
  const started = now();
62
62
  let lastProgress = started;
63
63
  let waited = false;
64
- for (;;) {
65
- const got = tryAcquireBuildSlot({
66
- max,
67
- root,
68
- logFile
69
- });
70
- if (got) return {
71
- ...got,
72
- slotWaitMs: waited ? Math.max(0, Math.round(now() - started)) : 0
73
- };
74
- waited = true;
75
- const elapsed = now() - started;
76
- if (elapsed >= ceilingMs) {
77
- const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for one of ${max} build slots, and every slot is held by a process that is still running, or by a holder Stim cannot identify. Slots live under ` + buildSlotsDir() + "; remove a slot directory whose builder is not really building, or raise concurrency.maxBuilds.");
78
- err.code = "STIM_BUILD_SLOT_TIMEOUT";
79
- throw err;
80
- }
81
- if (now() - lastProgress >= progressMs) {
82
- lastProgress = now();
83
- out(slotWaitingLine({
64
+ try {
65
+ for (;;) {
66
+ const got = tryAcquireBuildSlot({
67
+ max,
68
+ root,
69
+ logFile
70
+ });
71
+ if (got) return {
72
+ ...got,
73
+ slotWaitMs: waited ? Math.max(0, Math.round(now() - started)) : 0
74
+ };
75
+ waited = true;
76
+ waitingFor({
77
+ kind: "build-slot",
78
+ inUse: max,
84
79
  max,
85
- elapsedMs: elapsed
86
- }));
80
+ since: new Date(started).toISOString()
81
+ });
82
+ const elapsed = now() - started;
83
+ if (elapsed >= ceilingMs) {
84
+ const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for one of ${max} build slots, and every slot is held by a process that is still running, or by a holder Stim cannot identify. Slots live under ` + buildSlotsDir() + "; remove a slot directory whose builder is not really building, or raise concurrency.maxBuilds.");
85
+ err.code = "STIM_BUILD_SLOT_TIMEOUT";
86
+ throw err;
87
+ }
88
+ if (now() - lastProgress >= progressMs) {
89
+ lastProgress = now();
90
+ out(slotWaitingLine({
91
+ max,
92
+ elapsedMs: elapsed
93
+ }));
94
+ }
95
+ await sleep(intervalMs);
87
96
  }
88
- await sleep(intervalMs);
97
+ } finally {
98
+ waitingFor(null);
89
99
  }
90
100
  }
91
101
  function releaseBuildSlot(handle) {