@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -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 +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -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/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.50.0
4
+
5
+ ### Size budget
6
+
7
+ - New `patchLint.fileSizeMetric` (`raw` or `code`), `patchSizeMetric` (`diff` or `addedCode`), per-tier `patchSizeThresholds` and `patchSizeRegistrationFiles`. Added code skips comments, blank lines and Furnace fragment copies. Defaults are unchanged. Size messages name their unit, and a notice no longer claims to fail `--max-warnings 0`.
8
+ - `lintIgnore` entries can carry a size ceiling (`{ rule, max, reason }`). Size helpers are exported, and `lint --report` rows carry the metric.
9
+
10
+ ### Gates and policy
11
+
12
+ - Added `fireforge check`, which runs status, verify, per-patch lint, typecheck, Furnace validation, token coverage and `check.extraSteps` with one verdict. `status` gains `--fail-on ownership-conflict`, `--allow`, `--fail-on-unknown` and `--patches`; `verify` gains `--patches`.
13
+ - Opt-in `patchPolicy` rules (`upstreamAllowlist`, add-only marker mode, `scopeRules`, `verifyDocumentPaths`), `patchLint.tokenScales` and `fireforge token lint`. `furnace validate` gains `--json`, `--max-warnings` and `validationExceptions`.
14
+
15
+ ### Test harness
16
+
17
+ - Packaging coverage is kept per staged path, so a peer's scoped build no longer replaces it. The stale-build check ignores edits the packaged runtime already serves.
18
+ - Mixed xpcshell and browser paths, `--repeat <n>` and `--negative-control <diff>` run under one lock hold. A truncated exit-0 run is `FAIL`. The binary, checkJs and browser/port waits run before the engine lock.
19
+ - Added `--keep-log`, `--auto-ports`, `--no-furnace-apply` and `--restart-between-tests`. A path after an optional-value flag such as `--wait-lock` is read as a test path.
20
+
21
+ ### Locks and history
22
+
23
+ - File locks are granted in arrival order, and crashed or stopped waiters are skipped.
24
+ - The patch history log moves to `.fireforge/history.jsonl` (configurable as `history.path`) and is migrated on first write. Records carry an invocation id, and staged-dependency records log only the delta.
25
+
26
+ ### Patch queue
27
+
28
+ - `--refuse-foreign-drift` counts removed lines and combines with `--scan`. `--scan-file` accepts `<patch>:<path>`. `--refuse-adjacent-unmanaged` judges only files a re-export would capture, and gains `--expect-new` and `--adjacent-ignore`. Arguments are checked before the lock, and `--expect-from` is new.
29
+ - Added `patch retire`, which keeps the retired patch's order as a slot. `staged-dependency --add --from` takes a lint report. `move-files --create` and `split` keep staged declarations in step. Refusals name the earliest legal order and the exact commands.
30
+
31
+ ### Furnace
32
+
33
+ - `--wait-lock` works on every leaf. `deploy` takes several names, and `rename` rewrites registrations in place and gains `--dry-run`. `apply` no longer duplicates jar lines and says why it skipped. The unexported-drift belt and `furnace diff` compare against the fragment-expanded source.
34
+
35
+ ### Other
36
+
37
+ - Config writes keep `fireforge.json`'s formatting. `rebase` labels the move from the committed pin. `doctor` tolerates the `esr` suffix and names `./mach npm ci`. `Cu` is typed in the shim. `run --profile` purges caches, and `--smoke-exit` fails a silent early exit.
38
+ - Dev tooling: `eslint` 10.12.0 and `eslint-plugin-jsdoc` 65.0.2.
39
+
3
40
  ## 0.49.0
4
41
 
5
42
  ### Furnace and builds
@@ -7,6 +44,7 @@
7
44
  - Safer Furnace deployment and objdir cloning protect component sources and peer checkouts from shared files and directory aliases. Failed copies finish before rollback; cloned build metadata is relocated and verified.
8
45
  - `furnace create --localized` no longer scaffolds a `connectedCallback`/`disconnectedCallback` pair that calls `l10n.connectRoot(shadowRoot)` and `disconnectRoot`. `MozLitElement` already registers its shadow render root with `document.l10n`, so the generated pair registered the same root twice, which `DOMLocalization::ConnectRoot` asserts against in debug builds. The scaffold keeps the module-level `window.MozXULElement?.insertFTLIfNeeded(...)` call.
9
46
  - Build and test preflight detect broken partial configuration, stale artifacts and invalid build baselines. Scoped builds retain coverage only while their inputs remain unchanged, and builds remove dangling test links.
47
+ - Objdir relocation on Windows rewrites both the backslash and the forward-slash spelling of the old root in dependency metadata (mozbuild writes both), and replaces a directory link that `rename` refuses with `EPERM` in two steps.
10
48
 
11
49
  ### Test harness
12
50
 
@@ -25,11 +63,12 @@
25
63
  ### Command reliability
26
64
 
27
65
  - Fixed leaf-command `--wait-lock` handling, JSON error reporting and watch retries. Process tracking and independent cleanup preserve the original run outcome through cancellation and cleanup failures.
66
+ - Diff header parsing splits a rename/copy header with `indexOf` instead of a lazy regex, and token-category parsing strips CSS strings in one pass, so both run in linear time on pathological input.
28
67
 
29
68
  ### Tooling
30
69
 
31
70
  - Full-Firefox integration preserves existing workspace files and Git index state, and fails on incomplete recovery. Build identity includes untracked contents and binary changes. Release checks enforce the pinned toolchain and additional module coverage floors.
32
- - Dev tooling: `eslint-plugin-jsdoc` 65.0.1 and `@types/node` 26.6.4. Transitive `source-map-js` 1.2.2 (GHSA-68fv-2mgg-jv7q) and `smol-toml` 1.9.0 (GHSA-r4xh-jqrq-34v2) clear `npm audit`.
71
+ - Dev tooling: `eslint-plugin-jsdoc` 65.0.1, `@types/node` 26.6.4, `knip` 6.39.0 and `prettier` 3.9.9. Transitive `source-map-js` 1.2.2 (GHSA-68fv-2mgg-jv7q) and `smol-toml` 1.9.0 (GHSA-r4xh-jqrq-34v2) clear `npm audit`.
33
72
 
34
73
  ## 0.48.0
35
74
 
package/README.md CHANGED
@@ -15,7 +15,7 @@ Inspired by [fern.js](https://github.com/ghostery/user-agent-desktop) and [Melon
15
15
  - **Firefox source and build helpers** Download, bootstrap, build, run, test, package, smoke-check, etc.
16
16
  - **Wiring and registration** Add chrome scripts, DOM fragments, modules, styles, tests and manifest entries through commands built by learning from existing Firefox conventions.
17
17
  - **Furnace components** Create or override `MozLitElement` widgets easily to add new or adapt existing UI components to your needs.
18
- - **Quality** `lint`, `typecheck`, `verify` and `doctor` catch common issues early.
18
+ - **Quality** `lint`, `typecheck`, `verify` and `doctor` catch common issues early, and `check` runs the static gates together with one verdict.
19
19
  - **Tests** FireForge was built by taking apart and applying patches of all sorts to original Firefox source code across different versions and products, learning what works vs doesn't and creating some quite extensive tests based on that covering all manner of scenarios. Yes, we mock quite a bit, but when building a tool that modifies a separate code base, I think it's a solid compromise for the time being. Full end-to-end runs are currently run locally on my MacBook, as they require about 30 GB of disk and significant compute for multiple full builds. Full end-to-end via Actions will be added soonishlyTM but might need a different runner...
20
20
 
21
21
  ## Requirements
@@ -12,6 +12,7 @@ import { removeActivePgidFile } from '../src/commands/test-harness-teardown.js';
12
12
  import { emitKilledVerdict } from '../src/commands/test-verdict.js';
13
13
  import { forceReleaseHeldLocksForSignal } from '../src/core/file-lock.js';
14
14
  import { forceReleaseFurnaceLocksForActiveOperations, isSignalRollbackInFlight, rollbackActiveOperationsForSignal, } from '../src/core/furnace-operation.js';
15
+ import { restoreActiveNegativeControlsForSignal } from '../src/core/negative-control.js';
15
16
  import { closeActiveRunLog } from '../src/core/run-log.js';
16
17
  import { removeActiveRunProfiles } from '../src/core/run-profile.js';
17
18
  import { waitForActiveCriticalSections } from '../src/core/signal-critical.js';
@@ -97,6 +98,11 @@ function installFurnaceSignalHandler(signal, exitCode) {
97
98
  waitForActiveCriticalSections(SIGNAL_CRITICAL_SECTION_TIMEOUT_MS),
98
99
  waitForActiveChildShutdown(CHILD_SHUTDOWN_TIMEOUT_MS),
99
100
  ])
101
+ // A `test --negative-control` mutation must be undone while this
102
+ // process still holds the engine lock, or the next lock holder builds
103
+ // and tests the neuter. Runs after child shutdown, so the dying
104
+ // harness no longer reads the files. Never throws.
105
+ .then(() => restoreActiveNegativeControlsForSignal())
100
106
  // `withFileLock`'s `finally { rm }` never runs across `process.exit`,
101
107
  // so both sweeps exist for the same reason. The generic one covers
102
108
  // every lock this process holds (engine session, patch directory,
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.49.2",
4
- "commit": "c707978869b62aba07d1e8f0955be00c223d5ce0",
5
- "shortCommit": "c707978869b6",
3
+ "version": "0.50.0",
4
+ "commit": "996c59a67aa2144ff889a8c47e9304b071afae81",
5
+ "shortCommit": "996c59a67aa2",
6
6
  "dirty": false,
7
7
  "dirtyHash": null,
8
- "buildTime": "2026-10-06T21:50:15.508Z"
8
+ "buildTime": "2026-10-12T00:43:41.476Z"
9
9
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `fireforge build --wait-browser` spends its budget before the engine
3
+ * session lock, not while holding it.
4
+ *
5
+ * A build queued behind a running objdir browser (a demo batch, an
6
+ * end-to-end harness) used to wait inside `withEngineSessionLock`, so every
7
+ * peer's build, test and re-export queued behind a run that was doing
8
+ * nothing. The wait now happens first, and the in-lock objdir preflight
9
+ * re-checks once (the spent budget is stripped from the options it gets).
10
+ */
11
+ import type { BuildOptions } from '../types/commands/index.js';
12
+ /**
13
+ * Waits for this objdir's browser before the lock when `--wait-browser`
14
+ * was given and there is a launchable bundle to watch.
15
+ *
16
+ * @param projectRoot - Project root
17
+ * @param options - Parsed build options
18
+ * @returns The options the in-lock run should use
19
+ */
20
+ export declare function waitForBuildBrowserBeforeLock(projectRoot: string, options: BuildOptions): Promise<BuildOptions>;
@@ -0,0 +1,50 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `fireforge build --wait-browser` spends its budget before the engine
4
+ * session lock, not while holding it.
5
+ *
6
+ * A build queued behind a running objdir browser (a demo batch, an
7
+ * end-to-end harness) used to wait inside `withEngineSessionLock`, so every
8
+ * peer's build, test and re-export queued behind a run that was doing
9
+ * nothing. The wait now happens first, and the in-lock objdir preflight
10
+ * re-checks once (the spent budget is stripped from the options it gets).
11
+ */
12
+ import { join } from 'node:path';
13
+ import { getProjectPaths, loadConfig } from '../core/config.js';
14
+ import { hasBuildArtifacts, hasRunnableBundle } from '../core/mach.js';
15
+ import { ensureLaunchableBrowserNotRunning } from '../core/marionette-port.js';
16
+ import { waitBeforeLock } from '../core/preflight-wait.js';
17
+ import { pathExists } from '../utils/fs.js';
18
+ import { resolveWaitLockSeconds } from '../utils/options.js';
19
+ /**
20
+ * Waits for this objdir's browser before the lock when `--wait-browser`
21
+ * was given and there is a launchable bundle to watch.
22
+ *
23
+ * @param projectRoot - Project root
24
+ * @param options - Parsed build options
25
+ * @returns The options the in-lock run should use
26
+ */
27
+ export async function waitForBuildBrowserBeforeLock(projectRoot, options) {
28
+ const { waitBrowser, ...rest } = options;
29
+ if (waitBrowser === undefined)
30
+ return options;
31
+ const paths = getProjectPaths(projectRoot);
32
+ if (!(await pathExists(paths.engine)))
33
+ return options;
34
+ const { objDir } = await hasBuildArtifacts(paths.engine);
35
+ if (objDir === undefined)
36
+ return options;
37
+ let binaryName;
38
+ try {
39
+ ({ binaryName } = await loadConfig(projectRoot));
40
+ }
41
+ catch {
42
+ return options;
43
+ }
44
+ const bundle = await hasRunnableBundle(paths.engine, binaryName, objDir);
45
+ const expectedPath = bundle.expectedPath;
46
+ if (!bundle.runnable || expectedPath === undefined)
47
+ return options;
48
+ await waitBeforeLock(() => ensureLaunchableBrowserNotRunning(join(paths.engine, expectedPath)), resolveWaitLockSeconds(waitBrowser));
49
+ return rest;
50
+ }
@@ -5,7 +5,9 @@ import type { BuildOptions } from '../types/commands/index.js';
5
5
  * Runs the build command.
6
6
  * @param projectRoot - Root directory of the project
7
7
  * @param options - Build options
8
+ * @param underLock - Runs the build under the engine session lock; a
9
+ * `--wait-browser` budget is spent before it (see build-prelock.ts)
8
10
  */
9
- export declare function buildCommand(projectRoot: string, options: BuildOptions): Promise<void>;
11
+ export declare function buildCommand(projectRoot: string, options: BuildOptions, underLock?: (body: () => Promise<void>) => Promise<void>): Promise<void>;
10
12
  /** Registers the build command on the CLI program. */
11
13
  export declare function registerBuild(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -13,7 +13,7 @@ import { findObjdirRelocationViolation } from '../core/mach-build-artifacts.js';
13
13
  import { ensureLaunchableBrowserNotRunning } from '../core/marionette-port.js';
14
14
  import { assertHealthyPartialConfig, pruneDanglingTestLinks } from '../core/objdir-maintenance.js';
15
15
  import { waitForPreflight } from '../core/preflight-wait.js';
16
- import { closeActiveRunLog, getActiveRunLogPath, openRunLog, setActiveRunLog, } from '../core/run-log.js';
16
+ import { closeActiveRunLog, getActiveRunLogPath, keepRunLogCopy, openRunLog, setActiveRunLog, } from '../core/run-log.js';
17
17
  import { buildHarnessCrashMessage } from '../core/test-harness-crash.js';
18
18
  import { formatToolchainMismatchMessage, runToolchainPreflight, } from '../core/toolchain-preflight.js';
19
19
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
@@ -24,6 +24,7 @@ import { checkDiskSpace } from '../utils/fs.js';
24
24
  import { error, info, intro, notice, outro, spinner, verbose, warn } from '../utils/logger.js';
25
25
  import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../utils/options.js';
26
26
  import { isPositiveInteger } from '../utils/validation.js';
27
+ import { waitForBuildBrowserBeforeLock } from './build-prelock.js';
27
28
  function parseJobCount(value) {
28
29
  const parsed = Number(value);
29
30
  if (!Number.isSafeInteger(parsed) || parsed < 1) {
@@ -177,17 +178,20 @@ function buildFailureDiagnostics(result, engineDir, objDir, machCommand) {
177
178
  * Runs the build command.
178
179
  * @param projectRoot - Root directory of the project
179
180
  * @param options - Build options
181
+ * @param underLock - Runs the build under the engine session lock; a
182
+ * `--wait-browser` budget is spent before it (see build-prelock.ts)
180
183
  */
181
- export async function buildCommand(projectRoot, options) {
184
+ export async function buildCommand(projectRoot, options, underLock = (body) => body()) {
182
185
  const buildType = options.ui ? 'UI-only' : 'Full';
183
186
  const brandInfo = options.brand ? ` [${options.brand}]` : '';
184
187
  intro(`FireForge Build (${buildType}${brandInfo})`);
185
188
  // A build's own complete output, kept independently of whatever the
186
189
  // operator piped the run through. `build` prints no FIREFORGE-VERDICT
187
190
  // line, so the path is announced directly at the end of the run.
188
- setActiveRunLog(await openRunLog(projectRoot, 'build'));
191
+ setActiveRunLog(await keepRunLogCopy(await openRunLog(projectRoot, 'build'), options.keepLog, 'build'));
189
192
  try {
190
- await runBuildCommandBody(projectRoot, options);
193
+ const lockedOptions = await waitForBuildBrowserBeforeLock(projectRoot, options);
194
+ await underLock(() => runBuildCommandBody(projectRoot, lockedOptions));
191
195
  }
192
196
  finally {
193
197
  const logPath = getActiveRunLogPath();
@@ -400,11 +404,12 @@ export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
400
404
  .command('build')
401
405
  .description('Build the browser (auto-applies Furnace components first)')
402
406
  .option('--wait-browser [seconds]', 'Wait for this objdir browser to exit before changing its build (default 60)', commanderArgParser((raw) => resolveWaitLockSeconds(raw)))
407
+ .option('--keep-log <path>', "Also write this run's log to <path> (a directory receives it under its own name), outside .fireforge/logs/ retention. FIREFORGE_LOG_KEEP sets a session default.")
403
408
  .option('--ui', 'Fast UI-only rebuild')
404
409
  .option('-j, --jobs <n>', 'Number of parallel jobs', parseJobCount)
405
410
  .option('--brand <name>', 'Build specific brand')
406
411
  .option('--rewrite-mozinfo', 'On a mozinfo path mismatch, patch mozinfo.json paths in place and run mach configure instead of aborting with a clean-rebuild instruction. Refuses anything that is not a pure prefix-move.')
407
- .option('--refuse-unexported-drift', 'Refuse (instead of warning) when the build would overwrite engine content recorded in neither a patch body nor the pristine baseline — the multi-session hazard where an unexported edit is silently reverted and a later re-export captures a hybrid')
412
+ .option('--refuse-unexported-drift', 'Refuse (instead of warning) when the build would overwrite engine content recorded in neither a patch body nor the pristine baseline — the multi-session hazard where an unexported edit is silently reverted and a later re-export captures a hybrid. A deployed Furnace file that is a deployment of its own component source is allowed')
408
413
  .addHelpText('after', [
409
414
  '',
410
415
  'Furnace apply runs automatically before the build step, so edits in',
@@ -425,6 +430,8 @@ export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
425
430
  ].join('\n'));
426
431
  addWaitLockOption(build).action(withErrorHandling(async (options) => {
427
432
  const projectRoot = getProjectRoot();
428
- await withEngineSessionLock(projectRoot, 'build', () => buildCommand(projectRoot, pickDefined(options)), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
433
+ await buildCommand(projectRoot, pickDefined(options), (body) => withEngineSessionLock(projectRoot, 'build', body, {
434
+ waitLockSeconds: resolveWaitLockSeconds(options.waitLock),
435
+ }));
429
436
  }));
430
437
  }
@@ -0,0 +1,28 @@
1
+ import type { CheckStepResult } from './check-run.js';
2
+ /**
3
+ * Why a check failed. `step-failures`: at least one step went red.
4
+ * `lock-timeout`: every failing step exited 15, so nothing was judged red,
5
+ * the steps just never got their locks. Re-queue rather than fix.
6
+ */
7
+ export type CheckVerdictReason = 'step-failures' | 'lock-timeout';
8
+ /** The verdict over all steps. */
9
+ export interface CheckVerdict {
10
+ passed: boolean;
11
+ reason?: CheckVerdictReason;
12
+ failed: string[];
13
+ skipped: string[];
14
+ steps: number;
15
+ }
16
+ /** Judges the results: any failing step fails the check. */
17
+ export declare function judgeCheck(results: readonly CheckStepResult[]): CheckVerdict;
18
+ /**
19
+ * The verdict line: `FIREFORGE-VERDICT: PASS steps=<n> skipped=<n>` or
20
+ * `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n>`.
21
+ */
22
+ export declare function formatCheckVerdictLine(verdict: CheckVerdict): string;
23
+ /** The per-step table: one aligned row per step, with its detail. */
24
+ export declare function formatCheckTable(results: readonly CheckStepResult[]): string;
25
+ /** Writes the verdict line as the run's last stdout write, and seals stdout. */
26
+ export declare function writeCheckVerdictLine(verdict: CheckVerdict): void;
27
+ /** Writes the `--json` document (schemaVersion 1) and seals stdout. */
28
+ export declare function writeCheckJson(results: readonly CheckStepResult[], verdict: CheckVerdict): void;
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `fireforge check` output: the per-step table, the single
4
+ * `FIREFORGE-VERDICT:` line, and the `--json` document. The verdict is
5
+ * computed once here and shared by all three, so the exit code, the line
6
+ * and the JSON can never disagree.
7
+ */
8
+ import { setStdoutSealed } from '../utils/logger.js';
9
+ /** Exit code a step reports when its lock wait expired. */
10
+ const LOCK_TIMEOUT_EXIT = 15;
11
+ /** Judges the results: any failing step fails the check. */
12
+ export function judgeCheck(results) {
13
+ const failedResults = results.filter((result) => result.status === 'fail');
14
+ const verdict = {
15
+ passed: failedResults.length === 0,
16
+ failed: failedResults.map((result) => result.name),
17
+ skipped: results.filter((result) => result.status === 'skipped').map((result) => result.name),
18
+ steps: results.length,
19
+ };
20
+ if (failedResults.length > 0) {
21
+ verdict.reason = failedResults.every((result) => result.exitCode === LOCK_TIMEOUT_EXIT)
22
+ ? 'lock-timeout'
23
+ : 'step-failures';
24
+ }
25
+ return verdict;
26
+ }
27
+ /**
28
+ * The verdict line: `FIREFORGE-VERDICT: PASS steps=<n> skipped=<n>` or
29
+ * `FIREFORGE-VERDICT: FAIL reason=<reason> failed=<a,b> steps=<n> skipped=<n>`.
30
+ */
31
+ export function formatCheckVerdictLine(verdict) {
32
+ const counts = `steps=${verdict.steps} skipped=${verdict.skipped.length}`;
33
+ if (verdict.passed)
34
+ return `FIREFORGE-VERDICT: PASS ${counts}`;
35
+ return `FIREFORGE-VERDICT: FAIL reason=${verdict.reason ?? 'step-failures'} failed=${verdict.failed.join(',')} ${counts}`;
36
+ }
37
+ function formatDuration(ms) {
38
+ return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
39
+ }
40
+ /** The per-step table: one aligned row per step, with its detail. */
41
+ export function formatCheckTable(results) {
42
+ const rows = results.map((result) => [
43
+ result.name,
44
+ result.status.toUpperCase(),
45
+ result.status === 'skipped' ? '-' : formatDuration(result.durationMs),
46
+ result.detail,
47
+ ]);
48
+ const header = ['STEP', 'RESULT', 'TIME', 'DETAIL'];
49
+ const widths = header.map((title, column) => Math.max(title.length, ...rows.map((row) => row[column]?.length ?? 0)));
50
+ return [header, ...rows]
51
+ .map((row) => row
52
+ .map((cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column] ?? 0))
53
+ .join(' ')
54
+ .trimEnd())
55
+ .join('\n');
56
+ }
57
+ /** Writes the verdict line as the run's last stdout write, and seals stdout. */
58
+ export function writeCheckVerdictLine(verdict) {
59
+ process.stdout.write(`${formatCheckVerdictLine(verdict)}\n`);
60
+ setStdoutSealed(true);
61
+ }
62
+ /** Writes the `--json` document (schemaVersion 1) and seals stdout. */
63
+ export function writeCheckJson(results, verdict) {
64
+ const payload = {
65
+ schemaVersion: 1,
66
+ verdict: verdict.passed ? 'pass' : 'fail',
67
+ ...(verdict.reason !== undefined ? { reason: verdict.reason } : {}),
68
+ summary: {
69
+ steps: verdict.steps,
70
+ passed: results.filter((result) => result.status === 'pass').length,
71
+ failed: verdict.failed.length,
72
+ skipped: verdict.skipped.length,
73
+ },
74
+ steps: results,
75
+ };
76
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
77
+ setStdoutSealed(true);
78
+ }
@@ -0,0 +1,28 @@
1
+ import type { CheckStepPlan } from './check-steps.js';
2
+ /** What a step's table row and JSON entry report. */
3
+ export interface CheckStepResult {
4
+ name: string;
5
+ kind: 'builtin' | 'extra';
6
+ /** The command line, as the table prints it. */
7
+ command: string;
8
+ status: 'pass' | 'fail' | 'skipped';
9
+ /** The child's exit code. Null when it never ran or was killed at its budget. */
10
+ exitCode: number | null;
11
+ durationMs: number;
12
+ /** One-line summary for the table. */
13
+ detail: string;
14
+ /** Set when the step was killed at its `timeout`. */
15
+ timedOut?: true;
16
+ /** The tail of the step's combined output, for a failed step. */
17
+ outputTail?: string;
18
+ }
19
+ /**
20
+ * Runs every step and returns the results in plan order. A step starts
21
+ * once a slot is free and every step it runs `after` has finished
22
+ * (whatever its outcome: the constraint is ordering, not success).
23
+ *
24
+ * @param plans - The plan from `buildCheckPlan`, already filtered
25
+ * @param jobs - How many steps may run at once (at least 1)
26
+ * @param now - Clock, injectable for tests
27
+ */
28
+ export declare function runCheckSteps(plans: readonly CheckStepPlan[], jobs: number, now?: () => number): Promise<CheckStepResult[]>;
@@ -0,0 +1,104 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Runs the `fireforge check` plan: up to `jobs` steps at once, each as a
4
+ * child process in its own process group (so a timeout or a Ctrl+C takes
5
+ * the step's whole tree), honouring each step's `after` ordering.
6
+ */
7
+ import { ExecTimeoutError } from '../errors/base.js';
8
+ import { toError } from '../utils/errors.js';
9
+ import { execStream } from '../utils/process.js';
10
+ /** Combined output kept per step: enough for a tail, bounded for a chatty audit. */
11
+ const COMBINED_OUTPUT_CAP = 256 * 1024;
12
+ /** Lines of output a failed step carries in its result. */
13
+ const OUTPUT_TAIL_LINES = 40;
14
+ function tailLines(text) {
15
+ const lines = text.trimEnd().split(/\r?\n/);
16
+ return lines.slice(-OUTPUT_TAIL_LINES).join('\n');
17
+ }
18
+ /** Runs one planned step to completion. Never throws. */
19
+ async function runStep(plan, now) {
20
+ const base = { name: plan.name, kind: plan.kind, command: plan.display };
21
+ if (plan.skipReason !== undefined) {
22
+ return { ...base, status: 'skipped', exitCode: null, durationMs: 0, detail: plan.skipReason };
23
+ }
24
+ let stdout = '';
25
+ let combined = '';
26
+ const append = (chunk) => {
27
+ combined += chunk;
28
+ if (combined.length > COMBINED_OUTPUT_CAP)
29
+ combined = combined.slice(-COMBINED_OUTPUT_CAP);
30
+ };
31
+ const started = now();
32
+ try {
33
+ const exitCode = await execStream(plan.command, plan.args, {
34
+ cwd: plan.cwd,
35
+ processGroup: true,
36
+ ...(plan.timeoutMs !== undefined ? { timeout: plan.timeoutMs } : {}),
37
+ onStdout: (chunk) => {
38
+ stdout += chunk;
39
+ append(chunk);
40
+ },
41
+ onStderr: append,
42
+ });
43
+ const status = exitCode === 0 ? 'pass' : 'fail';
44
+ return {
45
+ ...base,
46
+ status,
47
+ exitCode,
48
+ durationMs: now() - started,
49
+ detail: plan.summarize({ stdout, combined, exitCode }),
50
+ ...(status === 'fail' ? { outputTail: tailLines(combined) } : {}),
51
+ };
52
+ }
53
+ catch (error) {
54
+ const timedOut = error instanceof ExecTimeoutError;
55
+ return {
56
+ ...base,
57
+ status: 'fail',
58
+ exitCode: null,
59
+ durationMs: now() - started,
60
+ detail: timedOut
61
+ ? `timed out after ${Math.round((plan.timeoutMs ?? 0) / 1000)}s`
62
+ : `could not start: ${toError(error).message}`,
63
+ ...(timedOut ? { timedOut: true } : {}),
64
+ outputTail: tailLines(combined),
65
+ };
66
+ }
67
+ }
68
+ /**
69
+ * Runs every step and returns the results in plan order. A step starts
70
+ * once a slot is free and every step it runs `after` has finished
71
+ * (whatever its outcome: the constraint is ordering, not success).
72
+ *
73
+ * @param plans - The plan from `buildCheckPlan`, already filtered
74
+ * @param jobs - How many steps may run at once (at least 1)
75
+ * @param now - Clock, injectable for tests
76
+ */
77
+ export async function runCheckSteps(plans, jobs, now = Date.now) {
78
+ const names = new Set(plans.map((plan) => plan.name));
79
+ const results = new Map();
80
+ const pending = [...plans];
81
+ const running = new Map();
82
+ const isReady = (plan) => plan.after.every((dependency) => !names.has(dependency) || results.has(dependency));
83
+ while (pending.length > 0 || running.size > 0) {
84
+ for (let index = 0; index < pending.length && running.size < Math.max(1, jobs);) {
85
+ const plan = pending[index];
86
+ if (plan === undefined || !isReady(plan)) {
87
+ index++;
88
+ continue;
89
+ }
90
+ pending.splice(index, 1);
91
+ running.set(plan.name, runStep(plan, now).then((result) => {
92
+ results.set(plan.name, result);
93
+ running.delete(plan.name);
94
+ }));
95
+ }
96
+ if (running.size === 0)
97
+ break;
98
+ await Promise.race(running.values());
99
+ }
100
+ return plans.flatMap((plan) => {
101
+ const result = results.get(plan.name);
102
+ return result === undefined ? [] : [result];
103
+ });
104
+ }
@@ -0,0 +1,67 @@
1
+ import type { CheckConfig, FireForgeConfig } from '../types/config.js';
2
+ /** One planned step. */
3
+ export interface CheckStepPlan {
4
+ name: string;
5
+ kind: 'builtin' | 'extra';
6
+ /** Executable to spawn. */
7
+ command: string;
8
+ /** Its arguments. */
9
+ args: string[];
10
+ /** The command line as the table prints it. */
11
+ display: string;
12
+ cwd: string;
13
+ timeoutMs?: number | undefined;
14
+ /** Steps that must finish before this one starts (ordering only). */
15
+ after: string[];
16
+ /** Set when the step is planned but not run, with the reason. */
17
+ skipReason?: string | undefined;
18
+ /** Condenses a finished run into the table's detail cell. */
19
+ summarize: (output: {
20
+ stdout: string;
21
+ combined: string;
22
+ exitCode: number;
23
+ }) => string;
24
+ }
25
+ /** The project facts the plan depends on. */
26
+ export interface CheckPlanInput {
27
+ projectRoot: string;
28
+ config: FireForgeConfig;
29
+ furnaceConfigured: boolean;
30
+ /** Resolved `--patches` filenames, forwarded to status, verify and lint. */
31
+ patches?: string[] | undefined;
32
+ }
33
+ /**
34
+ * The generic detail: nothing on success, and on failure the last line
35
+ * the step printed (its refusal, usually), cut to fit a table cell.
36
+ */
37
+ export declare function lastOutputLine(combined: string, exitCode: number): string;
38
+ /** Status detail from its `--json --summary` document: the failing classes. */
39
+ export declare function summarizeStatus(output: {
40
+ stdout: string;
41
+ combined: string;
42
+ exitCode: number;
43
+ }): string;
44
+ /** Furnace detail from its `--json` document: counts after exceptions. */
45
+ export declare function summarizeFurnace(output: {
46
+ stdout: string;
47
+ combined: string;
48
+ exitCode: number;
49
+ }): string;
50
+ /** Token-coverage detail: the coverage percentage it reports. */
51
+ export declare function summarizeTokenCoverage(output: {
52
+ stdout: string;
53
+ combined: string;
54
+ exitCode: number;
55
+ }): string;
56
+ /** The status step's `--check` flags from `check.status`. */
57
+ export declare function statusPolicyArgs(policy: CheckConfig['status']): string[];
58
+ /**
59
+ * Builds the full plan: the six built-in steps (with the ones this project
60
+ * cannot run marked skipped) followed by `check.extraSteps[]`.
61
+ *
62
+ * `furnace-validate` waits for `typecheck`, the one built-in step that
63
+ * writes: it regenerates a stale Furnace jsconfig, whose drift
64
+ * `furnace validate` reports. Run side by side, the two would race on that
65
+ * file and the verdict would depend on which won.
66
+ */
67
+ export declare function buildCheckPlan(input: CheckPlanInput): CheckStepPlan[];