stim 1.13.0 → 1.15.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 (133) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
  5. package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
  9. package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
  10. package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
  22. package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
  39. package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
  41. package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
  47. package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
  50. package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
  77. package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/slot-launch-BY0BrOLh.mjs +65 -0
  84. package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
  95. package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +44 -12
  105. package/dist/supervisor-run.mjs +276 -33
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
  113. package/dist/web-run.mjs +326 -13
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +55 -10
  121. package/shim/bundle-response.d.cts +1 -0
  122. package/shim/expo-metro-config.cjs +10 -2
  123. package/dist/build-progress-l7d-X-ZP.mjs +0 -247
  124. package/dist/in-use-wYEaKQM9.mjs +0 -249
  125. package/dist/ownership-PUP4Dwai.mjs +0 -1138
  126. package/dist/server-expo-BeYwP_Xy.mjs +0 -2
  127. package/dist/slot-launch-BIWEW-wI.mjs +0 -19
  128. package/dist/start-Rw2GD3nV.mjs +0 -2
  129. package/dist/stats-CaSwmsSZ.mjs +0 -62
  130. package/dist/stats-D8rUTPHd.mjs +0 -368
  131. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  132. package/dist/worktree-3cBa-bQt.mjs +0 -2
  133. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -0,0 +1,872 @@
1
+ import { D as withConfigLock$1 } from "./config-D0DbpZtM.mjs";
2
+ import { n as ownership_claim_exports } from "./process-identity-BmRm1qV0.mjs";
3
+ import { a as updateWorkspaceState, t as clearWorkspaceStateKey } from "./workspace-state-CL2V2Ons.mjs";
4
+ import { a as formatElapsed, u as plural } from "./command-output-CliHHn7a.mjs";
5
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
+ import { BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, LAST_BUILD_KEYS, STATS_VERSION, decodeStats, parseMissReason, readRunEstimates, readStats, statsFile, statsProjectKey as statsProjectKey$1, trimSamples, wholePhases, workspaceBuildDetailFile } from "@stim-cli/core/state";
8
+ //#region src/engine/stats.ts
9
+ const PLACEMENT_MAX_AGE_MS = 6048e5;
10
+ function emptyStats() {
11
+ return {
12
+ version: STATS_VERSION,
13
+ machine: {},
14
+ projects: {}
15
+ };
16
+ }
17
+ function updateStats(record, run, now) {
18
+ const at = new Date(now).toISOString();
19
+ const durationMs = wholeMs(run.durationMs);
20
+ const phases = {
21
+ coldBuildMs: wholeMs(run.coldBuildMs),
22
+ podsMs: wholeMs(run.podsMs)
23
+ };
24
+ const projects = { ...record.projects };
25
+ const scope = { ...projects[run.projectKey] };
26
+ const machine = { ...record.machine };
27
+ const before = scope[run.platform] ?? null;
28
+ const credit = creditMs(before, run, durationMs);
29
+ scope[run.platform] = applyRun(before, run, {
30
+ at,
31
+ durationMs,
32
+ credit,
33
+ phases
34
+ });
35
+ projects[run.projectKey] = scope;
36
+ machine[run.platform] = applyRun(machine[run.platform] ?? null, run, {
37
+ at,
38
+ durationMs,
39
+ credit,
40
+ phases
41
+ });
42
+ const history = { ...record.history };
43
+ if (run.phases && !run.failed && !run.waitedForBuild && !run.offloadedTo) {
44
+ const outcome = isHit(run.cacheHit) ? "hit" : "cold";
45
+ const project = { ...history[run.projectKey] };
46
+ const lists = { ...project[run.platform] };
47
+ const sample = {
48
+ at,
49
+ durationMs,
50
+ phases: wholePhases(run.phases),
51
+ ...run.deviceSetup === void 0 ? {} : { deviceSetup: run.deviceSetup }
52
+ };
53
+ lists[outcome] = trimSamples([...lists[outcome] ?? [], sample]);
54
+ project[run.platform] = lists;
55
+ history[run.projectKey] = project;
56
+ }
57
+ let placements = record.placements;
58
+ let buildMachines = record.buildMachines;
59
+ if (run.placement) {
60
+ const placement = buildPlacement(run, before, at);
61
+ placements = trimPlacements([...placements ?? [], placement], now);
62
+ if (placement.machine && placement.decision !== "here") buildMachines = {
63
+ ...buildMachines,
64
+ [placement.machine]: applyPlacement(buildMachines?.[placement.machine], placement)
65
+ };
66
+ }
67
+ return {
68
+ version: STATS_VERSION,
69
+ machine,
70
+ projects,
71
+ ...Object.keys(history).length ? { history } : {},
72
+ ...placements?.length ? { placements } : {},
73
+ ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
74
+ };
75
+ }
76
+ function buildPlacement(run, before, at) {
77
+ const placement = run.placement;
78
+ const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
79
+ const localEstimateMs = wholeMs(before?.lastColdBuildMs);
80
+ return {
81
+ at,
82
+ project: run.projectKey,
83
+ platform: run.platform,
84
+ decision: placement.decision,
85
+ reason: placement.reason,
86
+ ...placement.machine ? { machine: placement.machine } : {},
87
+ ...buildMs > 0 ? { buildMs } : {},
88
+ ...localEstimateMs > 0 ? { localEstimateMs } : {},
89
+ ...run.failed ? { failed: true } : {}
90
+ };
91
+ }
92
+ function applyPlacement(totals, placement) {
93
+ const next = totals ? { ...totals } : {
94
+ offloaded: 0,
95
+ offloadedMs: 0,
96
+ savedMs: 0,
97
+ fallbacks: 0
98
+ };
99
+ if (placement.decision === "fell-back") {
100
+ next.fallbacks += 1;
101
+ next.lastFallbackAt = placement.at;
102
+ return next;
103
+ }
104
+ next.offloaded += 1;
105
+ next.offloadedMs += placement.buildMs ?? 0;
106
+ if (placement.localEstimateMs && placement.buildMs) next.savedMs += placement.localEstimateMs - placement.buildMs;
107
+ next.lastOffloadAt = placement.at;
108
+ return next;
109
+ }
110
+ function trimPlacements(placements, now) {
111
+ return placements.filter((each) => now - Date.parse(each.at) <= PLACEMENT_MAX_AGE_MS).slice(-100);
112
+ }
113
+ function recordRunStats(run, now) {
114
+ return withConfigLock$1(() => {
115
+ const path = statsFile();
116
+ const loaded = loadForUpdate(path, now);
117
+ if (loaded.newerVersion !== null) return {
118
+ recorded: false,
119
+ note: `Run statistics at ${path} are version ${loaded.newerVersion}, which this Stim does not understand, so this run was not recorded.`
120
+ };
121
+ writeStats(path, updateStats(loaded.record, run, now));
122
+ return {
123
+ recorded: true,
124
+ note: loaded.note
125
+ };
126
+ });
127
+ }
128
+ function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
129
+ let projectKey = null;
130
+ let cacheKey = null;
131
+ let coldBuildMs = 0;
132
+ let podsMs = 0;
133
+ let placement = null;
134
+ let recorded = false;
135
+ return {
136
+ setProject(key) {
137
+ projectKey = key;
138
+ },
139
+ setCacheKey(key) {
140
+ cacheKey = key;
141
+ },
142
+ setBuildMs(ms) {
143
+ coldBuildMs = wholeMs(ms);
144
+ },
145
+ setPodsMs(ms) {
146
+ podsMs = wholeMs(ms);
147
+ },
148
+ setPlacement(next) {
149
+ placement = next;
150
+ },
151
+ record({ failed, cacheHit = false, waited = null, durationMs, offloadedTo = null }) {
152
+ if (!projectKey || !cacheKey || recorded) return;
153
+ recorded = true;
154
+ const ran = failed ? {} : phases?.() ?? {};
155
+ try {
156
+ const outcome = write({
157
+ platform,
158
+ projectKey,
159
+ failed,
160
+ cacheHit,
161
+ waitedForBuild: Boolean(waited),
162
+ durationMs,
163
+ ...offloadedTo ? { offloadedTo } : {},
164
+ ...coldBuildMs > 0 ? { coldBuildMs } : {},
165
+ ...podsMs > 0 ? { podsMs } : {},
166
+ ...Object.keys(ran).length ? { phases: ran } : {},
167
+ ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
168
+ ...placement ? { placement } : {}
169
+ }, now());
170
+ if (outcome?.note) note(outcome.note);
171
+ } catch (error) {
172
+ note(`Run statistics could not be recorded: ${error?.message || error}`);
173
+ }
174
+ }
175
+ };
176
+ }
177
+ function loadForUpdate(path, now) {
178
+ let text;
179
+ try {
180
+ text = readFileSync(path, "utf-8");
181
+ } catch {
182
+ return {
183
+ record: emptyStats(),
184
+ note: null,
185
+ newerVersion: null
186
+ };
187
+ }
188
+ const parsed = decodeStats(text);
189
+ if (!parsed.record && parsed.newerVersion === null) {
190
+ const aside = `${path}.corrupt-${now}`;
191
+ renameSync(path, aside);
192
+ return {
193
+ record: emptyStats(),
194
+ note: `Run statistics at ${path} could not be read, so they were moved to ${aside} and a new file was started.`,
195
+ newerVersion: null
196
+ };
197
+ }
198
+ if (parsed.newerVersion !== null) return {
199
+ record: emptyStats(),
200
+ note: null,
201
+ newerVersion: parsed.newerVersion
202
+ };
203
+ return {
204
+ record: parsed.record,
205
+ note: null,
206
+ newerVersion: null
207
+ };
208
+ }
209
+ function writeStats(path, record) {
210
+ const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
211
+ writeFileSync(tmp, `${JSON.stringify(record)}\n`);
212
+ try {
213
+ renameSync(tmp, path);
214
+ } catch (error) {
215
+ rmSync(tmp, { force: true });
216
+ throw error;
217
+ }
218
+ }
219
+ function applyRun(bucket, run, { at, durationMs, credit, phases }) {
220
+ const next = bucket ? { ...bucket } : {
221
+ runs: 0,
222
+ failed: 0,
223
+ hits: 0,
224
+ misses: 0,
225
+ coldRuns: 0,
226
+ coldRunMs: 0,
227
+ hitRuns: 0,
228
+ hitRunMs: 0,
229
+ timeSavedMs: 0,
230
+ firstRunAt: at,
231
+ lastRunAt: at
232
+ };
233
+ next.runs += 1;
234
+ next.lastRunAt = at;
235
+ if (phases.coldBuildMs > 0) next.lastColdBuildMs = phases.coldBuildMs;
236
+ if (phases.podsMs > 0) next.lastPodsMs = phases.podsMs;
237
+ if (run.failed) {
238
+ next.failed += 1;
239
+ return next;
240
+ }
241
+ if (isHit(run.cacheHit)) {
242
+ next.hits += 1;
243
+ if (!run.waitedForBuild) {
244
+ next.hitRuns += 1;
245
+ next.hitRunMs += durationMs;
246
+ next.timeSavedMs += credit;
247
+ }
248
+ return next;
249
+ }
250
+ next.misses += 1;
251
+ if (run.offloadedTo) {
252
+ next.offloadedRuns = (next.offloadedRuns ?? 0) + 1;
253
+ next.offloadedRunMs = (next.offloadedRunMs ?? 0) + durationMs;
254
+ next.lastOffloadHost = run.offloadedTo;
255
+ return next;
256
+ }
257
+ next.coldRuns += 1;
258
+ next.coldRunMs += durationMs;
259
+ return next;
260
+ }
261
+ function creditMs(bucket, run, durationMs) {
262
+ if (run.failed || !isHit(run.cacheHit) || run.waitedForBuild) return 0;
263
+ if (!bucket || bucket.coldRuns <= 0) return 0;
264
+ return Math.max(0, Math.round(bucket.coldRunMs / bucket.coldRuns) - durationMs);
265
+ }
266
+ function isHit(cacheHit) {
267
+ return cacheHit === "local" || cacheHit === "remote";
268
+ }
269
+ function wholeMs(value) {
270
+ const ms = Number(value);
271
+ return Number.isFinite(ms) && ms > 0 ? Math.round(ms) : 0;
272
+ }
273
+ //#endregion
274
+ //#region src/engine/build-detail.ts
275
+ const LINE_MAX = 160;
276
+ const TARGET_GRAPH = /^note: Target dependency graph \((\d+) targets?\)/;
277
+ const XCODE_ACTION = /^([A-Z][A-Za-z]+)\s(.*)\s\(in target '([^']+)' from project '([^']+)'\)$/;
278
+ const XCODE_CONFIGURE = /^(ComputeTargetDependencyGraph|CreateBuildDescription|CreateBuildRequest|Resolve Package Graph|Prepare packages)$/;
279
+ const GRADLE_TASK = /^> Task (:\S+)/;
280
+ /**
281
+ * xcodebuild actions that end a target's work: a clean build touches each product last, and an incremental build
282
+ * that touches nothing still signs framework products last. Targets that end any other way are not counted.
283
+ */
284
+ const XCODE_TARGET_DONE = /^(Touch|RegisterExecutionPolicyException|CodeSign)$/;
285
+ const GRADLE_CONFIGURE = /^> Configure project /;
286
+ const XCODE_STEPS = [
287
+ [/^(CpResource|CopyPNGFile|CopyStringsFile|CompileAssetCatalog\w*|CompileStoryboard|CompileXIB|LinkStoryboards|ProcessInfoPlistFile)$/, "resources"],
288
+ [/^(Compile\w*|Swift\w*|ScanDependencies|Precompile\w*)$/, "compile"],
289
+ [/^(Ld|Libtool|CreateUniversalBinary|GenerateDSYMFile)$/, "link"],
290
+ [/^PhaseScriptExecution$/, "script"],
291
+ [/^CodeSign$/, "sign"]
292
+ ];
293
+ const GRADLE_STEPS = [
294
+ [/[Dd]ex/, "dex"],
295
+ [/^(createBundle\w*JsAndAssets|generateCodegen\w*)$/, "script"],
296
+ [/(Resources?|Assets|Manifest|ResValues|RFile|JavaRes)$/, "resources"],
297
+ [/^(compile|kapt|ksp|javaPreCompile|buildCMake|configureCMake|externalNativeBuild)/, "compile"],
298
+ [/^(package|assemble|install|zipalign|validateSigning|merge\w*NativeLibs|strip\w*Symbols)/, "package"]
299
+ ];
300
+ function stepOf(name, table) {
301
+ return table.find(([pattern]) => pattern.test(name))?.[1] ?? null;
302
+ }
303
+ function clip(text) {
304
+ return text.length > LINE_MAX ? `${text.slice(0, 157)}...` : text;
305
+ }
306
+ function unescape(token) {
307
+ return token.replace(/\\(.)/g, "$1");
308
+ }
309
+ function baseName(path) {
310
+ return path.split("/").findLast(Boolean) ?? path;
311
+ }
312
+ /**
313
+ * An xcodebuild action line in short form: the action, the file it works on and its target, as in
314
+ * `CompileC sqlite3.c (sqlite3)`. The file is the last absolute path in the arguments, which xcodebuild prints
315
+ * as the source for compile, copy and plist steps and as the product for link and sign steps; a script phase
316
+ * shows its name instead.
317
+ */
318
+ function shortXcodeAction(action, args, target) {
319
+ const tokens = args.match(/(?:\\.|\S)+/g) ?? [];
320
+ const subject = action === "PhaseScriptExecution" ? unescape(tokens[0] ?? "") : baseName(unescape(tokens.findLast((token) => token.startsWith("/")) ?? ""));
321
+ return clip(`${action}${subject ? ` ${subject}` : ""} (${target})`);
322
+ }
323
+ /** What one line of xcodebuild or Gradle output says about the build's progress, or null for any other line. */
324
+ function parseBuildToolLine(msg) {
325
+ const graph = TARGET_GRAPH.exec(msg);
326
+ if (graph) return {
327
+ kind: "total",
328
+ unit: "targets",
329
+ total: Number(graph[1])
330
+ };
331
+ if (XCODE_CONFIGURE.test(msg) || GRADLE_CONFIGURE.test(msg)) return { kind: "configure" };
332
+ const action = XCODE_ACTION.exec(msg);
333
+ if (action) {
334
+ const [, name, args, target, project] = action;
335
+ const step = stepOf(name, XCODE_STEPS);
336
+ return {
337
+ kind: "unit",
338
+ unit: "targets",
339
+ id: `${project}/${target}`,
340
+ step,
341
+ line: step ? shortXcodeAction(name, args, target) : null,
342
+ finished: XCODE_TARGET_DONE.test(name)
343
+ };
344
+ }
345
+ const task = GRADLE_TASK.exec(msg);
346
+ if (task) {
347
+ const path = task[1];
348
+ return {
349
+ kind: "unit",
350
+ unit: "tasks",
351
+ id: path,
352
+ step: stepOf(path.slice(path.lastIndexOf(":") + 1), GRADLE_STEPS),
353
+ line: clip(`> Task ${path}`),
354
+ finished: false
355
+ };
356
+ }
357
+ return null;
358
+ }
359
+ /**
360
+ * Accumulates a build's tool output into its detail: xcodebuild counts the distinct targets it finished against the
361
+ * total its dependency graph note gives; Gradle counts the task lines it printed and has no total.
362
+ */
363
+ function createBuildDetailParser() {
364
+ let seen = false;
365
+ let step = null;
366
+ let unit = null;
367
+ let total = null;
368
+ let line = null;
369
+ let tasks = 0;
370
+ const targets = /* @__PURE__ */ new Set();
371
+ return {
372
+ push(msg) {
373
+ const parsed = parseBuildToolLine(msg);
374
+ if (!parsed) return false;
375
+ seen = true;
376
+ if (parsed.kind === "total") {
377
+ unit = "targets";
378
+ total = parsed.total;
379
+ return true;
380
+ }
381
+ if (parsed.kind === "configure") {
382
+ step = "configure";
383
+ return true;
384
+ }
385
+ unit = parsed.unit;
386
+ if (parsed.unit === "tasks") tasks += 1;
387
+ else if (parsed.finished) targets.add(parsed.id);
388
+ if (parsed.step) step = parsed.step;
389
+ if (parsed.line) line = parsed.line;
390
+ return true;
391
+ },
392
+ detail(updatedAt) {
393
+ if (!seen) return null;
394
+ const done = unit === "tasks" ? tasks : unit === "targets" ? targets.size : null;
395
+ return {
396
+ step,
397
+ unit,
398
+ done: done !== null && total !== null ? Math.min(done, total) : done,
399
+ total: unit === "tasks" ? null : total,
400
+ line,
401
+ updatedAt
402
+ };
403
+ }
404
+ };
405
+ }
406
+ //#endregion
407
+ //#region src/engine/build-progress.ts
408
+ const ACTIVE_BUILD_KEY = "activeBuild";
409
+ const NO_BUILD_PROGRESS = {
410
+ step: () => {},
411
+ estimate: () => {},
412
+ miss: () => {},
413
+ hit: () => {},
414
+ deviceSetup: () => {},
415
+ deviceSetupKnown: () => void 0,
416
+ place: () => {},
417
+ waitingOn: () => {},
418
+ output: () => {},
419
+ durations: () => ({}),
420
+ clear: () => {}
421
+ };
422
+ /** `writer`, with each record it writes also passed to `progress.output`. */
423
+ function tapBuildLog(writer, progress) {
424
+ if (progress === NO_BUILD_PROGRESS) return writer;
425
+ return {
426
+ get file() {
427
+ return writer.file;
428
+ },
429
+ write(record) {
430
+ progress.output(record);
431
+ return writer.write(record);
432
+ },
433
+ close: () => writer.close(),
434
+ get written() {
435
+ return writer.written;
436
+ },
437
+ get dropped() {
438
+ return writer.dropped;
439
+ },
440
+ get lastError() {
441
+ return writer.lastError;
442
+ }
443
+ };
444
+ }
445
+ const DETAIL_WRITE_MS = 2e3;
446
+ function toolLine(record) {
447
+ if (!record || typeof record !== "object") return null;
448
+ const { src, level, msg } = record;
449
+ return src === "build" && level === "debug" && typeof msg === "string" ? msg : null;
450
+ }
451
+ function writeBuildDetail(root, claimId, detail) {
452
+ const file = workspaceBuildDetailFile(root);
453
+ const tmp = `${file}.tmp-${process.pid}`;
454
+ mkdirSync(dirname(file), { recursive: true });
455
+ writeFileSync(tmp, JSON.stringify({
456
+ claimId,
457
+ detail
458
+ }));
459
+ renameSync(tmp, file);
460
+ }
461
+ const COLD_PHASES = [
462
+ "prebuild",
463
+ "pods",
464
+ "compile"
465
+ ];
466
+ const HIT_PHASES = ["install", "launch"];
467
+ function settledOutcome(phase, entered) {
468
+ if (COLD_PHASES.includes(phase)) return "cold";
469
+ if (phase === "device") return entered.some((entry) => entry.phase === "cache-lookup") ? "hit" : null;
470
+ return HIT_PHASES.includes(phase) ? "hit" : null;
471
+ }
472
+ function projectHistory(projectKey) {
473
+ try {
474
+ return readStats().record?.history?.[projectKey];
475
+ } catch {
476
+ return;
477
+ }
478
+ }
479
+ function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
480
+ const startedAt = new Date(now()).toISOString();
481
+ const record = {
482
+ platform,
483
+ slot,
484
+ startedAt,
485
+ phase: "prepare",
486
+ phaseStartedAt: startedAt,
487
+ phases: [{
488
+ phase: "prepare",
489
+ startedAt
490
+ }],
491
+ claim: {
492
+ root: claim.root,
493
+ path: claim.path,
494
+ claimId: claim.claimId,
495
+ pid: claim.owner.pid
496
+ }
497
+ };
498
+ let warned = false;
499
+ const guard = (fn) => {
500
+ try {
501
+ fn();
502
+ } catch (error) {
503
+ if (warned) return;
504
+ warned = true;
505
+ note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
506
+ }
507
+ };
508
+ const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
509
+ ...before(state),
510
+ [ACTIVE_BUILD_KEY]: record
511
+ })));
512
+ write(withInterruptedBuild);
513
+ let projectKey = null;
514
+ const parser = createBuildDetailParser();
515
+ let detailWrittenAt = -Infinity;
516
+ let detailTimer = null;
517
+ const flushDetail = () => {
518
+ detailTimer = null;
519
+ detailWrittenAt = now();
520
+ const detail = parser.detail(new Date(detailWrittenAt).toISOString());
521
+ if (detail) guard(() => writeBuildDetail(root, record.claim.claimId, detail));
522
+ };
523
+ const settle = (outcome) => {
524
+ record.outcome = outcome;
525
+ if (projectKey) record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
526
+ };
527
+ return {
528
+ step(phase) {
529
+ if (phase === record.phase) return;
530
+ const at = new Date(now()).toISOString();
531
+ record.phase = phase;
532
+ record.phaseStartedAt = at;
533
+ record.phases.push({
534
+ phase,
535
+ startedAt: at
536
+ });
537
+ if (phase !== "wait") delete record.waitingOn;
538
+ const outcome = record.outcome ? null : settledOutcome(phase, record.phases);
539
+ if (outcome) settle(outcome);
540
+ write();
541
+ },
542
+ estimate(key) {
543
+ projectKey = key;
544
+ record.estimate = estimateBuild(projectHistory(key), platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
545
+ write();
546
+ },
547
+ miss(reason, provisional = false) {
548
+ record.missReason = reason;
549
+ settle("cold");
550
+ if (provisional) record.missProvisional = true;
551
+ else delete record.missProvisional;
552
+ write();
553
+ },
554
+ hit() {
555
+ delete record.missReason;
556
+ delete record.missProvisional;
557
+ settle("hit");
558
+ write();
559
+ },
560
+ deviceSetup(setup) {
561
+ if (setup === void 0) return;
562
+ record.deviceSetup = setup;
563
+ write();
564
+ },
565
+ deviceSetupKnown() {
566
+ return record.deviceSetup;
567
+ },
568
+ place(remote) {
569
+ const current = record.placement;
570
+ if (!remote) {
571
+ if (!current) return;
572
+ delete record.placement;
573
+ return write();
574
+ }
575
+ if (current?.host === remote.host && current.phase === remote.phase) return;
576
+ const at = new Date(now()).toISOString();
577
+ record.placement = {
578
+ host: remote.host,
579
+ phase: remote.phase,
580
+ startedAt: current?.host === remote.host ? current.startedAt : at,
581
+ phaseStartedAt: at
582
+ };
583
+ write();
584
+ },
585
+ waitingOn(holder) {
586
+ if (holder === null) {
587
+ if (!record.waitingOn) return;
588
+ delete record.waitingOn;
589
+ } else {
590
+ if (record.waitingOn?.path === holder) return;
591
+ record.waitingOn = { path: holder };
592
+ }
593
+ write();
594
+ },
595
+ output(line) {
596
+ const msg = toolLine(line);
597
+ if (msg === null || !parser.push(msg) || detailTimer) return;
598
+ const wait = detailWrittenAt + DETAIL_WRITE_MS - now();
599
+ if (wait <= 0) return flushDetail();
600
+ detailTimer = setTimeout(flushDetail, wait);
601
+ detailTimer.unref?.();
602
+ },
603
+ durations() {
604
+ return phaseDurations(record.phases, now());
605
+ },
606
+ clear() {
607
+ if (detailTimer) clearTimeout(detailTimer);
608
+ detailTimer = null;
609
+ guard(() => rmSync(workspaceBuildDetailFile(root), { force: true }));
610
+ guard(() => {
611
+ clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
612
+ });
613
+ }
614
+ };
615
+ }
616
+ function phaseDurations(phases, now) {
617
+ const out = {};
618
+ phases.forEach(({ phase, startedAt }, index) => {
619
+ const start = Date.parse(startedAt);
620
+ const next = phases[index + 1];
621
+ const end = next ? Date.parse(next.startedAt) : now;
622
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return;
623
+ out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
624
+ });
625
+ return out;
626
+ }
627
+ const HISTORY_FIELDS = [
628
+ "platform",
629
+ "status",
630
+ "configuration",
631
+ "fingerprint",
632
+ "cacheKey",
633
+ "cacheHit",
634
+ "cacheSkipped",
635
+ "durationMs",
636
+ "startedAt",
637
+ "errorCode",
638
+ "missReason",
639
+ "buildMachine",
640
+ "builtOn",
641
+ "offloadedTo",
642
+ "offloadFallback",
643
+ "diagnostics"
644
+ ];
645
+ function withHistoryEntry(state, platform, entry) {
646
+ const saved = state[BUILD_HISTORY_KEY];
647
+ const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
648
+ const list = Array.isArray(history[platform]) ? history[platform] : [];
649
+ return {
650
+ ...state,
651
+ [BUILD_HISTORY_KEY]: {
652
+ ...history,
653
+ [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
654
+ }
655
+ };
656
+ }
657
+ function finishedResult(record) {
658
+ if (record.status === "ok") return "succeeded";
659
+ return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
660
+ }
661
+ function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now } = {}) {
662
+ const platform = record.platform;
663
+ update(root, (state) => {
664
+ const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
665
+ const own = active?.platform === platform ? active : null;
666
+ const entry = {
667
+ ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
668
+ result: finishedResult(record),
669
+ slot: own?.slot ?? "default",
670
+ phases: own ? phaseDurations(own.phases, now()) : {}
671
+ };
672
+ return withHistoryEntry({
673
+ ...state,
674
+ lastBuild: record,
675
+ [LAST_BUILD_KEYS[platform]]: record
676
+ }, platform, entry);
677
+ });
678
+ }
679
+ function withInterruptedBuild(state) {
680
+ const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
681
+ if (!left) return state;
682
+ const last = state[LAST_BUILD_KEYS[left.platform]];
683
+ if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
684
+ return withHistoryEntry(state, left.platform, {
685
+ platform: left.platform,
686
+ status: "failed",
687
+ result: "interrupted",
688
+ slot: left.slot,
689
+ configuration: null,
690
+ fingerprint: null,
691
+ cacheKey: null,
692
+ cacheHit: false,
693
+ cacheSkipped: false,
694
+ durationMs: null,
695
+ startedAt: left.startedAt,
696
+ phases: {
697
+ ...phaseDurations(left.phases, Date.parse(left.phaseStartedAt)),
698
+ [left.phase]: 0
699
+ }
700
+ });
701
+ }
702
+ function parseActiveBuild(value) {
703
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
704
+ const record = value;
705
+ const claim = record.claim;
706
+ if (record.platform !== "ios" && record.platform !== "android") return null;
707
+ if (!isPhase(record.phase)) return null;
708
+ if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
709
+ if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
710
+ const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
711
+ const missReason = parseMissReason(record.missReason);
712
+ const placement = parsePlacement(record.placement);
713
+ const waitingOn = parseWaitingOn(record.waitingOn);
714
+ const estimate = parseEstimate(record.estimate);
715
+ return {
716
+ platform: record.platform,
717
+ slot: typeof record.slot === "string" ? record.slot : "default",
718
+ startedAt: record.startedAt,
719
+ phase: record.phase,
720
+ phaseStartedAt: record.phaseStartedAt,
721
+ phases,
722
+ ...missReason ? { missReason } : {},
723
+ ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
724
+ ...placement ? { placement } : {},
725
+ ...waitingOn ? { waitingOn } : {},
726
+ ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
727
+ ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
728
+ ...estimate ? { estimate } : {},
729
+ claim: {
730
+ root: claim.root,
731
+ path: typeof claim.path === "string" ? claim.path : "",
732
+ claimId: claim.claimId,
733
+ pid: typeof claim.pid === "number" ? claim.pid : 0
734
+ }
735
+ };
736
+ }
737
+ function parsePlacement(value) {
738
+ if (!value || typeof value !== "object") return null;
739
+ const { host, phase, startedAt, phaseStartedAt } = value;
740
+ if ([
741
+ host,
742
+ phase,
743
+ startedAt,
744
+ phaseStartedAt
745
+ ].some((field) => typeof field !== "string")) return null;
746
+ return {
747
+ host,
748
+ phase,
749
+ startedAt,
750
+ phaseStartedAt
751
+ };
752
+ }
753
+ function parseWaitingOn(value) {
754
+ const path = value?.path;
755
+ return typeof path === "string" && path ? { path } : null;
756
+ }
757
+ function parseEstimate(value) {
758
+ if (!value || typeof value !== "object") return null;
759
+ const { outcome, expectedMs, basis, phaseMs, planned } = value;
760
+ if (outcome !== null && outcome !== "hit" && outcome !== "cold") return null;
761
+ if (expectedMs !== null && typeof expectedMs !== "number") return null;
762
+ if (typeof basis !== "number" || !Array.isArray(planned) || !phaseMs || typeof phaseMs !== "object") return null;
763
+ const durations = Object.entries(phaseMs).filter((entry) => isPhase(entry[0]) && typeof entry[1] === "number");
764
+ return {
765
+ outcome,
766
+ expectedMs,
767
+ basis,
768
+ phaseMs: Object.fromEntries(durations),
769
+ planned: planned.filter(isPhase)
770
+ };
771
+ }
772
+ function isPhase(value) {
773
+ return BUILD_PHASES.includes(value);
774
+ }
775
+ function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
776
+ if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
777
+ if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
778
+ return survey.unresolved.length ? "unknown" : "stale";
779
+ }
780
+ function median(values) {
781
+ if (!values.length) return null;
782
+ const sorted = values.toSorted((a, b) => a - b);
783
+ const mid = Math.floor(sorted.length / 2);
784
+ return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
785
+ }
786
+ function estimateBuild(history, platform, known, deviceSetup) {
787
+ const lists = history?.[platform];
788
+ const outcome = known ?? latestOutcome(lists);
789
+ const samples = matchDeviceSetup(outcome && lists?.[outcome] || [], deviceSetup);
790
+ const phaseMs = {};
791
+ const planned = [];
792
+ for (const phase of BUILD_PHASES) {
793
+ const durations = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
794
+ const ms = median(durations);
795
+ if (ms === null) continue;
796
+ phaseMs[phase] = ms;
797
+ if (durations.length * 2 >= samples.length) planned.push(phase);
798
+ }
799
+ return {
800
+ outcome,
801
+ expectedMs: median(samples.map((sample) => sample.durationMs)),
802
+ basis: samples.length,
803
+ phaseMs,
804
+ planned
805
+ };
806
+ }
807
+ const MIN_TAGGED_SAMPLES = 3;
808
+ function matchDeviceSetup(samples, deviceSetup) {
809
+ if (deviceSetup === void 0) return samples;
810
+ const tagged = samples.filter((sample) => sample.deviceSetup === deviceSetup);
811
+ if (deviceSetup || tagged.length >= MIN_TAGGED_SAMPLES) return tagged;
812
+ return samples.filter((sample) => sample.deviceSetup !== true);
813
+ }
814
+ function latestOutcome(lists) {
815
+ let latest = null;
816
+ for (const outcome of ["hit", "cold"]) {
817
+ const last = lists?.[outcome]?.at(-1);
818
+ const at = last ? Date.parse(last.at) : NaN;
819
+ if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
820
+ outcome,
821
+ at
822
+ };
823
+ }
824
+ return latest?.outcome ?? null;
825
+ }
826
+ function buildReport(record, { state, history }) {
827
+ const estimate = record.estimate ?? estimateBuild(history, record.platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
828
+ const plannedPhases = estimate.planned.flatMap((phase) => {
829
+ const expectedMs = estimate.phaseMs[phase];
830
+ return expectedMs === void 0 ? [] : [{
831
+ phase,
832
+ expectedMs
833
+ }];
834
+ });
835
+ return {
836
+ platform: record.platform,
837
+ slot: record.slot,
838
+ state,
839
+ phase: record.phase,
840
+ startedAt: record.startedAt,
841
+ phaseStartedAt: record.phaseStartedAt,
842
+ outcome: estimate.outcome,
843
+ outcomeKnown: record.outcome !== void 0,
844
+ ...record.outcome && record.phases.some(({ phase }) => phase === "cache-lookup") ? { cacheLookupOutcome: record.outcome === "hit" ? "hit" : "miss" } : {},
845
+ expectedMs: estimate.expectedMs,
846
+ expectedPhaseMs: estimate.phaseMs[record.phase] ?? null,
847
+ basis: estimate.basis,
848
+ plannedPhases: plannedPhases.length ? plannedPhases : null,
849
+ ...record.missReason ? { missReason: record.missReason } : {},
850
+ ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
851
+ placement: record.placement ?? "local",
852
+ ...record.waitingOn ? { waitingOn: record.waitingOn } : {}
853
+ };
854
+ }
855
+ function remainingText(remainingMs) {
856
+ return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
857
+ }
858
+ function buildStatusLine(report, now) {
859
+ const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
860
+ if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
861
+ const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
862
+ const remote = typeof report.placement === "object" ? ` on ${report.placement.host} (${report.placement.phase}, ${formatElapsed(Math.max(0, now - Date.parse(report.placement.phaseStartedAt)))})` : "";
863
+ const waiting = report.waitingOn ? ` on ${report.waitingOn.path}` : "";
864
+ const head = `build: ${report.platform}${slot} ${report.phase}${waiting}${remote}, ${formatElapsed(elapsedMs)} elapsed`;
865
+ if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
866
+ if (report.expectedMs === null) return head;
867
+ const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
868
+ const remainingMs = report.expectedMs - elapsedMs;
869
+ return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
870
+ }
871
+ //#endregion
872
+ export { buildStatusLine as a, recordFinishedBuild as c, createRunRecorder as d, readRunEstimates as f, statsProjectKey$1 as h, buildReport as i, startBuildProgress as l, recordRunStats as m, NO_BUILD_PROGRESS as n, estimateBuild as o, readStats as p, activeBuildState as r, parseActiveBuild as s, ACTIVE_BUILD_KEY as t, tapBuildLog as u };