@hominis/fireforge 0.43.1 → 0.44.1

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 (478) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +3 -3
  3. package/dist/bin/fireforge.d.ts +4 -9
  4. package/dist/bin/fireforge.js +42 -40
  5. package/dist/build-info.json +4 -4
  6. package/dist/src/cli.js +74 -8
  7. package/dist/src/commands/bootstrap.js +17 -20
  8. package/dist/src/commands/build.js +25 -32
  9. package/dist/src/commands/config.js +34 -41
  10. package/dist/src/commands/discard.js +8 -9
  11. package/dist/src/commands/doctor-check-core.d.ts +13 -18
  12. package/dist/src/commands/doctor-check-core.js +8 -35
  13. package/dist/src/commands/doctor-external-toolchains.js +2 -2
  14. package/dist/src/commands/doctor-furnace-config-sync.d.ts +18 -0
  15. package/dist/src/commands/{doctor-furnace-manifest-sync.js → doctor-furnace-config-sync.js} +27 -26
  16. package/dist/src/commands/doctor-furnace-jar.d.ts +7 -9
  17. package/dist/src/commands/doctor-furnace-jar.js +7 -9
  18. package/dist/src/commands/doctor-furnace.js +21 -24
  19. package/dist/src/commands/doctor-orphaned-harness.d.ts +16 -20
  20. package/dist/src/commands/doctor-orphaned-harness.js +18 -44
  21. package/dist/src/commands/doctor-patch-manifest.d.ts +8 -0
  22. package/dist/src/commands/doctor-patch-manifest.js +155 -0
  23. package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
  24. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  25. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  26. package/dist/src/commands/doctor-working-tree.js +11 -18
  27. package/dist/src/commands/doctor.d.ts +1 -1
  28. package/dist/src/commands/doctor.js +64 -96
  29. package/dist/src/commands/download.js +32 -48
  30. package/dist/src/commands/export-all.js +29 -33
  31. package/dist/src/commands/export-flow.js +6 -6
  32. package/dist/src/commands/export-placement-conflicts.js +10 -9
  33. package/dist/src/commands/export-placement-gate.js +5 -5
  34. package/dist/src/commands/export-shared.d.ts +30 -33
  35. package/dist/src/commands/export-shared.js +45 -43
  36. package/dist/src/commands/export.js +21 -27
  37. package/dist/src/commands/furnace/apply.js +14 -23
  38. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  39. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  40. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  41. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  42. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  43. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  44. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  45. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  46. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  47. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  48. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  49. package/dist/src/commands/furnace/create-readback.js +12 -13
  50. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  51. package/dist/src/commands/furnace/create-templates.js +35 -30
  52. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  53. package/dist/src/commands/furnace/create.js +33 -23
  54. package/dist/src/commands/furnace/deploy.js +16 -24
  55. package/dist/src/commands/furnace/diff.js +15 -10
  56. package/dist/src/commands/furnace/init.js +33 -36
  57. package/dist/src/commands/furnace/override.js +23 -25
  58. package/dist/src/commands/furnace/preview.js +47 -67
  59. package/dist/src/commands/furnace/refresh.js +12 -16
  60. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  61. package/dist/src/commands/furnace/remove-mutations.js +14 -17
  62. package/dist/src/commands/furnace/remove-state.js +1 -1
  63. package/dist/src/commands/furnace/remove.js +33 -38
  64. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  65. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  66. package/dist/src/commands/furnace/rename.js +59 -69
  67. package/dist/src/commands/furnace/scan.js +19 -33
  68. package/dist/src/commands/furnace/status.js +12 -9
  69. package/dist/src/commands/furnace/sync.js +9 -16
  70. package/dist/src/commands/furnace/validate.js +20 -20
  71. package/dist/src/commands/import.js +48 -37
  72. package/dist/src/commands/lint-per-patch.js +15 -10
  73. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  74. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  75. package/dist/src/commands/lint-report.js +6 -7
  76. package/dist/src/commands/lint.d.ts +15 -16
  77. package/dist/src/commands/lint.js +86 -102
  78. package/dist/src/commands/package.js +6 -11
  79. package/dist/src/commands/patch/compact.js +1 -1
  80. package/dist/src/commands/patch/delete.js +24 -33
  81. package/dist/src/commands/patch/index.d.ts +5 -6
  82. package/dist/src/commands/patch/index.js +10 -12
  83. package/dist/src/commands/patch/lint-ignore.js +14 -13
  84. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  85. package/dist/src/commands/patch/move-files-create.js +8 -8
  86. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  87. package/dist/src/commands/patch/move-files-into.js +8 -8
  88. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  89. package/dist/src/commands/patch/patch-context.js +2 -3
  90. package/dist/src/commands/patch/rename.d.ts +10 -11
  91. package/dist/src/commands/patch/rename.js +11 -12
  92. package/dist/src/commands/patch/reorder.js +8 -9
  93. package/dist/src/commands/patch/split-plan.js +1 -1
  94. package/dist/src/commands/patch/split.d.ts +8 -10
  95. package/dist/src/commands/patch/split.js +9 -11
  96. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  97. package/dist/src/commands/patch/staged-dependency.js +7 -3
  98. package/dist/src/commands/patch/tier.d.ts +9 -9
  99. package/dist/src/commands/patch/tier.js +18 -15
  100. package/dist/src/commands/re-export-adjacent.js +9 -8
  101. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  102. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  103. package/dist/src/commands/re-export-drift.js +10 -10
  104. package/dist/src/commands/re-export-files.d.ts +6 -7
  105. package/dist/src/commands/re-export-files.js +34 -44
  106. package/dist/src/commands/re-export-lint.d.ts +10 -13
  107. package/dist/src/commands/re-export-lint.js +16 -14
  108. package/dist/src/commands/re-export-options.d.ts +6 -6
  109. package/dist/src/commands/re-export-options.js +6 -6
  110. package/dist/src/commands/re-export-register.d.ts +4 -10
  111. package/dist/src/commands/re-export-register.js +5 -11
  112. package/dist/src/commands/re-export-scan.js +1 -1
  113. package/dist/src/commands/re-export-single.d.ts +3 -3
  114. package/dist/src/commands/re-export-single.js +33 -25
  115. package/dist/src/commands/re-export.js +13 -21
  116. package/dist/src/commands/rebase/abort.js +3 -3
  117. package/dist/src/commands/rebase/confirm.js +2 -1
  118. package/dist/src/commands/rebase/continue.d.ts +1 -1
  119. package/dist/src/commands/rebase/continue.js +14 -9
  120. package/dist/src/commands/rebase/index.js +11 -11
  121. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  122. package/dist/src/commands/rebase/patch-loop.js +60 -39
  123. package/dist/src/commands/rebase/summary.js +1 -1
  124. package/dist/src/commands/register.js +5 -7
  125. package/dist/src/commands/reset.js +2 -1
  126. package/dist/src/commands/resolve.d.ts +16 -13
  127. package/dist/src/commands/resolve.js +30 -33
  128. package/dist/src/commands/run.js +33 -35
  129. package/dist/src/commands/setup-support.js +15 -19
  130. package/dist/src/commands/setup.js +13 -9
  131. package/dist/src/commands/status-check.d.ts +6 -6
  132. package/dist/src/commands/status-json.d.ts +6 -6
  133. package/dist/src/commands/status-json.js +5 -0
  134. package/dist/src/commands/status-output.d.ts +5 -5
  135. package/dist/src/commands/status-output.js +6 -6
  136. package/dist/src/commands/status-ownership.d.ts +16 -14
  137. package/dist/src/commands/status-ownership.js +16 -14
  138. package/dist/src/commands/status.js +42 -34
  139. package/dist/src/commands/test-build-phase.js +26 -13
  140. package/dist/src/commands/test-diagnose.js +44 -44
  141. package/dist/src/commands/test-register.js +1 -0
  142. package/dist/src/commands/test-run.d.ts +14 -16
  143. package/dist/src/commands/test-run.js +21 -13
  144. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  145. package/dist/src/commands/test-stale-gate.js +15 -16
  146. package/dist/src/commands/test-verdict.d.ts +20 -3
  147. package/dist/src/commands/test-verdict.js +30 -0
  148. package/dist/src/commands/test.js +83 -76
  149. package/dist/src/commands/token-coverage.js +3 -4
  150. package/dist/src/commands/token.js +16 -18
  151. package/dist/src/commands/tree.js +47 -31
  152. package/dist/src/commands/typecheck.d.ts +10 -12
  153. package/dist/src/commands/typecheck.js +24 -27
  154. package/dist/src/commands/verify.js +13 -15
  155. package/dist/src/commands/watch.js +21 -23
  156. package/dist/src/commands/wire.js +52 -62
  157. package/dist/src/core/ast-utils.d.ts +8 -7
  158. package/dist/src/core/ast-utils.js +8 -7
  159. package/dist/src/core/branding.d.ts +9 -14
  160. package/dist/src/core/branding.js +7 -11
  161. package/dist/src/core/browser-wire.d.ts +1 -1
  162. package/dist/src/core/browser-wire.js +14 -19
  163. package/dist/src/core/build-audit-platform.js +25 -33
  164. package/dist/src/core/build-audit-registration.js +17 -25
  165. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  166. package/dist/src/core/build-audit-resolve.js +39 -36
  167. package/dist/src/core/build-audit-transforms.js +18 -19
  168. package/dist/src/core/build-audit.js +28 -42
  169. package/dist/src/core/build-baseline-types.d.ts +29 -35
  170. package/dist/src/core/build-baseline.d.ts +5 -4
  171. package/dist/src/core/build-baseline.js +14 -14
  172. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  173. package/dist/src/core/build-overwrite-guard.js +17 -27
  174. package/dist/src/core/build-prepare.d.ts +2 -2
  175. package/dist/src/core/build-prepare.js +8 -9
  176. package/dist/src/core/config-mutate.js +20 -19
  177. package/dist/src/core/config-state.js +12 -4
  178. package/dist/src/core/config-validate.js +8 -5
  179. package/dist/src/core/config.d.ts +5 -8
  180. package/dist/src/core/config.js +7 -16
  181. package/dist/src/core/coverage-extend.d.ts +15 -13
  182. package/dist/src/core/coverage-extend.js +15 -13
  183. package/dist/src/core/destructive.d.ts +31 -9
  184. package/dist/src/core/destructive.js +29 -26
  185. package/dist/src/core/diff-hunks.js +10 -12
  186. package/dist/src/core/discard-baseline.d.ts +8 -8
  187. package/dist/src/core/discard-baseline.js +16 -15
  188. package/dist/src/core/display-state.d.ts +12 -14
  189. package/dist/src/core/display-state.js +12 -14
  190. package/dist/src/core/engine-changes.d.ts +2 -4
  191. package/dist/src/core/engine-changes.js +2 -4
  192. package/dist/src/core/engine-precondition.d.ts +28 -13
  193. package/dist/src/core/engine-precondition.js +25 -10
  194. package/dist/src/core/engine-session-lock.d.ts +47 -21
  195. package/dist/src/core/engine-session-lock.js +168 -33
  196. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  197. package/dist/src/core/export-moved-code-shape.js +10 -10
  198. package/dist/src/core/file-lock.d.ts +62 -5
  199. package/dist/src/core/file-lock.js +196 -59
  200. package/dist/src/core/firefox-cache.js +1 -3
  201. package/dist/src/core/firefox-extract.js +10 -14
  202. package/dist/src/core/firefox.d.ts +7 -5
  203. package/dist/src/core/firefox.js +4 -5
  204. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  205. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  206. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  207. package/dist/src/core/furnace-apply-ftl.js +16 -13
  208. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  209. package/dist/src/core/furnace-apply-helpers.js +30 -21
  210. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  211. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  212. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  213. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  214. package/dist/src/core/furnace-apply.d.ts +5 -8
  215. package/dist/src/core/furnace-apply.js +19 -24
  216. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  217. package/dist/src/core/furnace-checksum-utils.js +16 -0
  218. package/dist/src/core/furnace-config.d.ts +21 -30
  219. package/dist/src/core/furnace-config.js +32 -54
  220. package/dist/src/core/furnace-constants.d.ts +8 -0
  221. package/dist/src/core/furnace-constants.js +10 -2
  222. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  223. package/dist/src/core/furnace-css-fragments.js +5 -5
  224. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  225. package/dist/src/core/furnace-jsconfig.js +11 -12
  226. package/dist/src/core/furnace-marker.d.ts +7 -8
  227. package/dist/src/core/furnace-marker.js +7 -8
  228. package/dist/src/core/furnace-operation.d.ts +31 -0
  229. package/dist/src/core/furnace-operation.js +54 -17
  230. package/dist/src/core/furnace-precondition.d.ts +61 -0
  231. package/dist/src/core/furnace-precondition.js +67 -0
  232. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  233. package/dist/src/core/furnace-registration-remove.js +6 -7
  234. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  235. package/dist/src/core/furnace-registration-validate.js +20 -1
  236. package/dist/src/core/furnace-registration.d.ts +5 -6
  237. package/dist/src/core/furnace-registration.js +10 -12
  238. package/dist/src/core/furnace-rollback.js +7 -7
  239. package/dist/src/core/furnace-scanner.d.ts +1 -0
  240. package/dist/src/core/furnace-scanner.js +1 -2
  241. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  242. package/dist/src/core/furnace-stale-export.js +10 -10
  243. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  244. package/dist/src/core/furnace-state-persist.js +6 -8
  245. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  246. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  247. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  248. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  249. package/dist/src/core/furnace-validate-helpers.js +0 -491
  250. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  251. package/dist/src/core/furnace-validate-registration.js +115 -41
  252. package/dist/src/core/furnace-validate.d.ts +16 -0
  253. package/dist/src/core/furnace-validate.js +188 -145
  254. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  255. package/dist/src/core/furnace-version-drift.js +10 -11
  256. package/dist/src/core/git-base.d.ts +21 -9
  257. package/dist/src/core/git-base.js +24 -9
  258. package/dist/src/core/git-diff.d.ts +20 -9
  259. package/dist/src/core/git-diff.js +148 -63
  260. package/dist/src/core/git-file-ops.d.ts +19 -19
  261. package/dist/src/core/git-file-ops.js +19 -19
  262. package/dist/src/core/git-readonly-index.d.ts +53 -19
  263. package/dist/src/core/git-readonly-index.js +75 -31
  264. package/dist/src/core/git-status.d.ts +5 -6
  265. package/dist/src/core/git-status.js +5 -6
  266. package/dist/src/core/git.d.ts +9 -10
  267. package/dist/src/core/git.js +57 -71
  268. package/dist/src/core/license-headers.d.ts +17 -23
  269. package/dist/src/core/license-headers.js +17 -23
  270. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  271. package/dist/src/core/mach-build-artifacts.js +57 -19
  272. package/dist/src/core/mach-error-hints.js +42 -29
  273. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  274. package/dist/src/core/mach-known-noise-filter.js +18 -20
  275. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  276. package/dist/src/core/mach-mozconfig.js +30 -0
  277. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  278. package/dist/src/core/mach-resource-shim.js +50 -67
  279. package/dist/src/core/mach.d.ts +37 -41
  280. package/dist/src/core/mach.js +50 -51
  281. package/dist/src/core/marionette-port.d.ts +36 -1
  282. package/dist/src/core/marionette-port.js +96 -31
  283. package/dist/src/core/marionette-preflight.d.ts +8 -12
  284. package/dist/src/core/marionette-preflight.js +86 -52
  285. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  286. package/dist/src/core/mochitest-server-port.js +119 -0
  287. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  288. package/dist/src/core/module-resolution-preflight.js +9 -11
  289. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  290. package/dist/src/core/moz-manifest-helpers.js +163 -0
  291. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  292. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  293. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  294. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  295. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  296. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  297. package/dist/src/core/ownership-table.d.ts +14 -16
  298. package/dist/src/core/ownership-table.js +14 -16
  299. package/dist/src/core/parser-fallback.d.ts +15 -1
  300. package/dist/src/core/parser-fallback.js +22 -3
  301. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  302. package/dist/src/core/patch-apply-fuzz.js +15 -18
  303. package/dist/src/core/patch-apply.d.ts +13 -34
  304. package/dist/src/core/patch-apply.js +16 -76
  305. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  306. package/dist/src/core/patch-artifact-normalize.js +13 -15
  307. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  308. package/dist/src/core/patch-export-metadata.js +13 -4
  309. package/dist/src/core/patch-export-update.d.ts +13 -1
  310. package/dist/src/core/patch-export-update.js +25 -8
  311. package/dist/src/core/patch-export.d.ts +15 -8
  312. package/dist/src/core/patch-export.js +10 -11
  313. package/dist/src/core/patch-files.d.ts +5 -6
  314. package/dist/src/core/patch-files.js +5 -6
  315. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  316. package/dist/src/core/patch-identifier-suggest.js +14 -19
  317. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  318. package/dist/src/core/patch-lint-binary.js +39 -0
  319. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  320. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  321. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  322. package/dist/src/core/patch-lint-checkjs.js +41 -20
  323. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  324. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  325. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  326. package/dist/src/core/patch-lint-creators.js +8 -8
  327. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  328. package/dist/src/core/patch-lint-cross.js +16 -16
  329. package/dist/src/core/patch-lint-css.d.ts +3 -5
  330. package/dist/src/core/patch-lint-css.js +17 -22
  331. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  332. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  333. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  334. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  335. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  336. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  337. package/dist/src/core/patch-lint-observer.js +7 -7
  338. package/dist/src/core/patch-lint.d.ts +38 -29
  339. package/dist/src/core/patch-lint.js +89 -121
  340. package/dist/src/core/patch-lock.d.ts +1 -1
  341. package/dist/src/core/patch-lock.js +45 -9
  342. package/dist/src/core/patch-manifest-consistency.d.ts +57 -6
  343. package/dist/src/core/patch-manifest-consistency.js +111 -40
  344. package/dist/src/core/patch-manifest-files-affected.d.ts +43 -0
  345. package/dist/src/core/patch-manifest-files-affected.js +77 -0
  346. package/dist/src/core/patch-manifest-io.js +14 -18
  347. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  348. package/dist/src/core/patch-manifest-resolve.js +9 -12
  349. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  350. package/dist/src/core/patch-manifest-validate.js +25 -13
  351. package/dist/src/core/patch-manifest.d.ts +2 -1
  352. package/dist/src/core/patch-manifest.js +2 -1
  353. package/dist/src/core/patch-parse.d.ts +24 -13
  354. package/dist/src/core/patch-parse.js +19 -14
  355. package/dist/src/core/patch-policy.d.ts +2 -2
  356. package/dist/src/core/patch-policy.js +2 -2
  357. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  358. package/dist/src/core/patch-registration-refs.js +9 -9
  359. package/dist/src/core/process-cpu-time.d.ts +12 -0
  360. package/dist/src/core/process-cpu-time.js +52 -0
  361. package/dist/src/core/rebase-session.d.ts +38 -26
  362. package/dist/src/core/rebase-session.js +55 -28
  363. package/dist/src/core/register-browser-content.js +9 -74
  364. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  365. package/dist/src/core/register-module.js +10 -43
  366. package/dist/src/core/register-scaffold.d.ts +8 -8
  367. package/dist/src/core/register-scaffold.js +10 -10
  368. package/dist/src/core/register-shared-css.d.ts +9 -12
  369. package/dist/src/core/register-shared-css.js +17 -87
  370. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  371. package/dist/src/core/register-xpcshell-test.js +7 -8
  372. package/dist/src/core/shared-ftl.js +12 -4
  373. package/dist/src/core/state-file.d.ts +45 -0
  374. package/dist/src/core/state-file.js +34 -0
  375. package/dist/src/core/status-binary.d.ts +18 -11
  376. package/dist/src/core/status-binary.js +19 -12
  377. package/dist/src/core/status-classify.d.ts +30 -36
  378. package/dist/src/core/status-classify.js +21 -26
  379. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  380. package/dist/src/core/test-changed-prefs.js +78 -0
  381. package/dist/src/core/test-harness-crash.d.ts +25 -63
  382. package/dist/src/core/test-harness-crash.js +121 -142
  383. package/dist/src/core/test-harness-signature.d.ts +18 -0
  384. package/dist/src/core/test-harness-signature.js +13 -0
  385. package/dist/src/core/test-path-scope.d.ts +12 -15
  386. package/dist/src/core/test-path-scope.js +15 -18
  387. package/dist/src/core/test-stale-check.d.ts +17 -17
  388. package/dist/src/core/test-stale-check.js +50 -43
  389. package/dist/src/core/test-stall-triage.d.ts +51 -0
  390. package/dist/src/core/test-stall-triage.js +126 -0
  391. package/dist/src/core/token-category.js +66 -14
  392. package/dist/src/core/token-coverage.js +9 -9
  393. package/dist/src/core/token-dark-mode.d.ts +9 -11
  394. package/dist/src/core/token-dark-mode.js +29 -13
  395. package/dist/src/core/token-docs.d.ts +2 -0
  396. package/dist/src/core/token-docs.js +3 -1
  397. package/dist/src/core/token-manager.d.ts +10 -2
  398. package/dist/src/core/token-manager.js +17 -8
  399. package/dist/src/core/token-scaffold.d.ts +7 -7
  400. package/dist/src/core/token-scaffold.js +7 -7
  401. package/dist/src/core/token-variant.d.ts +4 -4
  402. package/dist/src/core/token-variant.js +28 -6
  403. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  404. package/dist/src/core/toolchain-preflight.js +28 -31
  405. package/dist/src/core/tree-guard.js +12 -2
  406. package/dist/src/core/tree-store.d.ts +41 -27
  407. package/dist/src/core/tree-store.js +68 -43
  408. package/dist/src/core/typecheck-shim.d.ts +23 -24
  409. package/dist/src/core/typecheck-shim.js +23 -24
  410. package/dist/src/core/typecheck.js +15 -9
  411. package/dist/src/core/wire-destroy.d.ts +3 -3
  412. package/dist/src/core/wire-destroy.js +7 -15
  413. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  414. package/dist/src/core/wire-dom-fragment.js +11 -44
  415. package/dist/src/core/wire-init.d.ts +2 -3
  416. package/dist/src/core/wire-init.js +8 -19
  417. package/dist/src/core/wire-subscript.d.ts +2 -3
  418. package/dist/src/core/wire-subscript.js +4 -13
  419. package/dist/src/core/wire-utils.d.ts +7 -8
  420. package/dist/src/core/wire-utils.js +7 -8
  421. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  422. package/dist/src/core/xpcshell-appdir.js +38 -45
  423. package/dist/src/errors/base.d.ts +37 -16
  424. package/dist/src/errors/base.js +37 -16
  425. package/dist/src/errors/build.d.ts +13 -1
  426. package/dist/src/errors/build.js +14 -2
  427. package/dist/src/errors/codes.d.ts +16 -0
  428. package/dist/src/errors/codes.js +16 -0
  429. package/dist/src/errors/furnace.d.ts +4 -4
  430. package/dist/src/errors/git.d.ts +6 -10
  431. package/dist/src/errors/git.js +6 -10
  432. package/dist/src/errors/patch.d.ts +7 -7
  433. package/dist/src/errors/patch.js +7 -7
  434. package/dist/src/errors/rebase.d.ts +4 -4
  435. package/dist/src/errors/rebase.js +4 -4
  436. package/dist/src/index.d.ts +7 -1
  437. package/dist/src/index.js +5 -0
  438. package/dist/src/types/commands/index.d.ts +2 -2
  439. package/dist/src/types/commands/options.d.ts +111 -70
  440. package/dist/src/types/commands/patches.d.ts +24 -34
  441. package/dist/src/types/commands/project.d.ts +5 -34
  442. package/dist/src/types/config.d.ts +25 -23
  443. package/dist/src/types/config.js +6 -1
  444. package/dist/src/types/furnace.d.ts +37 -13
  445. package/dist/src/types/furnace.js +19 -1
  446. package/dist/src/types/index.d.ts +3 -3
  447. package/dist/src/utils/build-info.d.ts +7 -8
  448. package/dist/src/utils/build-info.js +12 -11
  449. package/dist/src/utils/errors.d.ts +6 -8
  450. package/dist/src/utils/errors.js +6 -8
  451. package/dist/src/utils/logger.d.ts +27 -10
  452. package/dist/src/utils/logger.js +28 -16
  453. package/dist/src/utils/machine-output.d.ts +12 -0
  454. package/dist/src/utils/machine-output.js +33 -0
  455. package/dist/src/utils/options.d.ts +31 -14
  456. package/dist/src/utils/options.js +86 -30
  457. package/dist/src/utils/paths.d.ts +10 -8
  458. package/dist/src/utils/paths.js +10 -8
  459. package/dist/src/utils/platform.d.ts +0 -25
  460. package/dist/src/utils/platform.js +1 -42
  461. package/dist/src/utils/process-group.d.ts +9 -4
  462. package/dist/src/utils/process-group.js +19 -5
  463. package/dist/src/utils/process.d.ts +42 -7
  464. package/dist/src/utils/process.js +44 -17
  465. package/dist/src/utils/ps-duration.d.ts +31 -0
  466. package/dist/src/utils/ps-duration.js +70 -0
  467. package/dist/src/utils/regex.d.ts +9 -10
  468. package/dist/src/utils/regex.js +9 -10
  469. package/dist/src/utils/validation.d.ts +58 -28
  470. package/dist/src/utils/validation.js +60 -33
  471. package/package.json +3 -3
  472. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  473. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  474. package/dist/src/core/manifest-helpers.d.ts +0 -38
  475. package/dist/src/core/manifest-helpers.js +0 -120
  476. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  477. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  478. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -4,10 +4,10 @@ import { join } from 'node:path';
4
4
  import { getProjectPaths, loadConfig } from '../../core/config.js';
5
5
  import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
6
6
  import { isComponentSourceFile, resolveFtlChromeSubPath, resolveFtlDir, resolveFtlLocaleJarMnPath, tagNameToClassName, } from '../../core/furnace-constants.js';
7
- import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
7
+ import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
8
8
  import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration, removeJarMnEntries, removeLocaleFtlJarMnEntry, } from '../../core/furnace-registration.js';
9
9
  import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, } from '../../core/furnace-registration-validate.js';
10
- import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
10
+ import { createRollbackJournal, recordCreatedDir, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
11
  import { getStoriesDir } from '../../core/furnace-stories.js';
12
12
  import { InvalidArgumentError } from '../../errors/base.js';
13
13
  import { FurnaceError } from '../../errors/furnace.js';
@@ -83,15 +83,14 @@ async function renameTestFiles(engineDir, projectRoot, oldName, newName, journal
83
83
  }
84
84
  }
85
85
  /**
86
- * Removes the deployed custom-widget directory at the old target path so
87
- * a subsequent `furnace apply` is the single writer of the new name's
86
+ * Removes the deployed custom-widget directory at the old target path so a
87
+ * subsequent `furnace apply` is the single writer of the new name's
88
88
  * deployment. Best-effort: logs a warning but never blocks the rename.
89
89
  *
90
- * 2026-04-21 eval: renaming `ff-chip-row` → `ff-chip-stack` registered
91
- * and deployed the new name correctly but left `engine/toolkit/content/
92
- * widgets/ff-chip-row/` in place. Subsequent `furnace sync` runs could
93
- * not clear the stale widget, and packaging would have pulled in both
94
- * copies. The snapshot is taken before the remove so the rollback
90
+ * Without it, a rename registers and deploys the new name correctly but
91
+ * leaves `engine/toolkit/content/widgets/<old>/` in place — subsequent
92
+ * `furnace sync` runs cannot clear the stale widget, and packaging pulls in
93
+ * both copies. The snapshot is taken before the remove so the rollback
95
94
  * journal restores the old directory if any later step in
96
95
  * `performRenameMutations` fails.
97
96
  */
@@ -110,19 +109,18 @@ async function removeStaleDeployedComponentDir(engineDir, oldTargetPath, journal
110
109
  }
111
110
  /**
112
111
  * Renames the mochikit test scaffold produced by `furnace create
113
- * --with-tests` when the default test style is used. The scaffold lives
114
- * at `engine/toolkit/content/tests/widgets/test_<name>.html`, and the
115
- * accompanying `chrome.toml` entry names the same file. Neither piece
116
- * was handled by the pre-0.16.0 rename, so operators were left with a
117
- * `test_<old>.html` file that still imported `chrome://global/content/
118
- * elements/<old>.mjs` and referenced `customElements.whenDefined("<old>")`
119
- * — the test ran against a component that no longer existed under that
120
- * name and either failed or (if the old component was still deployed)
121
- * passed for the wrong reason.
112
+ * --with-tests` when the default test style is used. The scaffold lives at
113
+ * `engine/toolkit/content/tests/widgets/test_<name>.html`, and the
114
+ * accompanying `chrome.toml` entry names the same file. Leaving both
115
+ * unhandled produces a `test_<old>.html` that still imports
116
+ * `chrome://global/content/elements/<old>.mjs` and references
117
+ * `customElements.whenDefined("<old>")` — running against a component that
118
+ * no longer exists under that name, and either failing or (if the old
119
+ * component is still deployed) passing for the wrong reason.
122
120
  *
123
- * Best-effort: individual failures log a warning. The same journal used
124
- * for the rest of the rename snapshots every touched file so a later
125
- * failure rolls the pair back together.
121
+ * Best-effort: individual failures log a warning. The same journal used for
122
+ * the rest of the rename snapshots every touched file so a later failure
123
+ * rolls the pair back together.
126
124
  */
127
125
  async function renameMochikitTestFiles(engineDir, oldName, newName, journal) {
128
126
  const testDir = join(engineDir, 'toolkit/content/tests/widgets');
@@ -223,13 +221,13 @@ async function performRenameMutations(args) {
223
221
  continue;
224
222
  const oldFileName = entry.name;
225
223
  // Rename only when the filename starts with the component name — the
226
- // scaffolding convention for both create and override is `${name}.ext`.
227
- // A plain `replace(oldName, newName)` produced wrong results when the
228
- // old name occurred more than once (e.g. `foo-foo.mjs` renamed `foo` →
229
- // `bar` became `bar-foo.mjs` instead of `bar-bar.mjs`) and also when
230
- // the old name appeared inside a file that was not the component
231
- // scaffold itself (e.g. a sibling helper). Unrelated files (stray
232
- // assets, editor backups) are copied verbatim.
224
+ // scaffolding convention for both create and override is
225
+ // `${name}.ext`. A plain `replace(oldName, newName)` produces wrong
226
+ // results when the old name occurs more than once (`foo-foo.mjs`
227
+ // renamed `foo` → `bar` becomes `bar-foo.mjs` instead of
228
+ // `bar-bar.mjs`) and when the old name appears inside a file that is
229
+ // not the component scaffold itself. Unrelated files (stray assets,
230
+ // editor backups) are copied verbatim.
233
231
  const newFileName = renameComponentFileName(oldFileName, oldName, newName);
234
232
  const oldPath = join(oldDir, oldFileName);
235
233
  const newPath = join(newDir, newFileName);
@@ -279,28 +277,25 @@ async function performRenameMutations(args) {
279
277
  // 7. Rename test files created by `furnace create --with-tests` (custom only).
280
278
  if (isCustom && (await pathExists(args.engineDir))) {
281
279
  await renameTestFiles(args.engineDir, projectRoot, oldName, newName, journal);
282
- // Mochikit scaffold + widgets/chrome.toml live in a different
280
+ // The mochikit scaffold and widgets/chrome.toml live in a different
283
281
  // tree than browser.toml-registered browser-chrome tests, so
284
- // renameTestFiles doesn't reach them. 2026-04-21 eval: a rename
285
- // left `engine/toolkit/content/tests/widgets/test_<old>.html`
286
- // and its `chrome.toml` entry pointing at the old name, which
287
- // either failed the test run outright or (worse) passed for the
288
- // wrong component.
282
+ // renameTestFiles does not reach them — leaving
283
+ // `engine/toolkit/content/tests/widgets/test_<old>.html` and its
284
+ // `chrome.toml` entry pointing at the old name, which either fails
285
+ // the test run outright or passes for the wrong component.
289
286
  await renameMochikitTestFiles(args.engineDir, oldName, newName, journal);
290
- // 2026-04-24 eval Finding 5: xpcshell scaffolds live in yet
291
- // another tree (`browser/base/content/test/<binary>-xpcshell/
292
- // <name>/`). Before this call, renaming a component scaffolded
293
- // with `--with-tests --xpcshell` left a directory whose name
294
- // still referenced the pre-rename component, plus a test file
295
- // whose underscored name referenced the old tag — both of
296
- // which then failed to match the new component.
287
+ // xpcshell scaffolds live in yet another tree
288
+ // (`browser/base/content/test/<binary>-xpcshell/<name>/`). Without
289
+ // this call, renaming a component scaffolded with
290
+ // `--with-tests --xpcshell` leaves a directory whose name still
291
+ // references the pre-rename component, plus a test file whose
292
+ // underscored name references the old tag.
297
293
  await renameXpcshellTestFiles(args.engineDir, projectRoot, oldName, newName, journal);
298
294
  // Clear the stale deployed component directory so the next
299
- // `furnace apply` is the single writer of the new name's
300
- // deployment. Without this, eval runs showed the old widget
301
- // still living at `engine/toolkit/content/widgets/<old>/`
302
- // alongside the newly-deployed `engine/toolkit/content/
303
- // widgets/<new>/`, with no signal to `status` / `verify`.
295
+ // `furnace apply` is the single writer of the new name's deployment.
296
+ // Without it the old widget stays at
297
+ // `engine/toolkit/content/widgets/<old>/` alongside the
298
+ // newly-deployed `<new>/`, with no signal to `status` / `verify`.
304
299
  if (oldCustomTargetPath) {
305
300
  await removeStaleDeployedComponentDir(args.engineDir, oldCustomTargetPath, journal);
306
301
  }
@@ -308,9 +303,9 @@ async function performRenameMutations(args) {
308
303
  info(`Renamed ${componentType} component: ${oldName} → ${newName}`);
309
304
  }
310
305
  catch (error) {
311
- // This body owns its rollback end to end, so tell the lifecycle wrapper
312
- // not to restore the same journal again on the way out.
313
- ctx.markRolledBack();
306
+ // Extra best-effort step this site owns: the half-created new
307
+ // directory is not in the journal, so drop it before the shared
308
+ // restore puts the old one back.
314
309
  try {
315
310
  if (await pathExists(newDir)) {
316
311
  await removeDir(newDir);
@@ -319,14 +314,12 @@ async function performRenameMutations(args) {
319
314
  catch {
320
315
  // Best effort cleanup
321
316
  }
322
- try {
323
- await restoreRollbackJournalOrThrow(journal, `Failed to rename component "${oldName}" to "${newName}"`);
324
- }
325
- catch (rollbackError) {
326
- await recordFurnaceRollbackFailure(projectRoot, 'rename-rollback', `rename "${oldName}" → "${newName}": ${toError(rollbackError).message}`);
327
- throw rollbackError;
328
- }
329
- throw error;
317
+ return await completeJournalRollback(ctx, journal, error, {
318
+ projectRoot,
319
+ operation: 'rename-rollback',
320
+ failureMessage: `Failed to rename component "${oldName}" to "${newName}"`,
321
+ subject: `rename "${oldName}" → "${newName}"`,
322
+ });
330
323
  }
331
324
  });
332
325
  }
@@ -360,11 +353,10 @@ async function updateEngineRegistrations(engineDir, oldName, newName, newDir, ft
360
353
  await removeFile(oldFtlPath);
361
354
  }
362
355
  // Re-wire the locale jar.mn chrome registration when the component is
363
- // localized. Before this, `updateEngineRegistrations` renamed the .ftl
364
- // file on disk but left the locale jar.mn pointing at
365
- // `locale/.../${oldName}.ftl`, so `furnace validate` passed while the
366
- // engine still carried a stale registration for the now-missing file
367
- // (eval finding: stale old-name registration after rename).
356
+ // localized. `updateEngineRegistrations` renames the .ftl file on disk but
357
+ // leaves the locale jar.mn pointing at `locale/.../${oldName}.ftl`, so
358
+ // `furnace validate` passes while the engine still carries a stale
359
+ // registration for the now-missing file.
368
360
  if (isLocalized) {
369
361
  const chromeSubPath = resolveFtlChromeSubPath(ftlDir);
370
362
  const localeJarRel = resolveFtlLocaleJarMnPath(ftlDir);
@@ -408,13 +400,11 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
408
400
  // `componentType` is the furnace-state key (singular: `custom` /
409
401
  // `override`); the on-disk directory label differs — custom components
410
402
  // live under `components/custom/` (singular) while overrides live under
411
- // `components/overrides/` (plural). Before 0.16.0, every rename
412
- // user-facing message appended an `s` to `componentType`, which
413
- // produced the wrong label `components/customs/` for custom components
414
- // and was technically correct for overrides only by coincidence.
415
- // `componentDirLabel` centralises the singular/plural pick so every
416
- // operator-facing string names the directory that actually exists on
417
- // disk.
403
+ // `components/overrides/` (plural). Appending an `s` to `componentType`
404
+ // produces the wrong label `components/customs/` for custom components
405
+ // and is correct for overrides only by coincidence. `componentDirLabel`
406
+ // centralises the pick so every operator-facing string names the
407
+ // directory that actually exists on disk.
418
408
  const componentDirLabel = isCustom ? 'custom' : 'overrides';
419
409
  const baseDir = isCustom ? furnacePaths.customDir : furnacePaths.overridesDir;
420
410
  const oldDir = join(baseDir, oldName);
@@ -1,13 +1,11 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { confirm, multiselect, select } from '@clack/prompts';
3
- import { getProjectPaths } from '../../core/config.js';
3
+ import { stdioIsInteractive } from '../../core/destructive.js';
4
4
  import { ensureFurnaceConfig, furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
5
- import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
6
- import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotFile, } from '../../core/furnace-rollback.js';
5
+ import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
6
+ import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
7
+ import { createRollbackJournal, snapshotFile } from '../../core/furnace-rollback.js';
7
8
  import { DEEP_SCAN_PATHS, scanWidgetsDirectory } from '../../core/furnace-scanner.js';
8
- import { FurnaceError } from '../../errors/furnace.js';
9
- import { toError } from '../../utils/errors.js';
10
- import { pathExists } from '../../utils/fs.js';
11
9
  import { cancel, info, intro, isCancel, note, outro, spinner, success, } from '../../utils/logger.js';
12
10
  import { furnaceOverrideCommand } from './override.js';
13
11
  /**
@@ -61,16 +59,15 @@ async function promptAddComponents(components, tracked, projectRoot) {
61
59
  /**
62
60
  * Persists discovered component tag names into the `stock` section of
63
61
  * furnace.json. Shared by the interactive confirm flow and the
64
- * non-interactive `--track` flag (0.34.0 field report: scan printed a full
65
- * inventory but persisted nothing and said nothing about where the
66
- * inventory goes).
62
+ * non-interactive `--track` flag — without it, scan prints a full inventory
63
+ * but persists nothing and says nothing about where the inventory goes.
67
64
  *
68
65
  * Wraps the furnace.json mutation in the standard furnace lifecycle so the
69
66
  * write goes through the furnace-wide lock and is visible to the global
70
67
  * SIGINT/SIGTERM rollback pathway. The journal snapshots furnace.json
71
68
  * *before* `ensureFurnaceConfig` runs, so a failed run after the file is
72
- * auto-created cleans up after itself instead of leaving an unwanted
73
- * default config behind.
69
+ * auto-created cleans up after itself instead of leaving an unwanted default
70
+ * config behind.
74
71
  */
75
72
  async function persistStockComponents(projectRoot, names) {
76
73
  await runFurnaceMutation(projectRoot, 'scan-rollback', async (ctx) => {
@@ -92,25 +89,18 @@ async function persistStockComponents(projectRoot, names) {
92
89
  await writeFurnaceConfig(projectRoot, config);
93
90
  }
94
91
  catch (error) {
95
- // This body owns its rollback end to end, so tell the lifecycle wrapper
96
- // not to restore the same journal again on the way out.
97
- ctx.markRolledBack();
98
- try {
99
- await restoreRollbackJournalOrThrow(journal, 'Failed to update furnace.json during scan');
100
- }
101
- catch (rollbackError) {
102
- await recordFurnaceRollbackFailure(projectRoot, 'scan-rollback', `furnace.json update during scan: ${toError(rollbackError).message}`);
103
- throw rollbackError;
104
- }
105
- throw error;
92
+ return await completeJournalRollback(ctx, journal, error, {
93
+ projectRoot: projectRoot,
94
+ operation: 'scan-rollback',
95
+ failureMessage: 'Failed to update furnace.json during scan',
96
+ subject: `furnace.json update during scan`,
97
+ });
106
98
  }
107
99
  });
108
100
  }
109
101
  /**
110
- * Renders a component's discovered capabilities as a display suffix.
111
- * Written twice before 0.41.0 — once for the interactive multiselect labels
112
- * and once for the report rows — which is part of what pinned
113
- * `furnaceScanCommand` at complexity 30/30.
102
+ * Renders a component's discovered capabilities as a display suffix. Shared
103
+ * by the interactive multiselect labels and the report rows.
114
104
  */
115
105
  function formatComponentFeatures(component) {
116
106
  const features = [];
@@ -129,10 +119,7 @@ function formatComponentFeatures(component) {
129
119
  */
130
120
  export async function furnaceScanCommand(projectRoot, options = {}) {
131
121
  intro(options.deep ? 'Furnace Scan (deep)' : 'Furnace Scan');
132
- const paths = getProjectPaths(projectRoot);
133
- if (!(await pathExists(paths.engine))) {
134
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
135
- }
122
+ const { paths } = await assertFurnaceEngineReady(projectRoot);
136
123
  // Load scan paths from config if available, merge with deep paths if requested
137
124
  const extraScanPaths = [];
138
125
  if (await furnaceConfigExists(projectRoot)) {
@@ -165,8 +152,7 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
165
152
  tracked.set(name, 'custom');
166
153
  }
167
154
  }
168
- // Partition once and reuse: the tracked/untracked split was computed twice
169
- // (imperatively here, declaratively again for --track) before 0.41.0.
155
+ // Partition once and reuse across the report and the `--track` path.
170
156
  const untrackedComponentList = components.filter((c) => !tracked.has(c.tagName));
171
157
  const untrackedCount = untrackedComponentList.length;
172
158
  const trackedCount = components.length - untrackedCount;
@@ -191,7 +177,7 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
191
177
  outro('Scan complete');
192
178
  return;
193
179
  }
194
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
180
+ const isInteractive = stdioIsInteractive();
195
181
  if (isInteractive && untrackedCount > 0) {
196
182
  await promptAddComponents(components, tracked, projectRoot);
197
183
  return;
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
3
  import { getProjectPaths, loadConfig } from '../../core/config.js';
4
- import { extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, } from '../../core/furnace-apply.js';
4
+ import { extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, } from '../../core/furnace-apply-helpers.js';
5
5
  import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceState, } from '../../core/furnace-config.js';
6
6
  import { resolveFtlDir } from '../../core/furnace-constants.js';
7
7
  import { checkRegistrationConsistency } from '../../core/furnace-validate-checks.js';
@@ -13,7 +13,11 @@ import { info, intro, note, outro, warn } from '../../utils/logger.js';
13
13
  * Displays detailed status for a single Furnace component, including registration drift.
14
14
  * @param name - Component tag name to inspect
15
15
  * @param config - Loaded Furnace configuration
16
+ * @param state - Loaded furnace state, for applied/engine checksums
16
17
  * @param projectRoot - Root directory of the project
18
+ * @param paths - Resolved project paths
19
+ * @param furnacePaths - Resolved Furnace-specific paths
20
+ * @param ftlDir - Engine-relative directory localized components deploy their `.ftl` into
17
21
  */
18
22
  async function showDetailedComponentStatus(name, config, state, projectRoot, paths, furnacePaths, ftlDir) {
19
23
  const customConfig = config.custom[name];
@@ -53,7 +57,7 @@ async function showDetailedComponentStatus(name, config, state, projectRoot, pat
53
57
  outro('Status complete');
54
58
  return;
55
59
  }
56
- const engineDrifted = await hasOverrideEngineDrift(paths.engine, overrideDir, overrideConfig, ftlDir);
60
+ const engineDrifted = await hasOverrideEngineDrift({ engineDir: paths.engine, componentDir: overrideDir, ftlDir }, overrideConfig);
57
61
  lines.push(`${engineDrifted ? '\u2717' : '\u2713'} Engine matches override workspace`);
58
62
  note(lines.join('\n'), `${name} Override Status`);
59
63
  outro('Status complete');
@@ -167,11 +171,10 @@ export async function furnaceStatusCommand(projectRoot, name) {
167
171
  *
168
172
  * The engine probe sits behind `else if` deliberately: a component whose
169
173
  * workspace already changed does not need an engine comparison to decide
170
- * what `status` reports, and the probe is the expensive half (it re-hashes
171
- * deployed files). The consequence is that engine drift can go UNREPORTED
172
- * for a component that also has workspace changes — the summary says
173
- * "workspace changed", not both. Preserved verbatim from the two
174
- * hand-written copies this replaced.
174
+ * what `status` reports, and the probe is the expensive half (it
175
+ * re-hashes deployed files). The consequence is that engine drift can go
176
+ * UNREPORTED for a component that also has workspace changes — the
177
+ * summary says "workspace changed", not both.
175
178
  */
176
179
  const scanFamily = async (entries, baseDir, kind, probeEngineDrift) => {
177
180
  for (const [componentName, componentConfig] of entries) {
@@ -189,8 +192,8 @@ export async function furnaceStatusCommand(projectRoot, name) {
189
192
  }
190
193
  }
191
194
  };
192
- await scanFamily(Object.entries(config.overrides), furnacePaths.overridesDir, 'override', (_name, componentDir, cfg) => hasOverrideEngineDrift(paths.engine, componentDir, cfg, ftlDir));
193
- await scanFamily(Object.entries(config.custom), furnacePaths.customDir, 'custom', (name, componentDir, cfg) => hasCustomEngineDrift(projectRoot, name, componentDir, cfg, ftlDir));
195
+ await scanFamily(Object.entries(config.overrides), furnacePaths.overridesDir, 'override', (_name, componentDir, cfg) => hasOverrideEngineDrift({ engineDir: paths.engine, componentDir, ftlDir }, cfg));
196
+ await scanFamily(Object.entries(config.custom), furnacePaths.customDir, 'custom', (name, componentDir, cfg) => hasCustomEngineDrift({ root: projectRoot, name, componentDir, ftlDir }, cfg));
194
197
  if (drift.workspaceChanged) {
195
198
  warn('Components have been modified since last apply. Run `fireforge build` or `fireforge furnace apply`.');
196
199
  }
@@ -1,12 +1,13 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { getProjectPaths, loadConfig } from '../../core/config.js';
4
+ import { loadConfig } from '../../core/config.js';
5
5
  import { applyAllComponents } from '../../core/furnace-apply.js';
6
6
  import { logApplyResult } from '../../core/furnace-apply-output.js';
7
- import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../core/furnace-config.js';
7
+ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
8
8
  import { reportJsconfigPathsSync } from '../../core/furnace-jsconfig.js';
9
9
  import { runFurnaceMutation } from '../../core/furnace-operation.js';
10
+ import { assertFurnaceReady } from '../../core/furnace-precondition.js';
10
11
  import { countEntriesWithBlockingStepErrors } from '../../core/furnace-step-errors.js';
11
12
  import { containsMergeConflictMarkers } from '../../core/furnace-validate-structure.js';
12
13
  import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
@@ -63,15 +64,7 @@ async function findComponentsWithConflictMarkers(furnacePaths, config) {
63
64
  */
64
65
  export async function furnaceSyncCommand(projectRoot, options = {}) {
65
66
  intro('Furnace Sync');
66
- // Pre-flight checks
67
- const paths = getProjectPaths(projectRoot);
68
- if (!(await pathExists(paths.engine))) {
69
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
70
- }
71
- if (!(await furnaceConfigExists(projectRoot))) {
72
- throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
73
- }
74
- const config = await loadFurnaceConfig(projectRoot);
67
+ const { config } = await assertFurnaceReady(projectRoot);
75
68
  const forgeConfig = await loadConfig(projectRoot);
76
69
  const overrideCount = Object.keys(config.overrides).length;
77
70
  const customCount = Object.keys(config.custom).length;
@@ -100,11 +93,11 @@ export async function furnaceSyncCommand(projectRoot, options = {}) {
100
93
  }
101
94
  // Phase 2.5: post-refresh gates. Refresh deliberately leaves conflict
102
95
  // markers in workspace files and does NOT bump baseVersion on conflict —
103
- // so before this gate existed, sync would warn about conflicts and then
104
- // copy the marker-laden files straight into the engine via Phase 3,
105
- // producing a broken build from the one command whose whole purpose is a
106
- // safe upgrade. Apply/deploy both enforce a drift gate; sync must not be
107
- // the back door around it.
96
+ // so without this gate sync warns about conflicts and then copies the
97
+ // marker-laden files straight into the engine via Phase 3, producing a
98
+ // broken build from the one command whose whole purpose is a safe upgrade.
99
+ // Apply and deploy both enforce a drift gate; sync must not be the back
100
+ // door around it.
108
101
  if (!options.dryRun) {
109
102
  const refreshedConfig = await loadFurnaceConfig(projectRoot);
110
103
  const furnacePaths = getFurnacePaths(projectRoot);
@@ -2,7 +2,8 @@
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { getProjectPaths } from '../../core/config.js';
5
- import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../core/furnace-config.js';
5
+ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
6
+ import { assertFurnaceReady } from '../../core/furnace-precondition.js';
6
7
  import { addCustomElementRegistration, addJarMnEntries, pruneStaleJarMnEntries, } from '../../core/furnace-registration.js';
7
8
  import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
8
9
  import { FurnaceError } from '../../errors/furnace.js';
@@ -43,9 +44,9 @@ async function autoFixIssues(projectRoot, issues) {
43
44
  jarMnFixesByComponent.set(issue.component, existing);
44
45
  }
45
46
  }
46
- // Prune stale jar.mn registrations (lines pointing at removed/renamed
47
- // component files — 0.34.0 field report: these broke packaging while
48
- // validate --fix reported success without touching them).
47
+ // Prune stale jar.mn registrations (lines pointing at removed or renamed
48
+ // component files). These break packaging, and validate --fix must not
49
+ // report success without touching them.
49
50
  if (issues.some((issue) => issue.check === 'stale-jar-registration')) {
50
51
  try {
51
52
  const pruned = await pruneStaleJarMnEntries(engineDir, furnacePaths.customDir, Object.keys(config.custom));
@@ -84,12 +85,11 @@ async function autoFixIssues(projectRoot, issues) {
84
85
  // Repair components missing from customElements.js entirely (reported by
85
86
  // validate as `missing-custom-element-registration`).
86
87
  //
87
- // Scoped to the components named in `issues`. Until 0.41.0 this iterated
88
- // every entry in `config.custom`, so `furnace validate <one-component> --fix`
89
- // wrote customElements.js registrations for EVERY custom component — outside
90
- // the issue list it was handed, and invisibly, since `fixed` was never
91
- // incremented here. The block's own comment already claimed this scoping;
92
- // the code did not implement it.
88
+ // Scoped to the components named in `issues`: iterating every entry in
89
+ // `config.custom` makes `furnace validate <one-component> --fix` write
90
+ // customElements.js registrations for EVERY custom component, outside the
91
+ // issue list it was handed and invisibly, since `fixed` is never
92
+ // incremented here.
93
93
  const componentsWithIssues = new Set(issues.map((issue) => issue.component));
94
94
  for (const componentName of componentsWithIssues) {
95
95
  const customConfig = config.custom[componentName];
@@ -123,10 +123,10 @@ async function autoFixIssues(projectRoot, issues) {
123
123
  */
124
124
  export async function furnaceValidateCommand(projectRoot, name, options = {}) {
125
125
  intro('Furnace Validate');
126
- if (!(await furnaceConfigExists(projectRoot))) {
127
- throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
128
- }
129
- const config = await loadFurnaceConfig(projectRoot);
126
+ // Gains the engine rung here: this was the one site in the family that
127
+ // checked furnace.json alone, so a validate against a missing engine
128
+ // produced downstream noise instead of the shared precondition refusal.
129
+ const { config } = await assertFurnaceReady(projectRoot);
130
130
  const furnacePaths = getFurnacePaths(projectRoot);
131
131
  let totalErrors = 0;
132
132
  let totalWarnings = 0;
@@ -193,12 +193,12 @@ export async function furnaceValidateCommand(projectRoot, name, options = {}) {
193
193
  }
194
194
  }
195
195
  }
196
- // Auto-fix fixable issues when --fix is passed. The auto-fix counter
197
- // returned by `autoFixIssues` only counts function calls that did not
198
- // throw — a write that succeeded but did not actually resolve the issue
199
- // (e.g. addJarMnEntries appended to a file mach later ignores) would
200
- // still bump the count. Re-validate the affected components and compute
201
- // the *actual* drop in fixable issues so the reported number is honest.
196
+ // Auto-fix fixable issues when --fix is passed. The counter returned by
197
+ // `autoFixIssues` only counts function calls that did not throw — a write
198
+ // that succeeded but did not actually resolve the issue (addJarMnEntries
199
+ // appending to a file mach later ignores) would still bump it. Re-validate
200
+ // the affected components and compute the *actual* drop in fixable issues
201
+ // so the reported number is honest.
202
202
  if (options.fix && allIssues.length > 0) {
203
203
  const fixableIssues = allIssues.filter((issue) => FIXABLE_CHECKS.has(issue.check));
204
204
  if (fixableIssues.length > 0) {