@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
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The exit-0 completeness veto for `fireforge test`.
3
+ *
4
+ * `classifyHarnessRun` trusts a mach exit code of 0 once any test started.
5
+ * That is wrong for one shape a downstream fork hit: the browser shut down
6
+ * cleanly inside the 105th of 314 files ("Application shut down (without
7
+ * crashing) in the middle of a test!"), nothing after it ran, mach still
8
+ * exited 0, and the verdict read `PASS checks=7919 unexpected=1`. The
9
+ * truncation checks the classifier already applies to a non-zero exit with
10
+ * a green-looking summary never ran, because the exit code short-circuited
11
+ * them.
12
+ *
13
+ * So a would-be pass is re-examined here against three facts the captured
14
+ * output carries: a non-zero `Unexpected results:` count, the harness's
15
+ * mid-test shutdown line, and requested test files that never produced a
16
+ * start marker. Any one of them turns the run into
17
+ * `FAIL reason=test-failures`, and a short start count is stamped on the
18
+ * verdict line as `started=<n>/<m> first-unstarted=<path>`.
19
+ *
20
+ * The start-count check only runs when at least one start marker names a
21
+ * requested file. The suite-specific xpcshell dispatch can prove execution
22
+ * with a result-summary block alone and print no `TEST_START` line, and a
23
+ * run that never names its files cannot be counted against them.
24
+ */
25
+ import { type HarnessRunVerdict } from './test-harness-crash.js';
26
+ import type { IncompleteRunEvidence } from './test-harness-signature.js';
27
+ /** Start and skip marker names seen in a run's output. */
28
+ export interface StartMarkers {
29
+ started: string[];
30
+ skipped: string[];
31
+ }
32
+ /**
33
+ * Collects start and skip markers from a dispatch's output as it streams.
34
+ *
35
+ * The capture `runMachCapture` hands back keeps only the last 2 MB of each
36
+ * stream, so a suite that prints more than that (a large browser directory,
37
+ * or any run under `--full-output`) loses its first files' start lines from
38
+ * the capture, and counting from that tail named files that ran and passed
39
+ * as never started. The tracker sees every chunk, splits it into whole lines
40
+ * per stream, and keeps only the marker names, so it grows with the file
41
+ * count, not the output size.
42
+ */
43
+ export interface StartMarkerTracker {
44
+ /** Feeds one output chunk; wire it to `MachOptions.onOutputChunk`. */
45
+ onChunk(stream: 'stdout' | 'stderr', chunk: string): void;
46
+ /** The markers seen so far, including any unterminated last line. */
47
+ markers(): StartMarkers;
48
+ }
49
+ /** Markers collected outside the capped capture, and where they came from. */
50
+ export interface TrackedStartMarkers {
51
+ markers: StartMarkers;
52
+ /** `stream`: every output chunk; `raw`: the mozlog structured log. */
53
+ source: 'stream' | 'raw';
54
+ }
55
+ /** Creates a {@link StartMarkerTracker}. */
56
+ export declare function createStartMarkerTracker(): StartMarkerTracker;
57
+ /**
58
+ * Counts the requested test files that produced a start or skip marker.
59
+ * Returns undefined when nothing can be counted: no test-file-shaped
60
+ * request, or no start line that names any requested file.
61
+ *
62
+ * @param evidence - Captured mach stdout and stderr, or the markers
63
+ * collected from the complete stream
64
+ * @param requestedPaths - Dispatched paths, in dispatch order
65
+ */
66
+ export declare function countStartedFiles(evidence: string | StartMarkers, requestedPaths: readonly string[]): {
67
+ started: number;
68
+ requested: number;
69
+ firstUnstarted?: string;
70
+ } | undefined;
71
+ /**
72
+ * Re-examines a passing verdict for a mach exit-0 run and fails it when
73
+ * the run is incomplete or reported unexpected results. Any other verdict
74
+ * is returned unchanged.
75
+ *
76
+ * @param verdict - The classifier's verdict
77
+ * @param output - Captured mach stdout and stderr
78
+ * @param requestedPaths - Dispatched paths, in dispatch order
79
+ * @param tracked - Start markers collected from the complete stream (or
80
+ * the structured raw log); without them the capped capture is scanned
81
+ */
82
+ export declare function vetoIncompletePass(verdict: HarnessRunVerdict, output: string, requestedPaths: readonly string[], tracked?: TrackedStartMarkers): HarnessRunVerdict;
83
+ /**
84
+ * Operator-facing explanation for a pass {@link vetoIncompletePass}
85
+ * refused. Names the first file that never started, because that is
86
+ * where a re-run has to pick up.
87
+ *
88
+ * @param evidence - The veto's evidence
89
+ */
90
+ export declare function buildIncompleteRunMessage(evidence: IncompleteRunEvidence): string;
@@ -0,0 +1,184 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * The exit-0 completeness veto for `fireforge test`.
4
+ *
5
+ * `classifyHarnessRun` trusts a mach exit code of 0 once any test started.
6
+ * That is wrong for one shape a downstream fork hit: the browser shut down
7
+ * cleanly inside the 105th of 314 files ("Application shut down (without
8
+ * crashing) in the middle of a test!"), nothing after it ran, mach still
9
+ * exited 0, and the verdict read `PASS checks=7919 unexpected=1`. The
10
+ * truncation checks the classifier already applies to a non-zero exit with
11
+ * a green-looking summary never ran, because the exit code short-circuited
12
+ * them.
13
+ *
14
+ * So a would-be pass is re-examined here against three facts the captured
15
+ * output carries: a non-zero `Unexpected results:` count, the harness's
16
+ * mid-test shutdown line, and requested test files that never produced a
17
+ * start marker. Any one of them turns the run into
18
+ * `FAIL reason=test-failures`, and a short start count is stamped on the
19
+ * verdict line as `started=<n>/<m> first-unstarted=<path>`.
20
+ *
21
+ * The start-count check only runs when at least one start marker names a
22
+ * requested file. The suite-specific xpcshell dispatch can prove execution
23
+ * with a result-summary block alone and print no `TEST_START` line, and a
24
+ * run that never names its files cannot be counted against them.
25
+ */
26
+ import { SHUTDOWN_REENTRY_PATTERN, START_MARKER_PATTERN, TEST_FILE_BASENAME_PATTERN, } from './test-harness-crash.js';
27
+ /**
28
+ * A skip marker naming a test (`TEST-SKIP | path`, `TEST_SKIP: path`). A
29
+ * manifest-skipped file is accounted for, not missing.
30
+ */
31
+ const SKIP_MARKER_PATTERN = /\bTEST[-_]SKIP(?:PED)?\b[:| ]*\s*(\S+)?/;
32
+ /** Basename of a `/`-separated path. */
33
+ function basenameOf(path) {
34
+ return path.slice(path.lastIndexOf('/') + 1);
35
+ }
36
+ /** Collects the start and skip marker names in `output`, line by line. */
37
+ function scanStartMarkers(output) {
38
+ const markers = { started: [], skipped: [] };
39
+ for (const line of output.split(/\r?\n/))
40
+ recordMarkerLine(line, markers);
41
+ return markers;
42
+ }
43
+ function recordMarkerLine(line, markers) {
44
+ const start = START_MARKER_PATTERN.exec(line)?.[1];
45
+ if (start !== undefined) {
46
+ markers.started.push(start);
47
+ return;
48
+ }
49
+ const skip = SKIP_MARKER_PATTERN.exec(line)?.[1];
50
+ if (skip !== undefined)
51
+ markers.skipped.push(skip);
52
+ }
53
+ /** Creates a {@link StartMarkerTracker}. */
54
+ export function createStartMarkerTracker() {
55
+ const collected = { started: [], skipped: [] };
56
+ const pending = { stdout: '', stderr: '' };
57
+ return {
58
+ onChunk(stream, chunk) {
59
+ const lines = `${pending[stream]}${chunk}`.split(/\r?\n/);
60
+ pending[stream] = lines.pop() ?? '';
61
+ for (const line of lines)
62
+ recordMarkerLine(line, collected);
63
+ },
64
+ markers() {
65
+ const tail = { started: [], skipped: [] };
66
+ recordMarkerLine(pending.stdout, tail);
67
+ recordMarkerLine(pending.stderr, tail);
68
+ return {
69
+ started: [...collected.started, ...tail.started],
70
+ skipped: [...collected.skipped, ...tail.skipped],
71
+ };
72
+ },
73
+ };
74
+ }
75
+ /**
76
+ * Counts the requested test files that produced a start or skip marker.
77
+ * Returns undefined when nothing can be counted: no test-file-shaped
78
+ * request, or no start line that names any requested file.
79
+ *
80
+ * @param evidence - Captured mach stdout and stderr, or the markers
81
+ * collected from the complete stream
82
+ * @param requestedPaths - Dispatched paths, in dispatch order
83
+ */
84
+ export function countStartedFiles(evidence, requestedPaths) {
85
+ const requested = requestedPaths.filter((path) => TEST_FILE_BASENAME_PATTERN.test(basenameOf(path)));
86
+ if (requested.length === 0)
87
+ return undefined;
88
+ const { started, skipped } = typeof evidence === 'string' ? scanStartMarkers(evidence) : evidence;
89
+ const names = (path, list) => {
90
+ const base = basenameOf(path);
91
+ return list.some((name) => name.includes(base));
92
+ };
93
+ // Counting needs a dialect that names the requested files on its start
94
+ // lines. When no start line names any of them, the output cannot be
95
+ // counted against the request at all.
96
+ if (!requested.some((path) => names(path, started)))
97
+ return undefined;
98
+ const unstarted = requested.filter((path) => !names(path, started) && !names(path, skipped));
99
+ const firstUnstarted = unstarted[0];
100
+ return {
101
+ started: requested.length - unstarted.length,
102
+ requested: requested.length,
103
+ ...(firstUnstarted !== undefined ? { firstUnstarted } : {}),
104
+ };
105
+ }
106
+ /**
107
+ * Re-examines a passing verdict for a mach exit-0 run and fails it when
108
+ * the run is incomplete or reported unexpected results. Any other verdict
109
+ * is returned unchanged.
110
+ *
111
+ * @param verdict - The classifier's verdict
112
+ * @param output - Captured mach stdout and stderr
113
+ * @param requestedPaths - Dispatched paths, in dispatch order
114
+ * @param tracked - Start markers collected from the complete stream (or
115
+ * the structured raw log); without them the capped capture is scanned
116
+ */
117
+ export function vetoIncompletePass(verdict, output, requestedPaths, tracked) {
118
+ if (verdict.kind !== 'tests-ran-ok')
119
+ return verdict;
120
+ const shutdownLine = output
121
+ .split(/\r?\n/)
122
+ .find((line) => SHUTDOWN_REENTRY_PATTERN.test(line))
123
+ ?.trim();
124
+ // Tracked markers win whenever they saw a start line at all. An empty
125
+ // tracker means nothing was streamed through it, and the capture is then
126
+ // the only evidence there is.
127
+ const useTracked = tracked !== undefined && tracked.markers.started.length > 0;
128
+ const counted = countStartedFiles(useTracked ? tracked.markers : output, requestedPaths);
129
+ const unexpected = verdict.unexpected ?? 0;
130
+ const truncated = counted?.firstUnstarted !== undefined;
131
+ if (unexpected === 0 && shutdownLine === undefined && !truncated)
132
+ return verdict;
133
+ const evidence = {
134
+ started: counted?.started ?? 0,
135
+ requested: counted?.requested ?? 0,
136
+ ...(counted?.firstUnstarted !== undefined
137
+ ? {
138
+ firstUnstarted: counted.firstUnstarted,
139
+ startedSource: useTracked ? tracked.source : 'capture',
140
+ }
141
+ : {}),
142
+ ...(shutdownLine !== undefined ? { shutdownLine } : {}),
143
+ ...(unexpected > 0 ? { unexpected } : {}),
144
+ };
145
+ // The pass-only annotations would misdescribe a failed run: "harness
146
+ // teardown noise ignored" and the green-summary override both say the
147
+ // run was trusted.
148
+ const rest = { ...verdict };
149
+ delete rest.greenSummaryOverride;
150
+ delete rest.note;
151
+ return {
152
+ ...rest,
153
+ kind: 'test-failures',
154
+ incomplete: evidence,
155
+ ...(shutdownLine !== undefined ? { realFailureLine: shutdownLine } : {}),
156
+ };
157
+ }
158
+ /**
159
+ * Operator-facing explanation for a pass {@link vetoIncompletePass}
160
+ * refused. Names the first file that never started, because that is
161
+ * where a re-run has to pick up.
162
+ *
163
+ * @param evidence - The veto's evidence
164
+ */
165
+ export function buildIncompleteRunMessage(evidence) {
166
+ const lines = [];
167
+ if (evidence.firstUnstarted !== undefined) {
168
+ lines.push(` - only ${evidence.started} of ${evidence.requested} requested test files started; ` +
169
+ `the first that never started is ${evidence.firstUnstarted}`);
170
+ }
171
+ if (evidence.shutdownLine !== undefined) {
172
+ lines.push(` - the browser shut down in the middle of a test: ${evidence.shutdownLine}`);
173
+ }
174
+ if (evidence.unexpected !== undefined) {
175
+ lines.push(` - the suite summary reported ${evidence.unexpected} unexpected result(s)`);
176
+ }
177
+ const rerun = evidence.firstUnstarted !== undefined
178
+ ? `Re-run from ${evidence.firstUnstarted} onward, or the whole selection once the engine is quiet.`
179
+ : 'Read the unexpected results in the output above.';
180
+ return ('mach exited 0, but FireForge did NOT treat the run as passed:\n\n' +
181
+ `${lines.join('\n')}\n\n` +
182
+ 'A browser that quits cleanly mid-suite exits 0, and its summary then covers only the ' +
183
+ `files that ran. ${rerun}`);
184
+ }
@@ -1,4 +1,5 @@
1
1
  import type { BuildBaseline, TestPackagingCoverage } from './build-baseline-types.js';
2
+ import { type StaleExemption } from './test-stale-exempt.js';
2
3
  /** Result of the stale-build preflight probe. */
3
4
  export interface StaleBuildResult {
4
5
  /** True when at least one packageable engine file changed since the baseline. */
@@ -22,6 +23,13 @@ export interface StaleBuildResult {
22
23
  * we have nothing to compare against and a warning would mislead.
23
24
  */
24
25
  baseline: BuildBaseline | undefined;
26
+ /**
27
+ * Changed packageable paths the packaged runtime already serves (objdir
28
+ * symlinks to the edited source, test files restaged at dispatch), which
29
+ * therefore do not make the build stale. Named in the refusal and in a
30
+ * notice so the exemption is visible.
31
+ */
32
+ exempt?: StaleExemption[];
25
33
  }
26
34
  /**
27
35
  * Probes the engine tree for packageable changes since the last successful
@@ -79,15 +87,33 @@ export declare function findUncoveredRequestPaths(coverage: TestPackagingCoverag
79
87
  * {@link STALE_PATHS_LIMIT}
80
88
  */
81
89
  export declare function findChangedTestManifestsForPaths(engineDir: string, baseline: BuildBaseline | undefined, uncovered: readonly string[]): Promise<string[]>;
90
+ /** Extra evidence for {@link formatTestCoverageRefusal}. */
91
+ export interface CoverageRefusalDetail {
92
+ /**
93
+ * Per uncovered path, the staging inputs that changed since the full
94
+ * build the recorded claim carries forward (what the replaced coverage
95
+ * affects).
96
+ */
97
+ changedInputs?: Readonly<Record<string, readonly string[]>>;
98
+ /** The record carries a full build's anchor forward for unchanged paths. */
99
+ carriesFullAnchor?: boolean;
100
+ }
82
101
  /**
102
+ * Formats the refusal shown when a non-`--build` run requests paths the
103
+ * packaged runtime's scoped `test --build` coverage does not include. It is
104
+ * enforced on every such run, with or without `--allow-stale-build`. Kept
105
+ * separate from the matcher so tests can pin structure and copy
106
+ * independently (same split as {@link formatStaleBuildWarning}).
107
+ *
83
108
  * @param uncovered - Request paths the recorded packaging does not cover
84
109
  * @param coverage - The recorded scoped coverage claim
85
110
  * @param changedManifests - Test manifests changed since the recorded
86
111
  * build that explain an uncovered path (from
87
112
  * {@link findChangedTestManifestsForPaths}). Omitted or empty renders
88
113
  * nothing
114
+ * @param detail - See {@link CoverageRefusalDetail}
89
115
  */
90
- export declare function formatTestCoverageRefusal(uncovered: string[], coverage: string[], changedManifests?: readonly string[]): string;
116
+ export declare function formatTestCoverageRefusal(uncovered: string[], coverage: string[], changedManifests?: readonly string[], detail?: CoverageRefusalDetail): string;
91
117
  /** Result of the compiled-StaticComponents staleness probe. */
92
118
  export interface StaticComponentsStaleResult {
93
119
  /** True when at least one `components.conf` genuinely diverged from the anchor. */
@@ -28,7 +28,9 @@ import { normalizePathSlashes } from '../utils/paths.js';
28
28
  import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
29
29
  import { readBuildBaseline } from './build-baseline.js';
30
30
  import { hashEngineFile } from './coverage-extend.js';
31
+ import { isTestManifestPath } from './coverage-records.js';
31
32
  import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
33
+ import { formatStaleExemptions, partitionStaleCandidates, } from './test-stale-exempt.js';
32
34
  /** Cap on the number of changed paths rendered inline. */
33
35
  const STALE_PATHS_LIMIT = 10;
34
36
  /**
@@ -61,12 +63,15 @@ export async function checkStaleBuildForTest(projectRoot, engineDir) {
61
63
  // fingerprints capture "these files had this content when the build ran".
62
64
  // A path stays stale only when its live hash diverges.
63
65
  packageable = await dropPathsMatchingFingerprints(engineDir, packageable, baseline.packageableFingerprints);
64
- if (packageable.length === 0) {
65
- return { stale: false, changedPaths: [], truncated: 0, baseline };
66
+ // Edits the runtime already serves (symlinked into the objdir, or test
67
+ // files `mach test` restages at dispatch) cannot make the packaging stale.
68
+ const { stale, exempt } = await partitionStaleCandidates(engineDir, packageable);
69
+ if (stale.length === 0) {
70
+ return { stale: false, changedPaths: [], truncated: 0, baseline, exempt };
66
71
  }
67
- const head = packageable.slice(0, STALE_PATHS_LIMIT);
68
- const truncated = Math.max(0, packageable.length - head.length);
69
- return { stale: true, changedPaths: head, truncated, baseline };
72
+ const head = stale.slice(0, STALE_PATHS_LIMIT);
73
+ const truncated = Math.max(0, stale.length - head.length);
74
+ return { stale: true, changedPaths: head, truncated, baseline, exempt };
70
75
  }
71
76
  /**
72
77
  * Formats a human-readable warning body from a {@link StaleBuildResult}.
@@ -76,10 +81,15 @@ export async function checkStaleBuildForTest(projectRoot, engineDir) {
76
81
  export function formatStaleBuildWarning(result) {
77
82
  const tail = result.truncated > 0 ? `, … (+${result.truncated} more)` : '';
78
83
  const list = result.changedPaths.join(', ') + tail;
84
+ const exempt = result.exempt ?? [];
85
+ const exemptLine = exempt.length > 0
86
+ ? `\nNot counted as stale, because the runtime already serves them: ${formatStaleExemptions(exempt)}.`
87
+ : '';
79
88
  return (`Engine tree has changed since the last successful fireforge build (${list}).\n` +
80
89
  'The current obj-*/dist/ bundle may not reflect those edits. If your test reads ' +
81
90
  'packaged chrome / jar.mn resources, rerun with "fireforge test --build" (or ' +
82
- '"fireforge build --ui") first. Passing --build skips this check.');
91
+ '"fireforge build --ui") first. Passing --build skips this check.' +
92
+ exemptLine);
83
93
  }
84
94
  /**
85
95
  * Sentinel returned by {@link findUncoveredRequestPaths} when a full-suite
@@ -120,37 +130,6 @@ export function findUncoveredRequestPaths(coverage, requestedPaths) {
120
130
  function normalizeCoveragePath(path) {
121
131
  return normalizePathSlashes(path.trim()).replace(/\/+$/, '');
122
132
  }
123
- /**
124
- * Formats the refusal shown when a non-`--build` run requests paths the
125
- * packaged runtime's scoped `test --build` coverage does not include. It is
126
- * enforced on every such run, with or without `--allow-stale-build`. Kept
127
- * separate from the matcher so tests can pin structure and copy
128
- * independently (same split as {@link formatStaleBuildWarning}).
129
- */
130
- /**
131
- * Basenames of the test manifests whose entries decide what a packaged test
132
- * runtime stages. A manifest that gained an entry is the single most common
133
- * reason a run needs coverage the last build never claimed, and it is a
134
- * fact FireForge already holds but never said, leaving the operator to
135
- * rediscover it after a correct refusal.
136
- */
137
- const TEST_MANIFEST_BASENAMES = new Set([
138
- 'xpcshell.toml',
139
- 'xpcshell.ini',
140
- 'browser.toml',
141
- 'browser.ini',
142
- 'mochitest.toml',
143
- 'mochitest.ini',
144
- 'chrome.toml',
145
- 'chrome.ini',
146
- 'a11y.toml',
147
- 'a11y.ini',
148
- ]);
149
- /** True for a test manifest path (by basename). */
150
- function isTestManifestPath(path) {
151
- const base = path.slice(path.lastIndexOf('/') + 1);
152
- return TEST_MANIFEST_BASENAMES.has(base);
153
- }
154
133
  /**
155
134
  * Test manifests changed since the recorded build that sit in (or above)
156
135
  * the directory of an uncovered request path, i.e. the manifests that
@@ -185,31 +164,35 @@ export async function findChangedTestManifestsForPaths(engineDir, baseline, unco
185
164
  return hits.slice(0, STALE_PATHS_LIMIT);
186
165
  }
187
166
  /**
167
+ * Formats the refusal shown when a non-`--build` run requests paths the
168
+ * packaged runtime's scoped `test --build` coverage does not include. It is
169
+ * enforced on every such run, with or without `--allow-stale-build`. Kept
170
+ * separate from the matcher so tests can pin structure and copy
171
+ * independently (same split as {@link formatStaleBuildWarning}).
172
+ *
188
173
  * @param uncovered - Request paths the recorded packaging does not cover
189
174
  * @param coverage - The recorded scoped coverage claim
190
175
  * @param changedManifests - Test manifests changed since the recorded
191
176
  * build that explain an uncovered path (from
192
177
  * {@link findChangedTestManifestsForPaths}). Omitted or empty renders
193
178
  * nothing
179
+ * @param detail - See {@link CoverageRefusalDetail}
194
180
  */
195
- export function formatTestCoverageRefusal(uncovered, coverage, changedManifests = []) {
181
+ export function formatTestCoverageRefusal(uncovered, coverage, changedManifests = [], detail = {}) {
196
182
  const cap = (paths) => {
197
183
  const head = paths.slice(0, STALE_PATHS_LIMIT);
198
184
  const truncated = paths.length - head.length;
199
185
  return head.join(', ') + (truncated > 0 ? `, … (+${truncated} more)` : '');
200
186
  };
201
187
  const rebuildTargets = uncovered.filter((p) => p !== FULL_SUITE_REQUEST);
202
- // Name --extend-coverage here or it is undiscoverable: a scoped build
203
- // replaces the claim, so under several concurrent sessions a peer's build
204
- // for an unrelated path erases coverage you legitimately still hold, and
205
- // the union that fixes it is a flag the operator has no reason to know
206
- // exists at the moment they need it. The anchor conditions are stated
207
- // because they are what decides whether it will work.
208
- const extendHint = rebuildTargets.length > 0
209
- ? ` Add --extend-coverage to that rebuild to UNION the new paths onto the recorded claim instead of replacing it — it holds while engine HEAD and engine/mozconfig are unchanged since the recorded build.`
188
+ // Per-path retention is the default, so rebuilding only the uncovered
189
+ // paths no longer costs anyone else's claim. Saying so is what keeps a
190
+ // peer from rebuilding everything "to be safe".
191
+ const keepHint = rebuildTargets.length > 0
192
+ ? ' That scoped build keeps every other recorded path whose own staging inputs (manifest directory and the fixtures its manifest names) and engine/mozconfig are unchanged.'
210
193
  : '';
211
194
  const rebuildHint = rebuildTargets.length > 0
212
- ? `Rerun "fireforge test --build ${rebuildTargets.slice(0, STALE_PATHS_LIMIT).join(' ')}" to package them, or run "fireforge build" for full coverage.${extendHint}`
195
+ ? `Rerun "fireforge test --build ${rebuildTargets.slice(0, STALE_PATHS_LIMIT).join(' ')}" to package them (or "fireforge test --build-only ..." to package without running), or run "fireforge build" for full coverage.${keepHint}`
213
196
  : 'Run "fireforge build" (or a path-less "fireforge test --build") for full coverage first.';
214
197
  // Why this run needs new coverage, when a changed manifest explains it: a
215
198
  // manifest that gained an entry is not covered by a build claim recorded
@@ -217,10 +200,17 @@ export function formatTestCoverageRefusal(uncovered, coverage, changedManifests
217
200
  const manifestLine = changedManifests.length > 0
218
201
  ? ` This run needs coverage the recorded build never claimed because a test manifest changed since it: ${changedManifests.map((m) => `engine/${m}`).join(', ')} — a manifest that gained an entry stages new support fixtures, and a claim recorded for other paths (or another harness) does not cover them.`
219
202
  : '';
220
- return (`The packaged test runtime was produced by a scoped "fireforge test --build" covering only: ${cap(coverage)}.\n` +
203
+ const affected = Object.entries(detail.changedInputs ?? {}).filter(([, files]) => files.length > 0);
204
+ const affectedLine = affected.length > 0
205
+ ? ` Staging inputs changed since the full build this record carries forward: ${affected.map(([path, files]) => `${path} (${cap(files)})`).join('; ')}.`
206
+ : '';
207
+ const scope = detail.carriesFullAnchor
208
+ ? `${cap(coverage)}, plus earlier full coverage for paths whose own staging inputs are unchanged`
209
+ : cap(coverage);
210
+ return (`The packaged test runtime was produced by a scoped "fireforge test --build" covering only: ${scope}.\n` +
221
211
  `The requested run needs ${cap(uncovered)}, which that packaging does not cover — support ` +
222
212
  'fixtures for those manifests may be missing from obj-*/_tests/, and the run can hang ' +
223
- `rather than fail.${manifestLine} ${rebuildHint} ` +
213
+ `rather than fail.${affectedLine}${manifestLine} ${rebuildHint} ` +
224
214
  '(--allow-stale-build does not bypass this check — it accepts stale content, not missing coverage.)');
225
215
  }
226
216
  /**
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Edits the stale-build preflight must not count. A test file the build
3
+ * never packages is restaged by `mach test` at dispatch, and a file whose
4
+ * every objdir copy is a symlink back to the edited source is already what
5
+ * the runtime reads. Refusing on either made every iteration on a test (or
6
+ * on symlinked chrome) pay a packaging pass or reach for
7
+ * `--allow-stale-build`, the flag whose refusal text says not to.
8
+ */
9
+ import { type InstallManifestIndex } from './objdir-install-manifests.js';
10
+ /** A changed path the preflight does not count as stale, and why. */
11
+ export interface StaleExemption {
12
+ path: string;
13
+ /**
14
+ * `symlinked`: every objdir copy is a symlink to the edited source.
15
+ * `test-only`: a test file the build does not package, restaged at dispatch.
16
+ */
17
+ why: 'symlinked' | 'test-only';
18
+ }
19
+ /** The single `obj-*` directory under the engine, or undefined when zero or several exist. */
20
+ export declare function findSingleObjDir(engineDir: string): Promise<string | undefined>;
21
+ /**
22
+ * The objdir's install-manifest index, or undefined when there is no single
23
+ * objdir or nothing in it is readable.
24
+ * @param engineDir - Absolute engine directory
25
+ */
26
+ export declare function loadEngineInstallIndex(engineDir: string): Promise<InstallManifestIndex | undefined>;
27
+ /**
28
+ * Splits changed packageable paths into the ones that make the packaged
29
+ * runtime stale and the ones it already serves. Without a readable objdir
30
+ * index every path stays stale, the behaviour before the exemption existed.
31
+ *
32
+ * @param engineDir - Absolute engine directory
33
+ * @param paths - Changed engine-relative paths, already fingerprint-filtered
34
+ */
35
+ export declare function partitionStaleCandidates(engineDir: string, paths: readonly string[]): Promise<{
36
+ stale: string[];
37
+ exempt: StaleExemption[];
38
+ }>;
39
+ /** Renders exemptions for a notice or refusal: `a (symlinked), b (test file)`. */
40
+ export declare function formatStaleExemptions(exempt: readonly StaleExemption[], limit?: number): string;
@@ -0,0 +1,70 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Edits the stale-build preflight must not count. A test file the build
4
+ * never packages is restaged by `mach test` at dispatch, and a file whose
5
+ * every objdir copy is a symlink back to the edited source is already what
6
+ * the runtime reads. Refusing on either made every iteration on a test (or
7
+ * on symlinked chrome) pay a packaging pass or reach for
8
+ * `--allow-stale-build`, the flag whose refusal text says not to.
9
+ */
10
+ import { readdir } from 'node:fs/promises';
11
+ import { isTestPath } from './build-audit-resolve.js';
12
+ import { classifyRuntimeDelivery, loadInstallManifestIndex, } from './objdir-install-manifests.js';
13
+ /** The single `obj-*` directory under the engine, or undefined when zero or several exist. */
14
+ export async function findSingleObjDir(engineDir) {
15
+ try {
16
+ const entries = await readdir(engineDir, { withFileTypes: true });
17
+ const objDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith('obj-'));
18
+ return objDirs.length === 1 ? objDirs[0]?.name : undefined;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ /**
25
+ * The objdir's install-manifest index, or undefined when there is no single
26
+ * objdir or nothing in it is readable.
27
+ * @param engineDir - Absolute engine directory
28
+ */
29
+ export async function loadEngineInstallIndex(engineDir) {
30
+ const objDir = await findSingleObjDir(engineDir);
31
+ return objDir === undefined ? undefined : loadInstallManifestIndex(engineDir, objDir);
32
+ }
33
+ /**
34
+ * Splits changed packageable paths into the ones that make the packaged
35
+ * runtime stale and the ones it already serves. Without a readable objdir
36
+ * index every path stays stale, the behaviour before the exemption existed.
37
+ *
38
+ * @param engineDir - Absolute engine directory
39
+ * @param paths - Changed engine-relative paths, already fingerprint-filtered
40
+ */
41
+ export async function partitionStaleCandidates(engineDir, paths) {
42
+ if (paths.length === 0)
43
+ return { stale: [], exempt: [] };
44
+ const index = await loadEngineInstallIndex(engineDir);
45
+ if (index === undefined)
46
+ return { stale: [...paths], exempt: [] };
47
+ const stale = [];
48
+ const exempt = [];
49
+ for (const path of paths) {
50
+ const delivery = await classifyRuntimeDelivery(index, engineDir, path);
51
+ if (delivery === 'live')
52
+ exempt.push({ path, why: 'symlinked' });
53
+ else if (delivery === 'at-dispatch' || (delivery === 'unlisted' && isTestPath(path))) {
54
+ exempt.push({ path, why: 'test-only' });
55
+ }
56
+ else
57
+ stale.push(path);
58
+ }
59
+ return { stale, exempt };
60
+ }
61
+ /** Renders exemptions for a notice or refusal: `a (symlinked), b (test file)`. */
62
+ export function formatStaleExemptions(exempt, limit = 10) {
63
+ const head = exempt
64
+ .slice(0, limit)
65
+ .map(({ path, why }) => why === 'symlinked'
66
+ ? `${path} (objdir symlink to the source)`
67
+ : `${path} (test file, restaged at dispatch)`);
68
+ const extra = exempt.length - head.length;
69
+ return head.join(', ') + (extra > 0 ? `, … (+${extra} more)` : '');
70
+ }
@@ -22,6 +22,8 @@ export interface XpcshellRetryOptions {
22
22
  kind?: MachTestSuiteKind | undefined;
23
23
  /** Whether the run was started with full harness output. */
24
24
  fullOutput?: boolean | undefined;
25
+ /** Per-chunk feed for the retried run's output (start-marker tracking). */
26
+ onOutputChunk?: ((stream: 'stdout' | 'stderr', chunk: string) => void) | undefined;
25
27
  }
26
28
  /**
27
29
  * Removes a stale xpcshell install symlink and retries the focused mach test
@@ -9,7 +9,7 @@ import { tryRepairStaleXpcshellTestSymlink } from './test-stale-symlink.js';
9
9
  * generic command.
10
10
  */
11
11
  export async function retryAfterXpcshellSymlinkRepair(options) {
12
- const { engineDir, objDir, result, classification, normalizedPaths, extraArgs, env, kind = 'test', fullOutput, } = options;
12
+ const { engineDir, objDir, result, classification, normalizedPaths, extraArgs, env, kind = 'test', fullOutput, onOutputChunk, } = options;
13
13
  if (result.exitCode !== 0 &&
14
14
  classification.xpcshell.length > 0 &&
15
15
  classification.nonXpcshell.length === 0) {
@@ -23,6 +23,7 @@ export async function retryAfterXpcshellSymlinkRepair(options) {
23
23
  args: extraArgs,
24
24
  env,
25
25
  fullOutput,
26
+ ...(onOutputChunk === undefined ? {} : { teardown: { onOutputChunk } }),
26
27
  });
27
28
  }
28
29
  }