@hominis/fireforge 0.35.0 → 0.37.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 (369) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +34 -9
  3. package/dist/bin/fireforge.js +21 -6
  4. package/dist/src/cli.js +22 -5
  5. package/dist/src/commands/bootstrap-checks.js +0 -1
  6. package/dist/src/commands/bootstrap.d.ts +2 -1
  7. package/dist/src/commands/bootstrap.js +16 -7
  8. package/dist/src/commands/build.js +5 -4
  9. package/dist/src/commands/config.js +30 -10
  10. package/dist/src/commands/discard.js +18 -3
  11. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  12. package/dist/src/commands/doctor-check-core.js +0 -1
  13. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  14. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  15. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  16. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  17. package/dist/src/commands/doctor-furnace.js +2 -2
  18. package/dist/src/commands/doctor-orphaned-harness.d.ts +52 -0
  19. package/dist/src/commands/doctor-orphaned-harness.js +132 -0
  20. package/dist/src/commands/doctor-working-tree.js +0 -1
  21. package/dist/src/commands/doctor.js +6 -1
  22. package/dist/src/commands/download.js +28 -13
  23. package/dist/src/commands/export-all.js +5 -3
  24. package/dist/src/commands/export-flow.js +12 -7
  25. package/dist/src/commands/export-placement-gate.js +0 -1
  26. package/dist/src/commands/export-placement-policy.js +0 -1
  27. package/dist/src/commands/export-shared.d.ts +5 -1
  28. package/dist/src/commands/export-shared.js +18 -6
  29. package/dist/src/commands/export.js +22 -10
  30. package/dist/src/commands/furnace/apply.js +22 -6
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  34. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  35. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  36. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  37. package/dist/src/commands/furnace/create-features.js +0 -1
  38. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  39. package/dist/src/commands/furnace/create-readback.js +0 -1
  40. package/dist/src/commands/furnace/create-templates.js +0 -1
  41. package/dist/src/commands/furnace/create-validation.js +0 -1
  42. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  43. package/dist/src/commands/furnace/create.js +0 -1
  44. package/dist/src/commands/furnace/deploy.js +7 -83
  45. package/dist/src/commands/furnace/diff.js +0 -1
  46. package/dist/src/commands/furnace/index.js +59 -25
  47. package/dist/src/commands/furnace/init.js +0 -1
  48. package/dist/src/commands/furnace/list.js +0 -1
  49. package/dist/src/commands/furnace/override.js +0 -1
  50. package/dist/src/commands/furnace/preview.js +3 -3
  51. package/dist/src/commands/furnace/refresh.js +0 -1
  52. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  53. package/dist/src/commands/furnace/remove-state.js +47 -1
  54. package/dist/src/commands/furnace/remove.js +3 -9
  55. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  56. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  57. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  58. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  59. package/dist/src/commands/furnace/rename.js +15 -36
  60. package/dist/src/commands/furnace/scan.js +0 -1
  61. package/dist/src/commands/furnace/status.js +0 -1
  62. package/dist/src/commands/furnace/sync.js +76 -4
  63. package/dist/src/commands/furnace/validate.js +0 -1
  64. package/dist/src/commands/furnace/validation-output.js +0 -1
  65. package/dist/src/commands/import.js +35 -9
  66. package/dist/src/commands/lint-per-patch.js +19 -7
  67. package/dist/src/commands/lint.js +13 -3
  68. package/dist/src/commands/manifest.js +1 -2
  69. package/dist/src/commands/package.js +2 -1
  70. package/dist/src/commands/patch/compact.js +1 -1
  71. package/dist/src/commands/patch/delete.js +1 -1
  72. package/dist/src/commands/patch/index.js +1 -1
  73. package/dist/src/commands/patch/lint-ignore.js +21 -6
  74. package/dist/src/commands/patch/move-files.js +1 -1
  75. package/dist/src/commands/patch/patch-context.js +0 -1
  76. package/dist/src/commands/patch/rename.js +4 -4
  77. package/dist/src/commands/patch/reorder.js +3 -4
  78. package/dist/src/commands/patch/split-plan.js +2 -1
  79. package/dist/src/commands/patch/split.js +4 -4
  80. package/dist/src/commands/patch/staged-dependency.d.ts +1 -1
  81. package/dist/src/commands/patch/staged-dependency.js +114 -52
  82. package/dist/src/commands/patch/tier.js +18 -5
  83. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  84. package/dist/src/commands/re-export-files.js +5 -1
  85. package/dist/src/commands/re-export-options.js +0 -1
  86. package/dist/src/commands/re-export-scan.js +0 -1
  87. package/dist/src/commands/re-export.js +14 -4
  88. package/dist/src/commands/rebase/abort.js +2 -9
  89. package/dist/src/commands/rebase/confirm.js +0 -1
  90. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  91. package/dist/src/commands/rebase/continue.js +0 -1
  92. package/dist/src/commands/rebase/index.d.ts +1 -1
  93. package/dist/src/commands/rebase/index.js +10 -15
  94. package/dist/src/commands/rebase/patch-loop.js +38 -7
  95. package/dist/src/commands/rebase/summary.js +2 -3
  96. package/dist/src/commands/rebase.js +0 -1
  97. package/dist/src/commands/register.js +1 -1
  98. package/dist/src/commands/reset.js +16 -9
  99. package/dist/src/commands/resolve.js +2 -2
  100. package/dist/src/commands/run.js +4 -4
  101. package/dist/src/commands/setup-support.js +0 -1
  102. package/dist/src/commands/setup.js +1 -2
  103. package/dist/src/commands/source.js +1 -2
  104. package/dist/src/commands/status-output.js +1 -12
  105. package/dist/src/commands/status.js +147 -133
  106. package/dist/src/commands/test-appdir.js +0 -1
  107. package/dist/src/commands/test-diagnose.js +38 -3
  108. package/dist/src/commands/test-modes.d.ts +15 -0
  109. package/dist/src/commands/test-modes.js +61 -0
  110. package/dist/src/commands/test-register.d.ts +4 -0
  111. package/dist/src/commands/test-register.js +64 -0
  112. package/dist/src/commands/test-run.d.ts +23 -7
  113. package/dist/src/commands/test-run.js +15 -14
  114. package/dist/src/commands/test.d.ts +0 -4
  115. package/dist/src/commands/test.js +147 -96
  116. package/dist/src/commands/token-coverage.js +0 -1
  117. package/dist/src/commands/token.js +1 -2
  118. package/dist/src/commands/typecheck.js +1 -1
  119. package/dist/src/commands/verify.js +3 -9
  120. package/dist/src/commands/watch.js +5 -1
  121. package/dist/src/commands/wire.js +1 -1
  122. package/dist/src/core/ast-utils.js +0 -1
  123. package/dist/src/core/brand-validation.js +0 -1
  124. package/dist/src/core/branding.js +0 -1
  125. package/dist/src/core/browser-wire.js +0 -1
  126. package/dist/src/core/build-audit-platform.js +0 -1
  127. package/dist/src/core/build-audit-registration.js +0 -1
  128. package/dist/src/core/build-audit-resolve.js +0 -1
  129. package/dist/src/core/build-audit-transforms.js +0 -1
  130. package/dist/src/core/build-audit.js +0 -1
  131. package/dist/src/core/build-baseline-types.d.ts +24 -0
  132. package/dist/src/core/build-baseline-types.js +0 -1
  133. package/dist/src/core/build-baseline.d.ts +5 -2
  134. package/dist/src/core/build-baseline.js +5 -2
  135. package/dist/src/core/build-prepare.js +2 -2
  136. package/dist/src/core/config-mutate.js +0 -1
  137. package/dist/src/core/config-paths.d.ts +2 -2
  138. package/dist/src/core/config-paths.js +6 -1
  139. package/dist/src/core/config-state.js +0 -1
  140. package/dist/src/core/config-validate-patch-policy.js +0 -1
  141. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  142. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  143. package/dist/src/core/config-validate.js +3 -1
  144. package/dist/src/core/config.js +0 -1
  145. package/dist/src/core/destructive.js +0 -1
  146. package/dist/src/core/diff-hunks.js +13 -7
  147. package/dist/src/core/engine-changes.js +0 -1
  148. package/dist/src/core/engine-session-lock.d.ts +12 -0
  149. package/dist/src/core/engine-session-lock.js +45 -0
  150. package/dist/src/core/file-lock.d.ts +8 -0
  151. package/dist/src/core/file-lock.js +157 -48
  152. package/dist/src/core/firefox-archive.d.ts +4 -0
  153. package/dist/src/core/firefox-archive.js +6 -1
  154. package/dist/src/core/firefox-cache.js +60 -4
  155. package/dist/src/core/firefox-download.js +0 -1
  156. package/dist/src/core/firefox-extract.js +75 -16
  157. package/dist/src/core/firefox-ignorefile.js +0 -1
  158. package/dist/src/core/firefox.d.ts +12 -0
  159. package/dist/src/core/firefox.js +64 -2
  160. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  161. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  162. package/dist/src/core/furnace-apply-ftl.js +10 -5
  163. package/dist/src/core/furnace-apply-helpers.js +0 -1
  164. package/dist/src/core/furnace-apply-output.js +2 -2
  165. package/dist/src/core/furnace-apply.d.ts +7 -1
  166. package/dist/src/core/furnace-apply.js +47 -13
  167. package/dist/src/core/furnace-checksum-utils.js +0 -1
  168. package/dist/src/core/furnace-config-array-utils.js +0 -1
  169. package/dist/src/core/furnace-config-custom.js +10 -1
  170. package/dist/src/core/furnace-config-order.js +0 -1
  171. package/dist/src/core/furnace-config-tokens.js +0 -1
  172. package/dist/src/core/furnace-config-validate.js +0 -1
  173. package/dist/src/core/furnace-config.d.ts +11 -0
  174. package/dist/src/core/furnace-config.js +37 -1
  175. package/dist/src/core/furnace-constants.js +0 -1
  176. package/dist/src/core/furnace-css-fragments.js +0 -1
  177. package/dist/src/core/furnace-graph-utils.js +0 -1
  178. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  179. package/dist/src/core/furnace-jsconfig.js +25 -18
  180. package/dist/src/core/furnace-marker.js +0 -1
  181. package/dist/src/core/furnace-operation.js +0 -1
  182. package/dist/src/core/furnace-refresh.js +0 -1
  183. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  184. package/dist/src/core/furnace-registration-ast.js +1 -2
  185. package/dist/src/core/furnace-registration-remove.js +0 -1
  186. package/dist/src/core/furnace-registration-validate.js +1 -5
  187. package/dist/src/core/furnace-registration.js +0 -1
  188. package/dist/src/core/furnace-rollback.js +0 -1
  189. package/dist/src/core/furnace-scanner.js +0 -1
  190. package/dist/src/core/furnace-stale-export.d.ts +59 -0
  191. package/dist/src/core/furnace-stale-export.js +123 -0
  192. package/dist/src/core/furnace-staleness.js +0 -1
  193. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  194. package/dist/src/core/furnace-state-persist.js +118 -0
  195. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  196. package/dist/src/core/furnace-step-errors.js +25 -0
  197. package/dist/src/core/furnace-stories.js +0 -1
  198. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  199. package/dist/src/core/furnace-validate-checks.js +0 -1
  200. package/dist/src/core/furnace-validate-compatibility.js +9 -3
  201. package/dist/src/core/furnace-validate-helpers.js +0 -1
  202. package/dist/src/core/furnace-validate-registration.js +7 -7
  203. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  204. package/dist/src/core/furnace-validate-structure.js +14 -4
  205. package/dist/src/core/furnace-validate.js +0 -1
  206. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  207. package/dist/src/core/furnace-version-drift.js +6 -4
  208. package/dist/src/core/git-base.js +0 -1
  209. package/dist/src/core/git-diff.js +0 -1
  210. package/dist/src/core/git-file-ops.js +0 -1
  211. package/dist/src/core/git-status.d.ts +13 -0
  212. package/dist/src/core/git-status.js +32 -2
  213. package/dist/src/core/git.d.ts +10 -1
  214. package/dist/src/core/git.js +63 -6
  215. package/dist/src/core/license-headers.js +0 -1
  216. package/dist/src/core/lint-cache.js +0 -1
  217. package/dist/src/core/lint-projection.js +0 -1
  218. package/dist/src/core/mach-build-artifacts.js +0 -1
  219. package/dist/src/core/mach-error-hints.js +0 -1
  220. package/dist/src/core/mach-known-noise-filter.d.ts +60 -0
  221. package/dist/src/core/mach-known-noise-filter.js +193 -0
  222. package/dist/src/core/mach-mozconfig.js +0 -1
  223. package/dist/src/core/mach-python.js +0 -1
  224. package/dist/src/core/mach-resource-shim.js +0 -1
  225. package/dist/src/core/mach.d.ts +8 -0
  226. package/dist/src/core/mach.js +36 -4
  227. package/dist/src/core/manifest-helpers.js +0 -1
  228. package/dist/src/core/manifest-register.js +0 -1
  229. package/dist/src/core/manifest-rules.js +0 -1
  230. package/dist/src/core/manifest-tokenizers.js +0 -1
  231. package/dist/src/core/marionette-port.d.ts +8 -0
  232. package/dist/src/core/marionette-port.js +34 -3
  233. package/dist/src/core/marionette-preflight.js +0 -1
  234. package/dist/src/core/markdown-table.js +0 -1
  235. package/dist/src/core/ownership-table.js +2 -9
  236. package/dist/src/core/parser-fallback.js +0 -1
  237. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  238. package/dist/src/core/patch-apply-fuzz.js +71 -34
  239. package/dist/src/core/patch-apply.d.ts +30 -0
  240. package/dist/src/core/patch-apply.js +101 -18
  241. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  242. package/dist/src/core/patch-artifact-normalize.js +19 -15
  243. package/dist/src/core/patch-export-coverage.js +0 -1
  244. package/dist/src/core/patch-export-metadata.js +0 -1
  245. package/dist/src/core/patch-export-update.js +2 -3
  246. package/dist/src/core/patch-export.d.ts +2 -6
  247. package/dist/src/core/patch-export.js +24 -48
  248. package/dist/src/core/patch-files.js +0 -1
  249. package/dist/src/core/patch-identifier-suggest.js +0 -1
  250. package/dist/src/core/patch-lint-checkjs.js +0 -1
  251. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-cross.js +80 -3
  253. package/dist/src/core/patch-lint-css.js +0 -1
  254. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  255. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  256. package/dist/src/core/patch-lint-diff.js +17 -32
  257. package/dist/src/core/patch-lint-imports.js +0 -1
  258. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  259. package/dist/src/core/patch-lint-observer.js +0 -1
  260. package/dist/src/core/patch-lint-ownership.js +0 -1
  261. package/dist/src/core/patch-lint-reexports.js +0 -1
  262. package/dist/src/core/patch-lint.d.ts +37 -2
  263. package/dist/src/core/patch-lint.js +47 -34
  264. package/dist/src/core/patch-lock.js +0 -1
  265. package/dist/src/core/patch-manifest-consistency.js +9 -1
  266. package/dist/src/core/patch-manifest-io.d.ts +19 -2
  267. package/dist/src/core/patch-manifest-io.js +57 -21
  268. package/dist/src/core/patch-manifest-query.js +18 -10
  269. package/dist/src/core/patch-manifest-resolve.js +0 -1
  270. package/dist/src/core/patch-manifest-validate.js +24 -1
  271. package/dist/src/core/patch-manifest.d.ts +1 -1
  272. package/dist/src/core/patch-manifest.js +1 -2
  273. package/dist/src/core/patch-parse.d.ts +59 -0
  274. package/dist/src/core/patch-parse.js +209 -92
  275. package/dist/src/core/patch-policy.js +0 -1
  276. package/dist/src/core/patch-registration-refs.js +16 -26
  277. package/dist/src/core/patch-source-metadata.js +0 -1
  278. package/dist/src/core/patch-transform.d.ts +4 -0
  279. package/dist/src/core/patch-transform.js +30 -38
  280. package/dist/src/core/rebase-session.js +0 -1
  281. package/dist/src/core/register-browser-content.js +0 -1
  282. package/dist/src/core/register-module.js +0 -1
  283. package/dist/src/core/register-result.js +0 -1
  284. package/dist/src/core/register-scaffold.js +0 -1
  285. package/dist/src/core/register-shared-css.js +0 -1
  286. package/dist/src/core/register-test-manifest.js +0 -1
  287. package/dist/src/core/register-xpcshell-test.js +0 -1
  288. package/dist/src/core/shared-ftl.js +0 -1
  289. package/dist/src/core/signal-critical.js +0 -1
  290. package/dist/src/core/smoke-patterns.js +0 -1
  291. package/dist/src/core/state-file.js +0 -1
  292. package/dist/src/core/status-classify.d.ts +16 -0
  293. package/dist/src/core/status-classify.js +24 -16
  294. package/dist/src/core/test-harness-crash.d.ts +80 -7
  295. package/dist/src/core/test-harness-crash.js +251 -15
  296. package/dist/src/core/test-harness-output.js +0 -1
  297. package/dist/src/core/test-path-scope.d.ts +21 -13
  298. package/dist/src/core/test-path-scope.js +43 -28
  299. package/dist/src/core/test-stale-check.d.ts +29 -1
  300. package/dist/src/core/test-stale-check.js +59 -1
  301. package/dist/src/core/test-stale-symlink.js +0 -1
  302. package/dist/src/core/test-xpcshell-retry.js +0 -1
  303. package/dist/src/core/token-coverage.js +0 -1
  304. package/dist/src/core/token-dark-mode.js +0 -1
  305. package/dist/src/core/token-docs.js +0 -1
  306. package/dist/src/core/token-manager.js +0 -1
  307. package/dist/src/core/token-scaffold.js +0 -1
  308. package/dist/src/core/token-variant.js +0 -1
  309. package/dist/src/core/toolchain-preflight.d.ts +31 -10
  310. package/dist/src/core/toolchain-preflight.js +97 -36
  311. package/dist/src/core/typecheck-shim.js +13 -1
  312. package/dist/src/core/typecheck.js +0 -1
  313. package/dist/src/core/wire-destroy.js +0 -1
  314. package/dist/src/core/wire-dom-fragment.js +0 -1
  315. package/dist/src/core/wire-init.js +0 -1
  316. package/dist/src/core/wire-subscript.js +0 -1
  317. package/dist/src/core/wire-targets.js +0 -1
  318. package/dist/src/core/wire-utils.js +0 -1
  319. package/dist/src/core/xpcshell-appdir.js +1 -4
  320. package/dist/src/errors/base.d.ts +29 -0
  321. package/dist/src/errors/base.js +35 -1
  322. package/dist/src/errors/build.js +0 -1
  323. package/dist/src/errors/codes.d.ts +6 -0
  324. package/dist/src/errors/codes.js +6 -1
  325. package/dist/src/errors/config.js +0 -1
  326. package/dist/src/errors/download.js +0 -1
  327. package/dist/src/errors/furnace.d.ts +11 -1
  328. package/dist/src/errors/furnace.js +6 -5
  329. package/dist/src/errors/git.js +0 -1
  330. package/dist/src/errors/patch.d.ts +17 -0
  331. package/dist/src/errors/patch.js +29 -1
  332. package/dist/src/errors/rebase.js +0 -1
  333. package/dist/src/errors/run.js +1 -1
  334. package/dist/src/index.js +0 -1
  335. package/dist/src/types/cli.js +0 -1
  336. package/dist/src/types/commands/index.d.ts +1 -1
  337. package/dist/src/types/commands/index.js +0 -1
  338. package/dist/src/types/commands/options.d.ts +40 -9
  339. package/dist/src/types/commands/options.js +0 -1
  340. package/dist/src/types/commands/patches.d.ts +37 -0
  341. package/dist/src/types/commands/patches.js +0 -1
  342. package/dist/src/types/commands/project.d.ts +11 -2
  343. package/dist/src/types/commands/project.js +0 -1
  344. package/dist/src/types/config.d.ts +32 -0
  345. package/dist/src/types/config.js +0 -1
  346. package/dist/src/types/furnace.d.ts +23 -0
  347. package/dist/src/types/furnace.js +0 -1
  348. package/dist/src/types/index.js +0 -1
  349. package/dist/src/types/typecheck.js +0 -1
  350. package/dist/src/utils/elapsed.js +0 -1
  351. package/dist/src/utils/errors.js +0 -1
  352. package/dist/src/utils/fs.js +37 -5
  353. package/dist/src/utils/logger.d.ts +7 -0
  354. package/dist/src/utils/logger.js +85 -3
  355. package/dist/src/utils/options.d.ts +15 -0
  356. package/dist/src/utils/options.js +25 -1
  357. package/dist/src/utils/package-root.js +11 -4
  358. package/dist/src/utils/parse.js +0 -1
  359. package/dist/src/utils/paths.js +0 -1
  360. package/dist/src/utils/platform.js +0 -1
  361. package/dist/src/utils/process-group.d.ts +33 -0
  362. package/dist/src/utils/process-group.js +161 -0
  363. package/dist/src/utils/process.d.ts +48 -4
  364. package/dist/src/utils/process.js +258 -59
  365. package/dist/src/utils/regex.d.ts +13 -3
  366. package/dist/src/utils/regex.js +53 -4
  367. package/dist/src/utils/validation.d.ts +0 -12
  368. package/dist/src/utils/validation.js +13 -26
  369. package/package.json +9 -8
@@ -5,9 +5,13 @@
5
5
  * per-file LOC budget.
6
6
  *
7
7
  * Every helper here degrades gracefully: if the locale jar.mn is missing or
8
- * the FTL tree is non-standard, apply logs a `stepError` rather than
9
- * aborting the whole command. Missing jar.mn on a fork without a locale
10
- * package should not block a working `.mjs`/`.css` from shipping.
8
+ * the FTL tree is non-standard, apply logs an ADVISORY `stepError`
9
+ * (`advisory: true`) rather than aborting the whole command. Advisory step
10
+ * errors are reported as warnings and never trigger rollback — missing
11
+ * jar.mn on a fork without a locale package should not block a working
12
+ * `.mjs`/`.css` from shipping (the blocking/advisory split exists because
13
+ * this contract used to be contradicted: any FTL step error rolled back
14
+ * the whole apply).
11
15
  */
12
16
  import { join, relative } from 'node:path';
13
17
  import { toError } from '../utils/errors.js';
@@ -86,7 +90,7 @@ export async function applySharedFtlPrune(engineDir, name, ftlDir, config, affec
86
90
  affectedPaths.push(prunedPath);
87
91
  }
88
92
  catch (error) {
89
- stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message });
93
+ stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message, advisory: true });
90
94
  }
91
95
  }
92
96
  /**
@@ -139,6 +143,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
139
143
  stepErrors.push({
140
144
  step: 'locale jar.mn registration',
141
145
  error: `Locale jar.mn not found at ${localeJarRel}; component "${name}" ships without a chrome URI for ${ftlFile}. Add the file manually or set furnace.json "ftlBasePath" to a tree that owns a jar.mn.`,
146
+ advisory: true,
142
147
  });
143
148
  return;
144
149
  }
@@ -155,6 +160,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
155
160
  stepErrors.push({
156
161
  step: 'locale jar.mn registration',
157
162
  error: toError(error).message,
163
+ advisory: true,
158
164
  });
159
165
  }
160
166
  }
@@ -199,4 +205,3 @@ export async function removeCustomFtlJarMnEntry(engineDir, fileName, ftlDir, con
199
205
  }
200
206
  await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, tagName, chromeSubPath);
201
207
  }
202
- //# sourceMappingURL=furnace-apply-ftl.js.map
@@ -421,4 +421,3 @@ export async function applyOverrideComponent(engineDir, name, componentDir, conf
421
421
  return { affectedPaths };
422
422
  }
423
423
  export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
424
- //# sourceMappingURL=furnace-apply-helpers.js.map
@@ -45,7 +45,8 @@ export function logApplyResult(result, isDryRun) {
45
45
  for (const applied of result.applied) {
46
46
  if (applied.stepErrors && applied.stepErrors.length > 0) {
47
47
  for (const stepErr of applied.stepErrors) {
48
- warn(`${applied.name}: [${stepErr.step}] ${stepErr.error}`);
48
+ const advisoryPrefix = stepErr.advisory === true ? '(advisory) ' : '';
49
+ warn(`${applied.name}: ${advisoryPrefix}[${stepErr.step}] ${stepErr.error}`);
49
50
  }
50
51
  }
51
52
  }
@@ -54,4 +55,3 @@ export function logApplyResult(result, isDryRun) {
54
55
  error(`${err.name} — ${err.error}`);
55
56
  }
56
57
  }
57
- //# sourceMappingURL=furnace-apply-output.js.map
@@ -31,4 +31,10 @@ export declare function applyAllComponents(root: string, dryRun?: boolean, optio
31
31
  }): Promise<ApplyResult & {
32
32
  actions?: DryRunAction[];
33
33
  rollbackJournal?: RollbackJournal;
34
- }>;
34
+ }>; /**
35
+ * Shared context threaded through the batch apply loops. Bundles the state
36
+ * both loops mutate (result/actions/checksums) with the immutable run
37
+ * parameters — the previous 12–13 positional parameters let call sites
38
+ * drift (the named-apply state-wipe bug) and made every new parameter a
39
+ * two-signature change.
40
+ */
@@ -13,6 +13,7 @@ import { resolveFurnaceMarkerComment } from './furnace-marker.js';
13
13
  import { recordFurnaceRollbackFailure } from './furnace-operation.js';
14
14
  import { addJarMnEntries, removeCustomElementRegistration, removeJarMnEntries, } from './furnace-registration.js';
15
15
  import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotFile, } from './furnace-rollback.js';
16
+ import { blockingStepErrors, hasBlockingStepErrors } from './furnace-step-errors.js';
16
17
  import { runPostApplyConsistencyChecks } from './furnace-validate-registration.js';
17
18
  export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
18
19
  function addMissingComponentError(result, name, directoryPath) {
@@ -29,7 +30,9 @@ function buildOverrideUndeployActions(name, config, engineDir, deletedFiles, ftl
29
30
  description: `Restore engine/${fileName.endsWith('.ftl') ? `${ftlDir}/${fileName}` : `${config.basePath}/${fileName}`} to Firefox baseline`,
30
31
  }));
31
32
  }
32
- async function applyOverrideBatch(config, furnacePaths, state, engineDir, ftlDir, dryRun, result, allActions, newChecksums, rollbackJournal, componentName) {
33
+ async function applyOverrideBatch(ctx) {
34
+ const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
35
+ const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
33
36
  const overrideEntries = Object.entries(config.overrides).filter(([name]) => !componentName || name === componentName);
34
37
  const totalOverrides = overrideEntries.length;
35
38
  let overrideIndex = 0;
@@ -162,7 +165,9 @@ async function reconcileCustomRegistrationAfterUndeploy(engineDir, name, config,
162
165
  filesAffected.push(CUSTOM_ELEMENTS_JS);
163
166
  }
164
167
  }
165
- async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ftlDir, dryRun, result, allActions, newChecksums, rollbackJournal, componentName, markerComment) {
168
+ async function applyCustomBatch(ctx, root, markerComment) {
169
+ const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
170
+ const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
166
171
  const allKnown = new Set([
167
172
  ...config.stock,
168
173
  ...Object.keys(config.overrides),
@@ -251,7 +256,7 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
251
256
  if (dryRun && actions) {
252
257
  allActions.push(...actions);
253
258
  }
254
- if (!dryRun && deletedFiles.length > 0 && stepErrors.length === 0) {
259
+ if (!dryRun && deletedFiles.length > 0 && blockingStepErrors(stepErrors).length === 0) {
255
260
  await reconcileCustomRegistrationAfterUndeploy(engineDir, name, customConfig, deletedFiles, currentChecksums, rollbackJournal, filesAffectedTotal);
256
261
  }
257
262
  filesAffectedTotal.push(...filesAffected);
@@ -261,9 +266,11 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
261
266
  filesAffected: filesAffectedTotal,
262
267
  ...(stepErrors.length > 0 ? { stepErrors } : {}),
263
268
  });
264
- // Only store checksums when the component applied without step errors,
265
- // so that partially failed components are re-applied on the next run.
266
- if (!dryRun && stepErrors.length === 0) {
269
+ // Only store checksums when the component applied without BLOCKING
270
+ // step errors, so that partially failed components are re-applied on
271
+ // the next run. Advisory errors (FTL degradation) still persist —
272
+ // re-applying cannot fix a fork that has no locale jar.mn.
273
+ if (!dryRun && blockingStepErrors(stepErrors).length === 0) {
267
274
  Object.assign(newChecksums, prefixChecksums(currentChecksums, 'custom', name));
268
275
  }
269
276
  }
@@ -338,16 +345,38 @@ export async function applyAllComponents(root, dryRun = false, options) {
338
345
  throw new FurnaceError(`Component "${componentName}" not found in furnace.json. Run "fireforge furnace list" to see registered components.`, componentName);
339
346
  }
340
347
  }
341
- await applyOverrideBatch(config, furnacePaths, state, engineDir, ftlDir, dryRun, result, allActions, newChecksums, rollbackJournal, componentName);
342
- await applyCustomBatch(root, config, furnacePaths, state, engineDir, ftlDir, dryRun, result, allActions, newChecksums, rollbackJournal, componentName, markerComment);
343
- // Check for any partial failures (step errors on applied components).
344
- const hasStepErrors = result.applied.some((entry) => 'stepErrors' in entry && entry.stepErrors.length > 0);
348
+ const batchContext = {
349
+ config,
350
+ furnacePaths,
351
+ state,
352
+ engineDir,
353
+ ftlDir,
354
+ dryRun,
355
+ result,
356
+ allActions,
357
+ newChecksums,
358
+ rollbackJournal,
359
+ componentName,
360
+ };
361
+ await applyOverrideBatch(batchContext);
362
+ await applyCustomBatch(batchContext, root, markerComment);
363
+ // Check for any partial failures (blocking step errors on applied
364
+ // components). Advisory step errors (e.g. FTL degradation) are warnings
365
+ // and never gate rollback.
366
+ const hasApplyStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
345
367
  // Orphaned components are implicitly cleaned up: newChecksums only
346
368
  // contains entries for components that still exist in furnace.json,
347
369
  // and it fully replaces state.appliedChecksums below.
348
- if (!dryRun && !hasStepErrors && result.errors.length === 0) {
370
+ if (!dryRun && !hasApplyStepErrors && result.errors.length === 0) {
349
371
  await runPostApplyConsistencyChecks(root, config, result, ftlDir);
350
372
  }
373
+ // Recompute AFTER the consistency checks: they MUTATE entry.stepErrors
374
+ // when the applied output is inconsistent (e.g. a jar.mn entry that
375
+ // silently mis-landed). Reading the pre-check snapshot meant those
376
+ // failures skipped rollback and persisted checksums for a component
377
+ // known to be inconsistent — while the CLI simultaneously reported
378
+ // "failed to apply cleanly".
379
+ const hasStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
351
380
  // --- Rollback on failure, persist on success (skip for dry-run) ---
352
381
  if (!dryRun) {
353
382
  if (result.errors.length > 0 || hasStepErrors) {
@@ -386,5 +415,10 @@ export async function applyAllComponents(root, dryRun = false, options) {
386
415
  result.actions = allActions;
387
416
  }
388
417
  return result;
389
- }
390
- //# sourceMappingURL=furnace-apply.js.map
418
+ } /**
419
+ * Shared context threaded through the batch apply loops. Bundles the state
420
+ * both loops mutate (result/actions/checksums) with the immutable run
421
+ * parameters — the previous 12–13 positional parameters let call sites
422
+ * drift (the named-apply state-wipe bug) and made every new parameter a
423
+ * two-signature change.
424
+ */
@@ -36,4 +36,3 @@ export function diffDeletedFiles(previous, current) {
36
36
  }
37
37
  return deleted.sort();
38
38
  }
39
- //# sourceMappingURL=furnace-checksum-utils.js.map
@@ -24,4 +24,3 @@ export function parseStringArray(value, fieldName) {
24
24
  }
25
25
  return items;
26
26
  }
27
- //# sourceMappingURL=furnace-config-array-utils.js.map
@@ -35,6 +35,13 @@ export function parseCustomConfig(data, name) {
35
35
  if (!isBoolean(data['localized'])) {
36
36
  throw new FurnaceError(`Furnace config: custom "${name}.localized" must be a boolean`);
37
37
  }
38
+ if (data['kind'] !== undefined && data['kind'] !== 'element' && data['kind'] !== 'library') {
39
+ throw new FurnaceError(`Furnace config: custom "${name}.kind" must be "element" or "library" when set`);
40
+ }
41
+ if (data['kind'] === 'library' && data['register']) {
42
+ throw new FurnaceError(`Furnace config: custom "${name}" has kind "library" but register: true — ` +
43
+ 'a library exports no custom element; set register: false');
44
+ }
38
45
  if (data['composes'] !== undefined) {
39
46
  parseStringArray(data['composes'], `${name}.composes`);
40
47
  }
@@ -58,7 +65,9 @@ export function parseCustomConfig(data, name) {
58
65
  ? { composes: parseStringArray(data['composes'], `${name}.composes`) }
59
66
  : {}),
60
67
  ...(data['keyboardCovered'] === true ? { keyboardCovered: true } : {}),
68
+ // Explicit `kind: "element"` is the default; normalize it away so the
69
+ // stored config carries the field only when it changes behavior.
70
+ ...(data['kind'] === 'library' ? { kind: 'library' } : {}),
61
71
  ...(sharedFtl !== undefined ? { sharedFtl } : {}),
62
72
  };
63
73
  }
64
- //# sourceMappingURL=furnace-config-custom.js.map
@@ -83,4 +83,3 @@ export function orderFurnaceConfigForWrite(existing, config) {
83
83
  }
84
84
  return ordered;
85
85
  }
86
- //# sourceMappingURL=furnace-config-order.js.map
@@ -40,4 +40,3 @@ export function validateRuntimeVariables(raw) {
40
40
  }
41
41
  }
42
42
  }
43
- //# sourceMappingURL=furnace-config-tokens.js.map
@@ -130,4 +130,3 @@ export function applyOptionalFurnaceFields(migrated, config) {
130
130
  config.scanPaths = paths;
131
131
  }
132
132
  }
133
- //# sourceMappingURL=furnace-config-validate.js.map
@@ -46,6 +46,17 @@ export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
46
46
  * @throws Error if config doesn't exist or is invalid
47
47
  */
48
48
  export declare function loadFurnaceConfig(root: string): Promise<FurnaceConfig>;
49
+ /**
50
+ * Clears applied furnace state while preserving `pendingRepair`.
51
+ *
52
+ * Used whenever the engine baseline is replaced or reset (download --force,
53
+ * reset, rebase, rebase --abort): every applied checksum describes content
54
+ * that no longer exists, but pendingRepair tracks authoring-side rollback
55
+ * issues in the component WORKSPACE and must survive an engine refresh.
56
+ * No-ops when the state file does not exist. Centralized because the same
57
+ * reset logic had drifted into four separate command files.
58
+ */
59
+ export declare function clearAppliedFurnaceState(root: string): Promise<void>;
49
60
  /**
50
61
  * Writes a furnace configuration to furnace.json.
51
62
  * @param root - Root directory of the project
@@ -122,6 +122,25 @@ export function validateFurnaceConfig(data) {
122
122
  detectComposesCycles(custom);
123
123
  // Validate that every composes reference points to a known component.
124
124
  validateComposesReferences(stock, overrides, custom);
125
+ // Warn when two custom components share a targetPath. Nothing technically
126
+ // prevents co-location, but per-component removal and orphan detection
127
+ // both reason about "files this component deployed into its directory" —
128
+ // shared directories make those judgements ambiguous, and historically a
129
+ // shared targetPath meant `furnace remove` of one component deleted the
130
+ // other's deployed files.
131
+ const targetPathOwners = new Map();
132
+ for (const [name, custom_] of Object.entries(custom)) {
133
+ const owners = targetPathOwners.get(custom_.targetPath) ?? [];
134
+ owners.push(name);
135
+ targetPathOwners.set(custom_.targetPath, owners);
136
+ }
137
+ for (const [targetPath, owners] of targetPathOwners) {
138
+ if (owners.length > 1) {
139
+ warn(`furnace.json: custom components ${owners.join(', ')} share targetPath "${targetPath}". ` +
140
+ 'Give each component its own directory — shared directories make per-component ' +
141
+ 'removal and drift detection ambiguous.');
142
+ }
143
+ }
125
144
  const config = {
126
145
  version: CURRENT_CONFIG_VERSION,
127
146
  componentPrefix: migrated['componentPrefix'],
@@ -308,6 +327,24 @@ export async function loadFurnaceConfig(root) {
308
327
  throw new FurnaceError(`Invalid furnace.json at ${paths.furnaceConfig}: ${toError(error).message}`);
309
328
  }
310
329
  }
330
+ /**
331
+ * Clears applied furnace state while preserving `pendingRepair`.
332
+ *
333
+ * Used whenever the engine baseline is replaced or reset (download --force,
334
+ * reset, rebase, rebase --abort): every applied checksum describes content
335
+ * that no longer exists, but pendingRepair tracks authoring-side rollback
336
+ * issues in the component WORKSPACE and must survive an engine refresh.
337
+ * No-ops when the state file does not exist. Centralized because the same
338
+ * reset logic had drifted into four separate command files.
339
+ */
340
+ export async function clearAppliedFurnaceState(root) {
341
+ const paths = getFurnacePaths(root);
342
+ if (!(await pathExists(paths.furnaceState)))
343
+ return;
344
+ await updateFurnaceState(root, (current) => ({
345
+ ...(current.pendingRepair ? { pendingRepair: current.pendingRepair } : {}),
346
+ }));
347
+ }
311
348
  /**
312
349
  * Writes a furnace configuration to furnace.json.
313
350
  * @param root - Root directory of the project
@@ -500,4 +537,3 @@ export async function collectFurnaceManagedPrefixes(root) {
500
537
  prefixes.add(FURNACE_STORYBOOK_STORIES_PREFIX);
501
538
  return prefixes;
502
539
  }
503
- //# sourceMappingURL=furnace-config.js.map
@@ -87,4 +87,3 @@ export function stripComponentPrefix(tagName, componentPrefix) {
87
87
  }
88
88
  return tagName;
89
89
  }
90
- //# sourceMappingURL=furnace-constants.js.map
@@ -240,4 +240,3 @@ export async function validateCssFragments(componentDir, tagName, sharedDir, eng
240
240
  }
241
241
  return issues;
242
242
  }
243
- //# sourceMappingURL=furnace-css-fragments.js.map
@@ -91,4 +91,3 @@ export function topologicalSortCustom(custom) {
91
91
  }
92
92
  return sorted;
93
93
  }
94
- //# sourceMappingURL=furnace-graph-utils.js.map
@@ -38,9 +38,8 @@ export interface JsconfigSyncResult {
38
38
  * writing.
39
39
  *
40
40
  * The consumer owns the jsconfig file: a missing file is an error with
41
- * guidance rather than a silent scaffold, and JSONC (comments/trailing
42
- * commas) is unsupported for the managed file — `readJson` is a strict
43
- * JSON parser, so the error message says so explicitly.
41
+ * guidance rather than a silent scaffold. JSONC comments and trailing commas
42
+ * are preserved; Furnace edits only `compilerOptions.paths`.
44
43
  *
45
44
  * @param root - Project root directory
46
45
  * @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
@@ -22,8 +22,9 @@
22
22
  */
23
23
  import { readdir } from 'node:fs/promises';
24
24
  import { dirname, join, relative, resolve } from 'node:path';
25
+ import { applyEdits, modify, parse } from 'jsonc-parser';
25
26
  import { FurnaceError } from '../errors/furnace.js';
26
- import { pathExists, readJson, writeJson } from '../utils/fs.js';
27
+ import { pathExists, readText, writeTextIfChanged } from '../utils/fs.js';
27
28
  import { info } from '../utils/logger.js';
28
29
  import { normalizePathSlashes } from '../utils/paths.js';
29
30
  import { getFurnacePaths } from './furnace-config.js';
@@ -89,9 +90,8 @@ function isManagedEntry(key, value, jsconfigDir, customDir) {
89
90
  * writing.
90
91
  *
91
92
  * The consumer owns the jsconfig file: a missing file is an error with
92
- * guidance rather than a silent scaffold, and JSONC (comments/trailing
93
- * commas) is unsupported for the managed file — `readJson` is a strict
94
- * JSON parser, so the error message says so explicitly.
93
+ * guidance rather than a silent scaffold. JSONC comments and trailing commas
94
+ * are preserved; Furnace edits only `compilerOptions.paths`.
95
95
  *
96
96
  * @param root - Project root directory
97
97
  * @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
@@ -109,19 +109,31 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
109
109
  `"compilerOptions.paths" entries under ${CHROME_ELEMENTS_URL_PREFIX}*), ` +
110
110
  'or remove the setting.');
111
111
  }
112
+ const jsconfigText = await readText(jsconfigAbs);
113
+ const parseErrors = [];
112
114
  let jsconfig;
113
115
  try {
114
- jsconfig = await readJson(jsconfigAbs);
116
+ jsconfig = parse(jsconfigText, parseErrors, {
117
+ allowTrailingComma: true,
118
+ disallowComments: false,
119
+ });
115
120
  }
116
121
  catch (error) {
117
- throw new FurnaceError(`Could not parse ${jsconfigRel} as JSON: ${error instanceof Error ? error.message : String(error)}. ` +
118
- 'Furnace manages paths entries only in plain-JSON jsconfig files — JSONC comments ' +
119
- 'and trailing commas are not supported for the managed file.');
122
+ throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: ${error instanceof Error ? error.message : String(error)}.`);
120
123
  }
124
+ if (parseErrors.length > 0 ||
125
+ typeof jsconfig !== 'object' ||
126
+ jsconfig === null ||
127
+ Array.isArray(jsconfig)) {
128
+ throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: expected an object jsconfig file.`);
129
+ }
130
+ const jsconfigObject = jsconfig;
121
131
  const furnacePaths = getFurnacePaths(root);
122
132
  const desired = await computeDesiredChromePathEntries(config, furnacePaths.customDir, jsconfigAbs);
123
133
  const jsconfigDir = dirname(jsconfigAbs);
124
- const currentPaths = { ...(jsconfig.compilerOptions?.paths ?? {}) };
134
+ const currentPaths = {
135
+ ...(jsconfigObject.compilerOptions?.paths ?? {}),
136
+ };
125
137
  const nextPaths = {};
126
138
  // Preserve every unmanaged entry verbatim; prune managed entries that are
127
139
  // no longer desired; update managed entries whose target moved.
@@ -156,14 +168,10 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
156
168
  result.changed = result.added.length > 0 || result.updated.length > 0 || result.pruned.length > 0;
157
169
  if (!result.changed || options?.dryRun === true)
158
170
  return result;
159
- const nextJsconfig = {
160
- ...jsconfig,
161
- compilerOptions: {
162
- ...(jsconfig.compilerOptions ?? {}),
163
- paths: nextPaths,
164
- },
165
- };
166
- await writeJson(jsconfigAbs, nextJsconfig);
171
+ const edits = modify(jsconfigText, ['compilerOptions', 'paths'], nextPaths, {
172
+ formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' },
173
+ });
174
+ await writeTextIfChanged(jsconfigAbs, applyEdits(jsconfigText, edits));
167
175
  return result;
168
176
  }
169
177
  /**
@@ -188,4 +196,3 @@ export async function reportJsconfigPathsSync(root, config, dryRun) {
188
196
  info(`${dryRun ? '[dry-run] Would update' : 'Updated'} ${config.typecheckJsconfig} chrome-module paths: ` +
189
197
  `+${sync.added.length} added, ~${sync.updated.length} updated, -${sync.pruned.length} pruned`);
190
198
  }
191
- //# sourceMappingURL=furnace-jsconfig.js.map
@@ -20,4 +20,3 @@ export function resolveFurnaceMarkerComment(forgeConfig) {
20
20
  return forgeConfig.binaryName.toUpperCase();
21
21
  return undefined;
22
22
  }
23
- //# sourceMappingURL=furnace-marker.js.map
@@ -252,4 +252,3 @@ export function __resetFurnaceOperationStateForTests() {
252
252
  nextOperationToken = 1;
253
253
  signalRollbackInFlight = false;
254
254
  }
255
- //# sourceMappingURL=furnace-operation.js.map
@@ -126,4 +126,3 @@ export async function refreshOverrideFile(engineDir, overridePath, engineRelPath
126
126
  }
127
127
  return { fileName, status: 'merged' };
128
128
  }
129
- //# sourceMappingURL=furnace-refresh.js.map
@@ -2,6 +2,20 @@
2
2
  * AST-based custom element registration updates for customElements.js.
3
3
  * Removal logic is in furnace-registration-remove.ts.
4
4
  */
5
+ /**
6
+ * Returns true when `content` already contains a registration for `tagName`.
7
+ *
8
+ * Tolerates trailing line comments (e.g. a project marker like `// MYBROWSER:`)
9
+ * that an operator may have appended to entries written by a previous apply.
10
+ * Without this, a re-apply would insert a duplicate entry, and the second
11
+ * `setElementCreationCallback` at window-load would throw `NotSupportedError`.
12
+ *
13
+ * Matches any of:
14
+ * 1. `setElementCreationCallback("tag"` / `setElementCreationCallback('tag'`
15
+ * 2. Single-line array entry: `["tag", "..."]` — column 0 only, comments allowed after
16
+ * 3. Multi-line array entry: `"tag",` on its own line, optional trailing `//` comment
17
+ */
18
+ export declare function isTagAlreadyRegistered(content: string, tagName: string): boolean;
5
19
  /**
6
20
  * Options that shape the lines `addCustomElementRegistration` writes into
7
21
  * `customElements.js`. Kept optional so existing call sites keep working.
@@ -26,7 +26,7 @@ import { validateRegistrationPlacement, validateTagName } from './furnace-regist
26
26
  * 2. Single-line array entry: `["tag", "..."]` — column 0 only, comments allowed after
27
27
  * 3. Multi-line array entry: `"tag",` on its own line, optional trailing `//` comment
28
28
  */
29
- function isTagAlreadyRegistered(content, tagName) {
29
+ export function isTagAlreadyRegistered(content, tagName) {
30
30
  const escaped = escapeRegex(tagName);
31
31
  if (content.includes(`setElementCreationCallback("${tagName}"`) ||
32
32
  content.includes(`setElementCreationCallback('${tagName}'`)) {
@@ -403,4 +403,3 @@ export async function validateCustomElementRegistration(engineDir, tagName, modu
403
403
  addRegistrationRegexFallback(content, tagName, modulePath, isESModule);
404
404
  }
405
405
  }
406
- //# sourceMappingURL=furnace-registration-ast.js.map
@@ -220,4 +220,3 @@ export async function removeCustomElementRegistration(engineDir, tagName) {
220
220
  await writeText(filePath, next);
221
221
  }
222
222
  }
223
- //# sourceMappingURL=furnace-registration-remove.js.map
@@ -4,7 +4,7 @@
4
4
  * Used after both AST and legacy code paths to avoid duplicating logic.
5
5
  */
6
6
  import { FurnaceError } from '../errors/furnace.js';
7
- import { stripJsComments } from '../utils/regex.js';
7
+ import { escapeRegex, stripJsComments } from '../utils/regex.js';
8
8
  /**
9
9
  * Regex for valid custom element tag names. A valid name is lowercase, starts
10
10
  * with a letter, and contains one or more hyphen-separated groups where each
@@ -88,7 +88,3 @@ function isTagInArrayConsumedInsideDOMContentLoaded(content, domContentLoadedIdx
88
88
  }
89
89
  return false;
90
90
  }
91
- function escapeRegex(value) {
92
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
93
- }
94
- //# sourceMappingURL=furnace-registration-validate.js.map
@@ -324,4 +324,3 @@ export async function validateJarMnInsertionForFiles(engineDir, tagName, files)
324
324
  throw new FurnaceError('Could not find a content/global/ section in jar.mn for element entries. The file may be malformed.', tagName);
325
325
  }
326
326
  }
327
- //# sourceMappingURL=furnace-registration.js.map
@@ -134,4 +134,3 @@ export async function restoreRollbackJournalOrThrow(journal, context) {
134
134
  throw new FurnaceError(`${context}; automatic rollback failed: ${message}`);
135
135
  }
136
136
  }
137
- //# sourceMappingURL=furnace-rollback.js.map
@@ -237,4 +237,3 @@ export async function isComponentInEngine(engineDir, tagName) {
237
237
  const componentDir = join(engineDir, WIDGETS_DIR, tagName);
238
238
  return pathExists(componentDir);
239
239
  }
240
- //# sourceMappingURL=furnace-scanner.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Stale-furnace-source gate for `fireforge patch export` / `re-export`.
3
+ *
4
+ * Exports capture the DEPLOYED engine copies of furnace-managed files, not
5
+ * the `components/` sources. Editing a component source and re-exporting
6
+ * its owning patch WITHOUT an intervening `furnace deploy`/`apply` silently
7
+ * exports the stale deployed copy — per-patch lint then flags the old line
8
+ * count and the patch body lags the source. This gate detects that
9
+ * sequence by comparing component source directories against the checksums
10
+ * recorded at the last apply (`FurnaceState.appliedChecksums`) — the same
11
+ * signal `warnIfFurnaceStale` uses for run/watch — and refuses the export
12
+ * unless the operator passes `--allow-stale-furnace`.
13
+ *
14
+ * Checksum-based on purpose: git checkouts and `furnace refresh` churn
15
+ * mtimes without content changes, so an mtime comparison would misfire.
16
+ *
17
+ * Probe failures (broken furnace config, missing state) degrade to a
18
+ * verbose log and an empty result — a broken furnace setup must never
19
+ * block non-furnace patch work.
20
+ */
21
+ /** One furnace component whose source drifted from its deployed copy. */
22
+ export interface StaleFurnaceComponent {
23
+ /** Component name (furnace.json key). */
24
+ name: string;
25
+ /** Component flavor — resolves the source directory root. */
26
+ type: 'custom' | 'override';
27
+ /** Engine-relative deployed prefix (`/`-terminated) the component owns. */
28
+ prefix: string;
29
+ }
30
+ /**
31
+ * Returns the furnace components whose deployed engine copies the given
32
+ * files fall under AND whose `components/` sources have changed since the
33
+ * last apply. Empty when furnace is not configured, never applied, or the
34
+ * probe fails.
35
+ *
36
+ * Files are attributed per component: the custom `targetPath` / override
37
+ * `basePath` prefixes, plus — for a localized non-sharedFtl custom
38
+ * component — its exact deployed `<ftlDir>/<name>.ftl` file (the shared
39
+ * FTL dir as a whole stays unclaimed; sibling files there belong to other
40
+ * components). `sharedFtl` bundles are NOT furnace-deployed (apply only
41
+ * prunes a dangling per-widget jar.mn line), so they have no deployed copy
42
+ * to go stale and get no candidate by design. The storybook stories prefix
43
+ * is likewise skipped.
44
+ *
45
+ * @param projectRoot - Project root directory
46
+ * @param files - Engine-relative paths the export will capture
47
+ */
48
+ export declare function findStaleFurnaceComponentsForFiles(projectRoot: string, files: readonly string[]): Promise<StaleFurnaceComponent[]>;
49
+ /**
50
+ * Gate helper for export/re-export: refuses (or, with `allow`, warns) when
51
+ * any exported file belongs to a furnace component whose source changed
52
+ * since the last apply — the export would capture the stale deployed copy.
53
+ *
54
+ * @param projectRoot - Project root directory
55
+ * @param files - Engine-relative paths the export will capture
56
+ * @param allow - True when the operator passed `--allow-stale-furnace`
57
+ * @param command - Which command runs the gate (message wording only)
58
+ */
59
+ export declare function enforceFreshFurnaceSources(projectRoot: string, files: readonly string[], allow: boolean, command: 'export' | 're-export'): Promise<void>;