@hominis/fireforge 0.49.2 → 0.50.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 (334) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +28 -0
  10. package/dist/src/commands/check-report.js +78 -0
  11. package/dist/src/commands/check-run.d.ts +28 -0
  12. package/dist/src/commands/check-run.js +104 -0
  13. package/dist/src/commands/check-steps.d.ts +67 -0
  14. package/dist/src/commands/check-steps.js +175 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  19. package/dist/src/commands/doctor-source-pin.js +4 -1
  20. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  21. package/dist/src/commands/export-adopt-check.js +98 -0
  22. package/dist/src/commands/export-flow.js +29 -14
  23. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  24. package/dist/src/commands/export-shared.d.ts +2 -1
  25. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  26. package/dist/src/commands/export-staged-registration.js +91 -0
  27. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  28. package/dist/src/commands/forward-import-remedy.js +63 -0
  29. package/dist/src/commands/furnace/apply.js +3 -3
  30. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  31. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  32. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  33. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  34. package/dist/src/commands/furnace/create.js +2 -2
  35. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  36. package/dist/src/commands/furnace/deploy-named.js +107 -0
  37. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  38. package/dist/src/commands/furnace/deploy.js +50 -86
  39. package/dist/src/commands/furnace/diff.js +26 -3
  40. package/dist/src/commands/furnace/index.js +66 -41
  41. package/dist/src/commands/furnace/init.js +13 -6
  42. package/dist/src/commands/furnace/override.js +4 -2
  43. package/dist/src/commands/furnace/preview.js +4 -3
  44. package/dist/src/commands/furnace/refresh.js +2 -2
  45. package/dist/src/commands/furnace/remove.js +4 -3
  46. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  47. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  48. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  49. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  50. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  51. package/dist/src/commands/furnace/rename-registrations.js +93 -0
  52. package/dist/src/commands/furnace/rename.d.ts +2 -1
  53. package/dist/src/commands/furnace/rename.js +28 -66
  54. package/dist/src/commands/furnace/scan.d.ts +9 -4
  55. package/dist/src/commands/furnace/scan.js +11 -9
  56. package/dist/src/commands/furnace/sync.js +3 -2
  57. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  58. package/dist/src/commands/furnace/validate-report.js +91 -0
  59. package/dist/src/commands/furnace/validate.d.ts +1 -1
  60. package/dist/src/commands/furnace/validate.js +127 -114
  61. package/dist/src/commands/furnace/validation-output.js +14 -3
  62. package/dist/src/commands/lint-per-patch.js +31 -60
  63. package/dist/src/commands/lint-report.d.ts +8 -4
  64. package/dist/src/commands/lint-report.js +15 -5
  65. package/dist/src/commands/lint.js +15 -34
  66. package/dist/src/commands/manifest.js +2 -0
  67. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  68. package/dist/src/commands/patch/delete.d.ts +1 -1
  69. package/dist/src/commands/patch/delete.js +13 -98
  70. package/dist/src/commands/patch/index.d.ts +1 -1
  71. package/dist/src/commands/patch/index.js +4 -2
  72. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  73. package/dist/src/commands/patch/lint-ignore.js +65 -28
  74. package/dist/src/commands/patch/move-files-create.js +26 -17
  75. package/dist/src/commands/patch/move-files-into.js +5 -3
  76. package/dist/src/commands/patch/move-files.js +3 -1
  77. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  78. package/dist/src/commands/patch/removal-dependents.js +133 -0
  79. package/dist/src/commands/patch/rename.js +7 -0
  80. package/dist/src/commands/patch/retire.d.ts +34 -0
  81. package/dist/src/commands/patch/retire.js +128 -0
  82. package/dist/src/commands/patch/split-plan.d.ts +7 -7
  83. package/dist/src/commands/patch/split-plan.js +45 -28
  84. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  85. package/dist/src/commands/patch/split-staged.js +202 -0
  86. package/dist/src/commands/patch/split.d.ts +5 -0
  87. package/dist/src/commands/patch/split.js +51 -15
  88. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  89. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  90. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  91. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  92. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  93. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  94. package/dist/src/commands/patch/staged-dependency.js +14 -41
  95. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  96. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  97. package/dist/src/commands/re-export-adjacent.d.ts +45 -2
  98. package/dist/src/commands/re-export-adjacent.js +156 -37
  99. package/dist/src/commands/re-export-drift.d.ts +36 -8
  100. package/dist/src/commands/re-export-drift.js +73 -49
  101. package/dist/src/commands/re-export-files.js +11 -8
  102. package/dist/src/commands/re-export-lint.d.ts +2 -1
  103. package/dist/src/commands/re-export-lint.js +23 -10
  104. package/dist/src/commands/re-export-options.js +45 -4
  105. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  106. package/dist/src/commands/re-export-preflight.js +160 -0
  107. package/dist/src/commands/re-export-register.js +42 -17
  108. package/dist/src/commands/re-export-removed.d.ts +12 -1
  109. package/dist/src/commands/re-export-removed.js +41 -11
  110. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  111. package/dist/src/commands/re-export-scan-owner.js +116 -0
  112. package/dist/src/commands/re-export-scan.d.ts +17 -9
  113. package/dist/src/commands/re-export-scan.js +56 -23
  114. package/dist/src/commands/re-export-single.js +22 -6
  115. package/dist/src/commands/re-export-summary.d.ts +28 -0
  116. package/dist/src/commands/re-export-summary.js +36 -0
  117. package/dist/src/commands/re-export.js +37 -24
  118. package/dist/src/commands/rebase/index.js +17 -17
  119. package/dist/src/commands/run.js +16 -2
  120. package/dist/src/commands/status-check.d.ts +55 -16
  121. package/dist/src/commands/status-check.js +92 -37
  122. package/dist/src/commands/status-json.d.ts +22 -9
  123. package/dist/src/commands/status-json.js +47 -32
  124. package/dist/src/commands/status-output.js +3 -1
  125. package/dist/src/commands/status-scope.d.ts +37 -0
  126. package/dist/src/commands/status-scope.js +54 -0
  127. package/dist/src/commands/status.js +66 -23
  128. package/dist/src/commands/test-browser-preflight.js +19 -3
  129. package/dist/src/commands/test-build-furnace.d.ts +21 -0
  130. package/dist/src/commands/test-build-furnace.js +48 -0
  131. package/dist/src/commands/test-build-phase.js +47 -41
  132. package/dist/src/commands/test-checkjs.js +7 -0
  133. package/dist/src/commands/test-diagnose.js +7 -0
  134. package/dist/src/commands/test-modes.d.ts +16 -1
  135. package/dist/src/commands/test-modes.js +24 -1
  136. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  137. package/dist/src/commands/test-multi-verdict.js +111 -0
  138. package/dist/src/commands/test-orchestrate.d.ts +43 -0
  139. package/dist/src/commands/test-orchestrate.js +222 -0
  140. package/dist/src/commands/test-perf-scope.d.ts +29 -0
  141. package/dist/src/commands/test-perf-scope.js +83 -0
  142. package/dist/src/commands/test-prelock.d.ts +51 -0
  143. package/dist/src/commands/test-prelock.js +177 -0
  144. package/dist/src/commands/test-register.js +54 -10
  145. package/dist/src/commands/test-run.js +4 -1
  146. package/dist/src/commands/test-stale-gate.js +57 -27
  147. package/dist/src/commands/test-verdict.d.ts +10 -1
  148. package/dist/src/commands/test-verdict.js +28 -1
  149. package/dist/src/commands/test.d.ts +6 -1
  150. package/dist/src/commands/test.js +66 -60
  151. package/dist/src/commands/token-lint.d.ts +14 -0
  152. package/dist/src/commands/token-lint.js +111 -0
  153. package/dist/src/commands/token.js +10 -0
  154. package/dist/src/commands/verify-scope.d.ts +28 -0
  155. package/dist/src/commands/verify-scope.js +67 -0
  156. package/dist/src/commands/verify.d.ts +10 -2
  157. package/dist/src/commands/verify.js +59 -35
  158. package/dist/src/core/build-baseline-types.d.ts +46 -0
  159. package/dist/src/core/build-baseline-validate.js +19 -0
  160. package/dist/src/core/build-baseline.d.ts +5 -1
  161. package/dist/src/core/build-baseline.js +3 -1
  162. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  163. package/dist/src/core/build-overwrite-guard.js +84 -4
  164. package/dist/src/core/build-prepare.d.ts +7 -0
  165. package/dist/src/core/build-prepare.js +14 -8
  166. package/dist/src/core/build-run-record.d.ts +74 -0
  167. package/dist/src/core/build-run-record.js +136 -0
  168. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  169. package/dist/src/core/checkjs-support-heads.js +104 -0
  170. package/dist/src/core/config-paths.d.ts +2 -2
  171. package/dist/src/core/config-paths.js +8 -0
  172. package/dist/src/core/config-validate-check.d.ts +8 -0
  173. package/dist/src/core/config-validate-check.js +143 -0
  174. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  175. package/dist/src/core/config-validate-file-size.js +91 -2
  176. package/dist/src/core/config-validate-history.d.ts +11 -0
  177. package/dist/src/core/config-validate-history.js +38 -0
  178. package/dist/src/core/config-validate-patch-policy.js +100 -0
  179. package/dist/src/core/config-validate-test-toolchains.js +7 -0
  180. package/dist/src/core/config-validate-token-scales.d.ts +6 -0
  181. package/dist/src/core/config-validate-token-scales.js +50 -0
  182. package/dist/src/core/config-validate.js +14 -5
  183. package/dist/src/core/config.d.ts +5 -0
  184. package/dist/src/core/config.js +16 -0
  185. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  186. package/dist/src/core/coverage-dispatch.js +44 -0
  187. package/dist/src/core/coverage-extend.d.ts +70 -45
  188. package/dist/src/core/coverage-extend.js +199 -144
  189. package/dist/src/core/coverage-records.d.ts +97 -0
  190. package/dist/src/core/coverage-records.js +350 -0
  191. package/dist/src/core/destructive.d.ts +7 -4
  192. package/dist/src/core/destructive.js +10 -10
  193. package/dist/src/core/engine-session-lock.d.ts +10 -1
  194. package/dist/src/core/engine-session-lock.js +28 -8
  195. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  196. package/dist/src/core/export-moved-code-shape.js +135 -44
  197. package/dist/src/core/file-lock-queue.d.ts +67 -0
  198. package/dist/src/core/file-lock-queue.js +221 -0
  199. package/dist/src/core/file-lock.d.ts +4 -25
  200. package/dist/src/core/file-lock.js +105 -138
  201. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  202. package/dist/src/core/furnace-apply-output.js +31 -1
  203. package/dist/src/core/furnace-apply.js +12 -2
  204. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  205. package/dist/src/core/furnace-composed-source.js +167 -0
  206. package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
  207. package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
  208. package/dist/src/core/furnace-config-validate.js +8 -0
  209. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  210. package/dist/src/core/furnace-css-fragments.js +26 -0
  211. package/dist/src/core/furnace-operation.d.ts +3 -3
  212. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  213. package/dist/src/core/furnace-registration-remove.js +53 -17
  214. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  215. package/dist/src/core/furnace-registration-rename.js +77 -0
  216. package/dist/src/core/furnace-registration.d.ts +14 -4
  217. package/dist/src/core/furnace-registration.js +44 -10
  218. package/dist/src/core/furnace-staleness.d.ts +7 -0
  219. package/dist/src/core/furnace-staleness.js +34 -24
  220. package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
  221. package/dist/src/core/furnace-validate-accessibility.js +10 -1
  222. package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
  223. package/dist/src/core/furnace-validate-exceptions.js +43 -0
  224. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  225. package/dist/src/core/furnace-validate-icon-button.js +49 -0
  226. package/dist/src/core/furnace-validate.js +1 -1
  227. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  228. package/dist/src/core/harness-port-fallback.js +96 -0
  229. package/dist/src/core/history-log-file.d.ts +61 -0
  230. package/dist/src/core/history-log-file.js +157 -0
  231. package/dist/src/core/history-log.d.ts +2 -1
  232. package/dist/src/core/history-log.js +2 -1
  233. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  234. package/dist/src/core/json-preserving-edit.js +199 -0
  235. package/dist/src/core/mach.js +8 -1
  236. package/dist/src/core/negative-control-diff.d.ts +39 -0
  237. package/dist/src/core/negative-control-diff.js +136 -0
  238. package/dist/src/core/negative-control.d.ts +138 -0
  239. package/dist/src/core/negative-control.js +356 -0
  240. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  241. package/dist/src/core/objdir-install-manifests.js +226 -0
  242. package/dist/src/core/patch-export-update.d.ts +2 -0
  243. package/dist/src/core/patch-export-update.js +1 -0
  244. package/dist/src/core/patch-export.js +3 -4
  245. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
  246. package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
  247. package/dist/src/core/patch-lint-checkjs.js +8 -11
  248. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  249. package/dist/src/core/patch-lint-code-lines.js +105 -0
  250. package/dist/src/core/patch-lint-cross.js +12 -4
  251. package/dist/src/core/patch-lint-css.d.ts +8 -2
  252. package/dist/src/core/patch-lint-css.js +10 -4
  253. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  254. package/dist/src/core/patch-lint-file-size.js +22 -11
  255. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
  256. package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
  257. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  258. package/dist/src/core/patch-lint-ignore.d.ts +152 -0
  259. package/dist/src/core/patch-lint-ignore.js +299 -0
  260. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  261. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  262. package/dist/src/core/patch-lint-size.d.ts +117 -0
  263. package/dist/src/core/patch-lint-size.js +323 -0
  264. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  265. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  266. package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
  267. package/dist/src/core/patch-lint-token-scale.js +109 -0
  268. package/dist/src/core/patch-lint.d.ts +11 -83
  269. package/dist/src/core/patch-lint.js +64 -286
  270. package/dist/src/core/patch-lock.js +7 -6
  271. package/dist/src/core/patch-manifest-consistency.js +7 -6
  272. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  273. package/dist/src/core/patch-manifest-io.js +19 -8
  274. package/dist/src/core/patch-manifest-validate.js +34 -2
  275. package/dist/src/core/patch-order-legal.d.ts +25 -0
  276. package/dist/src/core/patch-order-legal.js +83 -0
  277. package/dist/src/core/patch-policy-content.d.ts +40 -0
  278. package/dist/src/core/patch-policy-content.js +216 -0
  279. package/dist/src/core/patch-policy.d.ts +16 -3
  280. package/dist/src/core/patch-policy.js +16 -9
  281. package/dist/src/core/patch-retired.d.ts +35 -0
  282. package/dist/src/core/patch-retired.js +54 -0
  283. package/dist/src/core/patch-subset.d.ts +16 -0
  284. package/dist/src/core/patch-subset.js +66 -0
  285. package/dist/src/core/preflight-wait.d.ts +20 -0
  286. package/dist/src/core/preflight-wait.js +38 -0
  287. package/dist/src/core/rebase-from-version.d.ts +46 -0
  288. package/dist/src/core/rebase-from-version.js +71 -0
  289. package/dist/src/core/run-log.d.ts +20 -0
  290. package/dist/src/core/run-log.js +89 -3
  291. package/dist/src/core/run-profile.d.ts +7 -0
  292. package/dist/src/core/run-profile.js +7 -1
  293. package/dist/src/core/smoke-patterns.d.ts +5 -0
  294. package/dist/src/core/smoke-patterns.js +13 -0
  295. package/dist/src/core/test-harness-crash.d.ts +18 -1
  296. package/dist/src/core/test-harness-crash.js +6 -4
  297. package/dist/src/core/test-harness-signature.d.ts +17 -0
  298. package/dist/src/core/test-harness-signature.js +4 -0
  299. package/dist/src/core/test-run-completeness.d.ts +57 -0
  300. package/dist/src/core/test-run-completeness.js +144 -0
  301. package/dist/src/core/test-stale-check.d.ts +27 -1
  302. package/dist/src/core/test-stale-check.js +39 -49
  303. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  304. package/dist/src/core/test-stale-exempt.js +70 -0
  305. package/dist/src/core/token-lint.d.ts +50 -0
  306. package/dist/src/core/token-lint.js +126 -0
  307. package/dist/src/core/tree-guard.js +3 -0
  308. package/dist/src/core/typecheck-shim.d.ts +1 -1
  309. package/dist/src/core/typecheck-shim.js +90 -2
  310. package/dist/src/index.d.ts +8 -3
  311. package/dist/src/index.js +3 -1
  312. package/dist/src/types/commands/index.d.ts +2 -2
  313. package/dist/src/types/commands/options.d.ts +148 -21
  314. package/dist/src/types/commands/patches.d.ts +75 -1
  315. package/dist/src/types/config.d.ts +192 -0
  316. package/dist/src/types/config.js +9 -0
  317. package/dist/src/types/furnace.d.ts +43 -1
  318. package/dist/src/types/furnace.js +7 -0
  319. package/dist/src/types/index.d.ts +1 -1
  320. package/dist/src/utils/options.d.ts +34 -6
  321. package/dist/src/utils/options.js +86 -5
  322. package/dist/src/utils/path-glob.d.ts +15 -0
  323. package/dist/src/utils/path-glob.js +47 -0
  324. package/dist/src/utils/regex.d.ts +15 -3
  325. package/dist/src/utils/regex.js +37 -16
  326. package/docs/configuration.md +282 -9
  327. package/docs/exit-codes.md +18 -1
  328. package/docs/furnace.md +97 -3
  329. package/docs/lifecycle-invariants.md +52 -19
  330. package/docs/machine-output.md +74 -12
  331. package/docs/patch-workflow.md +297 -22
  332. package/docs/run-logs.md +20 -1
  333. package/docs/testing.md +276 -20
  334. package/package.json +1 -1
@@ -6,7 +6,7 @@ import { getProjectPaths, loadConfig } from '../../core/config.js';
6
6
  import { stdioIsInteractive } from '../../core/destructive.js';
7
7
  import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, writeFurnaceConfig, } from '../../core/furnace-config.js';
8
8
  import { resolveFtlDir, xpcshellTestParentDir } from '../../core/furnace-constants.js';
9
- import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
9
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
10
10
  import { createRollbackJournal, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
11
  import { isGitRepository } from '../../core/git.js';
12
12
  import { deregisterTestManifest } from '../../core/moz-manifest-register.js';
@@ -343,7 +343,7 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
343
343
  // rollback pathway.
344
344
  const paths = getProjectPaths(projectRoot);
345
345
  await requireGitEngineForRemove(type, name, paths.engine);
346
- await runFurnaceMutation(projectRoot, 'remove-rollback', async (ctx) => {
346
+ const mutate = async (ctx) => {
347
347
  const journal = createRollbackJournal();
348
348
  ctx.registerJournal(journal);
349
349
  try {
@@ -408,7 +408,8 @@ export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
408
408
  subject: `component "${name}"`,
409
409
  });
410
410
  }
411
- });
411
+ };
412
+ await runFurnaceMutation(projectRoot, 'remove-rollback', mutate, waitLockMutationOptions(options.waitLockSeconds));
412
413
  info(`Removed "${name}" from furnace.json`);
413
414
  outro('Component removed');
414
415
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `furnace rename --dry-run`: the plan a rename would carry out, rendered
3
+ * from reads only. A rename edits three engine registration files besides
4
+ * moving the component, and before this none of those edits was visible
5
+ * until it had run.
6
+ */
7
+ import type { FurnaceConfig } from '../../types/furnace.js';
8
+ /** Inputs for {@link renderRenamePlan}. */
9
+ export interface RenamePlanInput {
10
+ config: FurnaceConfig;
11
+ oldName: string;
12
+ newName: string;
13
+ /** Workspace directory of the component being renamed. */
14
+ oldDir: string;
15
+ /** On-disk directory label: `custom` or `overrides`. */
16
+ componentDirLabel: 'custom' | 'overrides';
17
+ engineDir: string;
18
+ /** Project marker a freshly added registration line would carry. */
19
+ markerComment: string | undefined;
20
+ }
21
+ /**
22
+ * Renders the rename plan: workspace file moves, the furnace.json re-key,
23
+ * and, for a custom component, every engine edit the real run would make
24
+ * (registrations only when the component is registered).
25
+ *
26
+ * @param input - See {@link RenamePlanInput}
27
+ * @returns The plan as display lines joined by newlines
28
+ */
29
+ export declare function renderRenamePlan(input: RenamePlanInput): Promise<string>;
@@ -0,0 +1,127 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * `furnace rename --dry-run`: the plan a rename would carry out, rendered
4
+ * from reads only. A rename edits three engine registration files besides
5
+ * moving the component, and before this none of those edits was visible
6
+ * until it had run.
7
+ */
8
+ import { readdir } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { isComponentSourceFile, JAR_MN, resolveFtlChromeSubPath, resolveFtlDir, resolveFtlLocaleJarMnPath, } from '../../core/furnace-constants.js';
11
+ import { hasLocaleFtlJarMnEntry } from '../../core/furnace-registration.js';
12
+ import { isTagAlreadyRegistered } from '../../core/furnace-registration-ast.js';
13
+ import { pathExists, readText } from '../../utils/fs.js';
14
+ import { renameComponentFileName, renameTagInDescription } from './rename-helpers.js';
15
+ /** Plan lines for the workspace move and the furnace.json re-key. */
16
+ async function workspaceLines(input) {
17
+ const { config, oldName, newName, oldDir, componentDirLabel } = input;
18
+ const lines = [
19
+ `Workspace: components/${componentDirLabel}/${oldName}/ → components/${componentDirLabel}/${newName}/`,
20
+ ];
21
+ const entries = (await readdir(oldDir, { withFileTypes: true }))
22
+ .filter((entry) => entry.isFile())
23
+ .map((entry) => entry.name)
24
+ .sort();
25
+ for (const fileName of entries) {
26
+ const renamed = renameComponentFileName(fileName, oldName, newName);
27
+ const rewrite = isComponentSourceFile(fileName) ? ' (tag and class references rewritten)' : '';
28
+ lines.push(` ${fileName}${renamed === fileName ? '' : ` → ${renamed}`}${rewrite}`);
29
+ }
30
+ const entry = config.custom[oldName] ?? config.overrides[oldName];
31
+ lines.push(`furnace.json: "${oldName}" → "${newName}"`);
32
+ if (entry) {
33
+ const description = renameTagInDescription(entry.description, oldName, newName);
34
+ if (description !== entry.description) {
35
+ lines.push(` description: "${entry.description}" → "${description}"`);
36
+ }
37
+ }
38
+ return lines;
39
+ }
40
+ /** Plan line for the customElements.js registration. */
41
+ async function customElementsLine(input) {
42
+ const { engineDir, oldName, newName, markerComment } = input;
43
+ const path = join(engineDir, 'toolkit/content/customElements.js');
44
+ if (!(await pathExists(path)))
45
+ return undefined;
46
+ const content = await readText(path);
47
+ const oldRegistered = isTagAlreadyRegistered(content, oldName);
48
+ if (isTagAlreadyRegistered(content, newName)) {
49
+ return `customElements.js: "${newName}" is already registered${oldRegistered ? `; remove the "${oldName}" entry` : ''}`;
50
+ }
51
+ if (oldRegistered) {
52
+ return `customElements.js: rewrite the "${oldName}" entry in place as "${newName}" (position and marker comments kept)`;
53
+ }
54
+ const marker = markerComment !== undefined ? ` marked // ${markerComment}:` : '';
55
+ return `customElements.js: add a "${newName}" entry${marker}`;
56
+ }
57
+ /** Plan line for the toolkit/content/jar.mn widget lines. */
58
+ async function jarLine(input) {
59
+ const { engineDir, oldName, newName, oldDir } = input;
60
+ const path = join(engineDir, JAR_MN);
61
+ if (!(await pathExists(path)))
62
+ return undefined;
63
+ const oldLines = (await readText(path))
64
+ .split('\n')
65
+ .filter((line) => line.includes(`(widgets/${oldName}/`)).length;
66
+ const newFiles = (await readdir(oldDir, { withFileTypes: true }))
67
+ .filter((entry) => entry.isFile() && /\.(?:mjs|css)$/.test(entry.name))
68
+ .map((entry) => renameComponentFileName(entry.name, oldName, newName))
69
+ .sort();
70
+ return (`${JAR_MN}: replace ${oldLines} "${oldName}" line(s) with ` +
71
+ (newFiles.length > 0 ? newFiles.join(', ') : 'none (no .mjs/.css files)'));
72
+ }
73
+ /** Plan lines for the deployed .ftl and its locale jar.mn line. */
74
+ async function localeLines(input, componentConfig) {
75
+ const { config, engineDir, oldName, newName } = input;
76
+ if (!componentConfig.localized)
77
+ return [];
78
+ const ftlDir = resolveFtlDir(config.ftlBasePath);
79
+ const lines = [];
80
+ if (await pathExists(join(engineDir, ftlDir, `${oldName}.ftl`))) {
81
+ lines.push(`${ftlDir}/${oldName}.ftl → ${newName}.ftl`);
82
+ }
83
+ const chromeSubPath = resolveFtlChromeSubPath(ftlDir);
84
+ const localeJarRel = resolveFtlLocaleJarMnPath(ftlDir);
85
+ if (chromeSubPath === undefined || localeJarRel === undefined)
86
+ return lines;
87
+ const present = await hasLocaleFtlJarMnEntry(engineDir, localeJarRel, oldName, chromeSubPath);
88
+ if (componentConfig.sharedFtl !== undefined) {
89
+ lines.push(`${localeJarRel}: no per-component line (sharedFtl: ${componentConfig.sharedFtl})` +
90
+ (present ? `; remove the stale "${oldName}.ftl" line` : ''));
91
+ }
92
+ else {
93
+ lines.push(present
94
+ ? `${localeJarRel}: re-point the "${oldName}.ftl" line to "${newName}.ftl"`
95
+ : `${localeJarRel}: no "${oldName}.ftl" line to re-point (left unchanged)`);
96
+ }
97
+ return lines;
98
+ }
99
+ /**
100
+ * Renders the rename plan: workspace file moves, the furnace.json re-key,
101
+ * and, for a custom component, every engine edit the real run would make
102
+ * (registrations only when the component is registered).
103
+ *
104
+ * @param input - See {@link RenamePlanInput}
105
+ * @returns The plan as display lines joined by newlines
106
+ */
107
+ export async function renderRenamePlan(input) {
108
+ const lines = await workspaceLines(input);
109
+ const componentConfig = input.config.custom[input.oldName];
110
+ if (!componentConfig || !(await pathExists(input.engineDir))) {
111
+ return lines.join('\n');
112
+ }
113
+ lines.push('Engine:');
114
+ const engineLines = componentConfig.register
115
+ ? [
116
+ await customElementsLine(input),
117
+ await jarLine(input),
118
+ ...(await localeLines(input, componentConfig)),
119
+ ].filter((line) => line !== undefined)
120
+ : ['registrations untouched (register: false)'];
121
+ if (await pathExists(join(input.engineDir, componentConfig.targetPath))) {
122
+ engineLines.push(`remove the stale deployed directory ${componentConfig.targetPath}/`);
123
+ }
124
+ lines.push(...engineLines.map((line) => ` ${line}`));
125
+ lines.push('Test scaffolds named after the old tag are renamed when present.');
126
+ return lines.join('\n');
127
+ }
@@ -3,6 +3,13 @@ import type { FurnaceConfig } from '../../types/furnace.js';
3
3
  * Applies a component rename to scaffold-owned filenames only.
4
4
  */
5
5
  export declare function renameComponentFileName(fileName: string, oldName: string, newName: string): string;
6
+ /**
7
+ * Rewrites whole-tag mentions of the old name in a component description,
8
+ * so a rename does not carry a description that still names the old tag.
9
+ * Boundary-aware like the source rewrite: `moz-panel` inside
10
+ * `moz-panel-group` is left alone.
11
+ */
12
+ export declare function renameTagInDescription(description: string, oldName: string, newName: string): string;
6
13
  /**
7
14
  * Re-keys a custom component config entry and same-config compose references.
8
15
  */
@@ -1,5 +1,6 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { updateFurnaceState } from '../../core/furnace-config.js';
3
+ import { escapeRegex } from '../../utils/regex.js';
3
4
  /**
4
5
  * Applies a component rename to scaffold-owned filenames only.
5
6
  */
@@ -11,6 +12,15 @@ export function renameComponentFileName(fileName, oldName, newName) {
11
12
  }
12
13
  return fileName;
13
14
  }
15
+ /**
16
+ * Rewrites whole-tag mentions of the old name in a component description,
17
+ * so a rename does not carry a description that still names the old tag.
18
+ * Boundary-aware like the source rewrite: `moz-panel` inside
19
+ * `moz-panel-group` is left alone.
20
+ */
21
+ export function renameTagInDescription(description, oldName, newName) {
22
+ return description.replace(new RegExp(`(?<![\\w-])${escapeRegex(oldName)}(?![\\w-])`, 'g'), newName);
23
+ }
14
24
  /**
15
25
  * Re-keys a custom component config entry and same-config compose references.
16
26
  */
@@ -20,6 +30,7 @@ export function updateConfigForCustomRename(config, oldName, newName) {
20
30
  return;
21
31
  config.custom[newName] = {
22
32
  ...oldConfig,
33
+ description: renameTagInDescription(oldConfig.description, oldName, newName),
23
34
  targetPath: oldConfig.targetPath.replace(new RegExp(`(^|/)${oldName}$`), `$1${newName}`),
24
35
  };
25
36
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- idiomatic key removal from config record
@@ -37,7 +48,10 @@ export function updateConfigForOverrideRename(config, oldName, newName) {
37
48
  const oldConfig = config.overrides[oldName];
38
49
  if (!oldConfig)
39
50
  return;
40
- config.overrides[newName] = { ...oldConfig };
51
+ config.overrides[newName] = {
52
+ ...oldConfig,
53
+ description: renameTagInDescription(oldConfig.description, oldName, newName),
54
+ };
41
55
  // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- idiomatic key removal from config record
42
56
  delete config.overrides[oldName];
43
57
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Engine registration edits for `furnace rename`: the customElements.js
3
+ * entry (rewritten in place, keeping its marker comments), the
4
+ * toolkit/content/jar.mn widget lines, the deployed `.ftl`, and the locale
5
+ * jar.mn line (re-pointed only when one existed, never for `sharedFtl`).
6
+ */
7
+ import { type RollbackJournal } from '../../core/furnace-rollback.js';
8
+ import type { CustomComponentConfig } from '../../types/furnace.js';
9
+ /**
10
+ * The project marker (`// <MARKER>:`) a registration line written by the
11
+ * rename carries, resolved exactly as `furnace apply` resolves it. An
12
+ * unreadable fireforge.json degrades to no marker rather than failing the
13
+ * rename.
14
+ */
15
+ export declare function loadMarkerComment(projectRoot: string): Promise<string | undefined>;
16
+ /** Inputs for {@link updateEngineRegistrations}. */
17
+ export interface UpdateEngineRegistrationsOptions {
18
+ /** Absolute path to the engine checkout. */
19
+ engineDir: string;
20
+ /** Tag name being renamed away from. */
21
+ oldName: string;
22
+ /** Tag name being renamed to. */
23
+ newName: string;
24
+ /** Workspace directory holding the renamed component's files. */
25
+ newDir: string;
26
+ /** Engine-relative directory localized components deploy their `.ftl` into. */
27
+ ftlDir: string;
28
+ /** The renamed component's furnace.json entry (`localized`, `sharedFtl`). */
29
+ componentConfig: CustomComponentConfig;
30
+ /** Project marker for a registration line the rename has to add fresh. */
31
+ markerComment: string | undefined;
32
+ /** Rollback journal every write is recorded in. */
33
+ journal: RollbackJournal;
34
+ }
35
+ /**
36
+ * Re-points the engine-side registrations (customElements.js, jar.mn, and
37
+ * the localized `.ftl` entries) from the old tag name to the new one.
38
+ *
39
+ * @param options - See {@link UpdateEngineRegistrationsOptions}
40
+ */
41
+ export declare function updateEngineRegistrations(options: UpdateEngineRegistrationsOptions): Promise<void>;
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Engine registration edits for `furnace rename`: the customElements.js
4
+ * entry (rewritten in place, keeping its marker comments), the
5
+ * toolkit/content/jar.mn widget lines, the deployed `.ftl`, and the locale
6
+ * jar.mn line (re-pointed only when one existed, never for `sharedFtl`).
7
+ */
8
+ import { readdir } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import { loadConfig } from '../../core/config.js';
11
+ import { resolveFtlChromeSubPath, resolveFtlLocaleJarMnPath, } from '../../core/furnace-constants.js';
12
+ import { resolveFurnaceMarkerComment } from '../../core/furnace-marker.js';
13
+ import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration, removeJarMnEntries, removeLocaleFtlJarMnEntry, renameCustomElementRegistration, } from '../../core/furnace-registration.js';
14
+ import { snapshotFile } from '../../core/furnace-rollback.js';
15
+ import { pathExists, readText, removeFile, writeText } from '../../utils/fs.js';
16
+ /**
17
+ * The project marker (`// <MARKER>:`) a registration line written by the
18
+ * rename carries, resolved exactly as `furnace apply` resolves it. An
19
+ * unreadable fireforge.json degrades to no marker rather than failing the
20
+ * rename.
21
+ */
22
+ export async function loadMarkerComment(projectRoot) {
23
+ try {
24
+ return resolveFurnaceMarkerComment(await loadConfig(projectRoot));
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ /**
31
+ * Re-points the engine-side registrations (customElements.js, jar.mn, and
32
+ * the localized `.ftl` entries) from the old tag name to the new one.
33
+ *
34
+ * @param options - See {@link UpdateEngineRegistrationsOptions}
35
+ */
36
+ export async function updateEngineRegistrations(options) {
37
+ const { engineDir, oldName, newName, newDir, ftlDir, componentConfig, markerComment, journal } = options;
38
+ const customElementsPath = join(engineDir, 'toolkit/content/customElements.js');
39
+ const jarMnPath = join(engineDir, 'toolkit/content/jar.mn');
40
+ if (await pathExists(customElementsPath)) {
41
+ await snapshotFile(journal, customElementsPath);
42
+ // Rewrite the existing entry in place: it keeps its position, layout and
43
+ // trailing `// <MARKER>:` comments. Only when there is no entry to
44
+ // rewrite is a fresh one added, carrying the project marker like every
45
+ // line `furnace apply` writes.
46
+ if (!(await renameCustomElementRegistration(engineDir, oldName, newName))) {
47
+ await removeCustomElementRegistration(engineDir, oldName);
48
+ await addCustomElementRegistration(engineDir, newName, `chrome://global/content/elements/${newName}.mjs`, markerComment !== undefined ? { markerComment } : {});
49
+ }
50
+ }
51
+ if (await pathExists(jarMnPath)) {
52
+ await snapshotFile(journal, jarMnPath);
53
+ await removeJarMnEntries(engineDir, oldName);
54
+ const files = (await readdir(newDir, { withFileTypes: true }))
55
+ .filter((e) => e.isFile() && (e.name.endsWith('.mjs') || e.name.endsWith('.css')))
56
+ .map((e) => e.name);
57
+ if (files.length > 0) {
58
+ await addJarMnEntries(engineDir, newName, files);
59
+ }
60
+ }
61
+ // Rename FTL localization files in the engine locale directory
62
+ const ftlDirPath = join(engineDir, ftlDir);
63
+ const oldFtlPath = join(ftlDirPath, `${oldName}.ftl`);
64
+ const newFtlPath = join(ftlDirPath, `${newName}.ftl`);
65
+ if (await pathExists(oldFtlPath)) {
66
+ await snapshotFile(journal, oldFtlPath);
67
+ await snapshotFile(journal, newFtlPath);
68
+ const ftlContent = await readText(oldFtlPath);
69
+ await writeText(newFtlPath, ftlContent);
70
+ await removeFile(oldFtlPath);
71
+ }
72
+ // Re-point the locale jar.mn chrome registration when the component is
73
+ // localized. Renaming the .ftl file on disk alone leaves the locale
74
+ // jar.mn pointing at `locale/.../${oldName}.ftl`, so `furnace validate`
75
+ // passes while the engine still carries a stale registration for the
76
+ // now-missing file. Only an existing line is re-pointed: a `sharedFtl`
77
+ // component has no per-component .ftl, and adding a line for one names a
78
+ // file that does not exist (an unmanaged edit `mach build` trips over).
79
+ if (componentConfig.localized) {
80
+ const chromeSubPath = resolveFtlChromeSubPath(ftlDir);
81
+ const localeJarRel = resolveFtlLocaleJarMnPath(ftlDir);
82
+ if (chromeSubPath !== undefined && localeJarRel !== undefined) {
83
+ const localeJarAbs = join(engineDir, localeJarRel);
84
+ if (await pathExists(localeJarAbs)) {
85
+ await snapshotFile(journal, localeJarAbs);
86
+ const removed = await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, oldName, chromeSubPath);
87
+ if (removed && componentConfig.sharedFtl === undefined) {
88
+ await addLocaleFtlJarMnEntry(engineDir, localeJarRel, newName, chromeSubPath);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
@@ -1,5 +1,6 @@
1
+ import type { FurnaceRenameOptions } from '../../types/commands/index.js';
1
2
  /**
2
3
  * Renames a custom or override component atomically: updates directory name,
3
4
  * file names, file contents, furnace.json, and engine registrations.
4
5
  */
5
- export declare function furnaceRenameCommand(projectRoot: string, oldName: string, newName: string): Promise<void>;
6
+ export declare function furnaceRenameCommand(projectRoot: string, oldName: string, newName: string, options?: FurnaceRenameOptions): Promise<void>;
@@ -3,9 +3,8 @@ import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { getProjectPaths, loadConfig } from '../../core/config.js';
5
5
  import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
6
- import { isComponentSourceFile, resolveFtlChromeSubPath, resolveFtlDir, resolveFtlLocaleJarMnPath, tagNameToClassName, } from '../../core/furnace-constants.js';
7
- import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
8
- import { addCustomElementRegistration, addJarMnEntries, addLocaleFtlJarMnEntry, removeCustomElementRegistration, removeJarMnEntries, removeLocaleFtlJarMnEntry, } from '../../core/furnace-registration.js';
6
+ import { isComponentSourceFile, resolveFtlDir, tagNameToClassName, } from '../../core/furnace-constants.js';
7
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
9
8
  import { CUSTOM_ELEMENT_TAG_PATTERN, CUSTOM_ELEMENT_TAG_RULES, } from '../../core/furnace-registration-validate.js';
10
9
  import { createRollbackJournal, recordCreatedDir, snapshotDir, snapshotFile, } from '../../core/furnace-rollback.js';
11
10
  import { getStoriesDir } from '../../core/furnace-stories.js';
@@ -16,7 +15,9 @@ import { copyFile, ensureDir, pathExists, readText, removeDir, removeFile, write
16
15
  import { info, intro, note, outro, warn } from '../../utils/logger.js';
17
16
  import { escapeRegex } from '../../utils/regex.js';
18
17
  import { updateBrowserChromeTestContent } from './rename-browser-test.js';
18
+ import { renderRenamePlan } from './rename-dry-run.js';
19
19
  import { rekeyStateChecksums, renameComponentFileName, updateConfigForCustomRename, updateConfigForOverrideRename, } from './rename-helpers.js';
20
+ import { loadMarkerComment, updateEngineRegistrations } from './rename-registrations.js';
20
21
  import { renameXpcshellTestFiles } from './rename-xpcshell.js';
21
22
  import { browserTestFileName } from './test-file-name.js';
22
23
  /**
@@ -165,7 +166,7 @@ async function performRenameMutations(args) {
165
166
  const { projectRoot, oldName, newName } = args;
166
167
  const oldClassName = tagNameToClassName(oldName);
167
168
  const newClassName = tagNameToClassName(newName);
168
- await runFurnaceMutation(projectRoot, 'rename-rollback', async (ctx) => {
169
+ const mutate = async (ctx) => {
169
170
  const journal = createRollbackJournal();
170
171
  ctx.registerJournal(journal);
171
172
  let newDir = args.newDir;
@@ -245,16 +246,16 @@ async function performRenameMutations(args) {
245
246
  }
246
247
  await writeFurnaceConfig(projectRoot, config);
247
248
  // 3. Update engine registrations (custom components only)
248
- if (isCustom && config.custom[newName]?.register && (await pathExists(args.engineDir))) {
249
- const ftlDir = resolveFtlDir(config.ftlBasePath);
250
- const isLocalized = config.custom[newName].localized;
249
+ const newConfig = config.custom[newName];
250
+ if (isCustom && newConfig?.register && (await pathExists(args.engineDir))) {
251
251
  await updateEngineRegistrations({
252
252
  engineDir: args.engineDir,
253
253
  oldName,
254
254
  newName,
255
255
  newDir,
256
- ftlDir,
257
- isLocalized,
256
+ ftlDir: resolveFtlDir(config.ftlBasePath),
257
+ componentConfig: newConfig,
258
+ markerComment: await loadMarkerComment(projectRoot),
258
259
  journal,
259
260
  });
260
261
  }
@@ -318,68 +319,15 @@ async function performRenameMutations(args) {
318
319
  subject: `rename "${oldName}" → "${newName}"`,
319
320
  });
320
321
  }
321
- });
322
- }
323
- /**
324
- * Re-points the engine-side registrations (customElements.js, jar.mn, and
325
- * the localized `.ftl` entries) from the old tag name to the new one.
326
- *
327
- * @param options - See {@link UpdateEngineRegistrationsOptions}
328
- */
329
- async function updateEngineRegistrations(options) {
330
- const { engineDir, oldName, newName, newDir, ftlDir, isLocalized, journal } = options;
331
- const customElementsPath = join(engineDir, 'toolkit/content/customElements.js');
332
- const jarMnPath = join(engineDir, 'toolkit/content/jar.mn');
333
- if (await pathExists(customElementsPath)) {
334
- await snapshotFile(journal, customElementsPath);
335
- await removeCustomElementRegistration(engineDir, oldName);
336
- await addCustomElementRegistration(engineDir, newName, `chrome://global/content/elements/${newName}.mjs`);
337
- }
338
- if (await pathExists(jarMnPath)) {
339
- await snapshotFile(journal, jarMnPath);
340
- await removeJarMnEntries(engineDir, oldName);
341
- const files = (await readdir(newDir, { withFileTypes: true }))
342
- .filter((e) => e.isFile() && (e.name.endsWith('.mjs') || e.name.endsWith('.css')))
343
- .map((e) => e.name);
344
- if (files.length > 0) {
345
- await addJarMnEntries(engineDir, newName, files);
346
- }
347
- }
348
- // Rename FTL localization files in the engine locale directory
349
- const ftlDirPath = join(engineDir, ftlDir);
350
- const oldFtlPath = join(ftlDirPath, `${oldName}.ftl`);
351
- const newFtlPath = join(ftlDirPath, `${newName}.ftl`);
352
- if (await pathExists(oldFtlPath)) {
353
- await snapshotFile(journal, oldFtlPath);
354
- await snapshotFile(journal, newFtlPath);
355
- const ftlContent = await readText(oldFtlPath);
356
- await writeText(newFtlPath, ftlContent);
357
- await removeFile(oldFtlPath);
358
- }
359
- // Re-wire the locale jar.mn chrome registration when the component is
360
- // localized. `updateEngineRegistrations` renames the .ftl file on disk but
361
- // leaves the locale jar.mn pointing at `locale/.../${oldName}.ftl`, so
362
- // `furnace validate` passes while the engine still carries a stale
363
- // registration for the now-missing file.
364
- if (isLocalized) {
365
- const chromeSubPath = resolveFtlChromeSubPath(ftlDir);
366
- const localeJarRel = resolveFtlLocaleJarMnPath(ftlDir);
367
- if (chromeSubPath !== undefined && localeJarRel !== undefined) {
368
- const localeJarAbs = join(engineDir, localeJarRel);
369
- if (await pathExists(localeJarAbs)) {
370
- await snapshotFile(journal, localeJarAbs);
371
- await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, oldName, chromeSubPath);
372
- await addLocaleFtlJarMnEntry(engineDir, localeJarRel, newName, chromeSubPath);
373
- }
374
- }
375
- }
322
+ };
323
+ await runFurnaceMutation(projectRoot, 'rename-rollback', mutate, waitLockMutationOptions(args.waitLockSeconds));
376
324
  }
377
325
  /**
378
326
  * Renames a custom or override component atomically: updates directory name,
379
327
  * file names, file contents, furnace.json, and engine registrations.
380
328
  */
381
- export async function furnaceRenameCommand(projectRoot, oldName, newName) {
382
- intro('Furnace Rename');
329
+ export async function furnaceRenameCommand(projectRoot, oldName, newName, options = {}) {
330
+ intro(options.dryRun ? 'Furnace Rename (dry run)' : 'Furnace Rename');
383
331
  if (!CUSTOM_ELEMENT_TAG_PATTERN.test(oldName)) {
384
332
  throw new InvalidArgumentError(`Invalid source name "${oldName}": ${CUSTOM_ELEMENT_TAG_RULES}`, 'old-name');
385
333
  }
@@ -419,6 +367,19 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
419
367
  if (await pathExists(newDir)) {
420
368
  throw new FurnaceError(`Target directory already exists: components/${componentDirLabel}/${newName}`, newName);
421
369
  }
370
+ if (options.dryRun) {
371
+ note(await renderRenamePlan({
372
+ config,
373
+ oldName,
374
+ newName,
375
+ oldDir,
376
+ componentDirLabel,
377
+ engineDir: paths.engine,
378
+ markerComment: await loadMarkerComment(projectRoot),
379
+ }), `${oldName} → ${newName}`);
380
+ outro('Dry run complete (no files modified)');
381
+ return;
382
+ }
422
383
  await performRenameMutations({
423
384
  projectRoot,
424
385
  oldName,
@@ -427,6 +388,7 @@ export async function furnaceRenameCommand(projectRoot, oldName, newName) {
427
388
  furnaceConfigPath: furnacePaths.furnaceConfig,
428
389
  furnacePaths,
429
390
  engineDir: paths.engine,
391
+ waitLockSeconds: options.waitLockSeconds,
430
392
  });
431
393
  note(`Component renamed: ${oldName} → ${newName}\n\n` +
432
394
  `Directory: components/${componentDirLabel}/${newName}/\n\n` +
@@ -1,9 +1,14 @@
1
+ import type { FurnaceLockWaitable } from '../../types/commands/index.js';
2
+ /** Options for `furnace scan`. */
3
+ export interface FurnaceScanOptions extends FurnaceLockWaitable {
4
+ /** Search additional Firefox directories beyond the default widgets path. */
5
+ deep?: boolean;
6
+ /** Persist every untracked component into furnace.json's stock section. */
7
+ track?: boolean;
8
+ }
1
9
  /**
2
10
  * Runs the furnace scan command to discover MozLitElement components.
3
11
  * @param projectRoot - Root directory of the project
4
12
  * @param options - Scan options
5
13
  */
6
- export declare function furnaceScanCommand(projectRoot: string, options?: {
7
- deep?: boolean;
8
- track?: boolean;
9
- }): Promise<void>;
14
+ export declare function furnaceScanCommand(projectRoot: string, options?: FurnaceScanOptions): Promise<void>;
@@ -2,7 +2,7 @@
2
2
  import { confirm, multiselect, select } from '@clack/prompts';
3
3
  import { stdioIsInteractive } from '../../core/destructive.js';
4
4
  import { ensureFurnaceConfig, furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
5
- import { completeJournalRollback, runFurnaceMutation } from '../../core/furnace-operation.js';
5
+ import { completeJournalRollback, runFurnaceMutation, waitLockMutationOptions, } from '../../core/furnace-operation.js';
6
6
  import { assertFurnaceEngineReady } from '../../core/furnace-precondition.js';
7
7
  import { createRollbackJournal, snapshotFile } from '../../core/furnace-rollback.js';
8
8
  import { DEEP_SCAN_PATHS, scanWidgetsDirectory } from '../../core/furnace-scanner.js';
@@ -13,8 +13,9 @@ import { furnaceOverrideCommand } from './override.js';
13
13
  * @param components - Components discovered in the engine scan
14
14
  * @param tracked - Existing Furnace tracking map keyed by tag name
15
15
  * @param projectRoot - Root directory of the project
16
+ * @param waitLockSeconds - Resolved `--wait-lock` budget for the furnace lock
16
17
  */
17
- async function promptAddComponents(components, tracked, projectRoot) {
18
+ async function promptAddComponents(components, tracked, projectRoot, waitLockSeconds) {
18
19
  const untrackedComponents = components.filter((c) => !tracked.has(c.tagName));
19
20
  const shouldAdd = await confirm({ message: 'Add components to furnace.json?' });
20
21
  if (isCancel(shouldAdd) || !shouldAdd) {
@@ -36,7 +37,7 @@ async function promptAddComponents(components, tracked, projectRoot) {
36
37
  outro('Scan complete');
37
38
  return;
38
39
  }
39
- await persistStockComponents(projectRoot, selected);
40
+ await persistStockComponents(projectRoot, selected, waitLockSeconds);
40
41
  const addedNames = selected;
41
42
  success(`Added ${addedNames.length} component${addedNames.length === 1 ? '' : 's'} to furnace.json`);
42
43
  // Offer to immediately override one of the just-added stock components.
@@ -54,7 +55,7 @@ async function promptAddComponents(components, tracked, projectRoot) {
54
55
  cancel('Cancelled');
55
56
  return;
56
57
  }
57
- await furnaceOverrideCommand(projectRoot, overrideTarget);
58
+ await furnaceOverrideCommand(projectRoot, overrideTarget, waitLockSeconds !== undefined ? { waitLockSeconds } : {});
58
59
  }
59
60
  /**
60
61
  * Persists discovered component tag names into the `stock` section of
@@ -69,8 +70,8 @@ async function promptAddComponents(components, tracked, projectRoot) {
69
70
  * auto-created cleans up after itself instead of leaving an unwanted default
70
71
  * config behind.
71
72
  */
72
- async function persistStockComponents(projectRoot, names) {
73
- await runFurnaceMutation(projectRoot, 'scan-rollback', async (ctx) => {
73
+ async function persistStockComponents(projectRoot, names, waitLockSeconds) {
74
+ const mutate = async (ctx) => {
74
75
  const journal = createRollbackJournal();
75
76
  ctx.registerJournal(journal);
76
77
  const furnacePaths = getFurnacePaths(projectRoot);
@@ -96,7 +97,8 @@ async function persistStockComponents(projectRoot, names) {
96
97
  subject: `furnace.json update during scan`,
97
98
  });
98
99
  }
99
- });
100
+ };
101
+ await runFurnaceMutation(projectRoot, 'scan-rollback', mutate, waitLockMutationOptions(waitLockSeconds));
100
102
  }
101
103
  /**
102
104
  * Renders a component's discovered capabilities as a display suffix. Shared
@@ -172,14 +174,14 @@ export async function furnaceScanCommand(projectRoot, options = {}) {
172
174
  return;
173
175
  }
174
176
  const untrackedNames = untrackedComponentList.map((c) => c.tagName);
175
- await persistStockComponents(projectRoot, untrackedNames);
177
+ await persistStockComponents(projectRoot, untrackedNames, options.waitLockSeconds);
176
178
  success(`Tracked ${untrackedNames.length} component${untrackedNames.length === 1 ? '' : 's'} in the stock section of furnace.json`);
177
179
  outro('Scan complete');
178
180
  return;
179
181
  }
180
182
  const isInteractive = stdioIsInteractive();
181
183
  if (isInteractive && untrackedCount > 0) {
182
- await promptAddComponents(components, tracked, projectRoot);
184
+ await promptAddComponents(components, tracked, projectRoot, options.waitLockSeconds);
183
185
  return;
184
186
  }
185
187
  if (untrackedCount > 0) {
@@ -3,7 +3,7 @@ import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { loadConfig } from '../../core/config.js';
5
5
  import { applyAllComponents } from '../../core/furnace-apply.js';
6
- import { logApplyResult } from '../../core/furnace-apply-output.js';
6
+ import { formatSkipBreakdown, logApplyResult } from '../../core/furnace-apply-output.js';
7
7
  import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
8
8
  import { reportJsconfigPathsSync } from '../../core/furnace-jsconfig.js';
9
9
  import { runFurnaceMutation, waitLockMutationOptions } from '../../core/furnace-operation.js';
@@ -136,7 +136,8 @@ export async function furnaceSyncCommand(projectRoot, options = {}) {
136
136
  throw new FurnaceError(`${totalFailures} component${totalFailures === 1 ? '' : 's'} failed to apply cleanly`);
137
137
  }
138
138
  await reportJsconfigPathsSync(projectRoot, config, false);
139
- outro(`Sync complete — ${result.applied.length} applied, ${result.skipped.length} skipped`);
139
+ outro(`Sync complete — ${result.applied.length} applied, ${result.skipped.length} skipped` +
140
+ formatSkipBreakdown(result.skipped));
140
141
  }
141
142
  else {
142
143
  outro('Dry run complete');