stim 1.17.2 → 1.18.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 (150) hide show
  1. package/README.md +50 -1
  2. package/dist/{activity-IRvJY_ru.mjs → activity-BxnkDZDi.mjs} +4 -4
  3. package/dist/{agent-device-usage-output-BK41vnzN.mjs → agent-device-usage-output-CZ66YA4T.mjs} +8 -8
  4. package/dist/android-BKA16DTf.mjs +4 -0
  5. package/dist/{android-B9yaNKwa.mjs → android-BvwuMUJ9.mjs} +192 -98
  6. package/dist/{android-DE4v59Mb.mjs → android-CBwCNicO.mjs} +2 -2
  7. package/dist/{android-B_YNZy61.mjs → android-DC3ZLuw0.mjs} +9 -9
  8. package/dist/{android-cas-ZFpjJBL_.mjs → android-cas-BBFyr9CA.mjs} +5 -5
  9. package/dist/android-cas-compiler.mjs +2 -2
  10. package/dist/api-D0VtFhqk.mjs +152 -0
  11. package/dist/api-run.d.mts +1 -0
  12. package/dist/api-run.mjs +145 -0
  13. package/dist/api.d.mts +212 -0
  14. package/dist/api.mjs +2 -0
  15. package/dist/{app-install-VPkI5WEg.mjs → app-install-D-PUENug.mjs} +3 -3
  16. package/dist/{attempt-Bk2ouQVL.mjs → attempt-DW-mQqM7.mjs} +14 -4
  17. package/dist/{budget-DfcgFdQB.mjs → budget-DIhL2BKx.mjs} +1578 -58
  18. package/dist/build-facts-DcLv2HSd.d.mts +111 -0
  19. package/dist/{cache-manifest-DnUHBJaU.mjs → cache-manifest-BI7ozfmH.mjs} +3 -3
  20. package/dist/cache-manifest.mjs +1 -1
  21. package/dist/{chrome-BrcVWQyD.mjs → chrome-DJnmnS5q.mjs} +1 -1
  22. package/dist/cli-DNJMwADx.mjs +68 -0
  23. package/dist/cli.mjs +1 -1
  24. package/dist/{client-SvBdgObD.mjs → client-DzhapoMB.mjs} +139 -32
  25. package/dist/collector-run.mjs +9 -9
  26. package/dist/{command-output-CDAIFD-8.mjs → command-output-CBtzyIgF.mjs} +2 -2
  27. package/dist/{config-D0DbpZtM.mjs → config-CgYMahz7.mjs} +5 -5
  28. package/dist/{created-devices-CpvzBL25.mjs → created-devices-DKbeWsiK.mjs} +2 -2
  29. package/dist/{dependency-state-CLcBHH2M.mjs → dependency-state-xKoOypBm.mjs} +2 -2
  30. package/dist/{detached-entry-CiTEgMR1.mjs → detached-entry-BLfcgc4O.mjs} +1 -1
  31. package/dist/{dev-client-B-XC4uRH.mjs → dev-client-CLnIH02x.mjs} +2 -2
  32. package/dist/{device-C8IOoJPv.mjs → device-D7oCHGWi.mjs} +8 -8
  33. package/dist/{device-capacity-ZC4XSZ4R.mjs → device-capacity-EUOOYr8T.mjs} +13 -13
  34. package/dist/device-host-worker.mjs +19 -19
  35. package/dist/{device-ios-C2Um-XUR.mjs → device-ios-6MewKKnc.mjs} +13 -13
  36. package/dist/{device-lease-DpoS36_u.mjs → device-lease-Co-EwifR.mjs} +9 -9
  37. package/dist/{device-lease-run-B8YltIH_.mjs → device-lease-run-w1xhCqIW.mjs} +4 -4
  38. package/dist/{device-pool-3o7s5lo4.mjs → device-pool-F5WvRPtD.mjs} +3 -3
  39. package/dist/{device-remote-hFSkpfQu.mjs → device-remote-tHbd1fKy.mjs} +15 -15
  40. package/dist/{doctor-CFv1fCrF.mjs → doctor-B9ckYUoU.mjs} +33 -30
  41. package/dist/{error-diagnostics-C-NMnhrk.mjs → error-diagnostics-BRXOqDzG.mjs} +11 -11
  42. package/dist/{exec-CaErVv7t.mjs → exec-DWhCZDGm.mjs} +65 -2
  43. package/dist/{gc-DEmkg3Xh.mjs → gc-Dh6mXOKC.mjs} +23 -469
  44. package/dist/{gradle-dJpre3Yn.mjs → gradle-C6GKnunQ.mjs} +6 -6
  45. package/dist/{guide-status-Du-ZQ3FD.mjs → guide-status-Bhf9SJXc.mjs} +3 -3
  46. package/dist/{guide-CSmhMTf8.mjs → guide-tm6igAaa.mjs} +290 -53
  47. package/dist/{hosted-android-E1Heoqnv.mjs → hosted-android-Bv285t_t.mjs} +3 -3
  48. package/dist/{hosted-client-Bp4knSwu.mjs → hosted-client-C-rMtppF.mjs} +2 -2
  49. package/dist/{hosted-ios-BnbeYOUD.mjs → hosted-ios-D5mNmtj4.mjs} +1 -1
  50. package/dist/{hosted-logs-OcgIBvCT.mjs → hosted-logs-DN8Iz4mJ.mjs} +9 -9
  51. package/dist/{hosted-macos-DcxKjtfH.mjs → hosted-macos-B-wEjlwG.mjs} +5 -5
  52. package/dist/{hosted-native-1Ebbig4w.mjs → hosted-native-DeeOG8xZ.mjs} +6 -6
  53. package/dist/{idle-shutdown-IGyNV8Cr.mjs → idle-shutdown-jjtxAn5C.mjs} +7 -8
  54. package/dist/{ios-DSfnPfse.mjs → ios-B3SbOWdw.mjs} +19 -7
  55. package/dist/{ios-Cr1h5gnO.mjs → ios-Bvbf0SGY.mjs} +1 -1
  56. package/dist/{ios-BJ4XUSe1.mjs → ios-CPdkoKaa.mjs} +181 -95
  57. package/dist/ios-DNpdygiv.mjs +7 -0
  58. package/dist/{ios-device-Ee7sDXfp.mjs → ios-device-Bn7jWDB1.mjs} +1 -1
  59. package/dist/{ios-device-pAN4t54-.mjs → ios-device-vOaByMhw.mjs} +3 -3
  60. package/dist/{ios-state-8L3lgum6.mjs → ios-state-B2pES6Vs.mjs} +1 -1
  61. package/dist/{launch-verify-lTnWZikR.mjs → launch-verify-hskNcWkZ.mjs} +5 -5
  62. package/dist/{logs-BAaS7gS2.mjs → logs-1Ylc1Ri6.mjs} +13 -13
  63. package/dist/macos-Ck7OBMNY.mjs +2 -0
  64. package/dist/{macos-BIRQS90f.mjs → macos-DF6C7wUU.mjs} +118 -34
  65. package/dist/macos-run.mjs +1 -1
  66. package/dist/maintenance-run.mjs +349 -47
  67. package/dist/{metro-g8NqiJp-.mjs → metro-Dh8T4Ytd.mjs} +5 -5
  68. package/dist/{metro-gateway-C-ijGI2F.mjs → metro-gateway-CEUw9RMH.mjs} +4 -4
  69. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-BAHFcJm6.mjs} +16 -2
  70. package/dist/{named-ports-C8m8EQs2.mjs → named-ports-BFIz1Shz.mjs} +61 -6
  71. package/dist/{native-run-D1Q56rAD.mjs → native-run-B06FkmZx.mjs} +4 -4
  72. package/dist/{native-runtime-BEK171xQ.mjs → native-runtime-D_603JiC.mjs} +6 -6
  73. package/dist/{ndjson-PclOIwVY.mjs → ndjson-LGZAMsqa.mjs} +8 -4
  74. package/dist/offload-worker.d.mts +3 -17
  75. package/dist/offload-worker.mjs +10 -11
  76. package/dist/{ownership-DyRQru0q.mjs → ownership-D6Se7gg_.mjs} +3 -3
  77. package/dist/{page-QF_Ke10s.mjs → page-Bv9s2KwY.mjs} +1 -1
  78. package/dist/placement-log-BcPhRwR4.mjs +84 -0
  79. package/dist/{build-plan-BDsSi09V.mjs → plan-placement-CtxkCO54.mjs} +122 -48
  80. package/dist/{ports-4nYqxPWC.mjs → ports-BLTtfBq-.mjs} +3 -3
  81. package/dist/{prebuild-D7RUMkwN.mjs → prebuild-juCyreaR.mjs} +5 -5
  82. package/dist/preview-D5V07Nrl.mjs +987 -0
  83. package/dist/{rolldown-runtime-BsOwBuO_.mjs → process-identity-Biv3arCO.mjs} +11 -1
  84. package/dist/{project-D47Rh0ly.mjs → project-C3ChRLZy.mjs} +4 -68
  85. package/dist/projects-DD7dBTCj.mjs +92 -0
  86. package/dist/{pull-request-pxnDCQzn.mjs → pull-request-u4vp7zmK.mjs} +1 -1
  87. package/dist/pull-requests.mjs +1 -1
  88. package/dist/{recordings-BNgsD1Tr.mjs → recordings-BAWltsVt.mjs} +1 -1
  89. package/dist/{reload-DcRxj3qG.mjs → reload-DvyWzVjJ.mjs} +15 -15
  90. package/dist/{remote-cache-Ci4DQYYj.mjs → remote-cache-BGg7aGIZ.mjs} +5 -5
  91. package/dist/{run-CkE9y0nc.mjs → run-vhtDgpHO.mjs} +5 -5
  92. package/dist/{server-bare-BkrSxSsZ.mjs → server-bare-B9E7S1aR.mjs} +4 -5
  93. package/dist/server-expo-Db9HBUoa.mjs +2 -0
  94. package/dist/{server-expo-CHTyXD0G.mjs → server-expo-DoBHhuzK.mjs} +5 -6
  95. package/dist/{settings-SR9OmW9h.mjs → settings-Byp2AQ_E.mjs} +6 -5
  96. package/dist/{settings-Cfd-0r2d.mjs → settings-CQyMElEP.mjs} +24 -763
  97. package/dist/{settings-BBpMcDbD.mjs → settings-Djl-mpjX.mjs} +1 -1
  98. package/dist/settings.schema.json +126 -3
  99. package/dist/{simslim-BWNrc3M3.mjs → simslim-EHBQ2R4G.mjs} +7 -7
  100. package/dist/{slot-launch-DmjBmpQV.mjs → slot-launch-C1nqh4mu.mjs} +6 -6
  101. package/dist/{spawn-claims-D0XfSe8u.mjs → spawn-claims-Kqiifsk2.mjs} +1 -1
  102. package/dist/{spawn-entry-jw19m8Nl.mjs → spawn-entry-Dcb7sDUz.mjs} +1 -0
  103. package/dist/{stage-CzaiwFsI.mjs → stage-g_WqWeaE.mjs} +14 -3
  104. package/dist/{start-DUz3mYdb.mjs → start-D-IeOyoK.mjs} +19 -19
  105. package/dist/{start-AlfCPRVU.mjs → start-NTc1Ifqs.mjs} +1 -1
  106. package/dist/{state-PeQiJuIu.mjs → state-BG-aAk4k.mjs} +4 -4
  107. package/dist/{state-B4PQFXMW.d.mts → state-lKh8shBp.d.mts} +2 -3
  108. package/dist/{state-DyaNTgfR.mjs → state-xnqpx3AD.mjs} +2 -2
  109. package/dist/{stats-Bq4fJOdB.mjs → stats-DY6ekz8F.mjs} +5 -5
  110. package/dist/{status-DlKvVJBE.mjs → status-BEeL6IKz.mjs} +6 -4
  111. package/dist/{status-BA2pehwf.mjs → status-DCjZeE-K.mjs} +33 -34
  112. package/dist/{status-CzS4bPO9.mjs → status-Dcr0Qexd.mjs} +5 -5
  113. package/dist/{stim-desktop-Cnz8CPnl.mjs → stim-desktop-Behws_fi.mjs} +1 -1
  114. package/dist/{stim-installations-QMEI32v1.mjs → stim-installations-aR_WsNOA.mjs} +1 -1
  115. package/dist/{stop-BIiM49EY.mjs → stop-BGhKMOuH.mjs} +5 -5
  116. package/dist/{stop-DQIasgKp.mjs → stop-BTjs3xbc.mjs} +17 -18
  117. package/dist/{stop-zF1y04f2.mjs → stop-DIJtD2GL.mjs} +2 -2
  118. package/dist/supervisor-run.d.mts +24 -22
  119. package/dist/supervisor-run.mjs +21 -22
  120. package/dist/{support-CsJNVfOc.mjs → support-BaayeYCf.mjs} +7 -7
  121. package/dist/{support-WvD__tSV.mjs → support-Bwvbrq5-.mjs} +5 -5
  122. package/dist/{toolchain-DNyR3NPq.mjs → toolchain-Bnyd_K3A.mjs} +471 -22
  123. package/dist/{trigger-Bemu8ytw.mjs → trigger-CJMhwXe8.mjs} +5 -5
  124. package/dist/trigger-DWAq9hkw.mjs +2 -0
  125. package/dist/{warm-progress-DF4JRvYI.mjs → warm-progress-CjB-MP6H.mjs} +7 -7
  126. package/dist/web-B6_bFDn7.mjs +2 -0
  127. package/dist/{web-Dl0KSRHf.mjs → web-DTU0uiWj.mjs} +22 -22
  128. package/dist/web-run.mjs +10 -10
  129. package/dist/{workspace-state-DqcCbpE7.mjs → workspace-state-C59dqG-Z.mjs} +3 -3
  130. package/dist/{ownership-BMLqyBgJ.mjs → workspaces-xXItD8Dj.mjs} +577 -23
  131. package/dist/{worktree-j3IcFAI5.mjs → worktree-BJfnU6r5.mjs} +1 -1
  132. package/dist/{worktree-Be_7NLKy.mjs → worktree-BSYlofkq.mjs} +234 -35
  133. package/dist/worktree-CsKYexJM.mjs +753 -0
  134. package/package.json +10 -5
  135. package/dist/build-progress-D0WkjCPd.mjs +0 -1036
  136. package/dist/build-slots-41v_d2x9.mjs +0 -142
  137. package/dist/cli-CYXb29oE.mjs +0 -51
  138. package/dist/deps-iKw0eD4P.mjs +0 -438
  139. package/dist/errors-B6W7FkxQ.mjs +0 -15
  140. package/dist/idle-BXG__1BA.mjs +0 -176
  141. package/dist/in-use-CvGOUn-x.mjs +0 -130
  142. package/dist/preview-B-g9d7kV.mjs +0 -318
  143. package/dist/process-identity-BmRm1qV0.mjs +0 -12
  144. package/dist/server-expo-CeakhI8P.mjs +0 -2
  145. package/dist/state-BKa8wNN5.mjs +0 -148
  146. package/dist/stop-cause-rhmGfwvt.mjs +0 -97
  147. package/dist/trigger-CXts6qt1.mjs +0 -2
  148. package/dist/watchman-APYjXSQa.mjs +0 -48
  149. package/dist/workspace-process-lock-2mNzOlaB.mjs +0 -58
  150. package/dist/workspaces-Bcib97-6.mjs +0 -306
@@ -1,1036 +0,0 @@
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 formatElapsed, u as plural } from "./command-output-CDAIFD-8.mjs";
4
- import { a as updateWorkspaceState, t as clearWorkspaceStateKey } from "./workspace-state-DqcCbpE7.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, isJsonObject, parseMissReason, readRunEstimates, readStats as readStats$1, statsFile, statsProjectKey as statsProjectKey$1, trimCapacityRefusals, trimCapacityWaits, 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
- const capacityRefusals = trimCapacityRefusals(record.capacityRefusals ?? [], now);
68
- const capacityWaits = trimCapacityWaits(record.capacityWaits ?? [], now);
69
- return {
70
- version: STATS_VERSION,
71
- machine,
72
- projects,
73
- ...Object.keys(history).length ? { history } : {},
74
- ...placements?.length ? { placements } : {},
75
- ...capacityRefusals.length ? { capacityRefusals } : {},
76
- ...capacityWaits.length ? { capacityWaits } : {},
77
- ...buildMachines && Object.keys(buildMachines).length ? { buildMachines } : {}
78
- };
79
- }
80
- function buildPlacement(run, before, at) {
81
- const placement = run.placement;
82
- const buildMs = wholeMs(placement.decision === "offloaded" ? placement.buildMs : run.coldBuildMs);
83
- const localEstimateMs = wholeMs(before?.lastColdBuildMs);
84
- const slotWaitMs = wholeMs(placement.slotWaitMs);
85
- const deviceSlotWaitMs = wholeMs(placement.deviceSlotWaitMs);
86
- return {
87
- at,
88
- project: run.projectKey,
89
- platform: run.platform,
90
- decision: placement.decision,
91
- reason: placement.reason,
92
- ...placement.machine ? { machine: placement.machine } : {},
93
- ...buildMs > 0 ? { buildMs } : {},
94
- ...slotWaitMs > 0 ? { slotWaitMs } : {},
95
- ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {},
96
- ...localEstimateMs > 0 ? { localEstimateMs } : {},
97
- ...run.failed ? { failed: true } : {}
98
- };
99
- }
100
- function applyPlacement(totals, placement) {
101
- const next = totals ? { ...totals } : {
102
- offloaded: 0,
103
- offloadedMs: 0,
104
- savedMs: 0,
105
- fallbacks: 0
106
- };
107
- if (placement.decision === "fell-back") {
108
- next.fallbacks += 1;
109
- next.lastFallbackAt = placement.at;
110
- return next;
111
- }
112
- next.offloaded += 1;
113
- next.offloadedMs += placement.buildMs ?? 0;
114
- if (placement.localEstimateMs && placement.buildMs) next.savedMs += placement.localEstimateMs - placement.buildMs;
115
- next.lastOffloadAt = placement.at;
116
- return next;
117
- }
118
- function trimPlacements(placements, now) {
119
- return placements.filter((each) => now - Date.parse(each.at) <= PLACEMENT_MAX_AGE_MS).slice(-100);
120
- }
121
- function recordRunStats(run, now) {
122
- return withConfigLock$1(() => {
123
- const path = statsFile();
124
- const loaded = loadForUpdate(path, now);
125
- if (loaded.newerVersion !== null) return {
126
- recorded: false,
127
- note: `Run statistics at ${path} are version ${loaded.newerVersion}, which this Stim does not understand, so this run was not recorded.`
128
- };
129
- writeStats(path, updateStats(loaded.record, run, now));
130
- return {
131
- recorded: true,
132
- note: loaded.note
133
- };
134
- });
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
- }
176
- function createRunRecorder({ platform, write, now, note, phases, deviceSetup }) {
177
- let projectKey = null;
178
- let cacheKey = null;
179
- let coldBuildMs = 0;
180
- let podsMs = 0;
181
- let placement = null;
182
- let deviceSlotWaitMs = 0;
183
- let recorded = false;
184
- return {
185
- setProject(key) {
186
- projectKey = key;
187
- },
188
- setCacheKey(key) {
189
- cacheKey = key;
190
- },
191
- setBuildMs(ms) {
192
- coldBuildMs = wholeMs(ms);
193
- },
194
- setPodsMs(ms) {
195
- podsMs = wholeMs(ms);
196
- },
197
- addDeviceSlotWaitMs(ms) {
198
- deviceSlotWaitMs += wholeMs(ms);
199
- },
200
- deviceSlotWaitMs() {
201
- return deviceSlotWaitMs || void 0;
202
- },
203
- setPlacement(next) {
204
- placement = next;
205
- },
206
- record({ failed, cacheHit = false, waited = null, durationMs, offloadedTo = null }) {
207
- if (!projectKey || !cacheKey || recorded) return;
208
- recorded = true;
209
- const ran = failed ? {} : phases?.() ?? {};
210
- try {
211
- const outcome = write({
212
- platform,
213
- projectKey,
214
- failed,
215
- cacheHit,
216
- waitedForBuild: Boolean(waited),
217
- durationMs,
218
- ...offloadedTo ? { offloadedTo } : {},
219
- ...coldBuildMs > 0 ? { coldBuildMs } : {},
220
- ...podsMs > 0 ? { podsMs } : {},
221
- ...Object.keys(ran).length ? { phases: ran } : {},
222
- ...failed || deviceSetup?.() === void 0 ? {} : { deviceSetup: deviceSetup() },
223
- ...placement ? { placement: {
224
- ...placement,
225
- ...deviceSlotWaitMs > 0 ? { deviceSlotWaitMs } : {}
226
- } } : {}
227
- }, now());
228
- if (outcome?.note) note(outcome.note);
229
- } catch (error) {
230
- note(`Run statistics could not be recorded: ${error?.message || error}`);
231
- }
232
- }
233
- };
234
- }
235
- function loadForUpdate(path, now, recoverCorrupt = true) {
236
- let text;
237
- try {
238
- text = readFileSync(path, "utf-8");
239
- } catch {
240
- return {
241
- record: emptyStats(),
242
- note: null,
243
- newerVersion: null
244
- };
245
- }
246
- const parsed = decodeStats(text);
247
- if (!parsed.record && parsed.newerVersion === null) {
248
- if (!recoverCorrupt) return {
249
- record: null,
250
- note: null,
251
- newerVersion: null
252
- };
253
- const aside = `${path}.corrupt-${now}`;
254
- renameSync(path, aside);
255
- return {
256
- record: emptyStats(),
257
- note: `Run statistics at ${path} could not be read, so they were moved to ${aside} and a new file was started.`,
258
- newerVersion: null
259
- };
260
- }
261
- if (parsed.newerVersion !== null) return {
262
- record: emptyStats(),
263
- note: null,
264
- newerVersion: parsed.newerVersion
265
- };
266
- return {
267
- record: parsed.record,
268
- note: null,
269
- newerVersion: null
270
- };
271
- }
272
- function writeStats(path, record) {
273
- const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
274
- writeFileSync(tmp, `${JSON.stringify(record)}\n`);
275
- try {
276
- renameSync(tmp, path);
277
- } catch (error) {
278
- rmSync(tmp, { force: true });
279
- throw error;
280
- }
281
- }
282
- function applyRun(bucket, run, { at, durationMs, credit, phases }) {
283
- const next = bucket ? { ...bucket } : {
284
- runs: 0,
285
- failed: 0,
286
- hits: 0,
287
- misses: 0,
288
- coldRuns: 0,
289
- coldRunMs: 0,
290
- hitRuns: 0,
291
- hitRunMs: 0,
292
- timeSavedMs: 0,
293
- firstRunAt: at,
294
- lastRunAt: at
295
- };
296
- next.runs += 1;
297
- next.lastRunAt = at;
298
- if (phases.coldBuildMs > 0) next.lastColdBuildMs = phases.coldBuildMs;
299
- if (phases.podsMs > 0) next.lastPodsMs = phases.podsMs;
300
- if (run.failed) {
301
- next.failed += 1;
302
- return next;
303
- }
304
- if (isHit(run.cacheHit)) {
305
- next.hits += 1;
306
- if (!run.waitedForBuild) {
307
- next.hitRuns += 1;
308
- next.hitRunMs += durationMs;
309
- next.timeSavedMs += credit;
310
- }
311
- return next;
312
- }
313
- next.misses += 1;
314
- if (run.offloadedTo) {
315
- next.offloadedRuns = (next.offloadedRuns ?? 0) + 1;
316
- next.offloadedRunMs = (next.offloadedRunMs ?? 0) + durationMs;
317
- next.lastOffloadHost = run.offloadedTo;
318
- return next;
319
- }
320
- next.coldRuns += 1;
321
- next.coldRunMs += durationMs;
322
- return next;
323
- }
324
- function creditMs(bucket, run, durationMs) {
325
- if (run.failed || !isHit(run.cacheHit) || run.waitedForBuild) return 0;
326
- if (!bucket || bucket.coldRuns <= 0) return 0;
327
- return Math.max(0, Math.round(bucket.coldRunMs / bucket.coldRuns) - durationMs);
328
- }
329
- function isHit(cacheHit) {
330
- return cacheHit === "local" || cacheHit === "remote";
331
- }
332
- function wholeMs(value) {
333
- const ms = Number(value);
334
- return Number.isFinite(ms) && ms > 0 ? Math.round(ms) : 0;
335
- }
336
- //#endregion
337
- //#region src/engine/build-detail.ts
338
- const LINE_MAX = 160;
339
- const TARGET_GRAPH = /^note: Target dependency graph \((\d+) targets?\)/;
340
- const XCODE_ACTION = /^([A-Z][A-Za-z]+)\s(.*)\s\(in target '([^']+)' from project '([^']+)'\)$/;
341
- const XCODE_CONFIGURE = /^(ComputeTargetDependencyGraph|CreateBuildDescription|CreateBuildRequest|Resolve Package Graph|Prepare packages)$/;
342
- const GRADLE_TASK = /^> Task (:\S+)/;
343
- /**
344
- * xcodebuild actions that end a target's work: a clean build touches each product last, and an incremental build
345
- * that touches nothing still signs framework products last. Targets that end any other way are not counted.
346
- */
347
- const XCODE_TARGET_DONE = /^(Touch|RegisterExecutionPolicyException|CodeSign)$/;
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)/;
356
- const XCODE_STEPS = [
357
- [/^(CpResource|CopyPNGFile|CopyStringsFile|CompileAssetCatalog\w*|CompileStoryboard|CompileXIB|LinkStoryboards|ProcessInfoPlistFile)$/, "resources"],
358
- [/^(Compile\w*|Swift\w*|ScanDependencies|Precompile\w*)$/, "compile"],
359
- [/^(Ld|Libtool|CreateUniversalBinary|GenerateDSYMFile)$/, "link"],
360
- [/^PhaseScriptExecution$/, "script"],
361
- [/^CodeSign$/, "sign"]
362
- ];
363
- const GRADLE_STEPS = [
364
- [/[Dd]ex/, "dex"],
365
- [/^(createBundle\w*JsAndAssets|generateCodegen\w*)$/, "script"],
366
- [/(Resources?|Assets|Manifest|ResValues|RFile|JavaRes)$/, "resources"],
367
- [/^(compile|kapt|ksp|javaPreCompile|buildCMake|configureCMake|externalNativeBuild)/, "compile"],
368
- [/^(package|assemble|install|zipalign|validateSigning|merge\w*NativeLibs|strip\w*Symbols)/, "package"]
369
- ];
370
- function stepOf(name, table) {
371
- return table.find(([pattern]) => pattern.test(name))?.[1] ?? null;
372
- }
373
- function clip(text) {
374
- return text.length > LINE_MAX ? `${text.slice(0, 157)}...` : text;
375
- }
376
- function unescape(token) {
377
- return token.replace(/\\(.)/g, "$1");
378
- }
379
- function baseName(path) {
380
- return path.split("/").findLast(Boolean) ?? path;
381
- }
382
- /**
383
- * An xcodebuild action line in short form: the action, the file it works on and its target, as in
384
- * `CompileC sqlite3.c (sqlite3)`. The file is the last absolute path in the arguments, which xcodebuild prints
385
- * as the source for compile, copy and plist steps and as the product for link and sign steps; a script phase
386
- * shows its name instead.
387
- */
388
- function shortXcodeAction(action, args, target) {
389
- const tokens = args.match(/(?:\\.|\S)+/g) ?? [];
390
- const subject = action === "PhaseScriptExecution" ? unescape(tokens[0] ?? "") : baseName(unescape(tokens.findLast((token) => token.startsWith("/")) ?? ""));
391
- return clip(`${action}${subject ? ` ${subject}` : ""} (${target})`);
392
- }
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. */
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" };
410
- const graph = TARGET_GRAPH.exec(msg);
411
- if (graph) return {
412
- kind: "total",
413
- unit: "targets",
414
- total: Number(graph[1])
415
- };
416
- if (XCODE_CONFIGURE.test(msg) || GRADLE_CONFIGURE.test(msg)) return { kind: "configure" };
417
- const action = XCODE_ACTION.exec(msg);
418
- if (action) {
419
- const [, name, args, target, project] = action;
420
- const step = stepOf(name, XCODE_STEPS);
421
- return {
422
- kind: "unit",
423
- unit: "targets",
424
- id: `${project}/${target}`,
425
- step,
426
- line: step ? shortXcodeAction(name, args, target) : null,
427
- finished: XCODE_TARGET_DONE.test(name)
428
- };
429
- }
430
- const task = GRADLE_TASK.exec(msg);
431
- if (task) {
432
- const path = task[1];
433
- return {
434
- kind: "unit",
435
- unit: "tasks",
436
- id: path,
437
- step: stepOf(path.slice(path.lastIndexOf(":") + 1), GRADLE_STEPS),
438
- line: clip(`> Task ${path}`),
439
- finished: false
440
- };
441
- }
442
- return null;
443
- }
444
- /**
445
- * Accumulates a build's tool output into its detail: xcodebuild counts the distinct targets it finished against the
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.
448
- */
449
- function createBuildDetailParser() {
450
- let seen = false;
451
- let step = null;
452
- let unit = null;
453
- let total = null;
454
- let line = null;
455
- let tasks = 0;
456
- let counted = null;
457
- const targets = /* @__PURE__ */ new Set();
458
- return {
459
- push(msg) {
460
- const parsed = parseBuildToolLine(msg);
461
- if (!parsed) return false;
462
- seen = true;
463
- if (parsed.kind === "total") {
464
- unit = "targets";
465
- total = parsed.total;
466
- return true;
467
- }
468
- if (parsed.kind === "configure") {
469
- step = "configure";
470
- return true;
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
- }
482
- unit = parsed.unit;
483
- if (parsed.unit === "tasks") tasks += 1;
484
- else if (parsed.finished) targets.add(parsed.id);
485
- if (parsed.step) step = parsed.step;
486
- if (parsed.line) line = parsed.line;
487
- return true;
488
- },
489
- detail(updatedAt) {
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
- };
499
- const done = unit === "tasks" ? tasks : unit === "targets" ? targets.size : null;
500
- return {
501
- step,
502
- unit,
503
- done: done !== null && total !== null ? Math.min(done, total) : done,
504
- total: unit === "tasks" ? null : total,
505
- line,
506
- updatedAt
507
- };
508
- }
509
- };
510
- }
511
- //#endregion
512
- //#region src/engine/build-progress.ts
513
- const ACTIVE_BUILD_KEY = "activeBuild";
514
- const NO_BUILD_PROGRESS = {
515
- step: () => {},
516
- estimate: () => {},
517
- miss: () => {},
518
- hit: () => {},
519
- deviceSetup: () => {},
520
- deviceSetupKnown: () => void 0,
521
- place: () => {},
522
- waitingOn: () => {},
523
- waitingFor: () => {},
524
- output: () => {},
525
- durations: () => ({}),
526
- clear: () => {}
527
- };
528
- /** `writer`, with each record it writes also passed to `progress.output`. */
529
- function tapBuildLog(writer, progress) {
530
- if (progress === NO_BUILD_PROGRESS) return writer;
531
- return {
532
- get file() {
533
- return writer.file;
534
- },
535
- write(record) {
536
- progress.output(record);
537
- return writer.write(record);
538
- },
539
- close: () => writer.close(),
540
- get written() {
541
- return writer.written;
542
- },
543
- get dropped() {
544
- return writer.dropped;
545
- },
546
- get lastError() {
547
- return writer.lastError;
548
- }
549
- };
550
- }
551
- const DETAIL_WRITE_MS = 2e3;
552
- function toolLine(record) {
553
- if (!record || typeof record !== "object") return null;
554
- const { src, level, msg } = record;
555
- return src === "build" && level === "debug" && typeof msg === "string" ? msg : null;
556
- }
557
- function writeBuildDetail(root, claimId, detail) {
558
- const file = workspaceBuildDetailFile(root);
559
- const tmp = `${file}.tmp-${process.pid}`;
560
- mkdirSync(dirname(file), { recursive: true });
561
- writeFileSync(tmp, JSON.stringify({
562
- claimId,
563
- detail
564
- }));
565
- renameSync(tmp, file);
566
- }
567
- const COLD_PHASES = [
568
- "prebuild",
569
- "pods",
570
- "compile"
571
- ];
572
- const HIT_PHASES = ["install", "launch"];
573
- function settledOutcome(phase, entered) {
574
- if (COLD_PHASES.includes(phase)) return "cold";
575
- if (phase === "device") return entered.some((entry) => entry.phase === "cache-lookup") ? "hit" : null;
576
- return HIT_PHASES.includes(phase) ? "hit" : null;
577
- }
578
- function projectHistory(projectKey) {
579
- try {
580
- return readStats$1().record?.history?.[projectKey];
581
- } catch {
582
- return;
583
- }
584
- }
585
- function startBuildProgress({ root, platform, slot, claim, now = Date.now, note = () => {} }) {
586
- const startedAt = new Date(now()).toISOString();
587
- const record = {
588
- platform,
589
- slot,
590
- startedAt,
591
- phase: "prepare",
592
- phaseStartedAt: startedAt,
593
- phases: [{
594
- phase: "prepare",
595
- startedAt
596
- }],
597
- claim: {
598
- root: claim.root,
599
- path: claim.path,
600
- claimId: claim.claimId,
601
- pid: claim.owner.pid
602
- }
603
- };
604
- let warned = false;
605
- const guard = (fn) => {
606
- try {
607
- fn();
608
- } catch (error) {
609
- if (warned) return;
610
- warned = true;
611
- note(`Build progress could not be recorded in the workspace state: ${error?.message || error}`);
612
- }
613
- };
614
- const write = (before = (state) => state) => guard(() => updateWorkspaceState(root, (state) => ({
615
- ...before(state),
616
- [ACTIVE_BUILD_KEY]: record
617
- })));
618
- write(withInterruptedBuild);
619
- let projectKey = null;
620
- const parser = createBuildDetailParser();
621
- let detailWrittenAt = -Infinity;
622
- let detailTimer = null;
623
- const flushDetail = () => {
624
- detailTimer = null;
625
- detailWrittenAt = now();
626
- const detail = parser.detail(new Date(detailWrittenAt).toISOString());
627
- if (detail) guard(() => writeBuildDetail(root, record.claim.claimId, detail));
628
- };
629
- const settle = (outcome) => {
630
- record.outcome = outcome;
631
- if (projectKey && platform !== "macos") record.estimate = estimateBuild(projectHistory(projectKey), platform, outcome, record.deviceSetup);
632
- };
633
- const waits = /* @__PURE__ */ new Map();
634
- return {
635
- step(phase) {
636
- if (phase === record.phase) return;
637
- const at = new Date(now()).toISOString();
638
- record.phase = phase;
639
- record.phaseStartedAt = at;
640
- record.phases.push({
641
- phase,
642
- startedAt: at
643
- });
644
- if (phase !== "wait") delete record.waitingOn;
645
- const outcome = record.outcome || platform === "macos" ? null : settledOutcome(phase, record.phases);
646
- if (outcome) settle(outcome);
647
- write();
648
- },
649
- estimate(key) {
650
- if (platform === "macos") return;
651
- projectKey = key;
652
- record.estimate = estimateBuild(projectHistory(key), platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
653
- write();
654
- },
655
- miss(reason, provisional = false) {
656
- record.missReason = reason;
657
- settle("cold");
658
- if (provisional) record.missProvisional = true;
659
- else delete record.missProvisional;
660
- write();
661
- },
662
- hit() {
663
- delete record.missReason;
664
- delete record.missProvisional;
665
- settle("hit");
666
- write();
667
- },
668
- deviceSetup(setup) {
669
- if (setup === void 0) return;
670
- record.deviceSetup = setup;
671
- write();
672
- },
673
- deviceSetupKnown() {
674
- return record.deviceSetup;
675
- },
676
- place(remote) {
677
- const current = record.placement;
678
- if (!remote) {
679
- if (!current) return;
680
- delete record.placement;
681
- return write();
682
- }
683
- if (current?.host === remote.host && current.phase === remote.phase) return;
684
- const at = new Date(now()).toISOString();
685
- record.placement = {
686
- host: remote.host,
687
- phase: remote.phase,
688
- startedAt: current?.host === remote.host ? current.startedAt : at,
689
- phaseStartedAt: at
690
- };
691
- write();
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
- },
702
- waitingOn(holder) {
703
- if (holder === null) {
704
- if (!record.waitingOn) return;
705
- delete record.waitingOn;
706
- } else {
707
- if (record.waitingOn?.path === holder) return;
708
- record.waitingOn = { path: holder };
709
- }
710
- write();
711
- },
712
- output(line) {
713
- const msg = toolLine(line);
714
- if (msg === null || !parser.push(msg) || detailTimer) return;
715
- const wait = detailWrittenAt + DETAIL_WRITE_MS - now();
716
- if (wait <= 0) return flushDetail();
717
- detailTimer = setTimeout(flushDetail, wait);
718
- detailTimer.unref?.();
719
- },
720
- durations() {
721
- return phaseDurations(record.phases, now());
722
- },
723
- clear() {
724
- if (detailTimer) clearTimeout(detailTimer);
725
- detailTimer = null;
726
- guard(() => rmSync(workspaceBuildDetailFile(root), { force: true }));
727
- guard(() => {
728
- clearWorkspaceStateKey(root, ACTIVE_BUILD_KEY, (value) => parseActiveBuild(value)?.claim.claimId === record.claim.claimId);
729
- });
730
- }
731
- };
732
- }
733
- function phaseDurations(phases, now) {
734
- const out = {};
735
- phases.forEach(({ phase, startedAt }, index) => {
736
- const start = Date.parse(startedAt);
737
- const next = phases[index + 1];
738
- const end = next ? Date.parse(next.startedAt) : now;
739
- if (!Number.isFinite(start) || !Number.isFinite(end)) return;
740
- out[phase] = (out[phase] ?? 0) + Math.max(0, end - start);
741
- });
742
- return out;
743
- }
744
- function completedPhaseDurations(phases) {
745
- if (phases.length < 2) return void 0;
746
- const durations = phaseDurations(phases.slice(0, -1), Date.parse(phases.at(-1).startedAt));
747
- return Object.keys(durations).length ? durations : void 0;
748
- }
749
- const HISTORY_FIELDS = [
750
- "devicePlacement",
751
- "platform",
752
- "status",
753
- "configuration",
754
- "fingerprint",
755
- "cacheKey",
756
- "cacheHit",
757
- "cacheSkipped",
758
- "durationMs",
759
- "startedAt",
760
- "errorCode",
761
- "missReason",
762
- "buildMachine",
763
- "builtOn",
764
- "offloadedTo",
765
- "offloadFallback",
766
- "diagnostics"
767
- ];
768
- function withHistoryEntry(state, platform, entry) {
769
- const saved = state[BUILD_HISTORY_KEY];
770
- const history = saved && typeof saved === "object" && !Array.isArray(saved) ? saved : {};
771
- const list = Array.isArray(history[platform]) ? history[platform] : [];
772
- return {
773
- ...state,
774
- [BUILD_HISTORY_KEY]: {
775
- ...history,
776
- [platform]: [entry, ...list].slice(0, BUILD_HISTORY_LIMIT)
777
- }
778
- };
779
- }
780
- function finishedResult(record) {
781
- if (record.status === "ok") return "succeeded";
782
- return record.errorCode === "STIM_CANCELLED" ? "cancelled" : "failed";
783
- }
784
- function recordFinishedBuild(root, record, { update = updateWorkspaceState, now = Date.now } = {}) {
785
- const platform = record.platform;
786
- update(root, (state) => {
787
- const active = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
788
- const own = active?.platform === platform ? active : null;
789
- const entry = {
790
- ...Object.fromEntries(HISTORY_FIELDS.filter((key) => key in record).map((key) => [key, record[key]])),
791
- result: finishedResult(record),
792
- slot: own?.slot ?? "default",
793
- phases: own ? phaseDurations(own.phases, now()) : {}
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);
818
- return withHistoryEntry({
819
- ...state,
820
- lastBuild: record,
821
- [LAST_BUILD_KEYS[platform]]: record
822
- }, platform, entry);
823
- });
824
- }
825
- function withInterruptedBuild(state) {
826
- const left = parseActiveBuild(state[ACTIVE_BUILD_KEY]);
827
- if (!left) return state;
828
- const last = left.platform === "macos" ? state[BUILD_HISTORY_KEY]?.macos?.[0] : state[LAST_BUILD_KEYS[left.platform]];
829
- if (Date.parse(String(last?.startedAt)) >= Date.parse(left.startedAt)) return state;
830
- return withHistoryEntry(state, left.platform, {
831
- platform: left.platform,
832
- status: "failed",
833
- result: "interrupted",
834
- slot: left.slot,
835
- configuration: null,
836
- fingerprint: null,
837
- cacheKey: null,
838
- cacheHit: false,
839
- cacheSkipped: false,
840
- durationMs: null,
841
- startedAt: left.startedAt,
842
- phases: {
843
- ...phaseDurations(left.phases, Date.parse(left.phaseStartedAt)),
844
- [left.phase]: 0
845
- }
846
- });
847
- }
848
- function parseActiveBuild(value) {
849
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
850
- const record = value;
851
- const claim = record.claim;
852
- if (record.platform !== "ios" && record.platform !== "android" && record.platform !== "macos") return null;
853
- if (!isPhase(record.phase)) return null;
854
- if (typeof record.startedAt !== "string" || typeof record.phaseStartedAt !== "string") return null;
855
- if (!claim || typeof claim.root !== "string" || typeof claim.claimId !== "string") return null;
856
- const phases = Array.isArray(record.phases) ? record.phases.filter((entry) => isPhase(entry?.phase) && typeof entry?.startedAt === "string") : [];
857
- const missReason = parseMissReason(record.missReason);
858
- const placement = parsePlacement(record.placement);
859
- const waitingOn = parseWaitingOn(record.waitingOn);
860
- const waitingFor = parseWaitingFor(record.waitingFor);
861
- const estimate = parseEstimate(record.estimate);
862
- return {
863
- platform: record.platform,
864
- slot: typeof record.slot === "string" ? record.slot : "default",
865
- startedAt: record.startedAt,
866
- phase: record.phase,
867
- phaseStartedAt: record.phaseStartedAt,
868
- phases,
869
- ...missReason ? { missReason } : {},
870
- ...missReason && record.missProvisional === true ? { missProvisional: true } : {},
871
- ...placement ? { placement } : {},
872
- ...waitingOn ? { waitingOn } : {},
873
- ...waitingFor ? { waitingFor } : {},
874
- ...typeof record.deviceSetup === "boolean" ? { deviceSetup: record.deviceSetup } : {},
875
- ...record.outcome === "hit" || record.outcome === "cold" ? { outcome: record.outcome } : {},
876
- ...estimate ? { estimate } : {},
877
- claim: {
878
- root: claim.root,
879
- path: typeof claim.path === "string" ? claim.path : "",
880
- claimId: claim.claimId,
881
- pid: typeof claim.pid === "number" ? claim.pid : 0
882
- }
883
- };
884
- }
885
- function parsePlacement(value) {
886
- if (!value || typeof value !== "object") return null;
887
- const { host, phase, startedAt, phaseStartedAt } = value;
888
- if ([
889
- host,
890
- phase,
891
- startedAt,
892
- phaseStartedAt
893
- ].some((field) => typeof field !== "string")) return null;
894
- return {
895
- host,
896
- phase,
897
- startedAt,
898
- phaseStartedAt
899
- };
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
- }
914
- function parseWaitingOn(value) {
915
- const path = value?.path;
916
- return typeof path === "string" && path ? { path } : null;
917
- }
918
- function parseEstimate(value) {
919
- if (!value || typeof value !== "object") return null;
920
- const { outcome, expectedMs, basis, phaseMs, planned } = value;
921
- if (outcome !== null && outcome !== "hit" && outcome !== "cold") return null;
922
- if (expectedMs !== null && typeof expectedMs !== "number") return null;
923
- if (typeof basis !== "number" || !Array.isArray(planned) || !phaseMs || typeof phaseMs !== "object") return null;
924
- const durations = Object.entries(phaseMs).filter((entry) => isPhase(entry[0]) && typeof entry[1] === "number");
925
- return {
926
- outcome,
927
- expectedMs,
928
- basis,
929
- phaseMs: Object.fromEntries(durations),
930
- planned: planned.filter(isPhase)
931
- };
932
- }
933
- function isPhase(value) {
934
- return BUILD_PHASES.includes(value);
935
- }
936
- function activeBuildState(claim, survey = (0, ownership_claim_exports.readClaimSet)(claim.root)) {
937
- if (survey.live.some((holder) => holder.claimId === claim.claimId)) return "running";
938
- if (survey.dead.some((holder) => holder.claimId === claim.claimId)) return "stale";
939
- return survey.unresolved.length ? "unknown" : "stale";
940
- }
941
- function median(values) {
942
- if (!values.length) return null;
943
- const sorted = values.toSorted((a, b) => a - b);
944
- const mid = Math.floor(sorted.length / 2);
945
- return sorted.length % 2 ? sorted[mid] : Math.round((sorted[mid - 1] + sorted[mid]) / 2);
946
- }
947
- function estimateBuild(history, platform, known, deviceSetup) {
948
- const lists = platform === "macos" ? void 0 : history?.[platform];
949
- const outcome = known ?? latestOutcome(lists);
950
- const samples = matchDeviceSetup(outcome && lists?.[outcome] || [], deviceSetup);
951
- const phaseMs = {};
952
- const planned = [];
953
- for (const phase of BUILD_PHASES) {
954
- const durations = samples.flatMap((sample) => phase in sample.phases ? [sample.phases[phase]] : []);
955
- const ms = median(durations);
956
- if (ms === null) continue;
957
- phaseMs[phase] = ms;
958
- if (durations.length * 2 >= samples.length) planned.push(phase);
959
- }
960
- return {
961
- outcome,
962
- expectedMs: median(samples.map((sample) => sample.durationMs)),
963
- basis: samples.length,
964
- phaseMs,
965
- planned
966
- };
967
- }
968
- const MIN_TAGGED_SAMPLES = 3;
969
- function matchDeviceSetup(samples, deviceSetup) {
970
- if (deviceSetup === void 0) return samples;
971
- const tagged = samples.filter((sample) => sample.deviceSetup === deviceSetup);
972
- if (deviceSetup || tagged.length >= MIN_TAGGED_SAMPLES) return tagged;
973
- return samples.filter((sample) => sample.deviceSetup !== true);
974
- }
975
- function latestOutcome(lists) {
976
- let latest = null;
977
- for (const outcome of ["hit", "cold"]) {
978
- const last = lists?.[outcome]?.at(-1);
979
- const at = last ? Date.parse(last.at) : NaN;
980
- if (Number.isFinite(at) && (!latest || at > latest.at)) latest = {
981
- outcome,
982
- at
983
- };
984
- }
985
- return latest?.outcome ?? null;
986
- }
987
- function buildReport(record, { state, history }) {
988
- const estimate = record.estimate ?? estimateBuild(history, record.platform, record.outcome ?? null, record.outcome ? record.deviceSetup : void 0);
989
- const plannedPhases = estimate.planned.flatMap((phase) => {
990
- const expectedMs = estimate.phaseMs[phase];
991
- return expectedMs === void 0 ? [] : [{
992
- phase,
993
- expectedMs
994
- }];
995
- });
996
- const completedPhaseMs = completedPhaseDurations(record.phases);
997
- return {
998
- platform: record.platform,
999
- slot: record.slot,
1000
- state,
1001
- phase: record.phase,
1002
- startedAt: record.startedAt,
1003
- phaseStartedAt: record.phaseStartedAt,
1004
- outcome: estimate.outcome,
1005
- outcomeKnown: record.outcome !== void 0,
1006
- ...record.outcome && record.phases.some(({ phase }) => phase === "cache-lookup") ? { cacheLookupOutcome: record.outcome === "hit" ? "hit" : "miss" } : {},
1007
- expectedMs: estimate.expectedMs,
1008
- expectedPhaseMs: estimate.phaseMs[record.phase] ?? null,
1009
- ...completedPhaseMs ? { completedPhaseMs } : {},
1010
- basis: estimate.basis,
1011
- plannedPhases: plannedPhases.length ? plannedPhases : null,
1012
- ...record.missReason ? { missReason: record.missReason } : {},
1013
- ...record.missReason && record.missProvisional ? { missProvisional: true } : {},
1014
- placement: record.placement ?? "local",
1015
- ...record.waitingOn ? { waitingOn: record.waitingOn } : {},
1016
- ...record.waitingFor ? { waitingFor: record.waitingFor } : {}
1017
- };
1018
- }
1019
- function remainingText(remainingMs) {
1020
- return remainingMs < 6e4 ? "under a minute left" : `about ${Math.ceil(remainingMs / 6e4)} min left`;
1021
- }
1022
- function buildStatusLine(report, now) {
1023
- const slot = report.slot === "default" ? "" : ` [${report.slot}]`;
1024
- if (report.state === "stale") return `build: stale ${report.platform}${slot} record from ${report.startedAt} (its run is gone; the next run replaces it)`;
1025
- const elapsedMs = Math.max(0, now - Date.parse(report.startedAt));
1026
- const remote = typeof report.placement === "object" ? ` on ${report.placement.host} (${report.placement.phase}, ${formatElapsed(Math.max(0, now - Date.parse(report.placement.phaseStartedAt)))})` : "";
1027
- const waiting = report.waitingOn ? ` on ${report.waitingOn.path}` : "";
1028
- const head = `build: ${report.platform}${slot} ${report.phase}${waiting}${remote}, ${formatElapsed(elapsedMs)} elapsed`;
1029
- if (report.state === "unknown") return `${head} (its native-run claim cannot be resolved, so it may not be running)`;
1030
- if (report.expectedMs === null) return head;
1031
- const basis = `median of ${plural(report.basis, `${report.outcome} run`)}`;
1032
- const remainingMs = report.expectedMs - elapsedMs;
1033
- return remainingMs > 0 ? `${head} -- ${remainingText(remainingMs)} (${basis})` : `${head} (usually ~${formatElapsed(report.expectedMs)}, ${basis})`;
1034
- }
1035
- //#endregion
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 };