@hominis/fireforge 0.49.1 → 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 (336) hide show
  1. package/CHANGELOG.md +41 -0
  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-templates.d.ts +8 -3
  35. package/dist/src/commands/furnace/create-templates.js +8 -23
  36. package/dist/src/commands/furnace/create.js +2 -2
  37. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  38. package/dist/src/commands/furnace/deploy-named.js +107 -0
  39. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  40. package/dist/src/commands/furnace/deploy.js +50 -86
  41. package/dist/src/commands/furnace/diff.js +26 -3
  42. package/dist/src/commands/furnace/index.js +66 -41
  43. package/dist/src/commands/furnace/init.js +13 -6
  44. package/dist/src/commands/furnace/override.js +4 -2
  45. package/dist/src/commands/furnace/preview.js +4 -3
  46. package/dist/src/commands/furnace/refresh.js +2 -2
  47. package/dist/src/commands/furnace/remove.js +4 -3
  48. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  49. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  50. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  51. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  52. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  53. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  54. package/dist/src/commands/furnace/rename.d.ts +2 -1
  55. package/dist/src/commands/furnace/rename.js +28 -66
  56. package/dist/src/commands/furnace/scan.d.ts +9 -4
  57. package/dist/src/commands/furnace/scan.js +11 -9
  58. package/dist/src/commands/furnace/sync.js +3 -2
  59. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  60. package/dist/src/commands/furnace/validate-report.js +91 -0
  61. package/dist/src/commands/furnace/validate.d.ts +1 -1
  62. package/dist/src/commands/furnace/validate.js +127 -114
  63. package/dist/src/commands/furnace/validation-output.js +14 -3
  64. package/dist/src/commands/lint-per-patch.js +31 -60
  65. package/dist/src/commands/lint-report.d.ts +8 -4
  66. package/dist/src/commands/lint-report.js +15 -5
  67. package/dist/src/commands/lint.js +15 -34
  68. package/dist/src/commands/manifest.js +2 -0
  69. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  70. package/dist/src/commands/patch/delete.d.ts +1 -1
  71. package/dist/src/commands/patch/delete.js +13 -98
  72. package/dist/src/commands/patch/index.d.ts +1 -1
  73. package/dist/src/commands/patch/index.js +4 -2
  74. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  75. package/dist/src/commands/patch/lint-ignore.js +65 -28
  76. package/dist/src/commands/patch/move-files-create.js +26 -17
  77. package/dist/src/commands/patch/move-files-into.js +5 -3
  78. package/dist/src/commands/patch/move-files.js +3 -1
  79. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  80. package/dist/src/commands/patch/removal-dependents.js +133 -0
  81. package/dist/src/commands/patch/rename.js +7 -0
  82. package/dist/src/commands/patch/retire.d.ts +34 -0
  83. package/dist/src/commands/patch/retire.js +128 -0
  84. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  85. package/dist/src/commands/patch/split-plan.js +45 -28
  86. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  87. package/dist/src/commands/patch/split-staged.js +202 -0
  88. package/dist/src/commands/patch/split.d.ts +5 -0
  89. package/dist/src/commands/patch/split.js +51 -15
  90. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  91. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  92. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  93. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  94. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  95. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  96. package/dist/src/commands/patch/staged-dependency.js +14 -41
  97. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  98. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  99. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  100. package/dist/src/commands/re-export-adjacent.js +156 -37
  101. package/dist/src/commands/re-export-drift.d.ts +36 -8
  102. package/dist/src/commands/re-export-drift.js +73 -49
  103. package/dist/src/commands/re-export-files.js +11 -8
  104. package/dist/src/commands/re-export-lint.d.ts +2 -1
  105. package/dist/src/commands/re-export-lint.js +23 -10
  106. package/dist/src/commands/re-export-options.js +45 -4
  107. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  108. package/dist/src/commands/re-export-preflight.js +160 -0
  109. package/dist/src/commands/re-export-register.js +42 -17
  110. package/dist/src/commands/re-export-removed.d.ts +12 -1
  111. package/dist/src/commands/re-export-removed.js +41 -11
  112. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  113. package/dist/src/commands/re-export-scan-owner.js +116 -0
  114. package/dist/src/commands/re-export-scan.d.ts +17 -9
  115. package/dist/src/commands/re-export-scan.js +56 -23
  116. package/dist/src/commands/re-export-single.js +22 -6
  117. package/dist/src/commands/re-export-summary.d.ts +28 -0
  118. package/dist/src/commands/re-export-summary.js +36 -0
  119. package/dist/src/commands/re-export.js +37 -24
  120. package/dist/src/commands/rebase/index.js +17 -17
  121. package/dist/src/commands/run.js +16 -2
  122. package/dist/src/commands/status-check.d.ts +55 -16
  123. package/dist/src/commands/status-check.js +92 -37
  124. package/dist/src/commands/status-json.d.ts +22 -9
  125. package/dist/src/commands/status-json.js +47 -32
  126. package/dist/src/commands/status-output.js +3 -1
  127. package/dist/src/commands/status-scope.d.ts +37 -0
  128. package/dist/src/commands/status-scope.js +54 -0
  129. package/dist/src/commands/status.js +66 -23
  130. package/dist/src/commands/test-browser-preflight.js +19 -3
  131. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  132. package/dist/src/commands/test-build-furnace.js +48 -0
  133. package/dist/src/commands/test-build-phase.js +47 -41
  134. package/dist/src/commands/test-checkjs.js +7 -0
  135. package/dist/src/commands/test-diagnose.js +7 -0
  136. package/dist/src/commands/test-modes.d.ts +16 -1
  137. package/dist/src/commands/test-modes.js +24 -1
  138. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  139. package/dist/src/commands/test-multi-verdict.js +111 -0
  140. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  141. package/dist/src/commands/test-orchestrate.js +222 -0
  142. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  143. package/dist/src/commands/test-perf-scope.js +83 -0
  144. package/dist/src/commands/test-prelock.d.ts +51 -0
  145. package/dist/src/commands/test-prelock.js +177 -0
  146. package/dist/src/commands/test-register.js +54 -10
  147. package/dist/src/commands/test-run.js +4 -1
  148. package/dist/src/commands/test-stale-gate.js +57 -27
  149. package/dist/src/commands/test-verdict.d.ts +10 -1
  150. package/dist/src/commands/test-verdict.js +28 -1
  151. package/dist/src/commands/test.d.ts +6 -1
  152. package/dist/src/commands/test.js +66 -60
  153. package/dist/src/commands/token-lint.d.ts +14 -0
  154. package/dist/src/commands/token-lint.js +111 -0
  155. package/dist/src/commands/token.js +10 -0
  156. package/dist/src/commands/verify-scope.d.ts +28 -0
  157. package/dist/src/commands/verify-scope.js +67 -0
  158. package/dist/src/commands/verify.d.ts +10 -2
  159. package/dist/src/commands/verify.js +59 -35
  160. package/dist/src/core/build-baseline-types.d.ts +46 -0
  161. package/dist/src/core/build-baseline-validate.js +19 -0
  162. package/dist/src/core/build-baseline.d.ts +5 -1
  163. package/dist/src/core/build-baseline.js +3 -1
  164. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  165. package/dist/src/core/build-overwrite-guard.js +84 -4
  166. package/dist/src/core/build-prepare.d.ts +7 -0
  167. package/dist/src/core/build-prepare.js +14 -8
  168. package/dist/src/core/build-run-record.d.ts +74 -0
  169. package/dist/src/core/build-run-record.js +136 -0
  170. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  171. package/dist/src/core/checkjs-support-heads.js +104 -0
  172. package/dist/src/core/config-paths.d.ts +2 -2
  173. package/dist/src/core/config-paths.js +8 -0
  174. package/dist/src/core/config-validate-check.d.ts +8 -0
  175. package/dist/src/core/config-validate-check.js +143 -0
  176. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  177. package/dist/src/core/config-validate-file-size.js +91 -2
  178. package/dist/src/core/config-validate-history.d.ts +11 -0
  179. package/dist/src/core/config-validate-history.js +38 -0
  180. package/dist/src/core/config-validate-patch-policy.js +100 -0
  181. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  182. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  183. package/dist/src/core/config-validate-token-scales.js +50 -0
  184. package/dist/src/core/config-validate.js +14 -5
  185. package/dist/src/core/config.d.ts +5 -0
  186. package/dist/src/core/config.js +16 -0
  187. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  188. package/dist/src/core/coverage-dispatch.js +44 -0
  189. package/dist/src/core/coverage-extend.d.ts +70 -45
  190. package/dist/src/core/coverage-extend.js +199 -144
  191. package/dist/src/core/coverage-records.d.ts +97 -0
  192. package/dist/src/core/coverage-records.js +350 -0
  193. package/dist/src/core/destructive.d.ts +7 -4
  194. package/dist/src/core/destructive.js +10 -10
  195. package/dist/src/core/engine-session-lock.d.ts +10 -1
  196. package/dist/src/core/engine-session-lock.js +28 -8
  197. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  198. package/dist/src/core/export-moved-code-shape.js +135 -44
  199. package/dist/src/core/file-lock-queue.d.ts +67 -0
  200. package/dist/src/core/file-lock-queue.js +221 -0
  201. package/dist/src/core/file-lock.d.ts +4 -25
  202. package/dist/src/core/file-lock.js +105 -138
  203. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  204. package/dist/src/core/furnace-apply-output.js +31 -1
  205. package/dist/src/core/furnace-apply.js +12 -2
  206. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  207. package/dist/src/core/furnace-composed-source.js +167 -0
  208. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  209. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  210. package/dist/src/core/furnace-config-validate.js +8 -0
  211. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  212. package/dist/src/core/furnace-css-fragments.js +26 -0
  213. package/dist/src/core/furnace-operation.d.ts +3 -3
  214. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  215. package/dist/src/core/furnace-registration-remove.js +53 -17
  216. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  217. package/dist/src/core/furnace-registration-rename.js +77 -0
  218. package/dist/src/core/furnace-registration.d.ts +14 -4
  219. package/dist/src/core/furnace-registration.js +44 -10
  220. package/dist/src/core/furnace-staleness.d.ts +7 -0
  221. package/dist/src/core/furnace-staleness.js +34 -24
  222. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  223. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  224. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  225. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  226. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  227. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  228. package/dist/src/core/furnace-validate.js +1 -1
  229. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  230. package/dist/src/core/harness-port-fallback.js +96 -0
  231. package/dist/src/core/history-log-file.d.ts +61 -0
  232. package/dist/src/core/history-log-file.js +157 -0
  233. package/dist/src/core/history-log.d.ts +2 -1
  234. package/dist/src/core/history-log.js +2 -1
  235. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  236. package/dist/src/core/json-preserving-edit.js +199 -0
  237. package/dist/src/core/mach.js +8 -1
  238. package/dist/src/core/negative-control-diff.d.ts +39 -0
  239. package/dist/src/core/negative-control-diff.js +136 -0
  240. package/dist/src/core/negative-control.d.ts +138 -0
  241. package/dist/src/core/negative-control.js +356 -0
  242. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  243. package/dist/src/core/objdir-install-manifests.js +226 -0
  244. package/dist/src/core/patch-export-update.d.ts +2 -0
  245. package/dist/src/core/patch-export-update.js +1 -0
  246. package/dist/src/core/patch-export.js +3 -4
  247. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  248. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  249. package/dist/src/core/patch-lint-checkjs.js +8 -11
  250. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  251. package/dist/src/core/patch-lint-code-lines.js +105 -0
  252. package/dist/src/core/patch-lint-cross.js +12 -4
  253. package/dist/src/core/patch-lint-css.d.ts +8 -2
  254. package/dist/src/core/patch-lint-css.js +10 -4
  255. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  256. package/dist/src/core/patch-lint-file-size.js +22 -11
  257. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  258. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  259. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  260. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  261. package/dist/src/core/patch-lint-ignore.js +299 -0
  262. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  263. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  264. package/dist/src/core/patch-lint-size.d.ts +117 -0
  265. package/dist/src/core/patch-lint-size.js +323 -0
  266. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  267. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  268. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  269. package/dist/src/core/patch-lint-token-scale.js +109 -0
  270. package/dist/src/core/patch-lint.d.ts +11 -83
  271. package/dist/src/core/patch-lint.js +64 -286
  272. package/dist/src/core/patch-lock.js +7 -6
  273. package/dist/src/core/patch-manifest-consistency.js +7 -6
  274. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  275. package/dist/src/core/patch-manifest-io.js +19 -8
  276. package/dist/src/core/patch-manifest-validate.js +34 -2
  277. package/dist/src/core/patch-order-legal.d.ts +25 -0
  278. package/dist/src/core/patch-order-legal.js +83 -0
  279. package/dist/src/core/patch-policy-content.d.ts +40 -0
  280. package/dist/src/core/patch-policy-content.js +216 -0
  281. package/dist/src/core/patch-policy.d.ts +16 -3
  282. package/dist/src/core/patch-policy.js +16 -9
  283. package/dist/src/core/patch-retired.d.ts +35 -0
  284. package/dist/src/core/patch-retired.js +54 -0
  285. package/dist/src/core/patch-subset.d.ts +16 -0
  286. package/dist/src/core/patch-subset.js +66 -0
  287. package/dist/src/core/preflight-wait.d.ts +20 -0
  288. package/dist/src/core/preflight-wait.js +38 -0
  289. package/dist/src/core/rebase-from-version.d.ts +46 -0
  290. package/dist/src/core/rebase-from-version.js +71 -0
  291. package/dist/src/core/run-log.d.ts +20 -0
  292. package/dist/src/core/run-log.js +89 -3
  293. package/dist/src/core/run-profile.d.ts +7 -0
  294. package/dist/src/core/run-profile.js +7 -1
  295. package/dist/src/core/smoke-patterns.d.ts +5 -0
  296. package/dist/src/core/smoke-patterns.js +13 -0
  297. package/dist/src/core/test-harness-crash.d.ts +18 -1
  298. package/dist/src/core/test-harness-crash.js +6 -4
  299. package/dist/src/core/test-harness-signature.d.ts +17 -0
  300. package/dist/src/core/test-harness-signature.js +4 -0
  301. package/dist/src/core/test-run-completeness.d.ts +57 -0
  302. package/dist/src/core/test-run-completeness.js +144 -0
  303. package/dist/src/core/test-stale-check.d.ts +27 -1
  304. package/dist/src/core/test-stale-check.js +39 -49
  305. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  306. package/dist/src/core/test-stale-exempt.js +70 -0
  307. package/dist/src/core/token-lint.d.ts +50 -0
  308. package/dist/src/core/token-lint.js +126 -0
  309. package/dist/src/core/tree-guard.js +3 -0
  310. package/dist/src/core/typecheck-shim.d.ts +1 -1
  311. package/dist/src/core/typecheck-shim.js +90 -2
  312. package/dist/src/index.d.ts +8 -3
  313. package/dist/src/index.js +3 -1
  314. package/dist/src/types/commands/index.d.ts +2 -2
  315. package/dist/src/types/commands/options.d.ts +148 -21
  316. package/dist/src/types/commands/patches.d.ts +75 -1
  317. package/dist/src/types/config.d.ts +192 -0
  318. package/dist/src/types/config.js +9 -0
  319. package/dist/src/types/furnace.d.ts +43 -1
  320. package/dist/src/types/furnace.js +7 -0
  321. package/dist/src/types/index.d.ts +1 -1
  322. package/dist/src/utils/options.d.ts +34 -6
  323. package/dist/src/utils/options.js +86 -5
  324. package/dist/src/utils/path-glob.d.ts +15 -0
  325. package/dist/src/utils/path-glob.js +47 -0
  326. package/dist/src/utils/regex.d.ts +15 -3
  327. package/dist/src/utils/regex.js +37 -16
  328. package/docs/configuration.md +282 -9
  329. package/docs/exit-codes.md +18 -1
  330. package/docs/furnace.md +97 -3
  331. package/docs/lifecycle-invariants.md +52 -19
  332. package/docs/machine-output.md +74 -12
  333. package/docs/patch-workflow.md +297 -22
  334. package/docs/run-logs.md +20 -1
  335. package/docs/testing.md +276 -20
  336. package/package.json +2 -2
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `furnace validate` result rendering: the human per-component lines and
3
+ * the `--json` document (schemaVersion 1, docs/machine-output.md), plus
4
+ * the verdict both share, so the exit code and the `passed` key can never
5
+ * disagree.
6
+ */
7
+ import { type ValidationExceptionOutcome } from '../../core/furnace-validate-exceptions.js';
8
+ import type { ValidationIssue } from '../../types/furnace.js';
9
+ /** One finished `furnace validate` run. */
10
+ export interface FurnaceValidateReport {
11
+ /** Components the run judged, in validation order. */
12
+ components: string[];
13
+ /** Findings after `validationExceptions` were applied. */
14
+ outcome: ValidationExceptionOutcome;
15
+ /** `--max-warnings`, when given. */
16
+ maxWarnings?: number | undefined;
17
+ }
18
+ /** The verdict: counts, and whether errors or the warning budget fail it. */
19
+ export interface FurnaceValidateVerdict {
20
+ errors: number;
21
+ warnings: number;
22
+ /** True when `--max-warnings` was given and the warnings exceed it. */
23
+ warningBudgetExceeded: boolean;
24
+ passed: boolean;
25
+ }
26
+ /** Computes the verdict both renderings report. */
27
+ export declare function judgeValidateReport(report: FurnaceValidateReport): FurnaceValidateVerdict;
28
+ /**
29
+ * Prints one line per clean component and every finding of the rest.
30
+ * Findings on names outside `components` (a stale exception for a removed
31
+ * component, an orphaned scaffold) print after them.
32
+ */
33
+ export declare function renderValidateHuman(components: readonly string[], issues: readonly ValidationIssue[]): void;
34
+ /**
35
+ * Writes the `--json` document and seals stdout. Every finding is listed
36
+ * once in `issues` (stale-exception warnings included); `excepted` and
37
+ * `staleExceptions` restate the exception bookkeeping for a consumer that
38
+ * audits the list itself.
39
+ */
40
+ export declare function renderValidateJson(report: FurnaceValidateReport): void;
41
+ /**
42
+ * Parses `--max-warnings <n>`: a non-negative integer, or undefined when
43
+ * the flag was not given.
44
+ */
45
+ export declare function parseMaxWarnings(raw: string | undefined): number | undefined;
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `furnace validate` result rendering: the human per-component lines and
4
+ * the `--json` document (schemaVersion 1, docs/machine-output.md), plus
5
+ * the verdict both share, so the exit code and the `passed` key can never
6
+ * disagree.
7
+ */
8
+ import { countValidationSeverities, } from '../../core/furnace-validate-exceptions.js';
9
+ import { InvalidArgumentError } from '../../errors/base.js';
10
+ import { setStdoutSealed, success } from '../../utils/logger.js';
11
+ import { displayValidationIssues } from './validation-output.js';
12
+ /** Computes the verdict both renderings report. */
13
+ export function judgeValidateReport(report) {
14
+ const { errors, warnings } = countValidationSeverities(report.outcome.issues);
15
+ const warningBudgetExceeded = report.maxWarnings !== undefined && warnings > report.maxWarnings;
16
+ return {
17
+ errors,
18
+ warnings,
19
+ warningBudgetExceeded,
20
+ passed: errors === 0 && !warningBudgetExceeded,
21
+ };
22
+ }
23
+ /** Groups findings by component, keeping the report's component order first. */
24
+ function groupByComponent(components, issues) {
25
+ const grouped = new Map(components.map((name) => [name, []]));
26
+ for (const issue of issues) {
27
+ const list = grouped.get(issue.component) ?? [];
28
+ list.push(issue);
29
+ grouped.set(issue.component, list);
30
+ }
31
+ return grouped;
32
+ }
33
+ /**
34
+ * Prints one line per clean component and every finding of the rest.
35
+ * Findings on names outside `components` (a stale exception for a removed
36
+ * component, an orphaned scaffold) print after them.
37
+ */
38
+ export function renderValidateHuman(components, issues) {
39
+ for (const [name, componentIssues] of groupByComponent(components, issues)) {
40
+ if (componentIssues.length === 0) {
41
+ success(`${name} — all checks passed`);
42
+ }
43
+ else {
44
+ displayValidationIssues(componentIssues);
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Writes the `--json` document and seals stdout. Every finding is listed
50
+ * once in `issues` (stale-exception warnings included); `excepted` and
51
+ * `staleExceptions` restate the exception bookkeeping for a consumer that
52
+ * audits the list itself.
53
+ */
54
+ export function renderValidateJson(report) {
55
+ const verdict = judgeValidateReport(report);
56
+ const grouped = groupByComponent(report.components, report.outcome.issues);
57
+ const payload = {
58
+ schemaVersion: 1,
59
+ passed: verdict.passed,
60
+ summary: {
61
+ components: report.components.length,
62
+ errors: verdict.errors,
63
+ warnings: verdict.warnings,
64
+ excepted: report.outcome.excepted.length,
65
+ staleExceptions: report.outcome.stale.length,
66
+ },
67
+ ...(report.maxWarnings !== undefined ? { maxWarnings: report.maxWarnings } : {}),
68
+ components: report.components.map((name) => ({
69
+ name,
70
+ ...countValidationSeverities(grouped.get(name) ?? []),
71
+ })),
72
+ issues: report.outcome.issues,
73
+ excepted: report.outcome.excepted,
74
+ staleExceptions: report.outcome.stale,
75
+ };
76
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
77
+ setStdoutSealed(true);
78
+ }
79
+ /**
80
+ * Parses `--max-warnings <n>`: a non-negative integer, or undefined when
81
+ * the flag was not given.
82
+ */
83
+ export function parseMaxWarnings(raw) {
84
+ if (raw === undefined)
85
+ return undefined;
86
+ const value = Number(raw);
87
+ if (!Number.isInteger(value) || value < 0) {
88
+ throw new InvalidArgumentError('--max-warnings must be a non-negative integer.', '--max-warnings');
89
+ }
90
+ return value;
91
+ }
@@ -3,6 +3,6 @@ import type { FurnaceValidateOptions } from '../../types/commands/index.js';
3
3
  * Runs the furnace validate command to perform static analysis on components.
4
4
  * @param projectRoot - Root directory of the project
5
5
  * @param name - Optional component name to validate (validates all if omitted)
6
- * @param options - Optional command options (e.g. --fix)
6
+ * @param options - Optional command options (`--fix`, `--json`, `--max-warnings`)
7
7
  */
8
8
  export declare function furnaceValidateCommand(projectRoot: string, name?: string, options?: FurnaceValidateOptions): Promise<void>;
@@ -6,11 +6,12 @@ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js
6
6
  import { assertFurnaceReady } from '../../core/furnace-precondition.js';
7
7
  import { addCustomElementRegistration, addJarMnEntries, pruneStaleJarMnEntries, } from '../../core/furnace-registration.js';
8
8
  import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
9
+ import { applyValidationExceptions, } from '../../core/furnace-validate-exceptions.js';
9
10
  import { FurnaceError } from '../../errors/furnace.js';
10
11
  import { toError } from '../../utils/errors.js';
11
12
  import { pathExists } from '../../utils/fs.js';
12
- import { info, intro, note, outro, success, warn } from '../../utils/logger.js';
13
- import { displayValidationIssues } from './validation-output.js';
13
+ import { info, intro, isMachineOutputMode, note, outro, setMachineOutputMode, warn, } from '../../utils/logger.js';
14
+ import { judgeValidateReport, renderValidateHuman, renderValidateJson } from './validate-report.js';
14
15
  /** Checks that auto-fix can correct. */
15
16
  const FIXABLE_CHECKS = new Set([
16
17
  'missing-jar-mn-mjs',
@@ -114,133 +115,152 @@ async function autoFixIssues(projectRoot, issues) {
114
115
  }
115
116
  return fixed;
116
117
  }
118
+ /**
119
+ * Resolves the named component's type and directory.
120
+ * @returns undefined for a stock component (nothing local to validate)
121
+ */
122
+ function resolveNamedComponent(name, config, furnacePaths) {
123
+ if (name in config.overrides) {
124
+ return { type: 'override', componentDir: join(furnacePaths.overridesDir, name) };
125
+ }
126
+ if (name in config.custom) {
127
+ return { type: 'custom', componentDir: join(furnacePaths.customDir, name) };
128
+ }
129
+ if (config.stock.includes(name))
130
+ return undefined;
131
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
132
+ }
133
+ /**
134
+ * Runs the first validation pass: one named component, or every override
135
+ * and custom component. Returns undefined when there is nothing local to
136
+ * validate (a stock component, or no components at all), after saying so.
137
+ */
138
+ async function runValidationPass(projectRoot, name, config, furnacePaths) {
139
+ if (name) {
140
+ const target = resolveNamedComponent(name, config, furnacePaths);
141
+ if (target === undefined) {
142
+ info(`"${name}" is a stock component. Stock components are not validated locally.`);
143
+ return undefined;
144
+ }
145
+ if (!(await pathExists(target.componentDir))) {
146
+ throw new FurnaceError(`Component directory not found for "${name}".`, name);
147
+ }
148
+ const issues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
149
+ return { results: new Map([[name, issues]]), judged: (component) => component === name };
150
+ }
151
+ if (Object.keys(config.overrides).length === 0 && Object.keys(config.custom).length === 0) {
152
+ info('No components to validate.');
153
+ return undefined;
154
+ }
155
+ if (config.stock.length > 0) {
156
+ info(`Skipping ${config.stock.length} stock component(s) (no local files to validate).`);
157
+ }
158
+ return { results: await validateAllComponents(projectRoot), judged: () => true };
159
+ }
160
+ /**
161
+ * Runs `--fix` over the fixable findings, then re-validates the affected
162
+ * components and swaps their findings in, so the summary and the JSON
163
+ * report what auto-fix actually accomplished. The counter `autoFixIssues`
164
+ * returns only counts calls that did not throw, so a write that succeeded
165
+ * but did not resolve the finding would otherwise still be reported fixed.
166
+ */
167
+ async function applyAutoFix(projectRoot, config, furnacePaths, run, countedIssues) {
168
+ const fixableIssues = countedIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
169
+ if (fixableIssues.length === 0) {
170
+ info('\nNo auto-fixable issues found. Remaining issues require manual resolution.');
171
+ return;
172
+ }
173
+ await autoFixIssues(projectRoot, fixableIssues);
174
+ const reValidated = await reValidateComponents(projectRoot, config, furnacePaths, new Set(fixableIssues.map((issue) => issue.component)));
175
+ for (const [component, issues] of reValidated)
176
+ run.results.set(component, issues);
177
+ const fixableAfter = [...reValidated.values()]
178
+ .flat()
179
+ .filter((issue) => FIXABLE_CHECKS.has(issue.check)).length;
180
+ const actuallyFixed = Math.max(0, fixableIssues.length - fixableAfter);
181
+ if (actuallyFixed > 0) {
182
+ info(`\nAuto-fixed ${actuallyFixed} issue(s).`);
183
+ }
184
+ if (fixableAfter > 0) {
185
+ warn(`${fixableAfter} fixable issue(s) remain after auto-fix — investigate manually.`);
186
+ }
187
+ }
188
+ /** Applies `validationExceptions` to every finding of the run. */
189
+ function exceptRun(run, config) {
190
+ return applyValidationExceptions([...run.results.values()].flat(), config.validationExceptions, run.judged);
191
+ }
117
192
  /**
118
193
  * Runs the furnace validate command to perform static analysis on components.
119
194
  * @param projectRoot - Root directory of the project
120
195
  * @param name - Optional component name to validate (validates all if omitted)
121
- * @param options - Optional command options (e.g. --fix)
196
+ * @param options - Optional command options (`--fix`, `--json`, `--max-warnings`)
122
197
  */
123
198
  export async function furnaceValidateCommand(projectRoot, name, options = {}) {
199
+ const previousMachineOutputMode = isMachineOutputMode();
200
+ // `--json` owns stdout: every diagnostic routes to stderr from here on.
201
+ // On a throw the mode stays engaged and withErrorHandling resets it, so
202
+ // the refusal cannot land on stdout after the payload.
203
+ if (options.json === true)
204
+ setMachineOutputMode(true);
205
+ await runFurnaceValidate(projectRoot, name, options);
206
+ if (isMachineOutputMode() !== previousMachineOutputMode) {
207
+ setMachineOutputMode(previousMachineOutputMode);
208
+ }
209
+ }
210
+ async function runFurnaceValidate(projectRoot, name, options) {
124
211
  intro('Furnace Validate');
125
212
  // Gains the engine rung here: this was the one site in the family that
126
213
  // checked furnace.json alone, so a validate against a missing engine
127
214
  // produced downstream noise instead of the shared precondition refusal.
128
215
  const { config } = await assertFurnaceReady(projectRoot);
129
216
  const furnacePaths = getFurnacePaths(projectRoot);
130
- let totalErrors = 0;
131
- let totalWarnings = 0;
132
- let componentCount;
133
- let allIssues = [];
134
- if (name) {
135
- // --- Single component validation ---
136
- let type;
137
- let componentDir;
138
- if (name in config.overrides) {
139
- type = 'override';
140
- componentDir = join(furnacePaths.overridesDir, name);
141
- }
142
- else if (name in config.custom) {
143
- type = 'custom';
144
- componentDir = join(furnacePaths.customDir, name);
145
- }
146
- else if (config.stock.includes(name)) {
147
- info(`"${name}" is a stock component. Stock components are not validated locally.`);
148
- outro('Validation complete');
149
- return;
150
- }
151
- else {
152
- throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
153
- }
154
- if (!(await pathExists(componentDir))) {
155
- throw new FurnaceError(`Component directory not found for "${name}".`, name);
156
- }
157
- const issues = await validateComponent(componentDir, name, type, config, projectRoot);
158
- componentCount = 1;
159
- allIssues = issues;
160
- if (issues.length === 0) {
161
- success(`${name} — all checks passed`);
162
- }
163
- else {
164
- const [e, w] = displayValidationIssues(issues);
165
- totalErrors += e;
166
- totalWarnings += w;
217
+ const run = await runValidationPass(projectRoot, name, config, furnacePaths);
218
+ if (run === undefined) {
219
+ if (options.json === true) {
220
+ renderValidateJson({
221
+ components: [],
222
+ outcome: { issues: [], excepted: [], stale: [] },
223
+ maxWarnings: options.maxWarnings,
224
+ });
167
225
  }
226
+ outro(name ? 'Validation complete' : 'Done');
227
+ return;
168
228
  }
169
- else {
170
- // --- Validate all components ---
171
- const overrideCount = Object.keys(config.overrides).length;
172
- const customCount = Object.keys(config.custom).length;
173
- if (overrideCount === 0 && customCount === 0) {
174
- info('No components to validate.');
175
- outro('Done');
176
- return;
177
- }
178
- if (config.stock.length > 0) {
179
- info(`Skipping ${config.stock.length} stock component(s) (no local files to validate).`);
180
- }
181
- const results = await validateAllComponents(projectRoot);
182
- componentCount = results.size;
183
- for (const [componentName, issues] of results) {
184
- allIssues.push(...issues);
185
- if (issues.length === 0) {
186
- success(`${componentName} — all checks passed`);
187
- }
188
- else {
189
- const [e, w] = displayValidationIssues(issues);
190
- totalErrors += e;
191
- totalWarnings += w;
192
- }
193
- }
229
+ const firstPass = exceptRun(run, config);
230
+ renderValidateHuman([...run.results.keys()], firstPass.issues);
231
+ let outcome = firstPass;
232
+ if (options.fix && firstPass.issues.length > 0) {
233
+ await applyAutoFix(projectRoot, config, furnacePaths, run, firstPass.issues);
234
+ outcome = exceptRun(run, config);
194
235
  }
195
- // Auto-fix fixable issues when --fix is passed. The counter returned by
196
- // `autoFixIssues` only counts function calls that did not throw, so a
197
- // write that succeeded but did not actually resolve the issue
198
- // (addJarMnEntries appending to a file mach later ignores) would still
199
- // bump it. Re-validate the affected components and compute the real drop
200
- // in fixable issues so the reported number is honest.
201
- if (options.fix && allIssues.length > 0) {
202
- const fixableIssues = allIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
203
- if (fixableIssues.length > 0) {
204
- await autoFixIssues(projectRoot, fixableIssues);
205
- const reValidated = await reValidateComponents(projectRoot, config, furnacePaths, new Set(fixableIssues.map((issue) => issue.component)));
206
- const fixableBefore = fixableIssues.length;
207
- const fixableAfter = reValidated.issues.filter((issue) => FIXABLE_CHECKS.has(issue.check)).length;
208
- const actuallyFixed = Math.max(0, fixableBefore - fixableAfter);
209
- // Replace the pre-fix issue totals with the post-fix view so the
210
- // summary reflects current reality. Issues that auto-fix could not
211
- // address still count toward totalErrors / totalWarnings.
212
- totalErrors = reValidated.totalErrors;
213
- totalWarnings = reValidated.totalWarnings;
214
- if (actuallyFixed > 0) {
215
- info(`\nAuto-fixed ${actuallyFixed} issue(s).`);
216
- }
217
- if (fixableAfter > 0) {
218
- warn(`${fixableAfter} fixable issue(s) remain after auto-fix — investigate manually.`);
219
- }
220
- }
221
- else {
222
- info('\nNo auto-fixable issues found. Remaining issues require manual resolution.');
223
- }
224
- }
225
- // Summary
226
- note(`${totalErrors} error(s), ${totalWarnings} warning(s) across ${componentCount} component(s)`, 'Validation Summary');
227
- if (totalErrors > 0) {
236
+ const report = {
237
+ components: [...run.results.keys()],
238
+ outcome,
239
+ maxWarnings: options.maxWarnings,
240
+ };
241
+ const verdict = judgeValidateReport(report);
242
+ const excepted = outcome.excepted.length > 0 ? `, ${outcome.excepted.length} excepted` : '';
243
+ note(`${verdict.errors} error(s), ${verdict.warnings} warning(s)${excepted} across ${report.components.length} component(s)`, 'Validation Summary');
244
+ if (options.json === true)
245
+ renderValidateJson(report);
246
+ if (verdict.errors > 0) {
228
247
  const fixHint = options.fix ? '' : ' Use --fix to auto-correct registration issues.';
229
248
  info(`Fix the errors above and run "fireforge furnace validate" again.${fixHint}`);
230
- throw new FurnaceError(`Validation failed with ${totalErrors} error(s).`);
249
+ throw new FurnaceError(`Validation failed with ${verdict.errors} error(s).`);
250
+ }
251
+ if (verdict.warningBudgetExceeded) {
252
+ throw new FurnaceError(`Validation failed: ${verdict.warnings} warning(s) exceed --max-warnings ${String(options.maxWarnings)}. ` +
253
+ 'Fix them, or record a reviewed finding in furnace.json validationExceptions.');
231
254
  }
232
255
  outro('Validation passed');
233
256
  }
234
257
  /**
235
258
  * Re-validates a specific set of components after an auto-fix pass and
236
- * returns the post-fix issue list with the recomputed error / warning
237
- * totals. Used by the `--fix` path to honestly report what auto-fix
238
- * actually accomplished.
259
+ * returns each one's post-fix findings. Used by the `--fix` path to
260
+ * honestly report what auto-fix actually accomplished.
239
261
  */
240
262
  async function reValidateComponents(projectRoot, config, furnacePaths, componentNames) {
241
- const issues = [];
242
- let totalErrors = 0;
243
- let totalWarnings = 0;
263
+ const results = new Map();
244
264
  for (const componentName of componentNames) {
245
265
  let type;
246
266
  let componentDir;
@@ -258,14 +278,7 @@ async function reValidateComponents(projectRoot, config, furnacePaths, component
258
278
  }
259
279
  if (!(await pathExists(componentDir)))
260
280
  continue;
261
- const componentIssues = await validateComponent(componentDir, componentName, type, config, projectRoot);
262
- issues.push(...componentIssues);
263
- for (const issue of componentIssues) {
264
- if (issue.severity === 'error')
265
- totalErrors += 1;
266
- else
267
- totalWarnings += 1;
268
- }
281
+ results.set(componentName, await validateComponent(componentDir, componentName, type, config, projectRoot));
269
282
  }
270
- return { issues, totalErrors, totalWarnings };
283
+ return results;
271
284
  }
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
3
  import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
4
+ import { applyValidationExceptions } from '../../core/furnace-validate-exceptions.js';
4
5
  import { FurnaceError } from '../../errors/furnace.js';
5
6
  import { pathExists } from '../../utils/fs.js';
6
7
  import { error, info, outro, success, warn } from '../../utils/logger.js';
@@ -36,6 +37,14 @@ function filterProjectedDryRunIssues(issues, actions) {
36
37
  return true;
37
38
  });
38
39
  }
40
+ /**
41
+ * Drops the findings `furnace.json#validationExceptions` records as
42
+ * reviewed, so a deploy does not fail on a finding `furnace validate`
43
+ * accepts. Staleness is `furnace validate`'s report, not a deploy's.
44
+ */
45
+ function withoutExceptedIssues(issues, config) {
46
+ return applyValidationExceptions(issues, config.validationExceptions, () => false).issues;
47
+ }
39
48
  function resolveNamedValidationTarget(name, config, furnacePaths) {
40
49
  if (name in config.overrides) {
41
50
  return {
@@ -67,7 +76,8 @@ async function validateNamedTarget(args) {
67
76
  throw new FurnaceError(`Component directory not found for "${name}".`, name);
68
77
  }
69
78
  const rawIssues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
70
- const issues = isDryRun ? filterProjectedDryRunIssues(rawIssues, dryRunActions) : rawIssues;
79
+ const counted = withoutExceptedIssues(rawIssues, config);
80
+ const issues = isDryRun ? filterProjectedDryRunIssues(counted, dryRunActions) : counted;
71
81
  if (issues.length === 0) {
72
82
  success(`${name} — all checks passed`);
73
83
  return [0, 0];
@@ -130,9 +140,10 @@ export async function runDeployValidation(options) {
130
140
  continue;
131
141
  }
132
142
  componentCount++;
143
+ const counted = withoutExceptedIssues(issues, config);
133
144
  const projectedIssues = isDryRun
134
- ? filterProjectedDryRunIssues(issues, dryRunActions)
135
- : issues;
145
+ ? filterProjectedDryRunIssues(counted, dryRunActions)
146
+ : counted;
136
147
  if (projectedIssues.length === 0) {
137
148
  success(`${componentName} — all checks passed`);
138
149
  }