@hominis/fireforge 0.43.1 → 0.44.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 (470) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +10 -1
  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 +4 -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-post-rebase-audit.d.ts +2 -0
  22. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  23. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  24. package/dist/src/commands/doctor-working-tree.js +11 -18
  25. package/dist/src/commands/doctor.js +35 -53
  26. package/dist/src/commands/download.js +32 -48
  27. package/dist/src/commands/export-all.js +29 -33
  28. package/dist/src/commands/export-flow.js +6 -6
  29. package/dist/src/commands/export-placement-conflicts.js +10 -9
  30. package/dist/src/commands/export-placement-gate.js +5 -5
  31. package/dist/src/commands/export-shared.d.ts +30 -33
  32. package/dist/src/commands/export-shared.js +45 -43
  33. package/dist/src/commands/export.js +21 -27
  34. package/dist/src/commands/furnace/apply.js +14 -23
  35. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  36. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  37. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  38. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  39. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  40. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  41. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  42. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  43. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  44. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  45. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  46. package/dist/src/commands/furnace/create-readback.js +12 -13
  47. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  48. package/dist/src/commands/furnace/create-templates.js +35 -30
  49. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  50. package/dist/src/commands/furnace/create.js +33 -23
  51. package/dist/src/commands/furnace/deploy.js +16 -24
  52. package/dist/src/commands/furnace/diff.js +9 -9
  53. package/dist/src/commands/furnace/init.js +33 -36
  54. package/dist/src/commands/furnace/override.js +23 -25
  55. package/dist/src/commands/furnace/preview.js +47 -67
  56. package/dist/src/commands/furnace/refresh.js +12 -16
  57. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  58. package/dist/src/commands/furnace/remove-mutations.js +9 -16
  59. package/dist/src/commands/furnace/remove-state.js +1 -1
  60. package/dist/src/commands/furnace/remove.js +33 -38
  61. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  62. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  63. package/dist/src/commands/furnace/rename.js +59 -69
  64. package/dist/src/commands/furnace/scan.js +19 -33
  65. package/dist/src/commands/furnace/status.js +12 -9
  66. package/dist/src/commands/furnace/sync.js +9 -16
  67. package/dist/src/commands/furnace/validate.js +20 -20
  68. package/dist/src/commands/import.js +42 -35
  69. package/dist/src/commands/lint-per-patch.js +15 -10
  70. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  71. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  72. package/dist/src/commands/lint-report.js +6 -7
  73. package/dist/src/commands/lint.d.ts +15 -16
  74. package/dist/src/commands/lint.js +86 -102
  75. package/dist/src/commands/package.js +6 -11
  76. package/dist/src/commands/patch/compact.js +1 -1
  77. package/dist/src/commands/patch/delete.js +24 -33
  78. package/dist/src/commands/patch/index.d.ts +5 -6
  79. package/dist/src/commands/patch/index.js +10 -12
  80. package/dist/src/commands/patch/lint-ignore.js +14 -13
  81. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  82. package/dist/src/commands/patch/move-files-create.js +8 -8
  83. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  84. package/dist/src/commands/patch/move-files-into.js +8 -8
  85. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  86. package/dist/src/commands/patch/patch-context.js +2 -3
  87. package/dist/src/commands/patch/rename.d.ts +10 -11
  88. package/dist/src/commands/patch/rename.js +11 -12
  89. package/dist/src/commands/patch/reorder.js +8 -9
  90. package/dist/src/commands/patch/split-plan.js +1 -1
  91. package/dist/src/commands/patch/split.d.ts +8 -10
  92. package/dist/src/commands/patch/split.js +9 -11
  93. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  94. package/dist/src/commands/patch/staged-dependency.js +7 -3
  95. package/dist/src/commands/patch/tier.d.ts +9 -9
  96. package/dist/src/commands/patch/tier.js +18 -15
  97. package/dist/src/commands/re-export-adjacent.js +9 -8
  98. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  99. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  100. package/dist/src/commands/re-export-drift.js +10 -10
  101. package/dist/src/commands/re-export-files.d.ts +6 -7
  102. package/dist/src/commands/re-export-files.js +34 -44
  103. package/dist/src/commands/re-export-lint.d.ts +10 -13
  104. package/dist/src/commands/re-export-lint.js +16 -14
  105. package/dist/src/commands/re-export-options.d.ts +6 -6
  106. package/dist/src/commands/re-export-options.js +6 -6
  107. package/dist/src/commands/re-export-register.d.ts +4 -10
  108. package/dist/src/commands/re-export-register.js +5 -11
  109. package/dist/src/commands/re-export-scan.js +1 -1
  110. package/dist/src/commands/re-export-single.d.ts +3 -3
  111. package/dist/src/commands/re-export-single.js +33 -25
  112. package/dist/src/commands/re-export.js +13 -21
  113. package/dist/src/commands/rebase/abort.js +3 -3
  114. package/dist/src/commands/rebase/confirm.js +2 -1
  115. package/dist/src/commands/rebase/continue.d.ts +1 -1
  116. package/dist/src/commands/rebase/continue.js +14 -9
  117. package/dist/src/commands/rebase/index.js +11 -11
  118. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  119. package/dist/src/commands/rebase/patch-loop.js +60 -39
  120. package/dist/src/commands/rebase/summary.js +1 -1
  121. package/dist/src/commands/register.js +5 -7
  122. package/dist/src/commands/reset.js +2 -1
  123. package/dist/src/commands/resolve.d.ts +16 -13
  124. package/dist/src/commands/resolve.js +30 -33
  125. package/dist/src/commands/run.js +33 -35
  126. package/dist/src/commands/setup-support.js +15 -19
  127. package/dist/src/commands/setup.js +13 -9
  128. package/dist/src/commands/status-check.d.ts +6 -6
  129. package/dist/src/commands/status-json.d.ts +6 -6
  130. package/dist/src/commands/status-json.js +5 -0
  131. package/dist/src/commands/status-output.d.ts +5 -5
  132. package/dist/src/commands/status-output.js +6 -6
  133. package/dist/src/commands/status-ownership.d.ts +16 -14
  134. package/dist/src/commands/status-ownership.js +16 -14
  135. package/dist/src/commands/status.js +42 -34
  136. package/dist/src/commands/test-build-phase.js +26 -13
  137. package/dist/src/commands/test-diagnose.js +44 -44
  138. package/dist/src/commands/test-register.js +1 -0
  139. package/dist/src/commands/test-run.d.ts +14 -16
  140. package/dist/src/commands/test-run.js +21 -13
  141. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  142. package/dist/src/commands/test-stale-gate.js +15 -16
  143. package/dist/src/commands/test-verdict.d.ts +20 -3
  144. package/dist/src/commands/test-verdict.js +30 -0
  145. package/dist/src/commands/test.js +83 -76
  146. package/dist/src/commands/token-coverage.js +3 -4
  147. package/dist/src/commands/token.js +16 -18
  148. package/dist/src/commands/tree.js +47 -31
  149. package/dist/src/commands/typecheck.d.ts +10 -12
  150. package/dist/src/commands/typecheck.js +24 -27
  151. package/dist/src/commands/verify.js +13 -15
  152. package/dist/src/commands/watch.js +21 -23
  153. package/dist/src/commands/wire.js +52 -62
  154. package/dist/src/core/ast-utils.d.ts +8 -7
  155. package/dist/src/core/ast-utils.js +8 -7
  156. package/dist/src/core/branding.d.ts +9 -14
  157. package/dist/src/core/branding.js +7 -11
  158. package/dist/src/core/browser-wire.d.ts +1 -1
  159. package/dist/src/core/browser-wire.js +14 -19
  160. package/dist/src/core/build-audit-platform.js +25 -33
  161. package/dist/src/core/build-audit-registration.js +17 -25
  162. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  163. package/dist/src/core/build-audit-resolve.js +27 -33
  164. package/dist/src/core/build-audit-transforms.js +18 -19
  165. package/dist/src/core/build-audit.js +28 -42
  166. package/dist/src/core/build-baseline-types.d.ts +29 -35
  167. package/dist/src/core/build-baseline.d.ts +5 -4
  168. package/dist/src/core/build-baseline.js +14 -14
  169. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  170. package/dist/src/core/build-overwrite-guard.js +17 -27
  171. package/dist/src/core/build-prepare.d.ts +2 -2
  172. package/dist/src/core/build-prepare.js +8 -9
  173. package/dist/src/core/config-state.js +12 -4
  174. package/dist/src/core/config-validate.js +8 -5
  175. package/dist/src/core/config.d.ts +5 -8
  176. package/dist/src/core/config.js +7 -16
  177. package/dist/src/core/coverage-extend.d.ts +15 -13
  178. package/dist/src/core/coverage-extend.js +15 -13
  179. package/dist/src/core/destructive.d.ts +31 -9
  180. package/dist/src/core/destructive.js +29 -26
  181. package/dist/src/core/diff-hunks.js +10 -12
  182. package/dist/src/core/discard-baseline.d.ts +8 -8
  183. package/dist/src/core/discard-baseline.js +16 -15
  184. package/dist/src/core/display-state.d.ts +12 -14
  185. package/dist/src/core/display-state.js +12 -14
  186. package/dist/src/core/engine-changes.d.ts +2 -4
  187. package/dist/src/core/engine-changes.js +2 -4
  188. package/dist/src/core/engine-precondition.d.ts +28 -13
  189. package/dist/src/core/engine-precondition.js +25 -10
  190. package/dist/src/core/engine-session-lock.d.ts +47 -21
  191. package/dist/src/core/engine-session-lock.js +168 -33
  192. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  193. package/dist/src/core/export-moved-code-shape.js +10 -10
  194. package/dist/src/core/file-lock.d.ts +62 -5
  195. package/dist/src/core/file-lock.js +196 -59
  196. package/dist/src/core/firefox-cache.js +1 -3
  197. package/dist/src/core/firefox-extract.js +10 -14
  198. package/dist/src/core/firefox.d.ts +7 -5
  199. package/dist/src/core/firefox.js +4 -5
  200. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  201. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  202. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  203. package/dist/src/core/furnace-apply-ftl.js +12 -12
  204. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  205. package/dist/src/core/furnace-apply-helpers.js +19 -16
  206. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  207. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  208. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  209. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  210. package/dist/src/core/furnace-apply.d.ts +5 -8
  211. package/dist/src/core/furnace-apply.js +19 -24
  212. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  213. package/dist/src/core/furnace-checksum-utils.js +16 -0
  214. package/dist/src/core/furnace-config.d.ts +21 -30
  215. package/dist/src/core/furnace-config.js +32 -54
  216. package/dist/src/core/furnace-constants.d.ts +8 -0
  217. package/dist/src/core/furnace-constants.js +10 -2
  218. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  219. package/dist/src/core/furnace-css-fragments.js +5 -5
  220. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  221. package/dist/src/core/furnace-jsconfig.js +11 -12
  222. package/dist/src/core/furnace-marker.d.ts +7 -8
  223. package/dist/src/core/furnace-marker.js +7 -8
  224. package/dist/src/core/furnace-operation.d.ts +31 -0
  225. package/dist/src/core/furnace-operation.js +54 -17
  226. package/dist/src/core/furnace-precondition.d.ts +61 -0
  227. package/dist/src/core/furnace-precondition.js +67 -0
  228. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  229. package/dist/src/core/furnace-registration-remove.js +6 -7
  230. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  231. package/dist/src/core/furnace-registration-validate.js +20 -1
  232. package/dist/src/core/furnace-registration.d.ts +5 -6
  233. package/dist/src/core/furnace-registration.js +10 -12
  234. package/dist/src/core/furnace-rollback.js +7 -7
  235. package/dist/src/core/furnace-scanner.d.ts +1 -0
  236. package/dist/src/core/furnace-scanner.js +1 -2
  237. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  238. package/dist/src/core/furnace-stale-export.js +10 -10
  239. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  240. package/dist/src/core/furnace-state-persist.js +6 -8
  241. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  242. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  243. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  244. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  245. package/dist/src/core/furnace-validate-helpers.js +0 -491
  246. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  247. package/dist/src/core/furnace-validate-registration.js +115 -41
  248. package/dist/src/core/furnace-validate.d.ts +16 -0
  249. package/dist/src/core/furnace-validate.js +188 -145
  250. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  251. package/dist/src/core/furnace-version-drift.js +10 -11
  252. package/dist/src/core/git-base.d.ts +7 -9
  253. package/dist/src/core/git-base.js +7 -9
  254. package/dist/src/core/git-diff.d.ts +20 -9
  255. package/dist/src/core/git-diff.js +148 -63
  256. package/dist/src/core/git-file-ops.d.ts +19 -19
  257. package/dist/src/core/git-file-ops.js +19 -19
  258. package/dist/src/core/git-readonly-index.d.ts +53 -19
  259. package/dist/src/core/git-readonly-index.js +75 -31
  260. package/dist/src/core/git-status.d.ts +5 -6
  261. package/dist/src/core/git-status.js +5 -6
  262. package/dist/src/core/git.d.ts +9 -10
  263. package/dist/src/core/git.js +54 -70
  264. package/dist/src/core/license-headers.d.ts +17 -23
  265. package/dist/src/core/license-headers.js +17 -23
  266. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  267. package/dist/src/core/mach-build-artifacts.js +57 -19
  268. package/dist/src/core/mach-error-hints.js +42 -29
  269. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  270. package/dist/src/core/mach-known-noise-filter.js +18 -20
  271. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  272. package/dist/src/core/mach-mozconfig.js +30 -0
  273. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  274. package/dist/src/core/mach-resource-shim.js +50 -67
  275. package/dist/src/core/mach.d.ts +37 -41
  276. package/dist/src/core/mach.js +50 -51
  277. package/dist/src/core/marionette-port.d.ts +36 -1
  278. package/dist/src/core/marionette-port.js +96 -31
  279. package/dist/src/core/marionette-preflight.d.ts +8 -12
  280. package/dist/src/core/marionette-preflight.js +86 -52
  281. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  282. package/dist/src/core/mochitest-server-port.js +119 -0
  283. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  284. package/dist/src/core/module-resolution-preflight.js +9 -11
  285. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  286. package/dist/src/core/moz-manifest-helpers.js +163 -0
  287. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  288. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  289. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  290. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  291. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  292. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  293. package/dist/src/core/ownership-table.d.ts +14 -16
  294. package/dist/src/core/ownership-table.js +14 -16
  295. package/dist/src/core/parser-fallback.d.ts +15 -1
  296. package/dist/src/core/parser-fallback.js +22 -3
  297. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  298. package/dist/src/core/patch-apply-fuzz.js +15 -18
  299. package/dist/src/core/patch-apply.d.ts +13 -34
  300. package/dist/src/core/patch-apply.js +16 -76
  301. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  302. package/dist/src/core/patch-artifact-normalize.js +13 -15
  303. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  304. package/dist/src/core/patch-export-metadata.js +13 -4
  305. package/dist/src/core/patch-export-update.d.ts +13 -1
  306. package/dist/src/core/patch-export-update.js +25 -8
  307. package/dist/src/core/patch-export.d.ts +15 -8
  308. package/dist/src/core/patch-export.js +10 -11
  309. package/dist/src/core/patch-files.d.ts +5 -6
  310. package/dist/src/core/patch-files.js +5 -6
  311. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  312. package/dist/src/core/patch-identifier-suggest.js +14 -19
  313. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  314. package/dist/src/core/patch-lint-binary.js +39 -0
  315. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  316. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  317. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  318. package/dist/src/core/patch-lint-checkjs.js +16 -16
  319. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  320. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  321. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  322. package/dist/src/core/patch-lint-creators.js +8 -8
  323. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  324. package/dist/src/core/patch-lint-cross.js +16 -16
  325. package/dist/src/core/patch-lint-css.d.ts +3 -5
  326. package/dist/src/core/patch-lint-css.js +17 -22
  327. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  328. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  329. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  330. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  331. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  332. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  333. package/dist/src/core/patch-lint-observer.js +7 -7
  334. package/dist/src/core/patch-lint.d.ts +38 -29
  335. package/dist/src/core/patch-lint.js +89 -121
  336. package/dist/src/core/patch-lock.d.ts +1 -1
  337. package/dist/src/core/patch-lock.js +45 -9
  338. package/dist/src/core/patch-manifest-consistency.d.ts +2 -1
  339. package/dist/src/core/patch-manifest-consistency.js +25 -27
  340. package/dist/src/core/patch-manifest-io.js +14 -18
  341. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  342. package/dist/src/core/patch-manifest-resolve.js +9 -12
  343. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  344. package/dist/src/core/patch-manifest-validate.js +25 -13
  345. package/dist/src/core/patch-parse.d.ts +24 -13
  346. package/dist/src/core/patch-parse.js +19 -14
  347. package/dist/src/core/patch-policy.d.ts +2 -2
  348. package/dist/src/core/patch-policy.js +2 -2
  349. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  350. package/dist/src/core/patch-registration-refs.js +9 -9
  351. package/dist/src/core/process-cpu-time.d.ts +12 -0
  352. package/dist/src/core/process-cpu-time.js +52 -0
  353. package/dist/src/core/rebase-session.d.ts +38 -26
  354. package/dist/src/core/rebase-session.js +55 -28
  355. package/dist/src/core/register-browser-content.js +9 -74
  356. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  357. package/dist/src/core/register-module.js +10 -43
  358. package/dist/src/core/register-scaffold.d.ts +8 -8
  359. package/dist/src/core/register-scaffold.js +10 -10
  360. package/dist/src/core/register-shared-css.d.ts +9 -12
  361. package/dist/src/core/register-shared-css.js +17 -87
  362. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  363. package/dist/src/core/register-xpcshell-test.js +7 -8
  364. package/dist/src/core/shared-ftl.js +12 -4
  365. package/dist/src/core/state-file.d.ts +45 -0
  366. package/dist/src/core/state-file.js +34 -0
  367. package/dist/src/core/status-binary.d.ts +18 -11
  368. package/dist/src/core/status-binary.js +19 -12
  369. package/dist/src/core/status-classify.d.ts +30 -36
  370. package/dist/src/core/status-classify.js +21 -26
  371. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  372. package/dist/src/core/test-changed-prefs.js +78 -0
  373. package/dist/src/core/test-harness-crash.d.ts +25 -63
  374. package/dist/src/core/test-harness-crash.js +121 -142
  375. package/dist/src/core/test-harness-signature.d.ts +18 -0
  376. package/dist/src/core/test-harness-signature.js +13 -0
  377. package/dist/src/core/test-path-scope.d.ts +12 -15
  378. package/dist/src/core/test-path-scope.js +15 -18
  379. package/dist/src/core/test-stale-check.d.ts +17 -17
  380. package/dist/src/core/test-stale-check.js +50 -43
  381. package/dist/src/core/test-stall-triage.d.ts +51 -0
  382. package/dist/src/core/test-stall-triage.js +126 -0
  383. package/dist/src/core/token-category.js +5 -7
  384. package/dist/src/core/token-coverage.js +9 -9
  385. package/dist/src/core/token-dark-mode.d.ts +9 -11
  386. package/dist/src/core/token-dark-mode.js +9 -11
  387. package/dist/src/core/token-docs.d.ts +2 -0
  388. package/dist/src/core/token-docs.js +2 -0
  389. package/dist/src/core/token-manager.d.ts +10 -2
  390. package/dist/src/core/token-manager.js +17 -8
  391. package/dist/src/core/token-scaffold.d.ts +7 -7
  392. package/dist/src/core/token-scaffold.js +7 -7
  393. package/dist/src/core/token-variant.d.ts +4 -4
  394. package/dist/src/core/token-variant.js +4 -4
  395. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  396. package/dist/src/core/toolchain-preflight.js +28 -31
  397. package/dist/src/core/tree-guard.js +12 -2
  398. package/dist/src/core/tree-store.d.ts +41 -27
  399. package/dist/src/core/tree-store.js +63 -42
  400. package/dist/src/core/typecheck-shim.d.ts +23 -24
  401. package/dist/src/core/typecheck-shim.js +23 -24
  402. package/dist/src/core/typecheck.js +8 -8
  403. package/dist/src/core/wire-destroy.d.ts +3 -3
  404. package/dist/src/core/wire-destroy.js +7 -15
  405. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  406. package/dist/src/core/wire-dom-fragment.js +11 -44
  407. package/dist/src/core/wire-init.d.ts +2 -3
  408. package/dist/src/core/wire-init.js +8 -19
  409. package/dist/src/core/wire-subscript.d.ts +2 -3
  410. package/dist/src/core/wire-subscript.js +4 -13
  411. package/dist/src/core/wire-utils.d.ts +7 -8
  412. package/dist/src/core/wire-utils.js +7 -8
  413. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  414. package/dist/src/core/xpcshell-appdir.js +38 -45
  415. package/dist/src/errors/base.d.ts +37 -16
  416. package/dist/src/errors/base.js +37 -16
  417. package/dist/src/errors/build.d.ts +13 -1
  418. package/dist/src/errors/build.js +14 -2
  419. package/dist/src/errors/codes.d.ts +16 -0
  420. package/dist/src/errors/codes.js +16 -0
  421. package/dist/src/errors/furnace.d.ts +4 -4
  422. package/dist/src/errors/git.d.ts +6 -10
  423. package/dist/src/errors/git.js +6 -10
  424. package/dist/src/errors/patch.d.ts +7 -7
  425. package/dist/src/errors/patch.js +7 -7
  426. package/dist/src/errors/rebase.d.ts +4 -4
  427. package/dist/src/errors/rebase.js +4 -4
  428. package/dist/src/index.d.ts +7 -1
  429. package/dist/src/index.js +5 -0
  430. package/dist/src/types/commands/index.d.ts +2 -2
  431. package/dist/src/types/commands/options.d.ts +88 -70
  432. package/dist/src/types/commands/patches.d.ts +24 -34
  433. package/dist/src/types/commands/project.d.ts +5 -34
  434. package/dist/src/types/config.d.ts +25 -23
  435. package/dist/src/types/config.js +6 -1
  436. package/dist/src/types/furnace.d.ts +37 -13
  437. package/dist/src/types/furnace.js +19 -1
  438. package/dist/src/types/index.d.ts +3 -3
  439. package/dist/src/utils/build-info.d.ts +7 -8
  440. package/dist/src/utils/build-info.js +12 -11
  441. package/dist/src/utils/errors.d.ts +6 -8
  442. package/dist/src/utils/errors.js +6 -8
  443. package/dist/src/utils/logger.d.ts +27 -10
  444. package/dist/src/utils/logger.js +28 -16
  445. package/dist/src/utils/machine-output.d.ts +12 -0
  446. package/dist/src/utils/machine-output.js +33 -0
  447. package/dist/src/utils/options.d.ts +31 -14
  448. package/dist/src/utils/options.js +86 -30
  449. package/dist/src/utils/paths.d.ts +10 -8
  450. package/dist/src/utils/paths.js +10 -8
  451. package/dist/src/utils/platform.d.ts +0 -25
  452. package/dist/src/utils/platform.js +1 -42
  453. package/dist/src/utils/process-group.d.ts +9 -4
  454. package/dist/src/utils/process-group.js +19 -5
  455. package/dist/src/utils/process.d.ts +42 -7
  456. package/dist/src/utils/process.js +44 -17
  457. package/dist/src/utils/ps-duration.d.ts +31 -0
  458. package/dist/src/utils/ps-duration.js +70 -0
  459. package/dist/src/utils/regex.d.ts +9 -10
  460. package/dist/src/utils/regex.js +9 -10
  461. package/dist/src/utils/validation.d.ts +58 -28
  462. package/dist/src/utils/validation.js +60 -33
  463. package/package.json +2 -2
  464. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  465. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  466. package/dist/src/core/manifest-helpers.d.ts +0 -38
  467. package/dist/src/core/manifest-helpers.js +0 -120
  468. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  469. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  470. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -3,6 +3,7 @@ import { stat } from 'node:fs/promises';
3
3
  import { basename, dirname, isAbsolute, join, normalize } from 'node:path';
4
4
  import { text } from '@clack/prompts';
5
5
  import { getProjectPaths, loadConfig, mutateConfig, writeConfig } from '../../core/config.js';
6
+ import { stdioIsInteractive } from '../../core/destructive.js';
6
7
  import { createDefaultFurnaceConfig, furnaceConfigExists, writeFurnaceConfig, } from '../../core/furnace-config.js';
7
8
  import { DEFAULT_LICENSE } from '../../core/license-headers.js';
8
9
  import { registerSharedCSS } from '../../core/register-shared-css.js';
@@ -15,17 +16,15 @@ import { cancel, info, intro, isCancel, note, outro, success, warn } from '../..
15
16
  /**
16
17
  * File extensions that are definitely FTL resources (not locale
17
18
  * directories). A value ending in one of these is almost certainly the
18
- * result of the operator pointing at a single FTL file instead of the
19
- * locale directory that contains it.
19
+ * result of the operator pointing at a single FTL file instead of the locale
20
+ * directory that contains it.
20
21
  *
21
- * 2026-04-21 eval: `furnace init --ftl-base-path browser/forgefresh.ftl`
22
- * produced a misleading success path — the subsequent
23
- * `furnace create --localized` scaffolded an `.mjs` referencing
24
- * `insertFTLIfNeeded("<name>.ftl")` while furnace.json had no component
25
- * entry, leaving the scaffold orphaned. Switching to a locale directory
26
- * (`toolkit/locales/en-US/toolkit/global`) fixed the downstream path.
27
- * Rejecting file-shaped values up-front keeps the operator on the
28
- * correct path before any partial state is written.
22
+ * `furnace init --ftl-base-path browser/<name>.ftl` otherwise produces a
23
+ * misleading success path: the subsequent `furnace create --localized`
24
+ * scaffolds an `.mjs` referencing `insertFTLIfNeeded("<name>.ftl")` while
25
+ * furnace.json has no component entry, leaving the scaffold orphaned.
26
+ * Rejecting file-shaped values up-front keeps the operator on the correct
27
+ * path before any partial state is written.
29
28
  */
30
29
  const FTL_FILE_EXTENSIONS = new Set(['.ftl', '.properties', '.dtd']);
31
30
  function hasFtlFileExtension(value) {
@@ -112,13 +111,13 @@ export async function furnaceInitCommand(projectRoot, options = {}) {
112
111
  }
113
112
  const paths = getProjectPaths(projectRoot);
114
113
  // Seed the default furnace config with a tokenPrefix derived from
115
- // fireforge.json's binaryName so `token coverage` sees real tokens on
116
- // the very first run. The 2026-04-21 eval initialised Furnace, added
117
- // tokens, ran coverage, and got `0 tokens / N unknown` — the prefix
118
- // default was absent and the scan had nothing to key off. Loading
119
- // fireforge.json here is best-effort: a project without one (e.g.
120
- // mid-setup) falls through to the prefix-less default, and
121
- // `token coverage` emits the existing "no tokenPrefix" warning.
114
+ // fireforge.json's binaryName so `token coverage` sees real tokens on the
115
+ // very first run — without the prefix default, a fresh init → token add →
116
+ // coverage sequence reports `0 tokens / N unknown` because the scan has
117
+ // nothing to key off. Loading fireforge.json here is best-effort: a
118
+ // project without one (mid-setup) falls through to the prefix-less
119
+ // default, and `token coverage` emits its existing "no tokenPrefix"
120
+ // warning.
122
121
  let derivedBinaryName;
123
122
  try {
124
123
  const fireForgeConfig = await loadConfig(projectRoot);
@@ -129,7 +128,7 @@ export async function furnaceInitCommand(projectRoot, options = {}) {
129
128
  // rare but not forbidden. Skip the prefix default in that case.
130
129
  }
131
130
  const config = createDefaultFurnaceConfig(derivedBinaryName ? { binaryName: derivedBinaryName } : {});
132
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
131
+ const isInteractive = stdioIsInteractive();
133
132
  const engineForValidation = (await pathExists(paths.engine)) ? paths.engine : undefined;
134
133
  // Resolve componentPrefix
135
134
  if (options.prefix !== undefined) {
@@ -231,12 +230,11 @@ async function scaffoldTokensCss(projectRoot) {
231
230
  info(`Tokens CSS already present at engine/${tokensCssPath}; leaving it untouched.`);
232
231
  }
233
232
  // Registering the tokens file in `patchLint.rawColorAllowlist` is the
234
- // complement to the scaffold itself: the file exists specifically to
235
- // carry raw color literals, and without the allowlist entry the very
236
- // first `fireforge lint` run against a post-`token add` workspace
237
- // fails on raw-color-value issues for tokens the operator just
238
- // created. The add is idempotent, so re-running `furnace init --force`
239
- // does not duplicate the entry.
233
+ // complement to the scaffold itself: the file exists specifically to carry
234
+ // raw color literals, and without the allowlist entry the first
235
+ // `fireforge lint` after a `token add` fails on raw-color-value issues for
236
+ // tokens the operator just created. The add is idempotent, so re-running
237
+ // `furnace init --force` does not duplicate the entry.
240
238
  try {
241
239
  const existingAllowlist = forgeConfig.patchLint?.rawColorAllowlist ?? [];
242
240
  if (!existingAllowlist.includes(tokensCssPath)) {
@@ -252,18 +250,17 @@ async function scaffoldTokensCss(projectRoot) {
252
250
  warn(`Could not register tokens CSS in patchLint.rawColorAllowlist: ${toError(error).message}. ` +
253
251
  `Add "${tokensCssPath}" manually under patchLint.rawColorAllowlist in fireforge.json if lint flags its contents.`);
254
252
  }
255
- // 2026-04-26 eval Finding 2: register the tokens CSS in
256
- // browser/themes/shared/jar.inc.mn so the file is owned end-to-end by
257
- // tooling. Pre-fix, `furnace init` only scaffolded + allowlisted the
258
- // file, so the very next `fireforge status` correctly flagged it as
259
- // unmanaged + unregistered and `furnace deploy --dry-run` reported
260
- // nothing to deploy — a documented init command turned a clean
261
- // project into an unclean one. The CSS lives at the canonical
262
- // `browser/themes/shared/<binaryName>-tokens.css` path that the
263
- // shared-CSS rule already targets, so the tokens file gets the same
264
- // `skin/classic/browser/<name>.css (../shared/<name>.css)` jar.inc.mn
265
- // entry as any other shared CSS. Idempotent — running
266
- // `furnace init --force` against a registered tree is a no-op.
253
+ // Register the tokens CSS in browser/themes/shared/jar.inc.mn so the file
254
+ // is owned end-to-end by tooling. Scaffolding and allowlisting it without
255
+ // registering leaves the very next `fireforge status` correctly flagging
256
+ // it as unmanaged + unregistered while `furnace deploy --dry-run` reports
257
+ // nothing to deploy — a documented init command turning a clean project
258
+ // unclean. The CSS lives at the canonical
259
+ // `browser/themes/shared/<binaryName>-tokens.css` path the shared-CSS rule
260
+ // already targets, so it gets the same
261
+ // `skin/classic/browser/<name>.css (../shared/<name>.css)` entry as any
262
+ // other shared CSS. Idempotent — `furnace init --force` against a
263
+ // registered tree is a no-op.
267
264
  try {
268
265
  const fileBase = `${forgeConfig.binaryName}-tokens.css`;
269
266
  const result = await registerSharedCSS(paths.engine, fileBase, undefined, false);
@@ -3,11 +3,13 @@ import { readdir } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { select, text } from '@clack/prompts';
5
5
  import { getProjectPaths, loadConfig, loadState } from '../../core/config.js';
6
+ import { stdioIsInteractive } from '../../core/destructive.js';
6
7
  import { createDefaultFurnaceConfig, furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
7
8
  import { resolveFtlDir } from '../../core/furnace-constants.js';
8
- import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
9
+ import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
10
+ import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
9
11
  import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, } from '../../core/furnace-registration-validate.js';
10
- import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotFile, } from '../../core/furnace-rollback.js';
12
+ import { createRollbackJournal, recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
13
  import { getComponentDetails, scanWidgetsDirectory } from '../../core/furnace-scanner.js';
12
14
  import { InvalidArgumentError } from '../../errors/base.js';
13
15
  import { FurnaceError } from '../../errors/furnace.js';
@@ -22,9 +24,14 @@ async function loadAuthoringFurnaceConfig(projectRoot) {
22
24
  }
23
25
  /**
24
26
  * Copies the source files needed for a new override into the workspace.
27
+ * @param engineDir - Absolute path to the engine checkout
25
28
  * @param srcDir - Original component directory in the engine checkout
26
29
  * @param destDir - Destination override directory in the workspace
30
+ * @param componentName - Custom element tag name being overridden
31
+ * @param hasFTL - Whether the component ships a localized `.ftl` file
27
32
  * @param overrideType - Requested override mode
33
+ * @param ftlDir - Engine-relative directory the shared `.ftl` deploys into
34
+ * @param journal - Rollback journal every write is recorded in
28
35
  * @returns Filenames copied into the override directory
29
36
  */
30
37
  async function copyOverrideFiles(engineDir, srcDir, destDir, componentName, hasFTL, overrideType, ftlDir, journal) {
@@ -81,10 +88,10 @@ async function copyOverrideFiles(engineDir, srcDir, destDir, componentName, hasF
81
88
  /**
82
89
  * Writes override metadata to disk and updates furnace.json with the new
83
90
  * override entry. Re-reads the current on-disk furnace.json inside the
84
- * operation lock and splices the new entry onto the fresh state so two
85
- * concurrent `furnace override` commands cannot race their read-modify
86
- * -write cycles into a single surviving entry (eval 2: parallel overrides
87
- * both reported success but furnace.json kept only the second writer).
91
+ * operation lock and splices the new entry onto the fresh state, so two
92
+ * concurrent `furnace override` commands cannot race their read-modify-write
93
+ * cycles into a single surviving entry — otherwise both report success and
94
+ * furnace.json keeps only the second writer's.
88
95
  */
89
96
  async function saveOverrideConfig(projectRoot, destDir, componentName, overrideType, description, details, firefoxVersion, journal, baseCommit) {
90
97
  const overrideJson = {
@@ -137,17 +144,12 @@ async function performOverrideMutations(args) {
137
144
  return filesCopied;
138
145
  }
139
146
  catch (error) {
140
- // This body owns its rollback end to end, so tell the lifecycle
141
- // wrapper not to restore the same journal again on the way out.
142
- ctx.markRolledBack();
143
- try {
144
- await restoreRollbackJournalOrThrow(journal, `Failed to override component "${args.componentName}"`);
145
- }
146
- catch (rollbackError) {
147
- await recordFurnaceRollbackFailure(args.projectRoot, 'override-rollback', `component "${args.componentName}": ${toError(rollbackError).message}`);
148
- throw rollbackError;
149
- }
150
- throw error;
147
+ return await completeJournalRollback(ctx, journal, error, {
148
+ projectRoot: args.projectRoot,
149
+ operation: 'override-rollback',
150
+ failureMessage: `Failed to override component "${args.componentName}"`,
151
+ subject: `component "${args.componentName}"`,
152
+ });
151
153
  }
152
154
  });
153
155
  }
@@ -190,7 +192,7 @@ function promoteStockToOverrideIfNeeded(config, componentName) {
190
192
  * @param options - CLI options for non-interactive mode
191
193
  */
192
194
  export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
193
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
195
+ const isInteractive = stdioIsInteractive();
194
196
  intro('Furnace Override');
195
197
  // --- Validate config-independent inputs BEFORE auto-creating furnace.json
196
198
  // so a failed authoring command never strands a fresh config in the
@@ -205,9 +207,7 @@ export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
205
207
  'Usage: fireforge furnace override <name> -t <type> -d "description"', 'name');
206
208
  }
207
209
  // Verify engine/ exists (config-independent precondition)
208
- if (!(await pathExists(paths.engine))) {
209
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
210
- }
210
+ await assertFurnaceEngineReady(projectRoot);
211
211
  // Load the current config without auto-creating a new furnace.json. A user
212
212
  // cancelling out of the interactive prompts should not leave a fresh config
213
213
  // behind in an otherwise untouched project.
@@ -334,14 +334,12 @@ export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
334
334
  */
335
335
  export async function furnaceBatchOverrideCommand(projectRoot, names, options = {}) {
336
336
  intro(`Furnace Override (batch: ${names.length} components)`);
337
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
337
+ const isInteractive = stdioIsInteractive();
338
338
  if (!options.type && !isInteractive) {
339
339
  throw new InvalidArgumentError('Override type is required for batch override in non-interactive mode. Use -t css-only or -t full.', 'type');
340
340
  }
341
341
  const paths = getProjectPaths(projectRoot);
342
- if (!(await pathExists(paths.engine))) {
343
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
344
- }
342
+ await assertFurnaceEngineReady(projectRoot);
345
343
  // Validate all names upfront before any mutations
346
344
  for (const name of names) {
347
345
  if (!CUSTOM_ELEMENT_TAG_PATTERN.test(name)) {
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { join } from 'node:path';
3
- import { getProjectPaths } from '../../core/config.js';
4
3
  import { applyAllComponents } from '../../core/furnace-apply.js';
5
- import { furnaceConfigExists, loadFurnaceConfig, updateFurnaceState, } from '../../core/furnace-config.js';
4
+ import { updateFurnaceState } from '../../core/furnace-config.js';
6
5
  import { runFurnaceMutation } from '../../core/furnace-operation.js';
6
+ import { assertFurnaceReady } from '../../core/furnace-precondition.js';
7
7
  import { restoreRollbackJournal } from '../../core/furnace-rollback.js';
8
8
  import { countEntriesWithBlockingStepErrors } from '../../core/furnace-step-errors.js';
9
9
  import { cleanStories, syncStories } from '../../core/furnace-stories.js';
@@ -49,14 +49,11 @@ async function runPreviewTeardown(engineDir, storiesCleanupRequired, journal) {
49
49
  return errors;
50
50
  }
51
51
  /**
52
- * Reports staging failures (component-level errors and per-step errors) to the
53
- * user and throws a single FurnaceError summarising the failure count.
54
- * Extracted from `furnacePreviewCommand` to keep that function under the
55
- * `max-lines-per-function` threshold and to colocate the failure-reporting
56
- * contract in one place.
52
+ * Reports staging failures (component-level errors and per-step errors) to
53
+ * the user and throws a single FurnaceError summarising the failure count.
57
54
  *
58
- * @returns The total failure count if there were any (always non-zero when
59
- * this returns; the function throws after logging).
55
+ * Never returns: it always throws after logging, which is what the `never`
56
+ * return type states.
60
57
  */
61
58
  function reportPreviewStagingFailures(stageResult) {
62
59
  for (const err of stageResult.errors) {
@@ -76,13 +73,12 @@ function reportPreviewStagingFailures(stageResult) {
76
73
  /**
77
74
  * Filenames emitted by the Firefox build backend (not by Storybook's npm
78
75
  * package set) — their absence means `mach build` has not produced its
79
- * post-configure artifacts, which is a different failure mode from a
80
- * missing Storybook workspace dependency tree. The eval log for finding
81
- * #11 reported `FileNotFoundError: [...] chrome-map.json` *after* a
82
- * successful Storybook `npm install`, and the pre-0.16 heuristic
83
- * misdiagnosed it as a dep failure and sent the operator back to
84
- * `--install`. Pattern list is narrow on purpose so we only surface the
85
- * backend-rebuild hint when we are confident.
76
+ * post-configure artifacts, which is a different failure mode from a missing
77
+ * Storybook workspace dependency tree. A
78
+ * `FileNotFoundError: [...] chrome-map.json` AFTER a successful Storybook
79
+ * `npm install` is the case a dep-focused heuristic misdiagnoses, sending
80
+ * the operator back to `--install`. The pattern list is narrow on purpose so
81
+ * the backend-rebuild hint only fires when it is confident.
86
82
  */
87
83
  const BACKEND_ARTIFACT_PATTERNS = [
88
84
  /chrome-map\.json/i,
@@ -124,17 +120,14 @@ export function buildStorybookFailureMessage(output, installRequested) {
124
120
  }
125
121
  /**
126
122
  * Preflights the Firefox build + toolchain prerequisites `mach storybook`
127
- * quietly assumes. Pre-0.16.0 the preview staged components and launched
128
- * a ~1000-package `mach storybook upgrade` npm install before the
129
- * backend surfaced a "missing chrome-map.json" / Cargo-config failure;
130
- * the preflight below refuses fast and leaves the workspace untouched.
131
- *
132
- * Extracted from `furnacePreviewCommand` so the main function stays
133
- * under the per-function LOC budget as the preflight list grows.
123
+ * quietly assumes. Without it, preview stages components and launches a
124
+ * ~1000-package `mach storybook upgrade` npm install before the backend
125
+ * surfaces a "missing chrome-map.json" / Cargo-config failure; this refuses
126
+ * fast and leaves the workspace untouched.
134
127
  *
135
128
  * @param engineDir - Resolved engine directory
136
- * @throws FurnaceError when the Firefox build hasn't produced dist/, or
137
- * when `.cargo/config.toml` is absent
129
+ * @throws FurnaceError when the Firefox build has not produced dist/, or
130
+ * when `.cargo/config.toml` is absent
138
131
  */
139
132
  async function assertPreviewPrerequisites(engineDir) {
140
133
  const buildCheck = await hasBuildArtifacts(engineDir);
@@ -146,14 +139,12 @@ async function assertPreviewPrerequisites(engineDir) {
146
139
  }
147
140
  // Accept either `.cargo/config.toml` (post-configure) or
148
141
  // `.cargo/config.toml.in` (post-bootstrap template, consumed at
149
- // `mach configure` time). Pre-0.16.0 the preflight insisted on the
150
- // plain file, but `fireforge bootstrap` alone produces only `.in` —
151
- // operators who followed the remediation instruction ("run bootstrap
152
- // then rerun preview") hit the same refusal on the retry. Either name
153
- // is sufficient to prove the Rust toolchain is registered; the stronger
154
- // `hasBuildArtifacts` check above already guards against a completely
155
- // un-configured tree, so relaxing this to an OR-check does not weaken
156
- // the signal we care about.
142
+ // `mach configure` time). Insisting on the plain file makes operators who
143
+ // follow the remediation instruction ("run bootstrap then rerun preview")
144
+ // hit the same refusal on the retry, because `fireforge bootstrap` alone
145
+ // produces only `.in`. Either name is sufficient to prove the Rust
146
+ // toolchain is registered; the stronger `hasBuildArtifacts` check above
147
+ // already guards against a completely un-configured tree.
157
148
  const cargoConfigPath = join(engineDir, '.cargo', 'config.toml');
158
149
  const cargoConfigInPath = join(engineDir, '.cargo', 'config.toml.in');
159
150
  const cargoConfigPresent = (await pathExists(cargoConfigPath)) || (await pathExists(cargoConfigInPath));
@@ -165,15 +156,14 @@ async function assertPreviewPrerequisites(engineDir) {
165
156
  }
166
157
  }
167
158
  /**
168
- * Emits a framing banner when the Storybook workspace has not yet had
169
- * its npm dependencies installed. `mach storybook` will drive the
170
- * install internally and print ELSPROBLEMS / UNMET DEPENDENCY lines
171
- * verbatim; without this banner operators reliably read the npm output
172
- * as a failure (2026-04-24 eval Finding 13).
159
+ * Emits a framing banner when the Storybook workspace has not yet had its npm
160
+ * dependencies installed. `mach storybook` drives the install internally and
161
+ * prints ELSPROBLEMS / UNMET DEPENDENCY lines verbatim; without this banner
162
+ * operators reliably read the npm output as a failure.
173
163
  *
174
- * Skipped when `--install` was explicitly requested — that path already
175
- * runs `mach storybook upgrade` before the preview launches, so the npm
176
- * output for the subsequent `mach storybook` invocation is a no-op.
164
+ * Skipped when `--install` was explicitly requested — that path already runs
165
+ * `mach storybook upgrade` before the preview launches, so the npm output
166
+ * for the subsequent `mach storybook` invocation is a no-op.
177
167
  */
178
168
  async function announceStorybookFirstRunIfNeeded(engineDir, installRequested) {
179
169
  if (installRequested)
@@ -203,16 +193,7 @@ function announceStorybookCleanExitIfApplicable(exitCode) {
203
193
  */
204
194
  export async function furnacePreviewCommand(projectRoot, options = {}) {
205
195
  intro('Furnace Preview (Storybook)');
206
- // Verify engine exists
207
- const paths = getProjectPaths(projectRoot);
208
- if (!(await pathExists(paths.engine))) {
209
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
210
- }
211
- // Load furnace config
212
- if (!(await furnaceConfigExists(projectRoot))) {
213
- throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
214
- }
215
- const config = await loadFurnaceConfig(projectRoot);
196
+ const { paths, config } = await assertFurnaceReady(projectRoot);
216
197
  const stockCount = config.stock.length;
217
198
  const overrideCount = Object.keys(config.overrides).length;
218
199
  const customCount = Object.keys(config.custom).length;
@@ -226,8 +207,8 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
226
207
  if (!(await pathExists(storybookRoot))) {
227
208
  throw new FurnaceError('This Firefox checkout does not contain browser/components/storybook. Furnace preview requires the upstream Storybook workspace to exist before stories can be synced.');
228
209
  }
229
- // Build + toolchain preflight (Finding #9). Extracted into a helper so
230
- // the function below stays under the per-function LOC budget.
210
+ // Build + toolchain preflight, extracted into a helper so the function
211
+ // below stays under the per-function LOC budget.
231
212
  await assertPreviewPrerequisites(paths.engine);
232
213
  let previewResult;
233
214
  // True once we are about to (or have) written to engine/.../stories/furnace.
@@ -302,10 +283,8 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
302
283
  }
303
284
  installSpinner.stop('Storybook dependencies reinstalled');
304
285
  }
305
- // 2026-04-24 eval Finding 13: frame the npm noise that `mach
306
- // storybook` emits on first-run as expected progress rather than a
307
- // failure. The banner-before / banner-after helpers are extracted
308
- // so the command body stays under the per-function LOC budget.
286
+ // Frame the npm noise `mach storybook` emits on first run as expected
287
+ // progress rather than a failure.
309
288
  await announceStorybookFirstRunIfNeeded(paths.engine, options.install ?? false);
310
289
  // Start Storybook
311
290
  info('Starting Storybook...');
@@ -317,16 +296,17 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
317
296
  primaryError = error;
318
297
  }
319
298
  // Teardown runs unconditionally and never short-circuits: a failure in
320
- // cleanStories must not prevent the journal restore, and vice versa. The
321
- // previous implementation ran teardown in a `finally` block that called
322
- // `restoreRollbackJournalOrThrow`, which threw synchronously — that throw
323
- // bypassed the primary error and, worse, skipped downstream handling so
324
- // the engine was left with staged files and the user got a teardown
325
- // message with no guidance. We now collect both failures and, if anything
326
- // went wrong, persist a `pendingRepair` marker that `fireforge doctor`
327
- // consumes to finish the reconciliation.
328
- // Teardown restores the journal itself, so the lifecycle wrapper must not
329
- // restore it again when the throw below propagates out of this body.
299
+ // cleanStories must not prevent the journal restore, and vice versa.
300
+ // Running teardown in a `finally` that calls
301
+ // `restoreRollbackJournalOrThrow` throws synchronously, bypassing the
302
+ // primary error and skipping downstream handling — the engine is left
303
+ // with staged files and the user gets a teardown message with no
304
+ // guidance. Both failures are collected instead and, if anything went
305
+ // wrong, a `pendingRepair` marker is persisted for `fireforge doctor` to
306
+ // finish the reconciliation.
307
+ //
308
+ // Teardown restores the journal itself, so the lifecycle wrapper must
309
+ // not restore it again when the throw below propagates out of this body.
330
310
  ctx.markRolledBack();
331
311
  const teardownErrors = await runPreviewTeardown(paths.engine, storiesCleanupRequired, previewJournal);
332
312
  if (teardownErrors.length > 0) {
@@ -6,9 +6,10 @@ import { getOverrideEngineTargetPath } from '../../core/furnace-apply-helpers.js
6
6
  import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
7
7
  import { resolveFtlDir } from '../../core/furnace-constants.js';
8
8
  import { isComponentSourceFile } from '../../core/furnace-constants.js';
9
- import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
9
+ import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
10
+ import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
10
11
  import { refreshOverrideFile } from '../../core/furnace-refresh.js';
11
- import { createRollbackJournal, restoreRollbackJournal, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
12
+ import { createRollbackJournal, restoreRollbackJournal, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
12
13
  import { getHead } from '../../core/git.js';
13
14
  import { FurnaceError } from '../../errors/furnace.js';
14
15
  import { toError } from '../../utils/errors.js';
@@ -144,17 +145,15 @@ async function refreshSingleOverride(projectRoot, name, options = {}) {
144
145
  return fileResults;
145
146
  }
146
147
  catch (error) {
148
+ // A dry run wrote nothing, so there is no journal to restore — it
149
+ // rethrows without touching the lifecycle wrapper.
147
150
  if (!dryRun) {
148
- // This body owns its rollback end to end, so tell the lifecycle
149
- // wrapper not to restore the same journal again on the way out.
150
- ctx.markRolledBack();
151
- try {
152
- await restoreRollbackJournalOrThrow(journal, `Failed to refresh override "${name}"`);
153
- }
154
- catch (rollbackError) {
155
- await recordFurnaceRollbackFailure(projectRoot, 'refresh-rollback', `override "${name}": ${toError(rollbackError).message}`);
156
- throw rollbackError;
157
- }
151
+ return await completeJournalRollback(ctx, journal, error, {
152
+ projectRoot,
153
+ operation: 'refresh-rollback',
154
+ failureMessage: `Failed to refresh override "${name}"`,
155
+ subject: `override "${name}"`,
156
+ });
158
157
  }
159
158
  throw error;
160
159
  }
@@ -228,10 +227,7 @@ export async function furnaceRefreshCommand(projectRoot, name, options = {}) {
228
227
  // Verify engine exists — refresh reads engine files for three-way merge
229
228
  // and --reset-base reads engine HEAD. Without this check, the user gets
230
229
  // an obscure git error instead of a clear precondition message.
231
- const paths = getProjectPaths(projectRoot);
232
- if (!(await pathExists(paths.engine))) {
233
- throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
234
- }
230
+ await assertFurnaceEngineReady(projectRoot);
235
231
  const dryRun = options.dryRun ?? false;
236
232
  if (name) {
237
233
  intro('Furnace Refresh');
@@ -5,12 +5,9 @@ import type { FurnaceState } from '../../types/furnace.js';
5
5
  /**
6
6
  * Engine-side and workspace-side removal for an OVERRIDE component.
7
7
  *
8
- * Extracted in 0.41.0: this and its custom-component sibling were the only
9
- * large inline blocks in the furnace subsystem never wrapped in a
10
- * `perform*Mutations` helper, and together they held `remove.ts` at 496/500
11
- * file lines with `furnaceRemoveCommand` at 149/150 — one line from a build
12
- * failure. Both run inside the caller's rollback journal rather than opening
13
- * their own, so a failure anywhere still restores the whole operation.
8
+ * Runs inside the caller's rollback journal rather than opening its own, so
9
+ * a failure anywhere in the remove still restores the whole operation as a
10
+ * single unit.
14
11
  */
15
12
  export declare function performOverrideRemovalMutations(args: {
16
13
  name: string;
@@ -2,12 +2,10 @@
2
2
  /**
3
3
  * Engine-side and workspace-side mutation bodies for `furnace remove`.
4
4
  *
5
- * Split out of `remove.ts` in 0.41.0. The two `perform*RemovalMutations`
6
- * functions were the only large inline blocks in the furnace subsystem never
7
- * wrapped in a helper, and together they held `remove.ts` at 496/500 file
8
- * lines with `furnaceRemoveCommand` at 149/150 — one line from a build
9
- * failure. Extracting them inside the same file traded a function-length
10
- * problem for a file-length one, so they live here instead.
5
+ * Split out of `remove.ts`, where the two `perform*RemovalMutations`
6
+ * functions held the file at the per-file line cap with
7
+ * `furnaceRemoveCommand` at the per-function cap. Extracting them inside the
8
+ * same file would trade a function-length problem for a file-length one.
11
9
  *
12
10
  * Both run inside the CALLER's rollback journal rather than opening their
13
11
  * own, so a failure anywhere in the remove still restores the whole
@@ -84,12 +82,9 @@ async function restoreOverrideEngineFiles(engineDir, overrideDir, overrideConfig
84
82
  /**
85
83
  * Engine-side and workspace-side removal for an OVERRIDE component.
86
84
  *
87
- * Extracted in 0.41.0: this and its custom-component sibling were the only
88
- * large inline blocks in the furnace subsystem never wrapped in a
89
- * `perform*Mutations` helper, and together they held `remove.ts` at 496/500
90
- * file lines with `furnaceRemoveCommand` at 149/150 — one line from a build
91
- * failure. Both run inside the caller's rollback journal rather than opening
92
- * their own, so a failure anywhere still restores the whole operation.
85
+ * Runs inside the caller's rollback journal rather than opening its own, so
86
+ * a failure anywhere in the remove still restores the whole operation as a
87
+ * single unit.
93
88
  */
94
89
  export async function performOverrideRemovalMutations(args) {
95
90
  const { name, paths, furnacePaths, freshConfig, freshState, ftlDir, journal } = args;
@@ -162,10 +157,8 @@ export async function performCustomRemovalMutations(args) {
162
157
  }
163
158
  // Drop the locale jar.mn chrome registration that `applyCustomFtlFile`
164
159
  // wrote during deploy — otherwise the engine is left with a
165
- // `locale/.../${name}.ftl` entry pointing at a file we just
166
- // deleted. 2026-04-21 eval (Finding #1): `furnace remove` left
167
- // `browser/locales/jar.mn` referencing the missing FTL, which
168
- // would break the next package-manifest validation.
160
+ // `locale/.../${name}.ftl` entry pointing at a file just deleted, which
161
+ // breaks the next package-manifest validation.
169
162
  await removeCustomFtlJarMnEntry(paths.engine, `${name}.ftl`, ftlDir, customConfig, journal);
170
163
  }
171
164
  }
@@ -1,4 +1,5 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
2
3
  import { join } from 'node:path';
3
4
  import { loadFurnaceState } from '../../core/furnace-config.js';
4
5
  import { snapshotFile } from '../../core/furnace-rollback.js';
@@ -52,7 +53,6 @@ export async function removeDeployedCustomFiles(projectRoot, engineDir, name, ta
52
53
  if (deletedCount > 0) {
53
54
  info(`Deleted ${deletedCount} deployed file(s) from engine/${targetPath}/`);
54
55
  }
55
- const { readdir } = await import('node:fs/promises');
56
56
  if ((await readdir(engineTargetDir)).length === 0) {
57
57
  await removeDir(engineTargetDir);
58
58
  info(`Removed now-empty engine/${targetPath}/`);