@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,164 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Where the patch history log lives, and who wrote each record.
4
+ *
5
+ * The log used to be `patches/.fireforge-history.jsonl`, inside the tracked
6
+ * patch queue, so it was committed with every export until consumers
7
+ * gitignored it, and it grew to tens of megabytes. It now defaults to
8
+ * `.fireforge/history.jsonl` (FireForge's own untracked data directory), or
9
+ * `fireforge.json#history.path` when a project sets one. A log left at the
10
+ * old path is moved on the first append.
11
+ *
12
+ * Several sessions sharing one checkout all append to the same log, so each
13
+ * record carries the invocation that wrote it, plus a session hint, letting
14
+ * a session select its own lines mechanically.
15
+ */
16
+ import { randomUUID } from 'node:crypto';
17
+ import { appendFile, link, readFile, rename, rm, stat } from 'node:fs/promises';
18
+ import { dirname, join, resolve } from 'node:path';
19
+ import { getNodeErrorCode, toError } from '../utils/errors.js';
20
+ import { ensureDir, pathExists } from '../utils/fs.js';
21
+ import { info, verbose } from '../utils/logger.js';
22
+ import { FIREFORGE_DIR } from './config-paths.js';
23
+ import { isValidHistoryPath } from './config-validate-history.js';
24
+ /** Filename of the history log at its pre-0.50 location, inside `patches/`. */
25
+ export const LEGACY_HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
26
+ /** Default history log location, relative to the project root. */
27
+ export const DEFAULT_HISTORY_LOG_PATH = join(FIREFORGE_DIR, 'history.jsonl');
28
+ /** Environment variable a caller sets to tag every record of its session. */
29
+ export const HISTORY_SESSION_ENV = 'FIREFORGE_SESSION_ID';
30
+ let invocationId;
31
+ /**
32
+ * Identity fields stamped on every history record: one id per FireForge
33
+ * process (every record one command writes shares it), the process and its
34
+ * parent, and the caller's `FIREFORGE_SESSION_ID` when set. The parent PID
35
+ * is a weak hint (usually the shell); the environment variable is the
36
+ * reliable way for a session to tag its own lines.
37
+ */
38
+ export function historyRecordIdentity() {
39
+ invocationId ??= randomUUID();
40
+ const session = process.env[HISTORY_SESSION_ENV]?.trim();
41
+ return {
42
+ invocationId,
43
+ ...(session !== undefined && session !== '' ? { sessionId: session } : {}),
44
+ pid: process.pid,
45
+ ppid: process.ppid,
46
+ };
47
+ }
48
+ /**
49
+ * Reads `history.path` from the project's raw `fireforge.json`.
50
+ *
51
+ * Fail-open: the writer runs after a mutation has already committed, so a
52
+ * missing or unreadable config means "use the default", never an error. An
53
+ * invalid value is refused by config validation when the command loads its
54
+ * config; here it also falls back to the default rather than writing
55
+ * somewhere validation would not allow.
56
+ */
57
+ async function readConfiguredHistoryPath(projectRoot) {
58
+ let data;
59
+ try {
60
+ data = JSON.parse(await readFile(join(projectRoot, 'fireforge.json'), 'utf-8'));
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ if (typeof data !== 'object' || data === null)
66
+ return undefined;
67
+ const history = data['history'];
68
+ if (typeof history !== 'object' || history === null)
69
+ return undefined;
70
+ const path = history['path'];
71
+ if (path === undefined)
72
+ return undefined;
73
+ if (!isValidHistoryPath(path)) {
74
+ verbose(`Ignoring invalid history.path in fireforge.json; using ${DEFAULT_HISTORY_LOG_PATH}`);
75
+ return undefined;
76
+ }
77
+ return path;
78
+ }
79
+ /**
80
+ * Resolves the history log path for a project.
81
+ *
82
+ * @param projectRoot - Project root containing `fireforge.json`
83
+ * @returns Absolute path of the history log
84
+ */
85
+ export async function resolveHistoryLogPath(projectRoot) {
86
+ const configured = await readConfiguredHistoryPath(projectRoot);
87
+ return resolve(projectRoot, configured ?? DEFAULT_HISTORY_LOG_PATH);
88
+ }
89
+ /**
90
+ * Moves a history log left at the legacy `patches/` path to `target`.
91
+ *
92
+ * Records are never dropped, whatever else is happening:
93
+ * - The legacy file is first claimed by renaming it aside, so of two
94
+ * commands migrating at once exactly one does the work and neither
95
+ * duplicates it.
96
+ * - When `target` does not exist yet (the first run), the claimed file is
97
+ * hard-linked into place, which cannot overwrite a log another command
98
+ * created in the meantime, and keeps a large log a constant-time move.
99
+ * - When both exist (an older FireForge appended to the legacy path after
100
+ * a migration, or `history.path` changed), the legacy records are
101
+ * appended to the target in one write. They then follow newer records;
102
+ * every record carries its own `ts`.
103
+ *
104
+ * A crash between the claim and the move leaves the records in a
105
+ * `.migrating-*` file next to the legacy path, never deleted.
106
+ *
107
+ * @param patchesDir - The patches directory that may hold a legacy log
108
+ * @param target - Absolute path of the history log
109
+ */
110
+ export async function migrateLegacyHistoryLog(patchesDir, target) {
111
+ const legacy = join(patchesDir, LEGACY_HISTORY_LOG_FILENAME);
112
+ if (resolve(legacy) === resolve(target))
113
+ return;
114
+ try {
115
+ await stat(legacy);
116
+ }
117
+ catch {
118
+ return;
119
+ }
120
+ const claimed = `${legacy}.migrating-${process.pid}-${randomUUID()}`;
121
+ try {
122
+ await rename(legacy, claimed);
123
+ }
124
+ catch (error) {
125
+ if (getNodeErrorCode(error) === 'ENOENT')
126
+ return;
127
+ throw error;
128
+ }
129
+ await ensureDir(dirname(target));
130
+ try {
131
+ await link(claimed, target);
132
+ }
133
+ catch (error) {
134
+ // Windows can report two racing renames of one file as both
135
+ // succeeding, the file ending under the other claimant's name. That
136
+ // claimant holds the records and moves them, so this one stands down.
137
+ if (getNodeErrorCode(error) === 'ENOENT' && !(await pathExists(claimed))) {
138
+ verbose('History log migration lost the claim to a concurrent command');
139
+ return;
140
+ }
141
+ // EEXIST: both logs exist. Anything else (a filesystem without hard
142
+ // links, a target on another device): the append below creates the
143
+ // target when it is missing, so it covers that case too.
144
+ verbose(`History log migration falls back to appending (${toError(error).message})`);
145
+ await appendFile(target, await readFile(claimed));
146
+ }
147
+ await rm(claimed, { force: true });
148
+ info(`Moved the patch history log from ${legacy} to ${target}. ` +
149
+ 'Nothing reads it back; it is an audit trail only.');
150
+ }
151
+ /**
152
+ * Appends one serialized record to the project's history log, migrating a
153
+ * legacy log first.
154
+ *
155
+ * @param patchesDir - The project's `patches/` directory; the project root is
156
+ * its parent, as every caller derives it from `getProjectPaths`
157
+ * @param line - One JSON record, without the trailing newline
158
+ */
159
+ export async function appendHistoryLine(patchesDir, line) {
160
+ const target = await resolveHistoryLogPath(dirname(resolve(patchesDir)));
161
+ await migrateLegacyHistoryLog(patchesDir, target);
162
+ await ensureDir(dirname(target));
163
+ await appendFile(target, `${line}\n`, 'utf-8');
164
+ }
@@ -10,7 +10,8 @@ import type { HistoryEntry } from './destructive.js';
10
10
  * reject would report a completed, irreversible mutation as a failure and send
11
11
  * the operator looking for damage that is not there. Because that reasoning is
12
12
  * easy to lose in a copy, it now lives in one place.
13
- * @param patchesDir - The `patches/` directory holding the history log.
13
+ * @param patchesDir - The project's `patches/` directory (the log itself lives
14
+ * under `.fireforge/` or `history.path`; see `history-log-file.ts`).
14
15
  * @param entry - The entry to append.
15
16
  * @param context - What just committed, rendered into
16
17
  * `History log append failed after <context>: <reason>`, e.g.
@@ -13,7 +13,8 @@ import { appendHistory } from './destructive.js';
13
13
  * reject would report a completed, irreversible mutation as a failure and send
14
14
  * the operator looking for damage that is not there. Because that reasoning is
15
15
  * easy to lose in a copy, it now lives in one place.
16
- * @param patchesDir - The `patches/` directory holding the history log.
16
+ * @param patchesDir - The project's `patches/` directory (the log itself lives
17
+ * under `.fireforge/` or `history.path`; see `history-log-file.ts`).
17
18
  * @param entry - The entry to append.
18
19
  * @param context - What just committed, rendered into
19
20
  * `History log append failed after <context>: <reason>`, e.g.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Format-preserving rewrites of a JSON document.
3
+ *
4
+ * `fireforge source set`, `fireforge config` and `furnace init` change a
5
+ * few keys of `fireforge.json`. Re-serializing the whole document with
6
+ * `JSON.stringify` expands every compact object and array the project keeps
7
+ * on one line, so a one-key change becomes a diff across unrelated policy
8
+ * sections. This module diffs the document on disk against the wanted one
9
+ * and edits only the values that differ, in place, keeping every other byte
10
+ * (indentation, compact containers, key order, trailing newline).
11
+ *
12
+ * Inserted values follow the style of the container they land in: one line
13
+ * inside a one-line container, the container's own indentation otherwise.
14
+ */
15
+ import type { JsonObject } from '../types/json.js';
16
+ /**
17
+ * Rewrites `text` so it parses to `next`, editing only the values that
18
+ * differ. Returns `undefined` when `text` is not a JSON object or the edit
19
+ * cannot be proven equivalent, so the caller can fall back to a full
20
+ * re-serialization.
21
+ */
22
+ export declare function rewriteJsonPreservingFormat(text: string, next: JsonObject): string | undefined;
@@ -0,0 +1,199 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Format-preserving rewrites of a JSON document.
4
+ *
5
+ * `fireforge source set`, `fireforge config` and `furnace init` change a
6
+ * few keys of `fireforge.json`. Re-serializing the whole document with
7
+ * `JSON.stringify` expands every compact object and array the project keeps
8
+ * on one line, so a one-key change becomes a diff across unrelated policy
9
+ * sections. This module diffs the document on disk against the wanted one
10
+ * and edits only the values that differ, in place, keeping every other byte
11
+ * (indentation, compact containers, key order, trailing newline).
12
+ *
13
+ * Inserted values follow the style of the container they land in: one line
14
+ * inside a one-line container, the container's own indentation otherwise.
15
+ */
16
+ import { applyEdits, findNodeAtLocation, modify, parseTree } from 'jsonc-parser';
17
+ import { isJsonObject } from '../utils/validation.js';
18
+ /** The same reviver `readJson` uses, so both sides of the diff agree. */
19
+ function parseJsonDocument(text) {
20
+ return JSON.parse(text, (key, value) => key === '__proto__' || key === 'constructor' || key === 'prototype' ? undefined : value);
21
+ }
22
+ function jsonEqual(a, b) {
23
+ if (a === b)
24
+ return true;
25
+ if (Array.isArray(a) || Array.isArray(b)) {
26
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
27
+ return false;
28
+ return a.every((value, index) => jsonEqual(value, b[index]));
29
+ }
30
+ if (!isJsonObject(a) || !isJsonObject(b))
31
+ return false;
32
+ const keysA = definedKeys(a);
33
+ const keysB = definedKeys(b);
34
+ return keysA.length === keysB.length && keysA.every((key) => jsonEqual(a[key], b[key]));
35
+ }
36
+ /** Keys whose value survives `JSON.stringify` (an `undefined` value is an absent key). */
37
+ function definedKeys(object) {
38
+ return Object.keys(object).filter((key) => object[key] !== undefined);
39
+ }
40
+ function collectChanges(path, before, after, changes) {
41
+ if (jsonEqual(before, after))
42
+ return;
43
+ if (isJsonObject(before) && isJsonObject(after)) {
44
+ for (const key of definedKeys(before)) {
45
+ if (after[key] === undefined)
46
+ changes.push({ kind: 'remove', path: [...path, key] });
47
+ }
48
+ for (const key of definedKeys(after)) {
49
+ const next = after[key];
50
+ const previous = before[key];
51
+ if (previous === undefined) {
52
+ changes.push({ kind: 'set', path: [...path, key], value: next });
53
+ }
54
+ else {
55
+ collectChanges([...path, key], previous, next, changes);
56
+ }
57
+ }
58
+ return;
59
+ }
60
+ if (Array.isArray(before) && Array.isArray(after)) {
61
+ const isAppend = after.length > before.length &&
62
+ before.every((value, index) => jsonEqual(value, after[index]));
63
+ if (isAppend) {
64
+ for (const value of after.slice(before.length))
65
+ changes.push({ kind: 'append', path, value });
66
+ return;
67
+ }
68
+ if (after.length === before.length) {
69
+ before.forEach((value, index) => {
70
+ collectChanges([...path, index], value, after[index], changes);
71
+ });
72
+ return;
73
+ }
74
+ }
75
+ changes.push({ kind: 'set', path, value: after });
76
+ }
77
+ function detectStyle(text) {
78
+ const indent = /^([ \t]+)\S/m.exec(text)?.[1];
79
+ return {
80
+ eol: text.includes('\r\n') ? '\r\n' : '\n',
81
+ indentUnit: indent === undefined ? ' ' : indent.startsWith('\t') ? '\t' : indent,
82
+ };
83
+ }
84
+ function isMultiline(text, node) {
85
+ return node !== undefined && text.slice(node.offset, node.offset + node.length).includes('\n');
86
+ }
87
+ /** The object or array holding `node` (skipping the property node of an object member). */
88
+ function containerOf(node) {
89
+ const parent = node.parent;
90
+ return parent?.type === 'property' ? parent.parent : parent;
91
+ }
92
+ /** Leading whitespace of the line `offset` sits on. */
93
+ function lineIndent(text, offset) {
94
+ const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
95
+ return /^[ \t]*/.exec(text.slice(lineStart))?.[0] ?? '';
96
+ }
97
+ function renderCompact(value) {
98
+ if (Array.isArray(value)) {
99
+ return `[${value.map(renderCompact).join(', ')}]`;
100
+ }
101
+ if (isJsonObject(value)) {
102
+ const entries = definedKeys(value).map((key) => `${JSON.stringify(key)}: ${renderCompact(value[key])}`);
103
+ return entries.length === 0 ? '{}' : `{ ${entries.join(', ')} }`;
104
+ }
105
+ return JSON.stringify(value);
106
+ }
107
+ function renderValue(value, multiline, indent, style) {
108
+ if (!multiline)
109
+ return renderCompact(value);
110
+ return JSON.stringify(value, null, style.indentUnit)
111
+ .split('\n')
112
+ .join(style.eol + indent);
113
+ }
114
+ function replaceRange(text, offset, length, content) {
115
+ return text.slice(0, offset) + content + text.slice(offset + length);
116
+ }
117
+ /** Replaces an existing value, keeping a container's one-line or multi-line shape. */
118
+ function replaceNode(text, node, value, style) {
119
+ const isContainer = node.type === 'object' || node.type === 'array';
120
+ const multiline = isContainer ? isMultiline(text, node) : isMultiline(text, containerOf(node));
121
+ const rendered = renderValue(value, multiline, lineIndent(text, node.offset), style);
122
+ return replaceRange(text, node.offset, node.length, rendered);
123
+ }
124
+ /** Appends `entry` (a property or an array element) after the container's last child. */
125
+ function appendEntry(text, container, renderEntry, style) {
126
+ const children = container.children ?? [];
127
+ const last = children[children.length - 1];
128
+ const containerIndent = lineIndent(text, container.offset);
129
+ if (last === undefined) {
130
+ // Empty container: open it up the way its parent is laid out.
131
+ const multiline = isMultiline(text, containerOf(container));
132
+ const [open, close] = container.type === 'array' ? ['[', ']'] : ['{', '}'];
133
+ const childIndent = containerIndent + style.indentUnit;
134
+ const body = multiline
135
+ ? `${open}${style.eol}${childIndent}${renderEntry(true, childIndent)}${style.eol}${containerIndent}${close}`
136
+ : container.type === 'array'
137
+ ? `${open}${renderEntry(false, childIndent)}${close}`
138
+ : `${open} ${renderEntry(false, childIndent)} ${close}`;
139
+ return replaceRange(text, container.offset, container.length, body);
140
+ }
141
+ const multiline = isMultiline(text, container);
142
+ const indent = multiline ? lineIndent(text, last.offset) : containerIndent;
143
+ const separator = multiline ? `,${style.eol}${indent}` : ', ';
144
+ return replaceRange(text, last.offset + last.length, 0, separator + renderEntry(multiline, indent));
145
+ }
146
+ function applyChange(text, change, style) {
147
+ const root = parseTree(text);
148
+ if (root === undefined) {
149
+ throw new Error('document is not valid JSON');
150
+ }
151
+ if (change.kind === 'remove') {
152
+ return applyEdits(text, modify(text, change.path, undefined, {}));
153
+ }
154
+ const target = findNodeAtLocation(root, change.path);
155
+ if (change.kind === 'append') {
156
+ if (target?.type !== 'array')
157
+ throw new Error('append target is not an array');
158
+ return appendEntry(text, target, (multiline, indent) => renderValue(change.value, multiline, indent, style), style);
159
+ }
160
+ if (target !== undefined) {
161
+ return replaceNode(text, target, change.value, style);
162
+ }
163
+ const parent = findNodeAtLocation(root, change.path.slice(0, -1));
164
+ const key = change.path[change.path.length - 1];
165
+ if (parent?.type !== 'object' || typeof key !== 'string') {
166
+ throw new Error('insertion parent is not an object');
167
+ }
168
+ return appendEntry(text, parent, (multiline, indent) => `${JSON.stringify(key)}: ${renderValue(change.value, multiline, indent, style)}`, style);
169
+ }
170
+ /**
171
+ * Rewrites `text` so it parses to `next`, editing only the values that
172
+ * differ. Returns `undefined` when `text` is not a JSON object or the edit
173
+ * cannot be proven equivalent, so the caller can fall back to a full
174
+ * re-serialization.
175
+ */
176
+ export function rewriteJsonPreservingFormat(text, next) {
177
+ let current;
178
+ try {
179
+ current = parseJsonDocument(text);
180
+ }
181
+ catch {
182
+ return undefined;
183
+ }
184
+ if (!isJsonObject(current))
185
+ return undefined;
186
+ const changes = [];
187
+ collectChanges([], current, next, changes);
188
+ const style = detectStyle(text);
189
+ try {
190
+ let edited = text;
191
+ for (const change of changes)
192
+ edited = applyChange(edited, change, style);
193
+ // The edits are structural; prove the result before trusting it.
194
+ return jsonEqual(parseJsonDocument(edited), next) ? edited : undefined;
195
+ }
196
+ catch {
197
+ return undefined;
198
+ }
199
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Prefixes every line of a chunked stream, for output from several
3
+ * concurrent harness instances (`fireforge test --jobs`) that share one
4
+ * terminal and one run log. Without a prefix, interleaved lines from two
5
+ * browsers cannot be attributed after the fact.
6
+ */
7
+ /** A stateful line prefixer for one stream. */
8
+ export interface LinePrefixer {
9
+ /** Returns `chunk` with the prefix inserted at the start of each line. */
10
+ transform(chunk: string): string;
11
+ }
12
+ /**
13
+ * Creates a prefixer. Line state carries across chunks, so a line split
14
+ * between two chunks gets the prefix exactly once.
15
+ *
16
+ * @param prefix - Text to insert, e.g. `[job 2] `
17
+ */
18
+ export declare function createLinePrefixer(prefix: string): LinePrefixer;
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Prefixes every line of a chunked stream, for output from several
4
+ * concurrent harness instances (`fireforge test --jobs`) that share one
5
+ * terminal and one run log. Without a prefix, interleaved lines from two
6
+ * browsers cannot be attributed after the fact.
7
+ */
8
+ /**
9
+ * Creates a prefixer. Line state carries across chunks, so a line split
10
+ * between two chunks gets the prefix exactly once.
11
+ *
12
+ * @param prefix - Text to insert, e.g. `[job 2] `
13
+ */
14
+ export function createLinePrefixer(prefix) {
15
+ let atLineStart = true;
16
+ return {
17
+ transform(chunk) {
18
+ if (chunk.length === 0)
19
+ return chunk;
20
+ let out = '';
21
+ let start = 0;
22
+ for (let index = chunk.indexOf('\n'); index !== -1; index = chunk.indexOf('\n', start)) {
23
+ out += `${atLineStart ? prefix : ''}${chunk.slice(start, index + 1)}`;
24
+ atLineStart = true;
25
+ start = index + 1;
26
+ }
27
+ if (start < chunk.length) {
28
+ out += `${atLineStart ? prefix : ''}${chunk.slice(start)}`;
29
+ atLineStart = false;
30
+ }
31
+ return out;
32
+ },
33
+ };
34
+ }
@@ -47,6 +47,11 @@ export interface MachOptions {
47
47
  onProcessGroup?: (pgid: number) => void;
48
48
  /** Exec-layer hook, see {@link ProcessGroupOptions.postCloseSweep}. Capture-only. */
49
49
  postCloseSweep?: () => Promise<void>;
50
+ /**
51
+ * Prefix for every echoed and logged line (concurrent `test --jobs`
52
+ * instances). The capture and `onOutputChunk` stay raw. Capture-only.
53
+ */
54
+ outputPrefix?: string | undefined;
50
55
  }
51
56
  /**
52
57
  * Result of running a mach command while capturing streamed output.
@@ -263,6 +268,8 @@ export interface MachTestSuiteOptions {
263
268
  * the harness launched. See {@link MachOptions}.
264
269
  */
265
270
  teardown?: Pick<MachOptions, 'onOutputChunk' | 'onProcessGroup' | 'postCloseSweep'> | undefined;
271
+ /** Line prefix for echo and run log, see {@link MachOptions.outputPrefix}. */
272
+ outputPrefix?: string | undefined;
266
273
  }
267
274
  /**
268
275
  * Runs one mach test command (`mach test` / `mach xpcshell-test` / `mach
@@ -1,11 +1,13 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { basename, join } from 'node:path';
2
+ import { basename, dirname, join } from 'node:path';
3
3
  import { MachNotFoundError } from '../errors/build.js';
4
4
  import { pathExists } from '../utils/fs.js';
5
5
  import { info, warn } from '../utils/logger.js';
6
6
  import { exec, execInherit, execInheritCapture, execSmokeRun, execStream, } from '../utils/process.js';
7
+ import { withBuildRunRecord } from './build-run-record.js';
7
8
  import { createSiblingLockPath, withFileLock } from './file-lock.js';
8
9
  import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
10
+ import { createLinePrefixer } from './line-prefixer.js';
9
11
  import { explainMachError } from './mach-error-hints.js';
10
12
  import { createKnownTeardownNoiseFilter, createTeardownNoiseContext, hasKnownTeardownNoise, KNOWN_TEARDOWN_NOISE_BUILD_NOTE, } from './mach-known-noise-filter.js';
11
13
  import { getPython } from './mach-python.js';
@@ -88,6 +90,15 @@ export async function runMachCapture(args, engineDir, options = {}) {
88
90
  const noiseContext = options.annotateKnownTeardownNoise === true ? createTeardownNoiseContext() : undefined;
89
91
  const stdoutFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
90
92
  const stderrFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
93
+ const prefix = options.outputPrefix;
94
+ const prefixers = prefix === undefined
95
+ ? undefined
96
+ : {
97
+ stdoutLog: createLinePrefixer(prefix),
98
+ stdoutEcho: createLinePrefixer(prefix),
99
+ stderrLog: createLinePrefixer(prefix),
100
+ stderrEcho: createLinePrefixer(prefix),
101
+ };
91
102
  const exitCode = await execStream(python, [machPath, ...args], {
92
103
  cwd: engineDir,
93
104
  ...(options.env ? { env: options.env } : {}),
@@ -109,8 +120,9 @@ export async function runMachCapture(args, engineDir, options = {}) {
109
120
  // echo filter only exists to shorten what a human scrolls past.
110
121
  // Collapsing a traceback in the artifact would reintroduce the loss
111
122
  // this log exists to prevent.
112
- writeToActiveRunLog(data);
113
- process.stdout.write(stdoutFilter ? stdoutFilter.transform(data) : data);
123
+ writeToActiveRunLog(prefixers ? prefixers.stdoutLog.transform(data) : data);
124
+ const echo = stdoutFilter ? stdoutFilter.transform(data) : data;
125
+ process.stdout.write(prefixers ? prefixers.stdoutEcho.transform(echo) : echo);
114
126
  },
115
127
  onStderr: (data) => {
116
128
  options.onOutputChunk?.('stderr', data);
@@ -118,19 +130,22 @@ export async function runMachCapture(args, engineDir, options = {}) {
118
130
  if (stderr.length > CAPTURE_TAIL_LIMIT) {
119
131
  stderr = stderr.slice(-CAPTURE_TAIL_LIMIT);
120
132
  }
121
- writeToActiveRunLog(data);
122
- process.stderr.write(stderrFilter ? stderrFilter.transform(data) : data);
133
+ writeToActiveRunLog(prefixers ? prefixers.stderrLog.transform(data) : data);
134
+ const echo = stderrFilter ? stderrFilter.transform(data) : data;
135
+ process.stderr.write(prefixers ? prefixers.stderrEcho.transform(echo) : echo);
123
136
  },
124
137
  });
125
138
  if (stdoutFilter) {
126
139
  const residue = stdoutFilter.flush();
127
- if (residue.length > 0)
128
- process.stdout.write(residue);
140
+ if (residue.length > 0) {
141
+ process.stdout.write(prefixers ? prefixers.stdoutEcho.transform(residue) : residue);
142
+ }
129
143
  }
130
144
  if (stderrFilter) {
131
145
  const residue = stderrFilter.flush();
132
- if (residue.length > 0)
133
- process.stderr.write(residue);
146
+ if (residue.length > 0) {
147
+ process.stderr.write(prefixers ? prefixers.stderrEcho.transform(residue) : residue);
148
+ }
134
149
  }
135
150
  return { stdout, stderr, exitCode };
136
151
  }
@@ -227,6 +242,12 @@ export async function runProtectedMachBuild(kind, engineDir, options = {}) {
227
242
  if (kind === 'full' && options.jobs !== undefined) {
228
243
  args.push('-j', String(options.jobs));
229
244
  }
245
+ // The run record lets any session see why a bundle is missing after this
246
+ // build dies part-way (see build-run-record.ts). The engine directory is
247
+ // always `<projectRoot>/engine`.
248
+ return withBuildRunRecord(dirname(engineDir), `mach ${args.join(' ')}`, () => runProtectedMachBuildAttempts(args, kind, engineDir, options));
249
+ }
250
+ async function runProtectedMachBuildAttempts(args, kind, engineDir, options) {
230
251
  const maxAttempts = Math.max(1, (options.retries ?? DEFAULT_BUILD_CRASH_RETRIES) + 1);
231
252
  for (let attempt = 1;; attempt += 1) {
232
253
  const { env } = await installMachResourceGuard(engineDir);
@@ -411,13 +432,14 @@ function testVerbosityEnvUnset(fullOutput) {
411
432
  * @param options - Engine checkout, test paths, extra args, env, verbosity
412
433
  */
413
434
  export async function runMachTestSuite(kind, options) {
414
- const { engineDir, testPaths = [], args = [], env, fullOutput, teardown } = options;
435
+ const { engineDir, testPaths = [], args = [], env, fullOutput, teardown, outputPrefix } = options;
415
436
  const guard = await installMachResourceGuard(engineDir);
416
437
  const envUnset = testVerbosityEnvUnset(fullOutput);
417
438
  return runMachCapture([kind, ...testPaths, ...args], engineDir, {
418
439
  env: { ...guard.env, ...env },
419
440
  ...(envUnset ? { envUnset } : {}),
420
441
  ...teardown,
442
+ ...(outputPrefix === undefined ? {} : { outputPrefix }),
421
443
  annotateKnownTeardownNoise: true,
422
444
  });
423
445
  }
@@ -18,6 +18,12 @@
18
18
  * no owned file is an upstream Firefox module, which FireForge does not
19
19
  * police and cannot cheaply enumerate. Policing only what the fork owns is
20
20
  * both the actionable set and the one that produces real incidents.
21
+ *
22
+ * An unscoped `verify` widens that set with the engine worktree: changed
23
+ * files import, and untracked files are targets. A chrome script's lazy
24
+ * getter for a new, not yet exported `.sys.mjs` with no moz.build line
25
+ * SIGSEGVed every start of a shared objdir, and neither file was in the
26
+ * queue yet, so the queue-only scan could not see it.
21
27
  */
22
28
  /** Why a queue-owned module import does not resolve to a packaged module. */
23
29
  export type UnresolvedModuleReason = 'missing-file' | 'unregistered';
@@ -31,16 +37,26 @@ export interface UnresolvedModuleImport {
31
37
  importers: string[];
32
38
  reason: UnresolvedModuleReason;
33
39
  }
40
+ /** Engine worktree files the preflight also judges, beside the queue's. */
41
+ export interface WorktreeModuleFiles {
42
+ /** Changed or untracked files: scanned for imports. */
43
+ changed: readonly string[];
44
+ /** Untracked files: new modules the fork has not exported yet. */
45
+ untracked: readonly string[];
46
+ }
47
+ /** Lists the engine's changed and untracked files for the worktree scan. */
48
+ export declare function collectWorktreeModuleFiles(engineDir: string): Promise<WorktreeModuleFiles>;
34
49
  /**
35
50
  * Resolves every `resource://` system-module import made by the
36
- * queue-owned files and reports the ones a packaged browser could not
37
- * load.
51
+ * queue-owned files (and, given `worktree`, by changed engine files) and
52
+ * reports the ones a packaged browser could not load.
38
53
  *
39
54
  * @param engineDir - Path to the engine checkout
40
55
  * @param ownedFiles - Engine-relative paths the patch queue owns
56
+ * @param worktree - Changed and untracked engine files to judge as well
41
57
  * @returns Findings sorted by module path. Empty when everything resolves
42
58
  */
43
- export declare function findUnresolvedSystemModuleImports(engineDir: string, ownedFiles: readonly string[]): Promise<UnresolvedModuleImport[]>;
59
+ export declare function findUnresolvedSystemModuleImports(engineDir: string, ownedFiles: readonly string[], worktree?: WorktreeModuleFiles): Promise<UnresolvedModuleImport[]>;
44
60
  /**
45
61
  * Renders findings as operator-facing lines, naming the unregistered or
46
62
  * unresolvable module, so the failure is a sentence instead of a bare