@hominis/fireforge 0.34.2 → 0.36.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 (360) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +43 -5
  3. package/dist/bin/fireforge.d.ts +4 -0
  4. package/dist/bin/fireforge.js +25 -6
  5. package/dist/src/cli.js +22 -5
  6. package/dist/src/commands/bootstrap-checks.js +0 -1
  7. package/dist/src/commands/bootstrap.d.ts +2 -1
  8. package/dist/src/commands/bootstrap.js +16 -7
  9. package/dist/src/commands/build.js +17 -3
  10. package/dist/src/commands/config.js +30 -10
  11. package/dist/src/commands/discard.js +24 -5
  12. package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
  13. package/dist/src/commands/doctor-check-core.js +0 -1
  14. package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
  15. package/dist/src/commands/doctor-external-toolchains.js +54 -0
  16. package/dist/src/commands/doctor-furnace-jar.js +0 -1
  17. package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
  18. package/dist/src/commands/doctor-furnace.js +2 -2
  19. package/dist/src/commands/doctor-working-tree.js +0 -1
  20. package/dist/src/commands/doctor.js +4 -1
  21. package/dist/src/commands/download.js +51 -15
  22. package/dist/src/commands/export-all.js +5 -3
  23. package/dist/src/commands/export-flow.js +12 -7
  24. package/dist/src/commands/export-placement-gate.js +0 -1
  25. package/dist/src/commands/export-placement-policy.js +0 -1
  26. package/dist/src/commands/export-shared.d.ts +5 -1
  27. package/dist/src/commands/export-shared.js +18 -6
  28. package/dist/src/commands/export.js +15 -10
  29. package/dist/src/commands/furnace/apply.js +22 -6
  30. package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
  31. package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
  32. package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +0 -1
  34. package/dist/src/commands/furnace/create-browser-test.js +0 -1
  35. package/dist/src/commands/furnace/create-dry-run.js +0 -1
  36. package/dist/src/commands/furnace/create-features.js +0 -1
  37. package/dist/src/commands/furnace/create-mochikit.js +0 -1
  38. package/dist/src/commands/furnace/create-readback.js +0 -1
  39. package/dist/src/commands/furnace/create-templates.js +0 -1
  40. package/dist/src/commands/furnace/create-validation.js +0 -1
  41. package/dist/src/commands/furnace/create-xpcshell.js +0 -1
  42. package/dist/src/commands/furnace/create.js +0 -1
  43. package/dist/src/commands/furnace/deploy.js +7 -83
  44. package/dist/src/commands/furnace/diff.js +0 -1
  45. package/dist/src/commands/furnace/index.js +59 -25
  46. package/dist/src/commands/furnace/init.js +0 -1
  47. package/dist/src/commands/furnace/list.js +0 -1
  48. package/dist/src/commands/furnace/override.js +0 -1
  49. package/dist/src/commands/furnace/preview.js +3 -3
  50. package/dist/src/commands/furnace/refresh.js +0 -1
  51. package/dist/src/commands/furnace/remove-state.d.ts +15 -0
  52. package/dist/src/commands/furnace/remove-state.js +47 -1
  53. package/dist/src/commands/furnace/remove.js +3 -9
  54. package/dist/src/commands/furnace/rename-browser-test.js +1 -5
  55. package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
  56. package/dist/src/commands/furnace/rename-helpers.js +30 -1
  57. package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
  58. package/dist/src/commands/furnace/rename.js +15 -36
  59. package/dist/src/commands/furnace/scan.js +0 -1
  60. package/dist/src/commands/furnace/status.js +0 -1
  61. package/dist/src/commands/furnace/sync.js +76 -4
  62. package/dist/src/commands/furnace/validate.js +0 -1
  63. package/dist/src/commands/furnace/validation-output.js +0 -1
  64. package/dist/src/commands/import.js +35 -9
  65. package/dist/src/commands/lint-per-patch.js +19 -7
  66. package/dist/src/commands/lint.js +13 -3
  67. package/dist/src/commands/manifest.js +1 -2
  68. package/dist/src/commands/package.js +2 -1
  69. package/dist/src/commands/patch/compact.js +1 -1
  70. package/dist/src/commands/patch/delete.js +1 -1
  71. package/dist/src/commands/patch/index.js +1 -1
  72. package/dist/src/commands/patch/lint-ignore.js +21 -6
  73. package/dist/src/commands/patch/move-files.js +1 -1
  74. package/dist/src/commands/patch/patch-context.js +0 -1
  75. package/dist/src/commands/patch/rename.js +4 -4
  76. package/dist/src/commands/patch/reorder.js +3 -4
  77. package/dist/src/commands/patch/split-plan.js +2 -1
  78. package/dist/src/commands/patch/split.js +4 -4
  79. package/dist/src/commands/patch/staged-dependency.js +1 -1
  80. package/dist/src/commands/patch/tier.js +18 -5
  81. package/dist/src/commands/re-export-bulk-scan.js +0 -1
  82. package/dist/src/commands/re-export-files.js +1 -1
  83. package/dist/src/commands/re-export-options.js +0 -1
  84. package/dist/src/commands/re-export-scan.js +0 -1
  85. package/dist/src/commands/re-export.js +6 -4
  86. package/dist/src/commands/rebase/abort.js +2 -9
  87. package/dist/src/commands/rebase/confirm.js +0 -1
  88. package/dist/src/commands/rebase/conflict-summary.js +0 -1
  89. package/dist/src/commands/rebase/continue.js +0 -1
  90. package/dist/src/commands/rebase/index.d.ts +1 -1
  91. package/dist/src/commands/rebase/index.js +10 -15
  92. package/dist/src/commands/rebase/patch-loop.js +38 -7
  93. package/dist/src/commands/rebase/summary.js +2 -3
  94. package/dist/src/commands/rebase.js +0 -1
  95. package/dist/src/commands/register.js +1 -1
  96. package/dist/src/commands/reset.js +16 -9
  97. package/dist/src/commands/resolve.js +2 -2
  98. package/dist/src/commands/run.js +18 -6
  99. package/dist/src/commands/setup-support.js +0 -1
  100. package/dist/src/commands/setup.js +1 -2
  101. package/dist/src/commands/source.js +1 -2
  102. package/dist/src/commands/status-output.js +1 -12
  103. package/dist/src/commands/status.js +147 -133
  104. package/dist/src/commands/test-appdir.js +0 -1
  105. package/dist/src/commands/test-diagnose.js +18 -2
  106. package/dist/src/commands/test-modes.d.ts +15 -0
  107. package/dist/src/commands/test-modes.js +61 -0
  108. package/dist/src/commands/test-register.d.ts +4 -0
  109. package/dist/src/commands/test-register.js +64 -0
  110. package/dist/src/commands/test-run.d.ts +23 -7
  111. package/dist/src/commands/test-run.js +15 -14
  112. package/dist/src/commands/test.d.ts +0 -4
  113. package/dist/src/commands/test.js +95 -56
  114. package/dist/src/commands/token-coverage.js +0 -1
  115. package/dist/src/commands/token.js +1 -2
  116. package/dist/src/commands/typecheck.js +1 -1
  117. package/dist/src/commands/verify.js +3 -9
  118. package/dist/src/commands/watch.js +5 -1
  119. package/dist/src/commands/wire.js +1 -1
  120. package/dist/src/core/ast-utils.js +0 -1
  121. package/dist/src/core/brand-validation.js +0 -1
  122. package/dist/src/core/branding.js +0 -1
  123. package/dist/src/core/browser-wire.js +0 -1
  124. package/dist/src/core/build-audit-platform.js +0 -1
  125. package/dist/src/core/build-audit-registration.js +0 -1
  126. package/dist/src/core/build-audit-resolve.js +0 -1
  127. package/dist/src/core/build-audit-transforms.js +0 -1
  128. package/dist/src/core/build-audit.js +0 -1
  129. package/dist/src/core/build-baseline-types.js +0 -1
  130. package/dist/src/core/build-baseline.js +0 -1
  131. package/dist/src/core/build-prepare.js +21 -7
  132. package/dist/src/core/config-mutate.js +0 -1
  133. package/dist/src/core/config-paths.d.ts +2 -2
  134. package/dist/src/core/config-paths.js +6 -1
  135. package/dist/src/core/config-state.js +0 -1
  136. package/dist/src/core/config-validate-patch-policy.js +0 -1
  137. package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
  138. package/dist/src/core/config-validate-test-toolchains.js +105 -0
  139. package/dist/src/core/config-validate.js +3 -1
  140. package/dist/src/core/config.js +0 -1
  141. package/dist/src/core/destructive.js +0 -1
  142. package/dist/src/core/diff-hunks.js +13 -7
  143. package/dist/src/core/engine-changes.js +0 -1
  144. package/dist/src/core/engine-session-lock.d.ts +12 -0
  145. package/dist/src/core/engine-session-lock.js +45 -0
  146. package/dist/src/core/file-lock.d.ts +8 -0
  147. package/dist/src/core/file-lock.js +157 -48
  148. package/dist/src/core/firefox-archive.d.ts +4 -0
  149. package/dist/src/core/firefox-archive.js +6 -1
  150. package/dist/src/core/firefox-cache.js +67 -8
  151. package/dist/src/core/firefox-download.js +0 -1
  152. package/dist/src/core/firefox-extract.d.ts +21 -1
  153. package/dist/src/core/firefox-extract.js +178 -9
  154. package/dist/src/core/firefox-ignorefile.js +0 -1
  155. package/dist/src/core/firefox.d.ts +12 -0
  156. package/dist/src/core/firefox.js +64 -2
  157. package/dist/src/core/furnace-apply-dry-run.js +0 -1
  158. package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
  159. package/dist/src/core/furnace-apply-ftl.js +10 -5
  160. package/dist/src/core/furnace-apply-helpers.js +0 -1
  161. package/dist/src/core/furnace-apply-output.js +2 -2
  162. package/dist/src/core/furnace-apply.d.ts +7 -1
  163. package/dist/src/core/furnace-apply.js +47 -13
  164. package/dist/src/core/furnace-checksum-utils.js +0 -1
  165. package/dist/src/core/furnace-config-array-utils.js +0 -1
  166. package/dist/src/core/furnace-config-custom.js +0 -1
  167. package/dist/src/core/furnace-config-order.js +0 -1
  168. package/dist/src/core/furnace-config-tokens.js +0 -1
  169. package/dist/src/core/furnace-config-validate.js +0 -1
  170. package/dist/src/core/furnace-config.d.ts +11 -0
  171. package/dist/src/core/furnace-config.js +37 -1
  172. package/dist/src/core/furnace-constants.js +0 -1
  173. package/dist/src/core/furnace-css-fragments.js +0 -1
  174. package/dist/src/core/furnace-graph-utils.js +0 -1
  175. package/dist/src/core/furnace-jsconfig.d.ts +2 -3
  176. package/dist/src/core/furnace-jsconfig.js +25 -18
  177. package/dist/src/core/furnace-marker.js +0 -1
  178. package/dist/src/core/furnace-operation.js +0 -1
  179. package/dist/src/core/furnace-refresh.js +0 -1
  180. package/dist/src/core/furnace-registration-ast.d.ts +14 -0
  181. package/dist/src/core/furnace-registration-ast.js +1 -2
  182. package/dist/src/core/furnace-registration-remove.js +0 -1
  183. package/dist/src/core/furnace-registration-validate.js +1 -5
  184. package/dist/src/core/furnace-registration.js +0 -1
  185. package/dist/src/core/furnace-rollback.js +0 -1
  186. package/dist/src/core/furnace-scanner.js +0 -1
  187. package/dist/src/core/furnace-staleness.js +0 -1
  188. package/dist/src/core/furnace-state-persist.d.ts +72 -0
  189. package/dist/src/core/furnace-state-persist.js +118 -0
  190. package/dist/src/core/furnace-step-errors.d.ts +23 -0
  191. package/dist/src/core/furnace-step-errors.js +25 -0
  192. package/dist/src/core/furnace-stories.js +0 -1
  193. package/dist/src/core/furnace-validate-accessibility.js +0 -1
  194. package/dist/src/core/furnace-validate-checks.js +0 -1
  195. package/dist/src/core/furnace-validate-compatibility.js +0 -1
  196. package/dist/src/core/furnace-validate-helpers.js +0 -1
  197. package/dist/src/core/furnace-validate-registration.js +7 -7
  198. package/dist/src/core/furnace-validate-structure.d.ts +8 -0
  199. package/dist/src/core/furnace-validate-structure.js +11 -2
  200. package/dist/src/core/furnace-validate.js +0 -1
  201. package/dist/src/core/furnace-version-drift.d.ts +6 -3
  202. package/dist/src/core/furnace-version-drift.js +6 -4
  203. package/dist/src/core/git-base.js +0 -1
  204. package/dist/src/core/git-diff.js +0 -1
  205. package/dist/src/core/git-file-ops.js +0 -1
  206. package/dist/src/core/git-status.d.ts +13 -0
  207. package/dist/src/core/git-status.js +32 -2
  208. package/dist/src/core/git.d.ts +10 -1
  209. package/dist/src/core/git.js +65 -8
  210. package/dist/src/core/license-headers.d.ts +16 -0
  211. package/dist/src/core/license-headers.js +19 -1
  212. package/dist/src/core/lint-cache.js +0 -1
  213. package/dist/src/core/lint-projection.js +0 -1
  214. package/dist/src/core/mach-build-artifacts.js +0 -1
  215. package/dist/src/core/mach-error-hints.js +27 -1
  216. package/dist/src/core/mach-mozconfig.js +0 -1
  217. package/dist/src/core/mach-python.js +0 -1
  218. package/dist/src/core/mach-resource-shim.js +0 -1
  219. package/dist/src/core/mach.js +0 -1
  220. package/dist/src/core/manifest-helpers.js +0 -1
  221. package/dist/src/core/manifest-register.js +0 -1
  222. package/dist/src/core/manifest-rules.js +0 -1
  223. package/dist/src/core/manifest-tokenizers.js +0 -1
  224. package/dist/src/core/marionette-port.d.ts +8 -0
  225. package/dist/src/core/marionette-port.js +34 -3
  226. package/dist/src/core/marionette-preflight.js +0 -1
  227. package/dist/src/core/markdown-table.js +0 -1
  228. package/dist/src/core/ownership-table.js +2 -9
  229. package/dist/src/core/parser-fallback.js +0 -1
  230. package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
  231. package/dist/src/core/patch-apply-fuzz.js +71 -34
  232. package/dist/src/core/patch-apply.d.ts +30 -0
  233. package/dist/src/core/patch-apply.js +152 -39
  234. package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
  235. package/dist/src/core/patch-artifact-normalize.js +19 -15
  236. package/dist/src/core/patch-export-coverage.js +0 -1
  237. package/dist/src/core/patch-export-metadata.js +0 -1
  238. package/dist/src/core/patch-export-update.js +2 -3
  239. package/dist/src/core/patch-export.d.ts +2 -6
  240. package/dist/src/core/patch-export.js +24 -48
  241. package/dist/src/core/patch-files.js +0 -1
  242. package/dist/src/core/patch-identifier-suggest.js +0 -1
  243. package/dist/src/core/patch-lint-checkjs.js +0 -1
  244. package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
  245. package/dist/src/core/patch-lint-cross.js +0 -1
  246. package/dist/src/core/patch-lint-css.js +0 -1
  247. package/dist/src/core/patch-lint-diff-tag.js +0 -1
  248. package/dist/src/core/patch-lint-diff.d.ts +2 -1
  249. package/dist/src/core/patch-lint-diff.js +17 -32
  250. package/dist/src/core/patch-lint-imports.js +0 -1
  251. package/dist/src/core/patch-lint-jsdoc.js +0 -1
  252. package/dist/src/core/patch-lint-observer.js +0 -1
  253. package/dist/src/core/patch-lint-ownership.js +0 -1
  254. package/dist/src/core/patch-lint-reexports.js +0 -1
  255. package/dist/src/core/patch-lint.d.ts +37 -2
  256. package/dist/src/core/patch-lint.js +48 -24
  257. package/dist/src/core/patch-lock.js +0 -1
  258. package/dist/src/core/patch-manifest-consistency.js +9 -1
  259. package/dist/src/core/patch-manifest-io.d.ts +16 -0
  260. package/dist/src/core/patch-manifest-io.js +37 -12
  261. package/dist/src/core/patch-manifest-query.d.ts +6 -0
  262. package/dist/src/core/patch-manifest-query.js +27 -12
  263. package/dist/src/core/patch-manifest-resolve.js +0 -1
  264. package/dist/src/core/patch-manifest-validate.js +0 -1
  265. package/dist/src/core/patch-manifest.d.ts +1 -1
  266. package/dist/src/core/patch-manifest.js +1 -2
  267. package/dist/src/core/patch-parse.d.ts +59 -0
  268. package/dist/src/core/patch-parse.js +209 -92
  269. package/dist/src/core/patch-policy.js +0 -1
  270. package/dist/src/core/patch-registration-refs.js +16 -26
  271. package/dist/src/core/patch-source-metadata.js +0 -1
  272. package/dist/src/core/patch-transform.d.ts +4 -0
  273. package/dist/src/core/patch-transform.js +30 -38
  274. package/dist/src/core/rebase-session.js +0 -1
  275. package/dist/src/core/register-browser-content.js +0 -1
  276. package/dist/src/core/register-module.js +0 -1
  277. package/dist/src/core/register-result.js +0 -1
  278. package/dist/src/core/register-scaffold.js +0 -1
  279. package/dist/src/core/register-shared-css.js +0 -1
  280. package/dist/src/core/register-test-manifest.js +0 -1
  281. package/dist/src/core/register-xpcshell-test.js +0 -1
  282. package/dist/src/core/shared-ftl.js +0 -1
  283. package/dist/src/core/signal-critical.js +0 -1
  284. package/dist/src/core/smoke-patterns.js +0 -1
  285. package/dist/src/core/state-file.js +0 -1
  286. package/dist/src/core/status-classify.d.ts +16 -0
  287. package/dist/src/core/status-classify.js +24 -16
  288. package/dist/src/core/test-harness-crash.d.ts +64 -7
  289. package/dist/src/core/test-harness-crash.js +196 -15
  290. package/dist/src/core/test-harness-output.js +0 -1
  291. package/dist/src/core/test-path-scope.d.ts +62 -0
  292. package/dist/src/core/test-path-scope.js +148 -0
  293. package/dist/src/core/test-stale-check.js +0 -1
  294. package/dist/src/core/test-stale-symlink.js +0 -1
  295. package/dist/src/core/test-xpcshell-retry.js +0 -1
  296. package/dist/src/core/token-coverage.js +0 -1
  297. package/dist/src/core/token-dark-mode.js +0 -1
  298. package/dist/src/core/token-docs.js +0 -1
  299. package/dist/src/core/token-manager.js +0 -1
  300. package/dist/src/core/token-scaffold.js +0 -1
  301. package/dist/src/core/token-variant.js +0 -1
  302. package/dist/src/core/toolchain-preflight.d.ts +85 -0
  303. package/dist/src/core/toolchain-preflight.js +256 -0
  304. package/dist/src/core/typecheck-shim.d.ts +4 -1
  305. package/dist/src/core/typecheck-shim.js +61 -18
  306. package/dist/src/core/typecheck.js +0 -1
  307. package/dist/src/core/wire-destroy.js +0 -1
  308. package/dist/src/core/wire-dom-fragment.js +0 -1
  309. package/dist/src/core/wire-init.js +0 -1
  310. package/dist/src/core/wire-subscript.js +0 -1
  311. package/dist/src/core/wire-targets.js +0 -1
  312. package/dist/src/core/wire-utils.js +0 -1
  313. package/dist/src/core/xpcshell-appdir.js +1 -4
  314. package/dist/src/errors/base.d.ts +29 -0
  315. package/dist/src/errors/base.js +35 -1
  316. package/dist/src/errors/build.js +0 -1
  317. package/dist/src/errors/codes.d.ts +6 -0
  318. package/dist/src/errors/codes.js +6 -1
  319. package/dist/src/errors/config.js +0 -1
  320. package/dist/src/errors/download.js +2 -1
  321. package/dist/src/errors/furnace.d.ts +11 -1
  322. package/dist/src/errors/furnace.js +6 -5
  323. package/dist/src/errors/git.js +0 -1
  324. package/dist/src/errors/patch.d.ts +17 -0
  325. package/dist/src/errors/patch.js +29 -1
  326. package/dist/src/errors/rebase.js +0 -1
  327. package/dist/src/errors/run.js +1 -1
  328. package/dist/src/index.js +0 -1
  329. package/dist/src/types/cli.js +0 -1
  330. package/dist/src/types/commands/index.js +0 -1
  331. package/dist/src/types/commands/options.d.ts +20 -5
  332. package/dist/src/types/commands/options.js +0 -1
  333. package/dist/src/types/commands/patches.d.ts +8 -0
  334. package/dist/src/types/commands/patches.js +0 -1
  335. package/dist/src/types/commands/project.d.ts +11 -2
  336. package/dist/src/types/commands/project.js +0 -1
  337. package/dist/src/types/config.d.ts +32 -0
  338. package/dist/src/types/config.js +0 -1
  339. package/dist/src/types/furnace.d.ts +10 -0
  340. package/dist/src/types/furnace.js +0 -1
  341. package/dist/src/types/index.js +0 -1
  342. package/dist/src/types/typecheck.js +0 -1
  343. package/dist/src/utils/elapsed.js +0 -1
  344. package/dist/src/utils/errors.js +0 -1
  345. package/dist/src/utils/fs.js +37 -5
  346. package/dist/src/utils/logger.d.ts +7 -0
  347. package/dist/src/utils/logger.js +85 -3
  348. package/dist/src/utils/options.d.ts +15 -0
  349. package/dist/src/utils/options.js +25 -1
  350. package/dist/src/utils/package-root.js +11 -4
  351. package/dist/src/utils/parse.js +0 -1
  352. package/dist/src/utils/paths.js +0 -1
  353. package/dist/src/utils/platform.js +0 -1
  354. package/dist/src/utils/process.d.ts +33 -4
  355. package/dist/src/utils/process.js +183 -28
  356. package/dist/src/utils/regex.d.ts +13 -3
  357. package/dist/src/utils/regex.js +53 -4
  358. package/dist/src/utils/validation.d.ts +0 -12
  359. package/dist/src/utils/validation.js +13 -26
  360. package/package.json +8 -7
@@ -1,11 +1,12 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { join } from 'node:path';
3
2
  import { getProjectPaths, loadConfig } from '../../core/config.js';
4
- import { applyAllComponents, computeComponentChecksums, prefixChecksums, } from '../../core/furnace-apply.js';
3
+ import { applyAllComponents } from '../../core/furnace-apply.js';
5
4
  import { logApplyResult } from '../../core/furnace-apply-output.js';
6
- import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, updateFurnaceState, } from '../../core/furnace-config.js';
5
+ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, } from '../../core/furnace-config.js';
7
6
  import { reportJsconfigPathsSync } from '../../core/furnace-jsconfig.js';
8
7
  import { runFurnaceMutation } from '../../core/furnace-operation.js';
8
+ import { getPersistableAppliedEntry, getStepFailureCount, persistSingleComponentState, shouldPersistSingleComponentState, } from '../../core/furnace-state-persist.js';
9
+ import { hasBlockingStepErrors } from '../../core/furnace-step-errors.js';
9
10
  import { findOverrideBaseVersionDrift, formatOverrideBaseVersionDriftError, formatOverrideBaseVersionDriftWarning, } from '../../core/furnace-version-drift.js';
10
11
  import { FurnaceError } from '../../errors/furnace.js';
11
12
  import { pathExists } from '../../utils/fs.js';
@@ -28,91 +29,15 @@ function buildDeployFailureMessage(applyErrors, validationErrors, isDryRun) {
28
29
  }
29
30
  return `${mode} completed with ${validationErrors} validation error(s).`;
30
31
  }
31
- function getStepFailureCount(result) {
32
- return result.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
33
- }
34
32
  function getFailedComponentNames(result) {
35
33
  const failed = new Set(result.errors.map((entry) => entry.name));
36
34
  for (const applied of result.applied) {
37
- if ((applied.stepErrors?.length ?? 0) > 0) {
35
+ if (hasBlockingStepErrors(applied)) {
38
36
  failed.add(applied.name);
39
37
  }
40
38
  }
41
39
  return failed;
42
40
  }
43
- function getPersistableAppliedEntry(name, appliedEntry) {
44
- if (!appliedEntry) {
45
- throw new FurnaceError(`Deploy for "${name}" finished without producing an applied component entry; ` +
46
- `furnace state was not modified. Run "fireforge doctor --repair-furnace" to ` +
47
- `reconcile state, then retry the deploy. If this persists, file a bug with the ` +
48
- `output of "fireforge doctor".`);
49
- }
50
- if (appliedEntry.type !== 'override' && appliedEntry.type !== 'custom') {
51
- throw new FurnaceError(`Deploy for "${name}" returned an unsupported component type "${appliedEntry.type}"; ` +
52
- `furnace state was not modified. Run "fireforge doctor --repair-furnace" to reconcile, ` +
53
- `then verify the component with "fireforge furnace validate" before retrying.`);
54
- }
55
- // Guard against future refactors that might reorder or misroute the
56
- // applied[] array: named deploy persists state under a single component
57
- // name, so the first applied entry MUST be that component. Persisting a
58
- // different component's checksums here would cause the next status/apply
59
- // run to mis-report health for both components involved.
60
- if (name !== undefined && appliedEntry.name !== name) {
61
- throw new FurnaceError(`Deploy for "${name}" returned an applied entry for a different component ` +
62
- `("${appliedEntry.name}"); refusing to persist mismatched state. ` +
63
- `Run "fireforge doctor --repair-furnace" to reconcile, then retry the deploy.`);
64
- }
65
- return {
66
- name: appliedEntry.name,
67
- type: appliedEntry.type,
68
- };
69
- }
70
- /**
71
- * Decides whether a single-component deploy completed cleanly enough to
72
- * persist its checksums into furnace-state.json.
73
- *
74
- * Named deploy is atomic: if any apply step fails, the rollback journal
75
- * restores the engine to its pre-deploy state and this helper returns
76
- * `false` so state is not touched. The conditions must stay in lock-step
77
- * with the rollback-trigger in `applyNamedComponent` — both now read from
78
- * this helper so a future refactor cannot drift them apart and accidentally
79
- * persist partial state.
80
- */
81
- function shouldPersistNamedDeployState(result, isDryRun) {
82
- if (isDryRun)
83
- return false;
84
- if (result.errors.length > 0)
85
- return false;
86
- if (getStepFailureCount(result) > 0)
87
- return false;
88
- return result.applied.length > 0;
89
- }
90
- /**
91
- * Persists checksum state for a successfully applied named component.
92
- * @param projectRoot - Root directory of the project
93
- * @param appliedEntry - Applied component descriptor from deploy
94
- * @param furnacePaths - Resolved Furnace workspace paths
95
- */
96
- async function persistSingleComponentState(projectRoot, appliedEntry, furnacePaths) {
97
- const componentDir = appliedEntry.type === 'override'
98
- ? join(furnacePaths.overridesDir, appliedEntry.name)
99
- : join(furnacePaths.customDir, appliedEntry.name);
100
- const checksums = await computeComponentChecksums(componentDir);
101
- const prefixed = prefixChecksums(checksums, appliedEntry.type, appliedEntry.name);
102
- const componentPrefix = `${appliedEntry.type}/${appliedEntry.name}/`;
103
- await updateFurnaceState(projectRoot, (current) => ({
104
- ...current,
105
- appliedChecksums: {
106
- ...Object.fromEntries(Object.entries(current.appliedChecksums ?? {}).filter(([key]) => !key.startsWith(componentPrefix))),
107
- ...prefixed,
108
- },
109
- engineChecksums: {
110
- ...Object.fromEntries(Object.entries(current.engineChecksums ?? {}).filter(([key]) => !key.startsWith(componentPrefix))),
111
- ...prefixed,
112
- },
113
- lastApply: new Date().toISOString(),
114
- }));
115
- }
116
41
  /**
117
42
  * Applies a single named override or custom component in targeted deploy mode.
118
43
  *
@@ -251,8 +176,8 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
251
176
  // already restored the engine to its pre-deploy state, so persisting
252
177
  // partial checksums here would mis-report the next status/apply run
253
178
  // against a workspace that was never actually deployed.
254
- if (shouldPersistNamedDeployState(namedApplyResult, isDryRun)) {
255
- await persistSingleComponentState(projectRoot, getPersistableAppliedEntry(name, namedApplyResult.applied[0]), furnacePaths);
179
+ if (shouldPersistSingleComponentState(namedApplyResult, isDryRun)) {
180
+ await persistSingleComponentState(projectRoot, getPersistableAppliedEntry('Deploy', name, namedApplyResult.applied[0]), furnacePaths);
256
181
  }
257
182
  return { kind: 'result', result: namedApplyResult };
258
183
  }
@@ -293,4 +218,3 @@ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
293
218
  // --- Step 3: Summary ---
294
219
  printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun);
295
220
  }
296
- //# sourceMappingURL=deploy.js.map
@@ -207,4 +207,3 @@ export async function furnaceDiffCommand(projectRoot, name) {
207
207
  }
208
208
  outro('Diff complete');
209
209
  }
210
- //# sourceMappingURL=diff.js.map
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
- import { Option } from 'commander';
2
+ import { Command, Option } from 'commander';
3
+ import { withEngineSessionLock } from '../../core/engine-session-lock.js';
3
4
  import { pickDefined } from '../../utils/options.js';
4
5
  import { furnaceApplyCommand } from './apply.js';
5
6
  import { furnaceChromeDocCreateCommand } from './chrome-doc.js';
@@ -18,20 +19,12 @@ import { furnaceScanCommand } from './scan.js';
18
19
  import { furnaceStatusCommand } from './status.js';
19
20
  import { furnaceSyncCommand } from './sync.js';
20
21
  import { furnaceValidateCommand } from './validate.js';
21
- /**
22
- * Registers Furnace commands for querying component state: status, scan,
23
- * and action commands like apply, deploy, and create.
24
- * @param furnace - Parent Furnace command
25
- * @param context - Shared CLI registration context
26
- */
27
- function registerFurnaceInfoCommands(furnace, context) {
22
+ async function runEngineLockedFurnaceCommand(context, command, operation) {
23
+ const projectRoot = context.getProjectRoot();
24
+ await withEngineSessionLock(projectRoot, command, () => operation(projectRoot));
25
+ }
26
+ function registerFurnaceApplyCommand(furnace, context) {
28
27
  const { getProjectRoot, withErrorHandling } = context;
29
- furnace
30
- .command('status [name]')
31
- .description('Show component status and registration details')
32
- .action(withErrorHandling(async (name) => {
33
- await furnaceStatusCommand(getProjectRoot(), name);
34
- }));
35
28
  furnace
36
29
  .command('apply [name]')
37
30
  .description('Apply components to the engine (optionally a single component)')
@@ -39,8 +32,17 @@ function registerFurnaceInfoCommands(furnace, context) {
39
32
  .option('--force', 'Proceed despite baseVersion drift (stale overrides)')
40
33
  .option('-w, --watch', 'Watch component directories and re-apply on changes')
41
34
  .action(withErrorHandling(async (name, options) => {
42
- await furnaceApplyCommand(getProjectRoot(), name, pickDefined(options ?? {}));
35
+ const parsed = pickDefined(options ?? {});
36
+ const run = (projectRoot) => furnaceApplyCommand(projectRoot, name, parsed);
37
+ if (parsed.dryRun === true) {
38
+ await run(getProjectRoot());
39
+ return;
40
+ }
41
+ await runEngineLockedFurnaceCommand(context, 'furnace apply', run);
43
42
  }));
43
+ }
44
+ function registerFurnaceDeployCommand(furnace, context) {
45
+ const { getProjectRoot, withErrorHandling } = context;
44
46
  furnace
45
47
  .command('deploy [name]')
46
48
  .description('Apply components and validate in one step')
@@ -48,8 +50,48 @@ function registerFurnaceInfoCommands(furnace, context) {
48
50
  .option('--force', 'Proceed despite baseVersion drift (stale overrides)')
49
51
  .option('--skip-validate', 'Skip the validation step (apply only)')
50
52
  .action(withErrorHandling(async (name, options) => {
51
- await furnaceDeployCommand(getProjectRoot(), name, pickDefined(options ?? {}));
53
+ const parsed = pickDefined(options ?? {});
54
+ const run = (projectRoot) => furnaceDeployCommand(projectRoot, name, parsed);
55
+ if (parsed.dryRun === true) {
56
+ await run(getProjectRoot());
57
+ return;
58
+ }
59
+ await runEngineLockedFurnaceCommand(context, 'furnace deploy', run);
60
+ }));
61
+ }
62
+ function registerFurnaceSyncCommand(furnace, context) {
63
+ const { getProjectRoot, withErrorHandling } = context;
64
+ furnace
65
+ .command('sync')
66
+ .description('Refresh drifted overrides and re-apply all components (recommended after fireforge download)')
67
+ .option('--dry-run', 'Show what would change without modifying files (reads may overlap concurrent mutations)')
68
+ .addOption(new Option('-s, --strategy <strategy>', 'Auto-resolve merge conflicts (ours = keep local, theirs = accept upstream)').choices(['ours', 'theirs']))
69
+ .action(withErrorHandling(async (options) => {
70
+ const parsed = pickDefined(options);
71
+ const run = (projectRoot) => furnaceSyncCommand(projectRoot, parsed);
72
+ if (parsed.dryRun === true) {
73
+ await run(getProjectRoot());
74
+ return;
75
+ }
76
+ await runEngineLockedFurnaceCommand(context, 'furnace sync', run);
52
77
  }));
78
+ }
79
+ /**
80
+ * Registers Furnace commands for querying component state: status, scan,
81
+ * and action commands like apply, deploy, and create.
82
+ * @param furnace - Parent Furnace command
83
+ * @param context - Shared CLI registration context
84
+ */
85
+ function registerFurnaceInfoCommands(furnace, context) {
86
+ const { getProjectRoot, withErrorHandling } = context;
87
+ furnace
88
+ .command('status [name]')
89
+ .description('Show component status and registration details')
90
+ .action(withErrorHandling(async (name) => {
91
+ await furnaceStatusCommand(getProjectRoot(), name);
92
+ }));
93
+ registerFurnaceApplyCommand(furnace, context);
94
+ registerFurnaceDeployCommand(furnace, context);
53
95
  furnace
54
96
  .command('scan')
55
97
  .description('Scan engine for available components. Report-only by default: persist the discovered ' +
@@ -188,14 +230,7 @@ function registerFurnaceModifyCommands(furnace, context) {
188
230
  .action(withErrorHandling(async (oldName, newName) => {
189
231
  await furnaceRenameCommand(getProjectRoot(), oldName, newName);
190
232
  }));
191
- furnace
192
- .command('sync')
193
- .description('Refresh drifted overrides and re-apply all components (recommended after fireforge download)')
194
- .option('--dry-run', 'Show what would change without modifying files (reads may overlap concurrent mutations)')
195
- .addOption(new Option('-s, --strategy <strategy>', 'Auto-resolve merge conflicts (ours = keep local, theirs = accept upstream)').choices(['ours', 'theirs']))
196
- .action(withErrorHandling(async (options) => {
197
- await furnaceSyncCommand(getProjectRoot(), pickDefined(options));
198
- }));
233
+ registerFurnaceSyncCommand(furnace, context);
199
234
  }
200
235
  /** Registers the furnace command on the CLI program. */
201
236
  export function registerFurnace(program, context) {
@@ -209,4 +244,3 @@ export function registerFurnace(program, context) {
209
244
  registerFurnaceInfoCommands(furnace, context);
210
245
  registerFurnaceModifyCommands(furnace, context);
211
246
  }
212
- //# sourceMappingURL=index.js.map
@@ -279,4 +279,3 @@ async function scaffoldTokensCss(projectRoot) {
279
279
  }
280
280
  return { tokensCssPath };
281
281
  }
282
- //# sourceMappingURL=init.js.map
@@ -102,4 +102,3 @@ export async function furnaceListCommand(projectRoot, options = {}) {
102
102
  note(`Stock: ${stockCount} Overrides: ${overrideCount} Custom: ${customCount} Total: ${total}`, 'Summary');
103
103
  outro('List complete');
104
104
  }
105
- //# sourceMappingURL=list.js.map
@@ -440,4 +440,3 @@ export async function furnaceBatchOverrideCommand(projectRoot, names, options =
440
440
  }
441
441
  outro(`Batch override complete: ${succeeded.length} succeeded, ${failed.length} failed`);
442
442
  }
443
- //# sourceMappingURL=override.js.map
@@ -5,6 +5,7 @@ import { applyAllComponents } from '../../core/furnace-apply.js';
5
5
  import { furnaceConfigExists, loadFurnaceConfig, updateFurnaceState, } from '../../core/furnace-config.js';
6
6
  import { runFurnaceMutation } from '../../core/furnace-operation.js';
7
7
  import { restoreRollbackJournal } from '../../core/furnace-rollback.js';
8
+ import { countEntriesWithBlockingStepErrors } from '../../core/furnace-step-errors.js';
8
9
  import { cleanStories, syncStories } from '../../core/furnace-stories.js';
9
10
  import { hasBuildArtifacts, runMach, runMachCapture } from '../../core/mach.js';
10
11
  import { FurnaceError } from '../../errors/furnace.js';
@@ -68,7 +69,7 @@ function reportPreviewStagingFailures(stageResult) {
68
69
  }
69
70
  }
70
71
  }
71
- const appliedWithStepErrorsCount = stageResult.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
72
+ const appliedWithStepErrorsCount = countEntriesWithBlockingStepErrors(stageResult.applied);
72
73
  const totalFailures = stageResult.errors.length + appliedWithStepErrorsCount;
73
74
  throw new FurnaceError(`${totalFailures} component${totalFailures === 1 ? '' : 's'} failed to stage for preview`);
74
75
  }
@@ -273,7 +274,7 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
273
274
  if (previewJournal) {
274
275
  ctx.registerJournal(previewJournal);
275
276
  }
276
- const appliedWithStepErrorsCount = stageResult.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
277
+ const appliedWithStepErrorsCount = countEntriesWithBlockingStepErrors(stageResult.applied);
277
278
  const totalFailures = stageResult.errors.length + appliedWithStepErrorsCount;
278
279
  if (totalFailures > 0) {
279
280
  stageSpinner.error('Failed to stage components');
@@ -362,4 +363,3 @@ export async function furnacePreviewCommand(projectRoot, options = {}) {
362
363
  }
363
364
  outro('Storybook stopped');
364
365
  }
365
- //# sourceMappingURL=preview.js.map
@@ -294,4 +294,3 @@ export async function furnaceRefreshCommand(projectRoot, name, options = {}) {
294
294
  }
295
295
  outro(dryRun ? 'Dry run complete' : 'Refresh complete');
296
296
  }
297
- //# sourceMappingURL=refresh.js.map
@@ -1,5 +1,20 @@
1
+ import { type RollbackJournal } from '../../core/furnace-rollback.js';
1
2
  import type { FurnaceState } from '../../types/furnace.js';
2
3
  /**
3
4
  * Removes every checksum entry owned by the removed component.
4
5
  */
5
6
  export declare function dropChecksumsByPrefix(state: FurnaceState, prefix: string): FurnaceState;
7
+ /**
8
+ * Deletes the files a custom component deployed into its engine targetPath.
9
+ *
10
+ * Per-file, never a recursive `removeDir` of the whole directory: nothing
11
+ * stops two components from sharing a targetPath (or a hand-edited config
12
+ * from pointing at an upstream-shared directory), and the recursive delete
13
+ * permanently destroyed the co-located files on success — the rollback
14
+ * journal only protects the failure path. Deployed files come from the
15
+ * component's state checksums, falling back to the conventional
16
+ * `<name>.{mjs,css,ftl}` set when state carries no record (cleared by a
17
+ * --force download or rebase). The directory itself is removed only when
18
+ * nothing else lives in it afterwards.
19
+ */
20
+ export declare function removeDeployedCustomFiles(projectRoot: string, engineDir: string, name: string, targetPath: string, journal: RollbackJournal): Promise<void>;
@@ -1,3 +1,9 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { loadFurnaceState } from '../../core/furnace-config.js';
4
+ import { snapshotFile } from '../../core/furnace-rollback.js';
5
+ import { pathExists, removeDir, removeFile } from '../../utils/fs.js';
6
+ import { info } from '../../utils/logger.js';
1
7
  /**
2
8
  * Removes every checksum entry owned by the removed component.
3
9
  */
@@ -11,4 +17,44 @@ export function dropChecksumsByPrefix(state, prefix) {
11
17
  }
12
18
  return result;
13
19
  }
14
- //# sourceMappingURL=remove-state.js.map
20
+ /**
21
+ * Deletes the files a custom component deployed into its engine targetPath.
22
+ *
23
+ * Per-file, never a recursive `removeDir` of the whole directory: nothing
24
+ * stops two components from sharing a targetPath (or a hand-edited config
25
+ * from pointing at an upstream-shared directory), and the recursive delete
26
+ * permanently destroyed the co-located files on success — the rollback
27
+ * journal only protects the failure path. Deployed files come from the
28
+ * component's state checksums, falling back to the conventional
29
+ * `<name>.{mjs,css,ftl}` set when state carries no record (cleared by a
30
+ * --force download or rebase). The directory itself is removed only when
31
+ * nothing else lives in it afterwards.
32
+ */
33
+ export async function removeDeployedCustomFiles(projectRoot, engineDir, name, targetPath, journal) {
34
+ const engineTargetDir = join(engineDir, targetPath);
35
+ if (!(await pathExists(engineTargetDir)))
36
+ return;
37
+ const state = await loadFurnaceState(projectRoot);
38
+ const checksumPrefix = `custom/${name}/`;
39
+ const recordedFiles = Object.keys(state.appliedChecksums ?? {})
40
+ .filter((key) => key.startsWith(checksumPrefix))
41
+ .map((key) => key.slice(checksumPrefix.length));
42
+ const deployedFiles = recordedFiles.length > 0 ? recordedFiles : [`${name}.mjs`, `${name}.css`, `${name}.ftl`];
43
+ let deletedCount = 0;
44
+ for (const file of deployedFiles) {
45
+ const target = join(engineTargetDir, file);
46
+ if (await pathExists(target)) {
47
+ await snapshotFile(journal, target);
48
+ await removeFile(target);
49
+ deletedCount++;
50
+ }
51
+ }
52
+ if (deletedCount > 0) {
53
+ info(`Deleted ${deletedCount} deployed file(s) from engine/${targetPath}/`);
54
+ }
55
+ const { readdir } = await import('node:fs/promises');
56
+ if ((await readdir(engineTargetDir)).length === 0) {
57
+ await removeDir(engineTargetDir);
58
+ info(`Removed now-empty engine/${targetPath}/`);
59
+ }
60
+ }
@@ -16,7 +16,7 @@ import { FurnaceError } from '../../errors/furnace.js';
16
16
  import { toError } from '../../utils/errors.js';
17
17
  import { pathExists, readText, removeDir, removeFile, writeText } from '../../utils/fs.js';
18
18
  import { cancel, info, intro, isCancel, outro, warn } from '../../utils/logger.js';
19
- import { dropChecksumsByPrefix } from './remove-state.js';
19
+ import { dropChecksumsByPrefix, removeDeployedCustomFiles } from './remove-state.js';
20
20
  /**
21
21
  * Removes an entire TOML section (header + body lines) for a given test file.
22
22
  * Matches from `["filename"]` up to the next section header `[` or end-of-file,
@@ -457,14 +457,9 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
457
457
  await removeDir(dir);
458
458
  info(`Deleted components/custom/${name}/`);
459
459
  }
460
- // Clean up deployed files in engine
460
+ // Clean up deployed files in engine (per-file — see helper doc).
461
461
  if (customConfig?.targetPath) {
462
- const engineDir = join(paths.engine, customConfig.targetPath);
463
- if (await pathExists(engineDir)) {
464
- await snapshotDir(journal, engineDir);
465
- await removeDir(engineDir);
466
- info(`Deleted deployed files from engine/${customConfig.targetPath}/`);
467
- }
462
+ await removeDeployedCustomFiles(projectRoot, paths.engine, name, customConfig.targetPath, journal);
468
463
  }
469
464
  // Localized components deploy a .ftl outside targetPath into the
470
465
  // shared Fluent tree; apply writes it, so remove must delete it too
@@ -530,4 +525,3 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
530
525
  info(`Removed "${name}" from furnace.json`);
531
526
  outro('Component removed');
532
527
  }
533
- //# sourceMappingURL=remove.js.map
@@ -1,9 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { tagNameToClassName } from '../../core/furnace-constants.js';
3
- /** Escapes regex metacharacters so a user-supplied name is literal inside a RegExp. */
4
- function escapeRegex(input) {
5
- return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
6
- }
3
+ import { escapeRegex } from '../../utils/regex.js';
7
4
  function deriveTestStem(componentName, binaryName) {
8
5
  const strippedName = componentName.startsWith('moz-') ? componentName.slice(4) : componentName;
9
6
  const withoutBinaryPrefix = strippedName.startsWith(binaryName + '-')
@@ -25,4 +22,3 @@ export function updateBrowserChromeTestContent(content, oldName, newName, binary
25
22
  .replace(new RegExp(escapeRegex(oldUnderscored), 'g'), newUnderscored)
26
23
  .replace(new RegExp(escapeRegex(oldTestStem), 'g'), newTestStem);
27
24
  }
28
- //# sourceMappingURL=rename-browser-test.js.map
@@ -11,3 +11,9 @@ export declare function updateConfigForCustomRename(config: FurnaceConfig, oldNa
11
11
  * Re-keys an override component config entry.
12
12
  */
13
13
  export declare function updateConfigForOverrideRename(config: FurnaceConfig, oldName: string, newName: string): void;
14
+ /**
15
+ * Re-keys checksum entries in furnace-state.json from the old component name
16
+ * to the new name so that `doctor` doesn't flag stale entries and the next
17
+ * `apply` can correctly detect whether the renamed component has changed.
18
+ */
19
+ export declare function rekeyStateChecksums(projectRoot: string, componentType: string, oldName: string, newName: string): Promise<void>;
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { updateFurnaceState } from '../../core/furnace-config.js';
1
3
  /**
2
4
  * Applies a component rename to scaffold-owned filenames only.
3
5
  */
@@ -39,4 +41,31 @@ export function updateConfigForOverrideRename(config, oldName, newName) {
39
41
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- idiomatic key removal from config record
40
42
  delete config.overrides[oldName];
41
43
  }
42
- //# sourceMappingURL=rename-helpers.js.map
44
+ /**
45
+ * Re-keys checksum entries in furnace-state.json from the old component name
46
+ * to the new name so that `doctor` doesn't flag stale entries and the next
47
+ * `apply` can correctly detect whether the renamed component has changed.
48
+ */
49
+ export async function rekeyStateChecksums(projectRoot, componentType, oldName, newName) {
50
+ const oldPrefix = `${componentType}/${oldName}/`;
51
+ const newPrefix = `${componentType}/${newName}/`;
52
+ await updateFurnaceState(projectRoot, (state) => {
53
+ const result = { ...state };
54
+ for (const field of ['appliedChecksums', 'engineChecksums']) {
55
+ const checksums = state[field];
56
+ if (!checksums)
57
+ continue;
58
+ const updated = {};
59
+ for (const [key, value] of Object.entries(checksums)) {
60
+ if (key.startsWith(oldPrefix)) {
61
+ updated[newPrefix + key.slice(oldPrefix.length)] = value;
62
+ }
63
+ else {
64
+ updated[key] = value;
65
+ }
66
+ }
67
+ result[field] = updated;
68
+ }
69
+ return result;
70
+ });
71
+ }
@@ -19,14 +19,11 @@ import { readdir } from 'node:fs/promises';
19
19
  import { join } from 'node:path';
20
20
  import { loadConfig } from '../../core/config.js';
21
21
  import { xpcshellTestParentDir } from '../../core/furnace-constants.js';
22
- import { snapshotFile } from '../../core/furnace-rollback.js';
22
+ import { recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
23
23
  import { toError } from '../../utils/errors.js';
24
24
  import { ensureDir, pathExists, readText, removeDir, removeFile, writeText, } from '../../utils/fs.js';
25
25
  import { info, warn } from '../../utils/logger.js';
26
- /** Escapes regex metacharacters so a user-supplied name stays literal. */
27
- function escapeRegex(input) {
28
- return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
- }
26
+ import { escapeRegex } from '../../utils/regex.js';
30
27
  /**
31
28
  * Renames an xpcshell test scaffold in place. Moves the directory,
32
29
  * rewrites the test filename, updates the `[test_name]` section header
@@ -64,6 +61,10 @@ export async function renameXpcshellTestFiles(engineDir, projectRoot, oldName, n
64
61
  const oldTestFileName = `test_${oldUnderscored}_packaged.js`;
65
62
  const newTestFileName = `test_${newUnderscored}_packaged.js`;
66
63
  try {
64
+ // Journal the new scaffold dir + files so rollback (including the
65
+ // SIGINT path) removes them; only the old-name files were journaled
66
+ // before, so a failed rename stranded the new-name scaffold.
67
+ recordCreatedDir(journal, newScaffoldDir);
67
68
  await ensureDir(newScaffoldDir);
68
69
  const entries = await readdir(oldScaffoldDir, { withFileTypes: true });
69
70
  for (const entry of entries) {
@@ -73,6 +74,7 @@ export async function renameXpcshellTestFiles(engineDir, projectRoot, oldName, n
73
74
  const renamedFileName = entry.name === oldTestFileName ? newTestFileName : entry.name;
74
75
  const newFilePath = join(newScaffoldDir, renamedFileName);
75
76
  await snapshotFile(journal, oldFilePath);
77
+ await snapshotFile(journal, newFilePath);
76
78
  const body = await readText(oldFilePath);
77
79
  let updated = body;
78
80
  if (entry.name === 'xpcshell.toml') {
@@ -94,4 +96,3 @@ export async function renameXpcshellTestFiles(engineDir, projectRoot, oldName, n
94
96
  warn(`Could not rename xpcshell scaffold — ${toError(error).message}. Rename the scaffold files manually if needed.`);
95
97
  }
96
98
  }
97
- //# sourceMappingURL=rename-xpcshell.js.map
@@ -2,25 +2,22 @@
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { getProjectPaths, loadConfig } from '../../core/config.js';
5
- import { getFurnacePaths, loadFurnaceConfig, updateFurnaceState, writeFurnaceConfig, } from '../../core/furnace-config.js';
5
+ import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
6
6
  import { isComponentSourceFile, resolveFtlChromeSubPath, resolveFtlDir, resolveFtlLocaleJarMnPath, tagNameToClassName, } from '../../core/furnace-constants.js';
7
7
  import { recordFurnaceRollbackFailure, runFurnaceMutation } from '../../core/furnace-operation.js';
8
8
  import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration, removeJarMnEntries, removeLocaleFtlJarMnEntry, } from '../../core/furnace-registration.js';
9
9
  import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, } from '../../core/furnace-registration-validate.js';
10
- import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
10
+ import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
11
  import { getStoriesDir } from '../../core/furnace-stories.js';
12
12
  import { InvalidArgumentError } from '../../errors/base.js';
13
13
  import { FurnaceError } from '../../errors/furnace.js';
14
14
  import { toError } from '../../utils/errors.js';
15
15
  import { copyFile, ensureDir, pathExists, readText, removeDir, removeFile, writeText, } from '../../utils/fs.js';
16
16
  import { info, intro, note, outro, warn } from '../../utils/logger.js';
17
+ import { escapeRegex } from '../../utils/regex.js';
17
18
  import { updateBrowserChromeTestContent } from './rename-browser-test.js';
18
- import { renameComponentFileName, updateConfigForCustomRename, updateConfigForOverrideRename, } from './rename-helpers.js';
19
+ import { rekeyStateChecksums, renameComponentFileName, updateConfigForCustomRename, updateConfigForOverrideRename, } from './rename-helpers.js';
19
20
  import { renameXpcshellTestFiles } from './rename-xpcshell.js';
20
- /** Escapes regex metacharacters so a user-supplied name is literal inside a RegExp. */
21
- function escapeRegex(input) {
22
- return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
23
- }
24
21
  /**
25
22
  * Derives the test file name for a component, matching the convention used by
26
23
  * `furnace create --with-tests`.
@@ -58,6 +55,7 @@ async function renameTestFiles(engineDir, projectRoot, oldName, newName, journal
58
55
  if (await pathExists(oldTestPath)) {
59
56
  try {
60
57
  await snapshotFile(journal, oldTestPath);
58
+ await snapshotFile(journal, newTestPath);
61
59
  const content = await readText(oldTestPath);
62
60
  await writeText(newTestPath, updateBrowserChromeTestContent(content, oldName, newName, binaryName));
63
61
  await removeFile(oldTestPath);
@@ -144,6 +142,7 @@ async function renameMochikitTestFiles(engineDir, oldName, newName, journal) {
144
142
  .replace(new RegExp(`Test the ${escapeRegex(oldName)} `, 'g'), `Test the ${newName} `)
145
143
  .replace(new RegExp(`add_task\\(async function test_${escapeRegex(oldName.replace(/-/g, '_'))}_defined\\(`, 'g'), `add_task(async function test_${newName.replace(/-/g, '_')}_defined(`)
146
144
  .replace(new RegExp(`"${escapeRegex(oldName)} custom element`, 'g'), `"${newName} custom element`);
145
+ await snapshotFile(journal, newTestPath);
147
146
  await writeText(newTestPath, updatedContent);
148
147
  await removeFile(oldTestPath);
149
148
  info(`Renamed mochikit test: ${oldTestFileName} → ${newTestFileName}`);
@@ -209,7 +208,14 @@ async function performRenameMutations(args) {
209
208
  }
210
209
  await snapshotDir(journal, oldDir);
211
210
  await snapshotFile(journal, args.furnaceConfigPath);
211
+ // Journal the state file BEFORE step 4 re-keys its checksums, plus
212
+ // the new dir and every new-name destination below (a snapshot of a
213
+ // missing path records {existed: false} → rollback DELETES it).
214
+ // Without these, a failed or SIGINT'd rename stranded the new-name
215
+ // scaffold and kept re-keyed state for a nonexistent component.
216
+ await snapshotFile(journal, getFurnacePaths(projectRoot).furnaceState);
212
217
  // 1. Create new directory with renamed files and updated content
218
+ recordCreatedDir(journal, newDir);
213
219
  await ensureDir(newDir);
214
220
  const entries = await readdir(oldDir, { withFileTypes: true });
215
221
  for (const entry of entries) {
@@ -227,6 +233,7 @@ async function performRenameMutations(args) {
227
233
  const newFileName = renameComponentFileName(oldFileName, oldName, newName);
228
234
  const oldPath = join(oldDir, oldFileName);
229
235
  const newPath = join(newDir, newFileName);
236
+ await snapshotFile(journal, newPath);
230
237
  if (isComponentSourceFile(oldFileName)) {
231
238
  let content = await readText(oldPath);
232
239
  // Use word-boundary-aware patterns so substrings in other
@@ -320,34 +327,6 @@ async function performRenameMutations(args) {
320
327
  }
321
328
  });
322
329
  }
323
- /**
324
- * Re-keys checksum entries in furnace-state.json from the old component name
325
- * to the new name so that `doctor` doesn't flag stale entries and the next
326
- * `apply` can correctly detect whether the renamed component has changed.
327
- */
328
- async function rekeyStateChecksums(projectRoot, componentType, oldName, newName) {
329
- const oldPrefix = `${componentType}/${oldName}/`;
330
- const newPrefix = `${componentType}/${newName}/`;
331
- await updateFurnaceState(projectRoot, (state) => {
332
- const result = { ...state };
333
- for (const field of ['appliedChecksums', 'engineChecksums']) {
334
- const checksums = state[field];
335
- if (!checksums)
336
- continue;
337
- const updated = {};
338
- for (const [key, value] of Object.entries(checksums)) {
339
- if (key.startsWith(oldPrefix)) {
340
- updated[newPrefix + key.slice(oldPrefix.length)] = value;
341
- }
342
- else {
343
- updated[key] = value;
344
- }
345
- }
346
- result[field] = updated;
347
- }
348
- return result;
349
- });
350
- }
351
330
  async function updateEngineRegistrations(engineDir, oldName, newName, newDir, ftlDir, isLocalized, journal) {
352
331
  const customElementsPath = join(engineDir, 'toolkit/content/customElements.js');
353
332
  const jarMnPath = join(engineDir, 'toolkit/content/jar.mn');
@@ -372,6 +351,7 @@ async function updateEngineRegistrations(engineDir, oldName, newName, newDir, ft
372
351
  const newFtlPath = join(ftlDirPath, `${newName}.ftl`);
373
352
  if (await pathExists(oldFtlPath)) {
374
353
  await snapshotFile(journal, oldFtlPath);
354
+ await snapshotFile(journal, newFtlPath);
375
355
  const ftlContent = await readText(oldFtlPath);
376
356
  await writeText(newFtlPath, ftlContent);
377
357
  await removeFile(oldFtlPath);
@@ -463,4 +443,3 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
463
443
  ' 3. Run "fireforge furnace apply" to update the engine', newName);
464
444
  outro('Rename complete');
465
445
  }
466
- //# sourceMappingURL=rename.js.map
@@ -208,4 +208,3 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
208
208
  }
209
209
  outro('Scan complete');
210
210
  }
211
- //# sourceMappingURL=scan.js.map