@hominis/fireforge 0.49.2 → 0.51.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 (400) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -14,18 +14,21 @@ import { join } from 'node:path';
14
14
  import { probeDisplaySleepState } from '../core/display-state.js';
15
15
  import { runMachTestSuite } from '../core/mach.js';
16
16
  import { parentExited } from '../core/parent-exit-watchdog.js';
17
- import { getActiveRunLogPath } from '../core/run-log.js';
17
+ import { activeRunLogLine, getActiveRunLogPath } from '../core/run-log.js';
18
18
  import { changedPrefNoiseVerdictNote } from '../core/test-changed-prefs.js';
19
- import { buildHarnessCrashMessage, classifyHarnessRun, headedDisplayAsleepVerdictNote, headedNoOutputTimeoutHint, } from '../core/test-harness-crash.js';
19
+ import { classifyHarnessRun, headedDisplayAsleepVerdictNote, } from '../core/test-harness-crash.js';
20
20
  import { reportTestHost, sampleTestHost, stampPerfHost } from '../core/test-host-state.js';
21
+ import { forwardedRawLogPath, hasForwardedRawLog, readRawTestLog, } from '../core/test-raw-log.js';
22
+ import { createStartMarkerTracker, vetoIncompletePass, } from '../core/test-run-completeness.js';
21
23
  import { retryAfterXpcshellSymlinkRepair } from '../core/test-xpcshell-retry.js';
22
24
  import { withXpcshellProfileDir } from '../core/xpcshell-profile-dir.js';
23
25
  import { TestFailureError } from '../errors/build.js';
24
- import { info, note, warn } from '../utils/logger.js';
26
+ import { warn } from '../utils/logger.js';
25
27
  import { getPlatform } from '../utils/platform.js';
26
28
  import { maybeInjectAppdirArg } from './test-appdir.js';
27
29
  import { createHarnessTeardown } from './test-harness-teardown.js';
28
- import { emitHarnessVerdict, setVerdictRunAttribute } from './test-verdict.js';
30
+ import { recordTestResults } from './test-results.js';
31
+ import { setVerdictRunAttribute } from './test-verdict.js';
29
32
  /** Default bounded retry budget for recognized harness crashes. */
30
33
  export const DEFAULT_HARNESS_RETRIES = 2;
31
34
  /** Resolves the mach test command a suite dispatches to. */
@@ -41,21 +44,21 @@ function machKindForSuite(suite) {
41
44
  * crashes up to the configured budget. Every attempt goes through the
42
45
  * stale-xpcshell-symlink repair path the single-run flow already used.
43
46
  */
44
- export async function runTestsWithRetries(ctx, paths) {
47
+ export async function runTestsWithRetries(ctx, paths, label = paths.join(' ')) {
45
48
  // Pure mochitest dispatches keep their env byte-identical, since the profile
46
49
  // variable is xpcshell-harness-specific. Generic `mach
47
50
  // test` runs may include xpcshell paths, so they get the isolation too.
48
51
  if (ctx.suite === 'mochitest') {
49
- return runTestsWithRetriesInner(ctx, paths, ctx.env);
52
+ return runTestsWithRetriesInner(ctx, paths, ctx.env, label);
50
53
  }
51
54
  // Fresh profile dir per harness invocation: shards isolate from each
52
55
  // other and from concurrent `fireforge test` processes. The crash /
53
56
  // symlink-repair retries inside one invocation reuse the same directory
54
57
  // on purpose (they are the same logical run and the harness
55
58
  // re-initialises the profile contents itself).
56
- return withXpcshellProfileDir(ctx.env, (env) => runTestsWithRetriesInner(ctx, paths, env));
59
+ return withXpcshellProfileDir(ctx.env, (env) => runTestsWithRetriesInner(ctx, paths, env, label));
57
60
  }
58
- async function runTestsWithRetriesInner(ctx, paths, env) {
61
+ async function runTestsWithRetriesInner(ctx, paths, env, label) {
59
62
  const extraArgs = [...ctx.baseExtraArgs];
60
63
  const appdirInjectionAttempted = await maybeInjectAppdirArg(ctx.engineDir, paths, ctx.objDir, extraArgs);
61
64
  const kind = machKindForSuite(ctx.suite);
@@ -63,9 +66,15 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
63
66
  let attempts = 0;
64
67
  let result;
65
68
  let verdict;
69
+ let rawLog;
70
+ let logLine;
66
71
  for (;;) {
67
72
  attempts += 1;
68
- result = await runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env);
73
+ let tracker = createStartMarkerTracker();
74
+ const raw = rawLogDispatchArgs(ctx, extraArgs);
75
+ logLine = activeRunLogLine();
76
+ const dispatched = await runTrackedMachTestSuite(ctx, kind, paths, raw.args, env, tracker);
77
+ result = dispatched;
69
78
  // The parent that was reading this run is gone and the watchdog has
70
79
  // already killed the harness. Classifying the corpse would call it a
71
80
  // harness crash and retry it under nobody's supervision.
@@ -73,19 +82,32 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
73
82
  throw new TestFailureError('The parent process exited while the harness was running; the harness tree was ' +
74
83
  'terminated and the run ended without a result.', `mach ${kind}`, undefined, getActiveRunLogPath());
75
84
  }
85
+ const retryTracker = createStartMarkerTracker();
76
86
  result = await retryAfterXpcshellSymlinkRepair({
77
87
  engineDir: ctx.engineDir,
78
88
  objDir: ctx.objDir,
79
89
  result,
80
90
  classification: ctx.classification,
81
91
  normalizedPaths: paths,
82
- extraArgs,
92
+ extraArgs: raw.args,
83
93
  env,
84
94
  kind,
85
95
  fullOutput: ctx.fullOutput,
96
+ onOutputChunk: (stream, chunk) => {
97
+ retryTracker.onChunk(stream, chunk);
98
+ },
86
99
  });
100
+ if (result !== dispatched)
101
+ tracker = retryTracker;
102
+ rawLog = raw.path === undefined ? undefined : await readRawTestLog(raw.path);
87
103
  const combined = `${result.stdout}\n${result.stderr}`;
88
- verdict = classifyHarnessRun(result.exitCode, combined, paths);
104
+ // An exit-0 run is still not a pass when it stopped partway or
105
+ // reported unexpected results (see test-run-completeness.ts). The
106
+ // start count reads the structured raw log when the run wrote one, and
107
+ // the streamed markers otherwise, never the 2 MB capture tail.
108
+ verdict = vetoIncompletePass(classifyHarnessRun(result.exitCode, combined, paths), combined, paths, rawLog !== undefined && rawLog.markers.started.length > 0
109
+ ? { markers: rawLog.markers, source: 'raw' }
110
+ : { markers: tracker.markers(), source: 'stream' });
89
111
  if (verdict.kind !== 'harness-crash' || attempts >= maxAttempts)
90
112
  break;
91
113
  warn(`Harness crash detected (${verdict.signature?.reason ?? 'unknown shape'}): ` +
@@ -110,14 +132,45 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
110
132
  if (note !== undefined)
111
133
  verdict = { ...verdict, note };
112
134
  }
113
- return { result, verdict, attempts, appdirInjectionAttempted, displayState };
135
+ if (rawLog !== undefined) {
136
+ recordTestResults(rawLog.results, rawLog.unfinished, {
137
+ run: ctx.repetition ?? 1,
138
+ shard: label,
139
+ logLine,
140
+ });
141
+ }
142
+ return {
143
+ result,
144
+ verdict,
145
+ attempts,
146
+ appdirInjectionAttempted,
147
+ displayState,
148
+ ...(rawLog === undefined ? {} : { fileResults: rawLog.results }),
149
+ };
150
+ }
151
+ /** Sequence for raw-log file names, unique within the process. */
152
+ let rawLogSequence = 0;
153
+ /**
154
+ * The dispatch arguments with a `--log-raw` destination added, when this run
155
+ * collects per-file results. An operator's own forwarded `--log-raw <file>`
156
+ * is read instead of adding a second one.
157
+ */
158
+ function rawLogDispatchArgs(ctx, extraArgs) {
159
+ if (ctx.rawLogDir === undefined)
160
+ return { args: [...extraArgs], path: undefined };
161
+ if (hasForwardedRawLog(extraArgs)) {
162
+ return { args: [...extraArgs], path: forwardedRawLogPath(extraArgs) };
163
+ }
164
+ rawLogSequence += 1;
165
+ const path = join(ctx.rawLogDir, `dispatch-${rawLogSequence}.raw.jsonl`);
166
+ return { args: [...extraArgs, `--log-raw=${path}`], path };
114
167
  }
115
168
  /**
116
169
  * One mach dispatch with its kill path attached: helper-pid tracking and
117
170
  * post-close reaping, the process-group announcement, and the parent-exit
118
171
  * watchdog for exactly as long as mach runs.
119
172
  */
120
- async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env) {
173
+ async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env, tracker) {
121
174
  const teardown = createHarnessTeardown({
122
175
  objDir: ctx.objDir === undefined ? undefined : join(ctx.engineDir, ctx.objDir),
123
176
  pgidFile: ctx.pgidFile,
@@ -139,7 +192,14 @@ async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env) {
139
192
  args: extraArgs,
140
193
  env,
141
194
  fullOutput: ctx.fullOutput,
142
- teardown: teardown.hooks,
195
+ ...(ctx.outputPrefix === undefined ? {} : { outputPrefix: ctx.outputPrefix }),
196
+ teardown: {
197
+ ...teardown.hooks,
198
+ onOutputChunk: (stream, chunk) => {
199
+ tracker.onChunk(stream, chunk);
200
+ teardown.hooks.onOutputChunk?.(stream, chunk);
201
+ },
202
+ },
143
203
  });
144
204
  }
145
205
  finally {
@@ -170,88 +230,3 @@ async function probeDisplayStateForStall(verdict, headless) {
170
230
  return undefined;
171
231
  return probeDisplaySleepState(getPlatform());
172
232
  }
173
- const SHARD_STATUS_LABEL = {
174
- 'tests-ran-ok': 'PASS',
175
- 'test-failures': 'FAIL',
176
- 'harness-crash': 'CRASH',
177
- 'no-tests': 'NO-TESTS',
178
- 'harness-arguments': 'ARGUMENTS',
179
- };
180
- /**
181
- * Derives the aggregate classification for a sharded run: all-green is
182
- * `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
183
- * shard makes the whole run `harness-crash`, then `no-tests`, then plain
184
- * `test-failures`, so the aggregate `reason=` keeps the classifier-driven
185
- * contract instead of collapsing every failure to `test-failures`.
186
- */
187
- export function deriveAggregateShardVerdict(shards) {
188
- const kinds = new Set(shards.map(({ outcome }) => outcome.verdict.kind));
189
- if (kinds.has('harness-arguments'))
190
- return { kind: 'harness-arguments' };
191
- if (kinds.has('harness-crash'))
192
- return { kind: 'harness-crash' };
193
- if (kinds.has('no-tests'))
194
- return { kind: 'no-tests' };
195
- if (kinds.has('test-failures'))
196
- return { kind: 'test-failures' };
197
- return { kind: 'tests-ran-ok' };
198
- }
199
- /**
200
- * Runs each requested path argument as its own sequential harness
201
- * invocation (a directory argument's enumerated files stay together in
202
- * one invocation, see {@link ShardGroup}) and prints an aggregate
203
- * report. Per-shard failures are diagnosed via `diagnoseShardFailure`
204
- * (which receives the throwing diagnosis chain from the command layer)
205
- * but downgraded to warnings so every shard runs. The aggregate verdict
206
- * line and the aggregate error are not produced here: the caller runs
207
- * the engine-generation integrity check first and then calls
208
- * {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
209
- * `engine/` mutation can never print `PASS` before the check fails.
210
- */
211
- export async function runShardedTests(ctx, groups, diagnoseShardFailure) {
212
- const shards = [];
213
- for (const [index, group] of groups.entries()) {
214
- info(`— Shard ${index + 1}/${groups.length}: ${group.label}`);
215
- const outcome = await runTestsWithRetries(ctx, group.paths);
216
- shards.push({ label: group.label, outcome });
217
- if (outcome.verdict.kind === 'harness-crash' && outcome.verdict.signature) {
218
- const base = buildHarnessCrashMessage(outcome.verdict.signature, outcome.attempts);
219
- const hint = headedNoOutputTimeoutHint(outcome.verdict.signature, {
220
- headless: ctx.headless,
221
- platform: getPlatform(),
222
- ...(outcome.displayState !== undefined ? { displayState: outcome.displayState } : {}),
223
- });
224
- warn(hint ? `${base}\n\n${hint}` : base);
225
- }
226
- else if (outcome.verdict.kind !== 'tests-ran-ok') {
227
- const diagnosis = diagnoseShardFailure(outcome, group.label);
228
- if (diagnosis)
229
- warn(diagnosis);
230
- }
231
- }
232
- const lines = shards.map(({ label, outcome }) => `${SHARD_STATUS_LABEL[outcome.verdict.kind].padEnd(8)} ${label}` +
233
- (outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : ''));
234
- const failing = shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
235
- note(`${lines.join('\n')}\n\n${shards.length - failing.length}/${shards.length} shard(s) passed`, 'Sharded Test Summary');
236
- return {
237
- shards,
238
- passed: shards.length - failing.length,
239
- total: shards.length,
240
- aggregate: deriveAggregateShardVerdict(shards),
241
- };
242
- }
243
- /**
244
- * Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
245
- * throws the aggregate error when any shard did not pass, the same
246
- * emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
247
- * the engine-generation check succeeded.
248
- */
249
- export function finalizeShardedOutcome(summary) {
250
- emitHarnessVerdict(summary.aggregate, { passed: summary.passed, total: summary.total });
251
- if (summary.passed < summary.total) {
252
- const failing = summary.shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
253
- throw new TestFailureError(`${failing.length} of ${summary.total} sharded test run(s) did not pass: ` +
254
- `${failing.map(({ label }) => label).join(', ')}. ` +
255
- 'See the per-shard diagnosis above. Use --no-shard to reproduce the combined single-invocation behaviour.', 'mach test', undefined, getActiveRunLogPath());
256
- }
257
- }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Sharded `fireforge test` runs: one harness invocation per shard, run in
3
+ * order on one harness instance, or on a pool of `--jobs <n>` instances.
4
+ *
5
+ * Each finished shard prints a `FIREFORGE-SHARD:` line, and the aggregate
6
+ * verdict names the shards that did not pass as `failed-shards=`. Before,
7
+ * the failing shards were named only in the prose of the aggregate error
8
+ * and the human summary box, so a wrapper that reruns failed shards had to
9
+ * parse a sentence that a reworded message would silently break.
10
+ *
11
+ * Under `--shard-size <n>` a shard is a chunk of n files, and the run is
12
+ * judged per file from each dispatch's raw log: `shards=` then counts files
13
+ * and `failed-shards=` names the files that failed.
14
+ */
15
+ import { type HarnessRunVerdict } from '../core/test-harness-crash.js';
16
+ import { type TestRunContext, type TestRunOutcome } from './test-run.js';
17
+ /**
18
+ * One harness invocation of a sharded run: a requested path argument and
19
+ * the mach paths dispatched for it. A file argument is a group of one. A
20
+ * directory argument groups its enumerated test files so the whole
21
+ * directory still runs in ONE browser instance, keeping cross-file state
22
+ * behaviour within a directory run. A `--shard-size` chunk is a group of
23
+ * up to n files.
24
+ */
25
+ export interface ShardGroup {
26
+ /** The path argument as the operator passed it (display label). */
27
+ label: string;
28
+ /** The mach dispatch paths for this argument. */
29
+ paths: string[];
30
+ }
31
+ /** Per-shard summary entry. */
32
+ export interface ShardOutcome {
33
+ label: string;
34
+ outcome: TestRunOutcome;
35
+ /** The shard's dispatch paths (per-file judging). */
36
+ paths?: string[];
37
+ }
38
+ /** Aggregate result of a sharded run, verdict emission deferred to the caller. */
39
+ export interface ShardedRunSummary {
40
+ shards: ShardOutcome[];
41
+ passed: number;
42
+ total: number;
43
+ /** What did not pass: shard labels, or file paths when judged per file. */
44
+ failed: string[];
45
+ /** Aggregate classifier verdict. See {@link deriveAggregateShardVerdict}. */
46
+ aggregate: HarnessRunVerdict;
47
+ }
48
+ /** How {@link runShardedTests} schedules and judges its shards. */
49
+ export interface ShardRunOptions {
50
+ /** Harness instances to run shards on at once (`--jobs`). Default 1. */
51
+ jobs?: number;
52
+ /**
53
+ * The context for instance `index` (0-based). Instance 0 must be the
54
+ * base context; others carry their own ports and output prefix.
55
+ */
56
+ contextFor?: (index: number) => Promise<TestRunContext> | TestRunContext;
57
+ /** Judge per file from the raw log (`--shard-size`). */
58
+ perFile?: boolean;
59
+ }
60
+ /**
61
+ * Derives the aggregate classification for a sharded run: all-green is
62
+ * `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
63
+ * shard makes the whole run `harness-crash`, then `no-tests`, then plain
64
+ * `test-failures`, so the aggregate `reason=` keeps the classifier-driven
65
+ * contract instead of collapsing every failure to `test-failures`.
66
+ */
67
+ export declare function deriveAggregateShardVerdict(shards: ShardOutcome[]): HarnessRunVerdict;
68
+ /**
69
+ * The `FIREFORGE-SHARD:` line for one finished shard: the verdict it would
70
+ * have printed alone, plus `shard=<i>/<n>` and `path=<label>`. Never a
71
+ * `FIREFORGE-VERDICT:` line, so the run keeps exactly one.
72
+ */
73
+ export declare function formatShardLine(shard: ShardOutcome, index: number, total: number, failedFiles?: readonly string[]): string;
74
+ /**
75
+ * The files of one shard that did not pass, judged from its raw log. A file
76
+ * with no `test_end` failed (it never started, or never finished). Returns
77
+ * undefined when the shard wrote no raw log, so the caller judges the
78
+ * shard as a whole.
79
+ */
80
+ export declare function failedFilesOf(shard: ShardOutcome): string[] | undefined;
81
+ /**
82
+ * Runs each shard as its own harness invocation and prints an aggregate
83
+ * report. Per-shard failures are diagnosed via `diagnoseShardFailure`
84
+ * (which receives the throwing diagnosis chain from the command layer)
85
+ * but downgraded to warnings so every shard runs. The aggregate verdict
86
+ * line and the aggregate error are not produced here: the caller runs
87
+ * the engine-generation integrity check first and then calls
88
+ * {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
89
+ * `engine/` mutation can never print `PASS` before the check fails.
90
+ */
91
+ export declare function runShardedTests(ctx: TestRunContext, groups: ShardGroup[], diagnoseShardFailure: (outcome: TestRunOutcome, label: string) => string | undefined, options?: ShardRunOptions): Promise<ShardedRunSummary>;
92
+ /**
93
+ * Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
94
+ * throws the aggregate error when any shard did not pass, the same
95
+ * emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
96
+ * the engine-generation check succeeded.
97
+ */
98
+ export declare function finalizeShardedOutcome(summary: ShardedRunSummary): void;
99
+ /**
100
+ * Splits the dispatch paths of every group into consecutive chunks of at
101
+ * most `size` files (`--shard-size`). Order is kept, and a chunk never
102
+ * mixes groups' order: it is the flattened request, cut every `size`
103
+ * files.
104
+ */
105
+ export declare function chunkShardGroups(groups: readonly ShardGroup[], size: number): ShardGroup[];
106
+ /**
107
+ * Splits one combined request into `parts` contiguous chunks of near-equal
108
+ * size, in order (`--jobs` with `--no-shard`).
109
+ */
110
+ export declare function splitContiguous(paths: readonly string[], parts: number): ShardGroup[];
@@ -0,0 +1,255 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Sharded `fireforge test` runs: one harness invocation per shard, run in
4
+ * order on one harness instance, or on a pool of `--jobs <n>` instances.
5
+ *
6
+ * Each finished shard prints a `FIREFORGE-SHARD:` line, and the aggregate
7
+ * verdict names the shards that did not pass as `failed-shards=`. Before,
8
+ * the failing shards were named only in the prose of the aggregate error
9
+ * and the human summary box, so a wrapper that reruns failed shards had to
10
+ * parse a sentence that a reworded message would silently break.
11
+ *
12
+ * Under `--shard-size <n>` a shard is a chunk of n files, and the run is
13
+ * judged per file from each dispatch's raw log: `shards=` then counts files
14
+ * and `failed-shards=` names the files that failed.
15
+ */
16
+ import { getActiveRunLogPath, writeToActiveRunLog } from '../core/run-log.js';
17
+ import { buildHarnessCrashMessage, formatFireforgeVerdictLine, headedNoOutputTimeoutHint, } from '../core/test-harness-crash.js';
18
+ import { TestFailureError } from '../errors/build.js';
19
+ import { info, note, warn } from '../utils/logger.js';
20
+ import { getPlatform } from '../utils/platform.js';
21
+ import { runTestsWithRetries } from './test-run.js';
22
+ import { emitHarnessVerdict } from './test-verdict.js';
23
+ const SHARD_STATUS_LABEL = {
24
+ 'tests-ran-ok': 'PASS',
25
+ 'test-failures': 'FAIL',
26
+ 'harness-crash': 'CRASH',
27
+ 'no-tests': 'NO-TESTS',
28
+ 'harness-arguments': 'ARGUMENTS',
29
+ };
30
+ /**
31
+ * Derives the aggregate classification for a sharded run: all-green is
32
+ * `tests-ran-ok`. Otherwise the most structural failure wins. A crashed
33
+ * shard makes the whole run `harness-crash`, then `no-tests`, then plain
34
+ * `test-failures`, so the aggregate `reason=` keeps the classifier-driven
35
+ * contract instead of collapsing every failure to `test-failures`.
36
+ */
37
+ export function deriveAggregateShardVerdict(shards) {
38
+ const kinds = new Set(shards.map(({ outcome }) => outcome.verdict.kind));
39
+ if (kinds.has('harness-arguments'))
40
+ return { kind: 'harness-arguments' };
41
+ if (kinds.has('harness-crash'))
42
+ return { kind: 'harness-crash' };
43
+ if (kinds.has('no-tests'))
44
+ return { kind: 'no-tests' };
45
+ if (kinds.has('test-failures'))
46
+ return { kind: 'test-failures' };
47
+ return { kind: 'tests-ran-ok' };
48
+ }
49
+ /**
50
+ * The `FIREFORGE-SHARD:` line for one finished shard: the verdict it would
51
+ * have printed alone, plus `shard=<i>/<n>` and `path=<label>`. Never a
52
+ * `FIREFORGE-VERDICT:` line, so the run keeps exactly one.
53
+ */
54
+ export function formatShardLine(shard, index, total, failedFiles) {
55
+ const body = formatFireforgeVerdictLine(shard.outcome.verdict).slice('FIREFORGE-VERDICT: '.length);
56
+ const files = failedFiles !== undefined && failedFiles.length > 0
57
+ ? ` failed-files=${failedFiles.join(',')}`
58
+ : '';
59
+ return `FIREFORGE-SHARD: ${body} shard=${index + 1}/${total} path=${shard.label.replace(/\s+/g, '_')}${files}`;
60
+ }
61
+ function writeShardLine(line) {
62
+ process.stdout.write(`${line}\n`);
63
+ writeToActiveRunLog(`${line}\n`);
64
+ }
65
+ /** True when a raw-log test id names the dispatched path. */
66
+ function namesPath(test, path) {
67
+ return test === path || test.endsWith(`/${path}`) || path.endsWith(`/${test}`);
68
+ }
69
+ /**
70
+ * The files of one shard that did not pass, judged from its raw log. A file
71
+ * with no `test_end` failed (it never started, or never finished). Returns
72
+ * undefined when the shard wrote no raw log, so the caller judges the
73
+ * shard as a whole.
74
+ */
75
+ export function failedFilesOf(shard) {
76
+ const results = shard.outcome.fileResults;
77
+ if (results === undefined || shard.paths === undefined)
78
+ return undefined;
79
+ return shard.paths.filter((path) => {
80
+ const result = results.find((entry) => namesPath(entry.test, path));
81
+ return result === undefined || result.unexpected;
82
+ });
83
+ }
84
+ function reportShardFailure(ctx, outcome, label, diagnoseShardFailure) {
85
+ if (outcome.verdict.kind === 'harness-crash' && outcome.verdict.signature) {
86
+ const base = buildHarnessCrashMessage(outcome.verdict.signature, outcome.attempts);
87
+ const hint = headedNoOutputTimeoutHint(outcome.verdict.signature, {
88
+ headless: ctx.headless,
89
+ platform: getPlatform(),
90
+ ...(outcome.displayState !== undefined ? { displayState: outcome.displayState } : {}),
91
+ });
92
+ warn(hint ? `${base}\n\n${hint}` : base);
93
+ }
94
+ else if (outcome.verdict.kind !== 'tests-ran-ok') {
95
+ const diagnosis = diagnoseShardFailure(outcome, label);
96
+ if (diagnosis)
97
+ warn(diagnosis);
98
+ }
99
+ }
100
+ /**
101
+ * Runs `count` items on up to `jobs` workers at once. Worker `w` handles
102
+ * the items it takes in order, and results keep item order.
103
+ */
104
+ async function runPool(count, jobs, worker) {
105
+ const results = new Array(count);
106
+ let next = 0;
107
+ const workers = Array.from({ length: Math.max(1, Math.min(jobs, count)) }, async (_, w) => {
108
+ while (next < count) {
109
+ const item = next;
110
+ next += 1;
111
+ results[item] = await worker(item, w);
112
+ }
113
+ });
114
+ await Promise.all(workers);
115
+ return results;
116
+ }
117
+ /**
118
+ * Runs each shard as its own harness invocation and prints an aggregate
119
+ * report. Per-shard failures are diagnosed via `diagnoseShardFailure`
120
+ * (which receives the throwing diagnosis chain from the command layer)
121
+ * but downgraded to warnings so every shard runs. The aggregate verdict
122
+ * line and the aggregate error are not produced here: the caller runs
123
+ * the engine-generation integrity check first and then calls
124
+ * {@link finalizeShardedOutcome}, so a run invalidated by a concurrent
125
+ * `engine/` mutation can never print `PASS` before the check fails.
126
+ */
127
+ export async function runShardedTests(ctx, groups, diagnoseShardFailure, options = {}) {
128
+ const jobs = options.jobs ?? 1;
129
+ const contexts = new Map([[0, ctx]]);
130
+ const contextFor = async (index) => {
131
+ const known = contexts.get(index);
132
+ if (known !== undefined)
133
+ return known;
134
+ const made = options.contextFor === undefined ? ctx : await options.contextFor(index);
135
+ contexts.set(index, made);
136
+ return made;
137
+ };
138
+ let finished = 0;
139
+ const shards = await runPool(groups.length, jobs, async (index, workerIndex) => {
140
+ const group = groups[index];
141
+ const instance = await contextFor(workerIndex);
142
+ info(`— Shard ${index + 1}/${groups.length}${jobs > 1 ? ` [job ${workerIndex + 1}]` : ''}: ${group.label}`);
143
+ const outcome = await runTestsWithRetries(instance, group.paths, group.label);
144
+ // Perf power-source changes are recorded on the instance's context.
145
+ if (instance.powerChanged === true)
146
+ ctx.powerChanged = true;
147
+ const shard = { label: group.label, outcome, paths: group.paths };
148
+ reportShardFailure(instance, outcome, group.label, diagnoseShardFailure);
149
+ finished += 1;
150
+ writeShardLine(formatShardLine(shard, index, groups.length, options.perFile === true ? failedFilesOf(shard) : undefined));
151
+ reportProgress(finished, groups.length, jobs);
152
+ return shard;
153
+ });
154
+ return summarizeShards(shards, options.perFile === true);
155
+ }
156
+ function reportProgress(finished, total, jobs) {
157
+ if (jobs > 1)
158
+ info(`${finished}/${total} shard(s) finished.`);
159
+ }
160
+ /**
161
+ * Builds the summary box and the counts. Per-file judging counts every
162
+ * dispatched file of a shard that wrote a raw log; a shard without one
163
+ * counts as a whole.
164
+ */
165
+ function summarizeShards(shards, perFile) {
166
+ const lines = shards.map(({ label, outcome }) => `${SHARD_STATUS_LABEL[outcome.verdict.kind].padEnd(8)} ${label}` +
167
+ (outcome.attempts > 1 ? ` (${outcome.attempts} attempts)` : ''));
168
+ const failingShards = shards.filter(({ outcome }) => outcome.verdict.kind !== 'tests-ran-ok');
169
+ let passed = shards.length - failingShards.length;
170
+ let total = shards.length;
171
+ let failed = failingShards.map(({ label }) => label);
172
+ if (perFile) {
173
+ passed = 0;
174
+ total = 0;
175
+ failed = [];
176
+ for (const shard of shards) {
177
+ const files = failedFilesOf(shard);
178
+ const ok = shard.outcome.verdict.kind === 'tests-ran-ok';
179
+ if (files === undefined) {
180
+ total += 1;
181
+ if (ok)
182
+ passed += 1;
183
+ else
184
+ failed.push(shard.label);
185
+ continue;
186
+ }
187
+ const dispatched = shard.paths ?? [];
188
+ total += dispatched.length;
189
+ // A red shard whose log names no failing file still failed: count
190
+ // every file of it, so a crash cannot read as all-green.
191
+ const red = files.length === 0 && !ok ? dispatched : files;
192
+ passed += dispatched.length - red.length;
193
+ failed.push(...red);
194
+ }
195
+ }
196
+ note(`${lines.join('\n')}\n\n${passed}/${total} ${perFile ? 'file(s)' : 'shard(s)'} passed`, 'Sharded Test Summary');
197
+ return { shards, passed, total, failed, aggregate: deriveAggregateShardVerdict(shards) };
198
+ }
199
+ /**
200
+ * Emits the aggregate FIREFORGE-VERDICT line for a sharded run, then
201
+ * throws the aggregate error when any shard did not pass, the same
202
+ * emit-then-throw shape as `finalizeSingleRunOutcome`. Called only after
203
+ * the engine-generation check succeeded.
204
+ */
205
+ export function finalizeShardedOutcome(summary) {
206
+ emitHarnessVerdict(summary.aggregate, {
207
+ passed: summary.passed,
208
+ total: summary.total,
209
+ failed: summary.failed.map((label) => label.replace(/\s+/g, '_')),
210
+ });
211
+ if (summary.passed < summary.total) {
212
+ throw new TestFailureError(`${summary.failed.length} of ${summary.total} sharded test run(s) did not pass: ` +
213
+ `${summary.failed.join(', ')}. ` +
214
+ 'See the per-shard diagnosis above. Use --no-shard to reproduce the combined single-invocation behaviour.', 'mach test', undefined, getActiveRunLogPath());
215
+ }
216
+ }
217
+ /**
218
+ * Splits the dispatch paths of every group into consecutive chunks of at
219
+ * most `size` files (`--shard-size`). Order is kept, and a chunk never
220
+ * mixes groups' order: it is the flattened request, cut every `size`
221
+ * files.
222
+ */
223
+ export function chunkShardGroups(groups, size) {
224
+ const paths = groups.flatMap((group) => group.paths);
225
+ const chunks = [];
226
+ for (let start = 0; start < paths.length; start += size) {
227
+ const chunk = paths.slice(start, start + size);
228
+ const first = chunk[0] ?? '';
229
+ chunks.push({
230
+ label: chunk.length === 1 ? first : `${first}+${chunk.length - 1}`,
231
+ paths: chunk,
232
+ });
233
+ }
234
+ return chunks;
235
+ }
236
+ /**
237
+ * Splits one combined request into `parts` contiguous chunks of near-equal
238
+ * size, in order (`--jobs` with `--no-shard`).
239
+ */
240
+ export function splitContiguous(paths, parts) {
241
+ const count = Math.max(1, Math.min(parts, paths.length));
242
+ const groups = [];
243
+ let start = 0;
244
+ for (let index = 0; index < count; index += 1) {
245
+ const size = Math.ceil((paths.length - start) / (count - index));
246
+ const chunk = paths.slice(start, start + size);
247
+ start += size;
248
+ const first = chunk[0] ?? '';
249
+ groups.push({
250
+ label: chunk.length === 1 ? first : `${first}+${chunk.length - 1}`,
251
+ paths: chunk,
252
+ });
253
+ }
254
+ return groups;
255
+ }
@@ -28,7 +28,11 @@ import type { TestOptions } from '../types/commands/index.js';
28
28
  * a full-suite request would refuse a probe that touches no fixtures. The
29
29
  * stale-content refusal still applies to it unchanged.
30
30
  */
31
- export declare function enforceStaleBuildGate(projectRoot: string, engineDir: string, options: TestOptions, normalizedPaths: readonly string[]): Promise<void>;
31
+ export declare function enforceStaleBuildGate(projectRoot: string, engineDir: string, options: TestOptions, normalizedPaths: readonly string[],
32
+ /** `quiet`: refuse only, without the exemption note (the pre-lock pass). */
33
+ gate?: {
34
+ quiet?: boolean;
35
+ }): Promise<void>;
32
36
  /**
33
37
  * Compiled-StaticComponents gate: refuses runs whose child process would
34
38
  * resolve a stale compiled component table. `components.conf` entries bake