@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
@@ -7,6 +7,7 @@ import { pathExists, readText } from '../utils/fs.js';
7
7
  import { warn } from '../utils/logger.js';
8
8
  import { escapeRegex, stripJsComments } from '../utils/regex.js';
9
9
  import { getProjectPaths, loadConfig } from './config.js';
10
+ import { normalizeForChecksum } from './furnace-checksum-utils.js';
10
11
  import { getFurnacePaths } from './furnace-config.js';
11
12
  import { CUSTOM_ELEMENTS_JS, FTL_DIR, JAR_MN } from './furnace-constants.js';
12
13
  import { expandCssFragments, listFragmentIncludes } from './furnace-css-fragments.js';
@@ -124,8 +125,12 @@ export async function checkRegistrationConsistency(root, name, config, ftlDir) {
124
125
  }
125
126
  }
126
127
  const destContent = await readText(destPath);
127
- const srcHash = createHash('sha256').update(srcContent).digest('hex');
128
- const destHash = createHash('sha256').update(destContent).digest('hex');
128
+ // Same normalization the checksums written by apply use. These three
129
+ // comparisons hashed raw bytes, so on a CRLF checkout `furnace status`
130
+ // and `furnace validate` reported drift for files apply had just
131
+ // decided were identical.
132
+ const srcHash = createHash('sha256').update(normalizeForChecksum(srcContent)).digest('hex');
133
+ const destHash = createHash('sha256').update(normalizeForChecksum(destContent)).digest('hex');
129
134
  if (srcHash !== destHash) {
130
135
  status.driftedFiles.push(entry.name);
131
136
  status.filesInSync = false;
@@ -150,8 +155,10 @@ export async function checkRegistrationConsistency(root, name, config, ftlDir) {
150
155
  else {
151
156
  const srcContent = await readText(ftlSrc);
152
157
  const destContent = await readText(ftlDest);
153
- const srcHash = createHash('sha256').update(srcContent).digest('hex');
154
- const destHash = createHash('sha256').update(destContent).digest('hex');
158
+ const srcHash = createHash('sha256').update(normalizeForChecksum(srcContent)).digest('hex');
159
+ const destHash = createHash('sha256')
160
+ .update(normalizeForChecksum(destContent))
161
+ .digest('hex');
155
162
  if (srcHash !== destHash) {
156
163
  status.driftedFiles.push(ftlName);
157
164
  status.filesInSync = false;
@@ -225,11 +232,9 @@ export async function validateJarMnEntries(root, config) {
225
232
  }
226
233
  }
227
234
  // Stale registrations: lines pointing at component files that no longer
228
- // exist in the workspace (left behind by a rename/delete under an older
229
- // FireForge). These break `mach build` at packaging ("File ... not
230
- // found"), so they are errors and `--fix` prunes them (0.34.0 field
231
- // report: both validate --fix and doctor --repair-furnace reported
232
- // success without pruning).
235
+ // exist in the workspace, left behind by a rename or delete. These break
236
+ // `mach build` at packaging ("File ... not found"), so they are errors and
237
+ // `--fix` prunes them.
233
238
  const staleEntries = await findStaleJarMnEntries(engineDir, furnacePaths.customDir, Object.keys(config.custom));
234
239
  for (const stale of staleEntries) {
235
240
  issues.push({
@@ -261,47 +266,106 @@ const AUTO_DETECT_HOST_DIR = 'browser/base/content';
261
266
  * `already`, so callers can merge them with the caller-configured set
262
267
  * without producing double entries in warning output.
263
268
  *
264
- * Motivating case: a fork that mounts a custom element from its own
265
- * top-level chrome document (e.g. `mybrowser.xhtml`) without setting
266
- * `tokenHostDocuments`. The stock `browser.xhtml` was the only thing
267
- * scanned, so the tokens CSS link in the ACTUAL host document went
268
- * unnoticed and the warning false-fired.
269
+ * This catches a fork that mounts a custom element from its own top-level
270
+ * chrome document (e.g. `mybrowser.xhtml`) without setting
271
+ * `tokenHostDocuments`: scanning only the stock `browser.xhtml` would miss
272
+ * the tokens CSS link in the ACTUAL host document and false-fire the
273
+ * warning.
269
274
  *
270
275
  * @param engineDir Absolute engine root.
271
276
  * @param tagName Custom element tag the CSS belongs to.
272
277
  * @param already Paths already in the scan set (POSIX, engine-relative).
273
278
  */
274
- async function autoDetectTokenHostDocuments(engineDir, tagName, already) {
275
- const contentDir = join(engineDir, AUTO_DETECT_HOST_DIR);
276
- if (!(await pathExists(contentDir)))
277
- return [];
278
- let entries;
279
+ /**
280
+ * Per-run caches for work that is invariant across components.
281
+ *
282
+ * `validateAllComponents` walks every component, and re-parsing
283
+ * `fireforge.json` plus re-scanning every `.xhtml` under the chrome-document
284
+ * directory per component is one config parse and one full directory read
285
+ * PER COMPONENT, for work that depends only on the project and the engine.
286
+ *
287
+ * Keyed by root/engine dir and cleared by
288
+ * {@link resetRegistrationValidationCaches} at the start of each run, so a
289
+ * long-lived process (the test suite, `watch`) never serves a stale read.
290
+ */
291
+ const tokensCssFileNameCache = new Map();
292
+ const chromeDocumentCache = new Map();
293
+ /**
294
+ * Whether a batch validation is in flight.
295
+ *
296
+ * The caches are consulted ONLY while this is set. Outside a batch — a direct
297
+ * `validateComponent` call from `furnace status`, from apply's consistency
298
+ * check, or from a test — every read goes to disk, so a caller that mutates
299
+ * the engine between calls can never be served a stale document.
300
+ */
301
+ let batchInFlight = false;
302
+ /**
303
+ * Runs `body` with the per-run caches enabled, then clears them.
304
+ *
305
+ * @param body - The batch to run
306
+ * @returns Whatever `body` resolves to
307
+ */
308
+ export async function withRegistrationValidationCache(body) {
309
+ const outer = batchInFlight;
310
+ batchInFlight = true;
279
311
  try {
280
- entries = await readdir(contentDir);
312
+ return await body();
281
313
  }
282
- catch {
283
- // No readable chrome-document directory means no documents reference the
284
- // tag, which is the same verdict as an empty directory.
285
- return [];
314
+ finally {
315
+ batchInFlight = outer;
316
+ if (!outer) {
317
+ tokensCssFileNameCache.clear();
318
+ chromeDocumentCache.clear();
319
+ }
286
320
  }
287
- const alreadySet = new Set(already);
288
- const detected = [];
289
- for (const entry of entries) {
290
- if (!entry.endsWith('.xhtml'))
291
- continue;
292
- const relPath = `${AUTO_DETECT_HOST_DIR}/${entry}`;
293
- if (alreadySet.has(relPath))
294
- continue;
295
- const absPath = join(contentDir, entry);
296
- let content;
321
+ }
322
+ /**
323
+ * Reads every chrome document under the auto-detect directory once per run.
324
+ *
325
+ * @param engineDir - Absolute path to the engine checkout
326
+ * @returns Map of engine-relative document path to its content
327
+ */
328
+ async function loadChromeDocuments(engineDir) {
329
+ const cached = batchInFlight ? chromeDocumentCache.get(engineDir) : undefined;
330
+ if (cached)
331
+ return cached;
332
+ const documents = new Map();
333
+ const contentDir = join(engineDir, AUTO_DETECT_HOST_DIR);
334
+ if (await pathExists(contentDir)) {
335
+ let entries;
297
336
  try {
298
- content = await readText(absPath);
337
+ entries = await readdir(contentDir);
299
338
  }
300
339
  catch {
301
- // An unreadable chrome document cannot be searched for the tag; skip it and
302
- // keep scanning the rest.
303
- continue;
340
+ // No readable chrome-document directory means no documents reference
341
+ // the tag, which is the same verdict as an empty directory.
342
+ entries = [];
343
+ }
344
+ for (const entry of entries) {
345
+ if (!entry.endsWith('.xhtml'))
346
+ continue;
347
+ const rel = `${AUTO_DETECT_HOST_DIR}/${entry}`;
348
+ try {
349
+ documents.set(rel, await readText(join(contentDir, entry)));
350
+ }
351
+ catch {
352
+ // Unreadable document: treated as not referencing the tag.
353
+ }
304
354
  }
355
+ }
356
+ if (batchInFlight)
357
+ chromeDocumentCache.set(engineDir, documents);
358
+ return documents;
359
+ }
360
+ async function autoDetectTokenHostDocuments(engineDir, tagName, already) {
361
+ // Reads the directory ONCE per run rather than once per component: only the
362
+ // tag being searched for differs between components.
363
+ const documents = await loadChromeDocuments(engineDir);
364
+ const alreadySet = new Set(already);
365
+ const detected = [];
366
+ for (const [relPath, content] of documents) {
367
+ if (alreadySet.has(relPath))
368
+ continue;
305
369
  if (content.includes(tagName)) {
306
370
  detected.push(relPath);
307
371
  }
@@ -337,9 +401,19 @@ export async function validateTokenLink(componentDir, tagName, root, tokenPrefix
337
401
  : DEFAULT_TOKEN_HOST_DOCUMENTS;
338
402
  let tokensCssFile;
339
403
  try {
340
- const forgeConfig = await loadConfig(root);
341
- const segments = getTokensCssPath(forgeConfig.binaryName).split('/');
342
- tokensCssFile = segments[segments.length - 1] ?? '';
404
+ // Cached per run: the filename depends on `binaryName` alone, so parsing
405
+ // fireforge.json once per token-using component was pure repetition.
406
+ const cachedName = batchInFlight ? tokensCssFileNameCache.get(root) : undefined;
407
+ if (cachedName !== undefined) {
408
+ tokensCssFile = cachedName;
409
+ }
410
+ else {
411
+ const forgeConfig = await loadConfig(root);
412
+ const segments = getTokensCssPath(forgeConfig.binaryName).split('/');
413
+ tokensCssFile = segments[segments.length - 1] ?? '';
414
+ if (batchInFlight)
415
+ tokensCssFileNameCache.set(root, tokensCssFile);
416
+ }
343
417
  }
344
418
  catch (error) {
345
419
  const reason = toError(error).message;
@@ -23,3 +23,19 @@ export declare function validateComponent(componentDir: string, tagName: string,
23
23
  * @returns Map of component name to its validation issues
24
24
  */
25
25
  export declare function validateAllComponents(root: string): Promise<Map<string, ValidationIssue[]>>;
26
+ /**
27
+ * Flags engine-side files that a previous deploy of `tagName` left behind
28
+ * after their workspace source was renamed or removed.
29
+ *
30
+ * Detection keys on the furnace state file: every `appliedChecksums` entry
31
+ * under `custom/<tagName>/` whose workspace source no longer exists but whose
32
+ * engine target is still present is an orphan. The next deploy prunes it, but
33
+ * until then jar.mn and the deployed directory disagree with the workspace,
34
+ * and a re-export could capture the stale state into a patch.
35
+ *
36
+ * Custom components only: override undeploys restore the upstream baseline
37
+ * rather than deleting files, so "orphan" has no meaning there.
38
+ */
39
+ export declare function findOrphanedEngineFiles(root: string, config: FurnaceConfig, tagName: string, state: {
40
+ appliedChecksums?: Record<string, string>;
41
+ }, ftlDir: string): Promise<ValidationIssue[]>;
@@ -4,13 +4,15 @@ import { join } from 'node:path';
4
4
  import { toError } from '../utils/errors.js';
5
5
  import { pathExists } from '../utils/fs.js';
6
6
  import { getProjectPaths, loadConfig } from './config.js';
7
+ import { extractComponentChecksums } from './furnace-checksum-utils.js';
7
8
  import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState } from './furnace-config.js';
8
9
  import { resolveFtlDir, xpcshellTestParentDir } from './furnace-constants.js';
9
10
  import { validateCssFragments } from './furnace-css-fragments.js';
10
11
  import { detectComposesCycles, validateComposesReferences } from './furnace-graph-utils.js';
11
12
  import { findJsconfigPathsDrift } from './furnace-jsconfig.js';
12
13
  import { validateAccessibility, validateCompatibility, validateJarMnEntries, validateRegistrationPatterns, validateStructure, validateTokenLink, } from './furnace-validate-checks.js';
13
- import { findOrphanedEngineFiles } from './furnace-validate-helpers.js';
14
+ import { createIssue } from './furnace-validate-helpers.js';
15
+ import { withRegistrationValidationCache } from './furnace-validate-registration.js';
14
16
  import { findOverrideBaseVersionDrift, } from './furnace-version-drift.js';
15
17
  function buildOverrideVersionDriftIssues(config, currentVersion, tagName) {
16
18
  return findOverrideBaseVersionDrift(config, currentVersion)
@@ -59,9 +61,8 @@ export async function validateComponent(componentDir, tagName, type, config, roo
59
61
  if (root) {
60
62
  issues.push(...(await validateTokenLink(componentDir, tagName, root, config?.tokenPrefix, config?.tokenHostDocuments)));
61
63
  }
62
- // CSS fragment checks (field report D2): missing fragment files are
63
- // structural errors; stale deployed expansions are drift the next deploy
64
- // refreshes.
64
+ // CSS fragment checks: missing fragment files are structural errors;
65
+ // stale deployed expansions are drift the next deploy refreshes.
65
66
  if (type === 'custom') {
66
67
  const furnacePaths = getFurnacePaths(root ?? join(componentDir, '..', '..', '..'));
67
68
  const engineTargetDir = root && config?.custom[tagName]
@@ -69,11 +70,10 @@ export async function validateComponent(componentDir, tagName, type, config, roo
69
70
  : undefined;
70
71
  issues.push(...(await validateCssFragments(componentDir, tagName, furnacePaths.sharedDir, engineTargetDir)));
71
72
  }
72
- // Engine-side orphan detection (field report D1): files a previous deploy
73
- // placed in the engine whose workspace source has since been renamed or
74
- // deleted. Surfaces as drift even when every current workspace file is
75
- // in sync, which is exactly the gap that let stale jar.mn lines reach a
76
- // later re-export.
73
+ // Engine-side orphan detection: files a previous deploy placed in the
74
+ // engine whose workspace source has since been renamed or deleted.
75
+ // Surfaces as drift even when every current workspace file is in sync,
76
+ // which is the gap that lets stale jar.mn lines reach a later re-export.
77
77
  if (root && config && type === 'custom') {
78
78
  const state = await loadFurnaceState(root);
79
79
  issues.push(...(await findOrphanedEngineFiles(root, config, tagName, state, resolveFtlDir(config.ftlBasePath))));
@@ -102,160 +102,165 @@ export async function validateComponent(componentDir, tagName, type, config, roo
102
102
  * @returns Map of component name to its validation issues
103
103
  */
104
104
  export async function validateAllComponents(root) {
105
- const config = await loadFurnaceConfig(root);
106
- const furnacePaths = getFurnacePaths(root);
107
- const results = new Map();
108
- // Validate composition graph integrity (dangling references and cycles)
109
- try {
110
- validateComposesReferences(config.stock, config.overrides, config.custom);
111
- }
112
- catch (err) {
113
- const message = toError(err).message;
114
- // Attribute the issue to the first custom component with a bad composes reference
115
- for (const [name, cfg] of Object.entries(config.custom)) {
116
- if (cfg.composes) {
117
- const existing = results.get(name) ?? [];
118
- existing.push({
119
- component: name,
120
- severity: 'error',
121
- check: 'composes-dangling-reference',
122
- message,
123
- });
124
- results.set(name, existing);
125
- break;
105
+ // Wrapped so the config parse and the chrome-document scan happen once for
106
+ // the whole batch instead of once per component. The caches are inert
107
+ // outside this window, so a direct `validateComponent` caller is never
108
+ // served a stale read.
109
+ return await withRegistrationValidationCache(async () => {
110
+ const config = await loadFurnaceConfig(root);
111
+ const furnacePaths = getFurnacePaths(root);
112
+ const results = new Map();
113
+ // Validate composition graph integrity (dangling references and cycles)
114
+ try {
115
+ validateComposesReferences(config.stock, config.overrides, config.custom);
116
+ }
117
+ catch (err) {
118
+ const message = toError(err).message;
119
+ // Attribute the issue to the first custom component with a bad composes reference
120
+ for (const [name, cfg] of Object.entries(config.custom)) {
121
+ if (cfg.composes) {
122
+ const existing = results.get(name) ?? [];
123
+ existing.push({
124
+ component: name,
125
+ severity: 'error',
126
+ check: 'composes-dangling-reference',
127
+ message,
128
+ });
129
+ results.set(name, existing);
130
+ break;
131
+ }
126
132
  }
127
133
  }
128
- }
129
- try {
130
- detectComposesCycles(config.custom);
131
- }
132
- catch (err) {
133
- const message = toError(err).message;
134
- // Attribute the cycle issue to the first custom component in the cycle
135
- for (const name of Object.keys(config.custom)) {
136
- if (config.custom[name]?.composes) {
137
- const existing = results.get(name) ?? [];
138
- existing.push({
139
- component: name,
140
- severity: 'error',
141
- check: 'composes-cycle',
142
- message,
143
- });
144
- results.set(name, existing);
145
- break;
134
+ try {
135
+ detectComposesCycles(config.custom);
136
+ }
137
+ catch (err) {
138
+ const message = toError(err).message;
139
+ // Attribute the cycle issue to the first custom component in the cycle
140
+ for (const name of Object.keys(config.custom)) {
141
+ if (config.custom[name]?.composes) {
142
+ const existing = results.get(name) ?? [];
143
+ existing.push({
144
+ component: name,
145
+ severity: 'error',
146
+ check: 'composes-cycle',
147
+ message,
148
+ });
149
+ results.set(name, existing);
150
+ break;
151
+ }
146
152
  }
147
153
  }
148
- }
149
- // Override components
150
- for (const name of Object.keys(config.overrides)) {
151
- const componentDir = join(furnacePaths.overridesDir, name);
152
- if (!(await pathExists(componentDir))) {
153
- results.set(name, [
154
- {
155
- component: name,
156
- severity: 'error',
157
- check: 'missing-component-dir',
158
- message: `Component directory not found: components/overrides/${name}`,
159
- },
160
- ]);
161
- continue;
154
+ // Override components
155
+ for (const name of Object.keys(config.overrides)) {
156
+ const componentDir = join(furnacePaths.overridesDir, name);
157
+ if (!(await pathExists(componentDir))) {
158
+ results.set(name, [
159
+ {
160
+ component: name,
161
+ severity: 'error',
162
+ check: 'missing-component-dir',
163
+ message: `Component directory not found: components/overrides/${name}`,
164
+ },
165
+ ]);
166
+ continue;
167
+ }
168
+ const issues = await validateComponent(componentDir, name, 'override', config, root);
169
+ results.set(name, issues);
162
170
  }
163
- const issues = await validateComponent(componentDir, name, 'override', config, root);
164
- results.set(name, issues);
165
- }
166
- // Custom components
167
- for (const name of Object.keys(config.custom)) {
168
- const componentDir = join(furnacePaths.customDir, name);
169
- if (!(await pathExists(componentDir))) {
170
- results.set(name, [
171
- {
172
- component: name,
173
- severity: 'error',
174
- check: 'missing-component-dir',
175
- message: `Component directory not found: components/custom/${name}`,
176
- },
177
- ]);
178
- continue;
171
+ // Custom components
172
+ for (const name of Object.keys(config.custom)) {
173
+ const componentDir = join(furnacePaths.customDir, name);
174
+ if (!(await pathExists(componentDir))) {
175
+ results.set(name, [
176
+ {
177
+ component: name,
178
+ severity: 'error',
179
+ check: 'missing-component-dir',
180
+ message: `Component directory not found: components/custom/${name}`,
181
+ },
182
+ ]);
183
+ continue;
184
+ }
185
+ // Pass root so that per-component token link validation runs.
186
+ // Skip registration/jar.mn checks inside validateComponent — the aggregate
187
+ // validators below run them exactly once across all components, which both
188
+ // surfaces cross-component issues and avoids double-counting.
189
+ const issues = await validateComponent(componentDir, name, 'custom', config, root, {
190
+ skipAggregateChecks: true,
191
+ });
192
+ results.set(name, issues);
179
193
  }
180
- // Pass root so that per-component token link validation runs.
181
- // Skip registration/jar.mn checks inside validateComponent — the aggregate
182
- // validators below run them exactly once across all components, which both
183
- // surfaces cross-component issues and avoids double-counting.
184
- const issues = await validateComponent(componentDir, name, 'custom', config, root, {
185
- skipAggregateChecks: true,
186
- });
187
- results.set(name, issues);
188
- }
189
- // Registration pattern validation (customElements.js Pattern A vs B)
190
- const registrationIssues = await validateRegistrationPatterns(root, config);
191
- for (const issue of registrationIssues) {
192
- const existing = results.get(issue.component) ?? [];
193
- existing.push(issue);
194
- results.set(issue.component, existing);
195
- }
196
- // jar.mn entry validation
197
- const jarMnIssues = await validateJarMnEntries(root, config);
198
- for (const issue of jarMnIssues) {
199
- const existing = results.get(issue.component) ?? [];
200
- existing.push(issue);
201
- results.set(issue.component, existing);
202
- }
203
- // 2026-04-24 eval Finding 5: orphan xpcshell scaffold detection.
204
- // `furnace create --with-tests --xpcshell` scaffolds a test directory
205
- // at `browser/base/content/test/<binary>-xpcshell/<name>/`, and prior
206
- // `furnace remove` + `furnace rename` flows did not touch that tree.
207
- // A leftover scaffold whose `<name>` is not in furnace.json is almost
208
- // always the aftermath of one of those incomplete flows; flag it as
209
- // an `orphan-xpcshell-scaffold` error so operators know to delete or
210
- // re-create the scaffold instead of discovering the mismatch only at
211
- // test run time. Missing engine or missing scaffold parent directory
212
- // both degrade silently — this check never introduces noise on a
213
- // project that never used xpcshell scaffolding.
214
- try {
215
- const orphanIssues = await findOrphanXpcshellScaffolds(root, config);
216
- for (const issue of orphanIssues) {
194
+ // Registration pattern validation (customElements.js Pattern A vs B)
195
+ const registrationIssues = await validateRegistrationPatterns(root, config);
196
+ for (const issue of registrationIssues) {
217
197
  const existing = results.get(issue.component) ?? [];
218
198
  existing.push(issue);
219
199
  results.set(issue.component, existing);
220
200
  }
221
- }
222
- catch {
223
- // Validation degrades gracefully — the absence of an engine
224
- // directory, permission denial reading the scaffold tree, or any
225
- // other transient fs issue should never cascade into false
226
- // "orphan" reports.
227
- }
228
- // jsconfig chrome-module paths drift (field report D3): when
229
- // `typecheckJsconfig` is configured, deploy maintains a paths mapping per
230
- // deployed module file; missing or stale entries mean typed cross-module
231
- // imports are silently degrading to `any` in the consumer's typecheck.
232
- if (config.typecheckJsconfig) {
201
+ // jar.mn entry validation
202
+ const jarMnIssues = await validateJarMnEntries(root, config);
203
+ for (const issue of jarMnIssues) {
204
+ const existing = results.get(issue.component) ?? [];
205
+ existing.push(issue);
206
+ results.set(issue.component, existing);
207
+ }
208
+ // Orphan xpcshell scaffold detection.
209
+ // `furnace create --with-tests --xpcshell` scaffolds a test directory at
210
+ // `browser/base/content/test/<binary>-xpcshell/<name>/`. A leftover
211
+ // scaffold whose `<name>` is not in furnace.json is almost always the
212
+ // aftermath of an incomplete `furnace remove` or `rename`; flag it as an
213
+ // `orphan-xpcshell-scaffold` error so operators delete or re-create the
214
+ // scaffold instead of discovering the mismatch at test run time. A
215
+ // missing engine or scaffold parent directory degrades silently, so this
216
+ // check never adds noise on a project that never used xpcshell
217
+ // scaffolding.
233
218
  try {
234
- const drift = await findJsconfigPathsDrift(root, config);
235
- if (drift.changed) {
236
- const detail = [
237
- ...drift.added.map((key) => `missing: ${key}`),
238
- ...drift.updated.map((key) => `stale: ${key}`),
239
- ...drift.pruned.map((key) => `orphaned: ${key}`),
240
- ].join('; ');
241
- const issue = {
242
- component: 'furnace',
243
- severity: 'warning',
244
- check: 'jsconfig-paths-drift',
245
- message: `${config.typecheckJsconfig} chrome-module paths are out of sync with the workspace (${detail}). ` +
246
- 'Run "fireforge furnace deploy" to update them.',
247
- };
219
+ const orphanIssues = await findOrphanXpcshellScaffolds(root, config);
220
+ for (const issue of orphanIssues) {
248
221
  const existing = results.get(issue.component) ?? [];
249
222
  existing.push(issue);
250
223
  results.set(issue.component, existing);
251
224
  }
252
225
  }
253
226
  catch {
254
- // Drift detection must not break validation when the jsconfig is
255
- // missing or unparsable — deploy reports those cases with guidance.
227
+ // Validation degrades gracefully — the absence of an engine
228
+ // directory, permission denial reading the scaffold tree, or any
229
+ // other transient fs issue should never cascade into false
230
+ // "orphan" reports.
256
231
  }
257
- }
258
- return results;
232
+ // jsconfig chrome-module paths drift: when `typecheckJsconfig` is
233
+ // configured, deploy maintains a paths mapping per deployed module file;
234
+ // missing or stale entries mean typed cross-module imports are silently
235
+ // degrading to `any` in the consumer's typecheck.
236
+ if (config.typecheckJsconfig) {
237
+ try {
238
+ const drift = await findJsconfigPathsDrift(root, config);
239
+ if (drift.changed) {
240
+ const detail = [
241
+ ...drift.added.map((key) => `missing: ${key}`),
242
+ ...drift.updated.map((key) => `stale: ${key}`),
243
+ ...drift.pruned.map((key) => `orphaned: ${key}`),
244
+ ].join('; ');
245
+ const issue = {
246
+ component: 'furnace',
247
+ severity: 'warning',
248
+ check: 'jsconfig-paths-drift',
249
+ message: `${config.typecheckJsconfig} chrome-module paths are out of sync with the workspace (${detail}). ` +
250
+ 'Run "fireforge furnace deploy" to update them.',
251
+ };
252
+ const existing = results.get(issue.component) ?? [];
253
+ existing.push(issue);
254
+ results.set(issue.component, existing);
255
+ }
256
+ }
257
+ catch {
258
+ // Drift detection must not break validation when the jsconfig is
259
+ // missing or unparsable — deploy reports those cases with guidance.
260
+ }
261
+ }
262
+ return results;
263
+ });
259
264
  }
260
265
  /**
261
266
  * Scans the per-binary xpcshell scaffold directory for entries whose
@@ -300,3 +305,41 @@ async function findOrphanXpcshellScaffolds(root, config) {
300
305
  }
301
306
  return issues;
302
307
  }
308
+ /**
309
+ * Flags engine-side files that a previous deploy of `tagName` left behind
310
+ * after their workspace source was renamed or removed.
311
+ *
312
+ * Detection keys on the furnace state file: every `appliedChecksums` entry
313
+ * under `custom/<tagName>/` whose workspace source no longer exists but whose
314
+ * engine target is still present is an orphan. The next deploy prunes it, but
315
+ * until then jar.mn and the deployed directory disagree with the workspace,
316
+ * and a re-export could capture the stale state into a patch.
317
+ *
318
+ * Custom components only: override undeploys restore the upstream baseline
319
+ * rather than deleting files, so "orphan" has no meaning there.
320
+ */
321
+ export async function findOrphanedEngineFiles(root, config, tagName, state, ftlDir) {
322
+ const customConfig = config.custom[tagName];
323
+ if (!customConfig)
324
+ return [];
325
+ const previous = extractComponentChecksums(state.appliedChecksums, 'custom', tagName);
326
+ const fileNames = Object.keys(previous);
327
+ if (fileNames.length === 0)
328
+ return [];
329
+ const { engine: engineDir, componentsDir } = getProjectPaths(root);
330
+ const componentDir = join(componentsDir, 'custom', tagName);
331
+ const issues = [];
332
+ for (const fileName of fileNames) {
333
+ if (await pathExists(join(componentDir, fileName)))
334
+ continue;
335
+ const enginePath = fileName.endsWith('.ftl')
336
+ ? join(engineDir, ftlDir, fileName)
337
+ : join(engineDir, customConfig.targetPath, fileName);
338
+ if (!(await pathExists(enginePath)))
339
+ continue;
340
+ issues.push(createIssue(tagName, 'warning', 'orphaned-engine-file', `Engine file ${fileName} was deployed by a previous apply but its workspace source ` +
341
+ `is gone (renamed or deleted). The deployed copy${customConfig.register ? ' and any stale jar.mn entry' : ''} ` +
342
+ `will linger until the next deploy prunes it. Run "fireforge furnace deploy ${tagName}".`));
343
+ }
344
+ return issues;
345
+ }