@hominis/fireforge 0.9.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 (316) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +294 -0
  3. package/README.md +435 -0
  4. package/dist/bin/fireforge.d.ts +10 -0
  5. package/dist/bin/fireforge.js +29 -0
  6. package/dist/src/cli.d.ts +33 -0
  7. package/dist/src/cli.js +180 -0
  8. package/dist/src/commands/bootstrap.d.ts +9 -0
  9. package/dist/src/commands/bootstrap.js +73 -0
  10. package/dist/src/commands/build.d.ts +11 -0
  11. package/dist/src/commands/build.js +102 -0
  12. package/dist/src/commands/config.d.ts +13 -0
  13. package/dist/src/commands/config.js +135 -0
  14. package/dist/src/commands/discard.d.ts +12 -0
  15. package/dist/src/commands/discard.js +84 -0
  16. package/dist/src/commands/doctor.d.ts +18 -0
  17. package/dist/src/commands/doctor.js +356 -0
  18. package/dist/src/commands/download.d.ts +11 -0
  19. package/dist/src/commands/download.js +127 -0
  20. package/dist/src/commands/export-all.d.ts +11 -0
  21. package/dist/src/commands/export-all.js +122 -0
  22. package/dist/src/commands/export-shared.d.ts +48 -0
  23. package/dist/src/commands/export-shared.js +208 -0
  24. package/dist/src/commands/export.d.ts +13 -0
  25. package/dist/src/commands/export.js +178 -0
  26. package/dist/src/commands/furnace/apply.d.ts +7 -0
  27. package/dist/src/commands/furnace/apply.js +80 -0
  28. package/dist/src/commands/furnace/create.d.ts +8 -0
  29. package/dist/src/commands/furnace/create.js +377 -0
  30. package/dist/src/commands/furnace/deploy.d.ts +8 -0
  31. package/dist/src/commands/furnace/deploy.js +338 -0
  32. package/dist/src/commands/furnace/diff.d.ts +7 -0
  33. package/dist/src/commands/furnace/diff.js +119 -0
  34. package/dist/src/commands/furnace/index.d.ts +16 -0
  35. package/dist/src/commands/furnace/index.js +121 -0
  36. package/dist/src/commands/furnace/list.d.ts +5 -0
  37. package/dist/src/commands/furnace/list.js +65 -0
  38. package/dist/src/commands/furnace/override.d.ts +8 -0
  39. package/dist/src/commands/furnace/override.js +188 -0
  40. package/dist/src/commands/furnace/preview.d.ts +7 -0
  41. package/dist/src/commands/furnace/preview.js +96 -0
  42. package/dist/src/commands/furnace/remove.d.ts +8 -0
  43. package/dist/src/commands/furnace/remove.js +159 -0
  44. package/dist/src/commands/furnace/scan.d.ts +5 -0
  45. package/dist/src/commands/furnace/scan.js +112 -0
  46. package/dist/src/commands/furnace/status.d.ts +7 -0
  47. package/dist/src/commands/furnace/status.js +137 -0
  48. package/dist/src/commands/furnace/validate.d.ts +6 -0
  49. package/dist/src/commands/furnace/validate.js +91 -0
  50. package/dist/src/commands/furnace/validation-output.d.ts +7 -0
  51. package/dist/src/commands/furnace/validation-output.js +22 -0
  52. package/dist/src/commands/import.d.ts +11 -0
  53. package/dist/src/commands/import.js +241 -0
  54. package/dist/src/commands/lint.d.ts +10 -0
  55. package/dist/src/commands/lint.js +118 -0
  56. package/dist/src/commands/package.d.ts +11 -0
  57. package/dist/src/commands/package.js +80 -0
  58. package/dist/src/commands/re-export.d.ts +12 -0
  59. package/dist/src/commands/re-export.js +242 -0
  60. package/dist/src/commands/rebase/abort.d.ts +7 -0
  61. package/dist/src/commands/rebase/abort.js +49 -0
  62. package/dist/src/commands/rebase/confirm.d.ts +18 -0
  63. package/dist/src/commands/rebase/confirm.js +33 -0
  64. package/dist/src/commands/rebase/continue.d.ts +7 -0
  65. package/dist/src/commands/rebase/continue.js +81 -0
  66. package/dist/src/commands/rebase/index.d.ts +22 -0
  67. package/dist/src/commands/rebase/index.js +127 -0
  68. package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
  69. package/dist/src/commands/rebase/patch-loop.js +135 -0
  70. package/dist/src/commands/rebase/summary.d.ts +12 -0
  71. package/dist/src/commands/rebase/summary.js +43 -0
  72. package/dist/src/commands/rebase.d.ts +4 -0
  73. package/dist/src/commands/rebase.js +6 -0
  74. package/dist/src/commands/register.d.ts +13 -0
  75. package/dist/src/commands/register.js +67 -0
  76. package/dist/src/commands/reset.d.ts +11 -0
  77. package/dist/src/commands/reset.js +83 -0
  78. package/dist/src/commands/resolve.d.ts +9 -0
  79. package/dist/src/commands/resolve.js +124 -0
  80. package/dist/src/commands/run.d.ts +9 -0
  81. package/dist/src/commands/run.js +91 -0
  82. package/dist/src/commands/setup-support.d.ts +23 -0
  83. package/dist/src/commands/setup-support.js +310 -0
  84. package/dist/src/commands/setup.d.ts +11 -0
  85. package/dist/src/commands/setup.js +94 -0
  86. package/dist/src/commands/status.d.ts +11 -0
  87. package/dist/src/commands/status.js +268 -0
  88. package/dist/src/commands/test.d.ts +12 -0
  89. package/dist/src/commands/test.js +182 -0
  90. package/dist/src/commands/token-coverage.d.ts +5 -0
  91. package/dist/src/commands/token-coverage.js +57 -0
  92. package/dist/src/commands/token.d.ts +14 -0
  93. package/dist/src/commands/token.js +121 -0
  94. package/dist/src/commands/watch.d.ts +9 -0
  95. package/dist/src/commands/watch.js +112 -0
  96. package/dist/src/commands/wire.d.ts +13 -0
  97. package/dist/src/commands/wire.js +149 -0
  98. package/dist/src/core/ast-utils.d.ts +47 -0
  99. package/dist/src/core/ast-utils.js +57 -0
  100. package/dist/src/core/brand-validation.d.ts +7 -0
  101. package/dist/src/core/brand-validation.js +15 -0
  102. package/dist/src/core/branding.d.ts +49 -0
  103. package/dist/src/core/branding.js +229 -0
  104. package/dist/src/core/browser-wire.d.ts +40 -0
  105. package/dist/src/core/browser-wire.js +66 -0
  106. package/dist/src/core/build-prepare.d.ts +25 -0
  107. package/dist/src/core/build-prepare.js +93 -0
  108. package/dist/src/core/config-mutate.d.ts +15 -0
  109. package/dist/src/core/config-mutate.js +51 -0
  110. package/dist/src/core/config-paths.d.ts +28 -0
  111. package/dist/src/core/config-paths.js +65 -0
  112. package/dist/src/core/config-state.d.ts +28 -0
  113. package/dist/src/core/config-state.js +152 -0
  114. package/dist/src/core/config-validate.d.ts +11 -0
  115. package/dist/src/core/config-validate.js +141 -0
  116. package/dist/src/core/config.d.ts +39 -0
  117. package/dist/src/core/config.js +70 -0
  118. package/dist/src/core/file-lock.d.ts +11 -0
  119. package/dist/src/core/file-lock.js +80 -0
  120. package/dist/src/core/firefox-archive.d.ts +40 -0
  121. package/dist/src/core/firefox-archive.js +63 -0
  122. package/dist/src/core/firefox-cache.d.ts +23 -0
  123. package/dist/src/core/firefox-cache.js +134 -0
  124. package/dist/src/core/firefox-download.d.ts +21 -0
  125. package/dist/src/core/firefox-download.js +129 -0
  126. package/dist/src/core/firefox-extract.d.ts +21 -0
  127. package/dist/src/core/firefox-extract.js +53 -0
  128. package/dist/src/core/firefox.d.ts +34 -0
  129. package/dist/src/core/firefox.js +78 -0
  130. package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
  131. package/dist/src/core/furnace-apply-helpers.js +244 -0
  132. package/dist/src/core/furnace-apply.d.ts +16 -0
  133. package/dist/src/core/furnace-apply.js +147 -0
  134. package/dist/src/core/furnace-config.d.ts +94 -0
  135. package/dist/src/core/furnace-config.js +372 -0
  136. package/dist/src/core/furnace-constants.d.ts +4 -0
  137. package/dist/src/core/furnace-constants.js +6 -0
  138. package/dist/src/core/furnace-registration-ast.d.ts +24 -0
  139. package/dist/src/core/furnace-registration-ast.js +218 -0
  140. package/dist/src/core/furnace-registration-remove.d.ts +14 -0
  141. package/dist/src/core/furnace-registration-remove.js +89 -0
  142. package/dist/src/core/furnace-registration-validate.d.ts +20 -0
  143. package/dist/src/core/furnace-registration-validate.js +40 -0
  144. package/dist/src/core/furnace-registration.d.ts +29 -0
  145. package/dist/src/core/furnace-registration.js +96 -0
  146. package/dist/src/core/furnace-rollback.d.ts +20 -0
  147. package/dist/src/core/furnace-rollback.js +66 -0
  148. package/dist/src/core/furnace-scanner.d.ts +40 -0
  149. package/dist/src/core/furnace-scanner.js +143 -0
  150. package/dist/src/core/furnace-stories.d.ts +37 -0
  151. package/dist/src/core/furnace-stories.js +185 -0
  152. package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
  153. package/dist/src/core/furnace-validate-accessibility.js +32 -0
  154. package/dist/src/core/furnace-validate-checks.d.ts +4 -0
  155. package/dist/src/core/furnace-validate-checks.js +7 -0
  156. package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
  157. package/dist/src/core/furnace-validate-compatibility.js +57 -0
  158. package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
  159. package/dist/src/core/furnace-validate-helpers.js +129 -0
  160. package/dist/src/core/furnace-validate-registration.d.ts +37 -0
  161. package/dist/src/core/furnace-validate-registration.js +220 -0
  162. package/dist/src/core/furnace-validate-structure.d.ts +6 -0
  163. package/dist/src/core/furnace-validate-structure.js +66 -0
  164. package/dist/src/core/furnace-validate.d.ts +16 -0
  165. package/dist/src/core/furnace-validate.js +103 -0
  166. package/dist/src/core/git-base.d.ts +47 -0
  167. package/dist/src/core/git-base.js +50 -0
  168. package/dist/src/core/git-diff.d.ts +63 -0
  169. package/dist/src/core/git-diff.js +246 -0
  170. package/dist/src/core/git-file-ops.d.ts +65 -0
  171. package/dist/src/core/git-file-ops.js +141 -0
  172. package/dist/src/core/git-status.d.ts +65 -0
  173. package/dist/src/core/git-status.js +163 -0
  174. package/dist/src/core/git.d.ts +113 -0
  175. package/dist/src/core/git.js +363 -0
  176. package/dist/src/core/license-headers.d.ts +36 -0
  177. package/dist/src/core/license-headers.js +83 -0
  178. package/dist/src/core/mach-build-artifacts.d.ts +29 -0
  179. package/dist/src/core/mach-build-artifacts.js +117 -0
  180. package/dist/src/core/mach-mozconfig.d.ts +17 -0
  181. package/dist/src/core/mach-mozconfig.js +50 -0
  182. package/dist/src/core/mach-python.d.ts +16 -0
  183. package/dist/src/core/mach-python.js +126 -0
  184. package/dist/src/core/mach.d.ts +106 -0
  185. package/dist/src/core/mach.js +166 -0
  186. package/dist/src/core/manifest-helpers.d.ts +25 -0
  187. package/dist/src/core/manifest-helpers.js +96 -0
  188. package/dist/src/core/manifest-register.d.ts +30 -0
  189. package/dist/src/core/manifest-register.js +65 -0
  190. package/dist/src/core/manifest-rules.d.ts +39 -0
  191. package/dist/src/core/manifest-rules.js +151 -0
  192. package/dist/src/core/manifest-tokenizers.d.ts +34 -0
  193. package/dist/src/core/manifest-tokenizers.js +84 -0
  194. package/dist/src/core/parser-fallback.d.ts +36 -0
  195. package/dist/src/core/parser-fallback.js +43 -0
  196. package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
  197. package/dist/src/core/patch-apply-fuzz.js +70 -0
  198. package/dist/src/core/patch-apply.d.ts +46 -0
  199. package/dist/src/core/patch-apply.js +235 -0
  200. package/dist/src/core/patch-export.d.ts +99 -0
  201. package/dist/src/core/patch-export.js +314 -0
  202. package/dist/src/core/patch-files.d.ts +11 -0
  203. package/dist/src/core/patch-files.js +51 -0
  204. package/dist/src/core/patch-lint.d.ts +72 -0
  205. package/dist/src/core/patch-lint.js +403 -0
  206. package/dist/src/core/patch-lock.d.ts +8 -0
  207. package/dist/src/core/patch-lock.js +29 -0
  208. package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
  209. package/dist/src/core/patch-manifest-consistency.js +135 -0
  210. package/dist/src/core/patch-manifest-io.d.ts +36 -0
  211. package/dist/src/core/patch-manifest-io.js +77 -0
  212. package/dist/src/core/patch-manifest-query.d.ts +48 -0
  213. package/dist/src/core/patch-manifest-query.js +124 -0
  214. package/dist/src/core/patch-manifest-validate.d.ts +22 -0
  215. package/dist/src/core/patch-manifest-validate.js +72 -0
  216. package/dist/src/core/patch-manifest.d.ts +11 -0
  217. package/dist/src/core/patch-manifest.js +12 -0
  218. package/dist/src/core/patch-parse.d.ts +43 -0
  219. package/dist/src/core/patch-parse.js +143 -0
  220. package/dist/src/core/patch-transform.d.ts +21 -0
  221. package/dist/src/core/patch-transform.js +138 -0
  222. package/dist/src/core/rebase-session.d.ts +47 -0
  223. package/dist/src/core/rebase-session.js +65 -0
  224. package/dist/src/core/register-browser-content.d.ts +11 -0
  225. package/dist/src/core/register-browser-content.js +116 -0
  226. package/dist/src/core/register-module.d.ts +11 -0
  227. package/dist/src/core/register-module.js +76 -0
  228. package/dist/src/core/register-shared-css.d.ts +11 -0
  229. package/dist/src/core/register-shared-css.js +117 -0
  230. package/dist/src/core/register-test-manifest.d.ts +18 -0
  231. package/dist/src/core/register-test-manifest.js +99 -0
  232. package/dist/src/core/state-file.d.ts +4 -0
  233. package/dist/src/core/state-file.js +25 -0
  234. package/dist/src/core/token-coverage.d.ts +12 -0
  235. package/dist/src/core/token-coverage.js +74 -0
  236. package/dist/src/core/token-manager.d.ts +55 -0
  237. package/dist/src/core/token-manager.js +387 -0
  238. package/dist/src/core/wire-destroy.d.ts +21 -0
  239. package/dist/src/core/wire-destroy.js +103 -0
  240. package/dist/src/core/wire-dom-fragment.d.ts +23 -0
  241. package/dist/src/core/wire-dom-fragment.js +129 -0
  242. package/dist/src/core/wire-init.d.ts +23 -0
  243. package/dist/src/core/wire-init.js +201 -0
  244. package/dist/src/core/wire-subscript.d.ts +20 -0
  245. package/dist/src/core/wire-subscript.js +134 -0
  246. package/dist/src/core/wire-targets.d.ts +7 -0
  247. package/dist/src/core/wire-targets.js +9 -0
  248. package/dist/src/core/wire-utils.d.ts +88 -0
  249. package/dist/src/core/wire-utils.js +279 -0
  250. package/dist/src/errors/base.d.ts +60 -0
  251. package/dist/src/errors/base.js +87 -0
  252. package/dist/src/errors/build.d.ts +52 -0
  253. package/dist/src/errors/build.js +114 -0
  254. package/dist/src/errors/codes.d.ts +29 -0
  255. package/dist/src/errors/codes.js +30 -0
  256. package/dist/src/errors/config.d.ts +31 -0
  257. package/dist/src/errors/config.js +61 -0
  258. package/dist/src/errors/download.d.ts +42 -0
  259. package/dist/src/errors/download.js +95 -0
  260. package/dist/src/errors/furnace.d.ts +10 -0
  261. package/dist/src/errors/furnace.js +22 -0
  262. package/dist/src/errors/git.d.ts +41 -0
  263. package/dist/src/errors/git.js +99 -0
  264. package/dist/src/errors/patch.d.ts +10 -0
  265. package/dist/src/errors/patch.js +26 -0
  266. package/dist/src/errors/rebase.d.ts +20 -0
  267. package/dist/src/errors/rebase.js +30 -0
  268. package/dist/src/index.d.ts +21 -0
  269. package/dist/src/index.js +21 -0
  270. package/dist/src/types/cli.d.ts +14 -0
  271. package/dist/src/types/cli.js +2 -0
  272. package/dist/src/types/commands/index.d.ts +6 -0
  273. package/dist/src/types/commands/index.js +6 -0
  274. package/dist/src/types/commands/options.d.ts +239 -0
  275. package/dist/src/types/commands/options.js +6 -0
  276. package/dist/src/types/commands/patches.d.ts +89 -0
  277. package/dist/src/types/commands/patches.js +6 -0
  278. package/dist/src/types/commands/project.d.ts +71 -0
  279. package/dist/src/types/commands/project.js +6 -0
  280. package/dist/src/types/config.d.ts +101 -0
  281. package/dist/src/types/config.js +2 -0
  282. package/dist/src/types/furnace.d.ts +158 -0
  283. package/dist/src/types/furnace.js +2 -0
  284. package/dist/src/types/index.d.ts +6 -0
  285. package/dist/src/types/index.js +6 -0
  286. package/dist/src/utils/errors.d.ts +2 -0
  287. package/dist/src/utils/errors.js +15 -0
  288. package/dist/src/utils/fs.d.ts +72 -0
  289. package/dist/src/utils/fs.js +179 -0
  290. package/dist/src/utils/logger.d.ts +58 -0
  291. package/dist/src/utils/logger.js +120 -0
  292. package/dist/src/utils/options.d.ts +8 -0
  293. package/dist/src/utils/options.js +16 -0
  294. package/dist/src/utils/package-root.d.ts +10 -0
  295. package/dist/src/utils/package-root.js +53 -0
  296. package/dist/src/utils/parse.d.ts +110 -0
  297. package/dist/src/utils/parse.js +200 -0
  298. package/dist/src/utils/paths.d.ts +10 -0
  299. package/dist/src/utils/paths.js +43 -0
  300. package/dist/src/utils/platform.d.ts +38 -0
  301. package/dist/src/utils/platform.js +56 -0
  302. package/dist/src/utils/process.d.ts +80 -0
  303. package/dist/src/utils/process.js +188 -0
  304. package/dist/src/utils/regex.d.ts +24 -0
  305. package/dist/src/utils/regex.js +40 -0
  306. package/dist/src/utils/validation.d.ts +133 -0
  307. package/dist/src/utils/validation.js +250 -0
  308. package/package.json +106 -0
  309. package/templates/configs/common.mozconfig +24 -0
  310. package/templates/configs/darwin.mozconfig +10 -0
  311. package/templates/configs/linux.mozconfig +12 -0
  312. package/templates/configs/win32.mozconfig +14 -0
  313. package/templates/licenses/0BSD.md +14 -0
  314. package/templates/licenses/EUPL-1.2.md +294 -0
  315. package/templates/licenses/GPL-2.0-or-later.md +339 -0
  316. package/templates/licenses/MPL-2.0.md +383 -0
@@ -0,0 +1,338 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { getProjectPaths } from '../../core/config.js';
4
+ import { applyAllComponents, applyCustomComponent, applyOverrideComponent, computeComponentChecksums, prefixChecksums, } from '../../core/furnace-apply.js';
5
+ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, updateFurnaceState, } from '../../core/furnace-config.js';
6
+ import { createRollbackJournal, restoreRollbackJournalOrThrow, } from '../../core/furnace-rollback.js';
7
+ import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
8
+ import { FurnaceError } from '../../errors/furnace.js';
9
+ import { toError } from '../../utils/errors.js';
10
+ import { pathExists } from '../../utils/fs.js';
11
+ import { error, info, intro, note, outro, spinner, success, warn } from '../../utils/logger.js';
12
+ import { displayValidationIssues } from './validation-output.js';
13
+ /**
14
+ * Builds the final deploy failure summary from apply and validation error counts.
15
+ * @param applyErrors - Number of component application failures
16
+ * @param validationErrors - Number of validation failures
17
+ * @param isDryRun - Whether deploy was running in dry-run mode
18
+ * @returns User-facing deploy summary message
19
+ */
20
+ function buildDeployFailureMessage(applyErrors, validationErrors, isDryRun) {
21
+ const mode = isDryRun ? 'Dry run' : 'Deploy';
22
+ if (applyErrors > 0 && validationErrors > 0) {
23
+ return `${mode} completed with ${applyErrors} apply error(s) and ${validationErrors} validation error(s).`;
24
+ }
25
+ if (applyErrors > 0) {
26
+ return `${mode} completed with ${applyErrors} apply error(s).`;
27
+ }
28
+ return `${mode} completed with ${validationErrors} validation error(s).`;
29
+ }
30
+ function getStepFailureCount(result) {
31
+ return result.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
32
+ }
33
+ function getFailedComponentNames(result) {
34
+ const failed = new Set(result.errors.map((entry) => entry.name));
35
+ for (const applied of result.applied) {
36
+ if ((applied.stepErrors?.length ?? 0) > 0) {
37
+ failed.add(applied.name);
38
+ }
39
+ }
40
+ return failed;
41
+ }
42
+ /**
43
+ * Persists checksum state for a successfully applied named component.
44
+ * @param projectRoot - Root directory of the project
45
+ * @param appliedEntry - Applied component descriptor from deploy
46
+ * @param furnacePaths - Resolved Furnace workspace paths
47
+ */
48
+ async function persistSingleComponentState(projectRoot, appliedEntry, furnacePaths) {
49
+ const componentDir = appliedEntry.type === 'override'
50
+ ? join(furnacePaths.overridesDir, appliedEntry.name)
51
+ : join(furnacePaths.customDir, appliedEntry.name);
52
+ const checksums = await computeComponentChecksums(componentDir);
53
+ const prefixed = prefixChecksums(checksums, appliedEntry.type, appliedEntry.name);
54
+ await updateFurnaceState(projectRoot, (current) => ({
55
+ ...current,
56
+ appliedChecksums: { ...(current.appliedChecksums ?? {}), ...prefixed },
57
+ lastApply: new Date().toISOString(),
58
+ }));
59
+ }
60
+ /**
61
+ * Applies a single named override or custom component in targeted deploy mode.
62
+ * @param name - Component name to apply
63
+ * @param engineDir - Firefox engine source directory
64
+ * @param furnacePaths - Resolved Furnace workspace paths
65
+ * @param config - Loaded Furnace configuration
66
+ * @param isDryRun - Whether file writes should be skipped
67
+ * @returns Apply result for the named component, or `stock` for stock-only entries
68
+ */
69
+ async function applyNamedComponent(name, engineDir, furnacePaths, config, isDryRun) {
70
+ const rollbackJournal = isDryRun ? undefined : createRollbackJournal();
71
+ const result = {
72
+ applied: [],
73
+ skipped: [],
74
+ errors: [],
75
+ actions: [],
76
+ };
77
+ const overrideConfig = config.overrides[name];
78
+ const customConfig = config.custom[name];
79
+ if (overrideConfig) {
80
+ const componentDir = join(furnacePaths.overridesDir, name);
81
+ if (!(await pathExists(componentDir))) {
82
+ throw new FurnaceError(`Component directory not found: components/overrides/${name}`, name);
83
+ }
84
+ try {
85
+ const { affectedPaths: filesAffected, actions } = await applyOverrideComponent(engineDir, name, componentDir, overrideConfig, isDryRun, rollbackJournal);
86
+ if (isDryRun && actions) {
87
+ result.actions = actions;
88
+ }
89
+ result.applied.push({ name, type: 'override', filesAffected });
90
+ }
91
+ catch (error) {
92
+ result.errors.push({ name, error: toError(error).message });
93
+ }
94
+ if (!isDryRun && result.errors.length > 0 && rollbackJournal) {
95
+ await restoreRollbackJournalOrThrow(rollbackJournal, `Furnace deploy failed for "${name}"`);
96
+ }
97
+ return result;
98
+ }
99
+ if (customConfig) {
100
+ const componentDir = join(furnacePaths.customDir, name);
101
+ if (!(await pathExists(componentDir))) {
102
+ throw new FurnaceError(`Component directory not found: components/custom/${name}`, name);
103
+ }
104
+ try {
105
+ const { affectedPaths: filesAffected, stepErrors, actions, } = await applyCustomComponent(engineDir, name, componentDir, customConfig, isDryRun, rollbackJournal);
106
+ if (isDryRun && actions) {
107
+ result.actions = actions;
108
+ }
109
+ result.applied.push({
110
+ name,
111
+ type: 'custom',
112
+ filesAffected,
113
+ ...(stepErrors.length > 0 ? { stepErrors } : {}),
114
+ });
115
+ }
116
+ catch (error) {
117
+ result.errors.push({ name, error: toError(error).message });
118
+ }
119
+ if (!isDryRun && getStepFailureCount(result) > 0 && rollbackJournal) {
120
+ await restoreRollbackJournalOrThrow(rollbackJournal, `Furnace deploy failed for "${name}"`);
121
+ }
122
+ return result;
123
+ }
124
+ if (config.stock.includes(name)) {
125
+ return 'stock';
126
+ }
127
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
128
+ }
129
+ /**
130
+ * Resolves the validation target for a single named component.
131
+ * @param name - Component name to validate
132
+ * @param config - Loaded Furnace configuration
133
+ * @param furnacePaths - Resolved Furnace workspace paths
134
+ * @returns Validation target details, or `stock` for stock-only entries
135
+ */
136
+ function resolveNamedValidationTarget(name, config, furnacePaths) {
137
+ if (name in config.overrides) {
138
+ return {
139
+ type: 'override',
140
+ componentDir: join(furnacePaths.overridesDir, name),
141
+ };
142
+ }
143
+ if (name in config.custom) {
144
+ return {
145
+ type: 'custom',
146
+ componentDir: join(furnacePaths.customDir, name),
147
+ };
148
+ }
149
+ if (config.stock.includes(name)) {
150
+ return 'stock';
151
+ }
152
+ throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
153
+ }
154
+ /**
155
+ * Prints the deploy summary after apply and validation complete.
156
+ * @param result - Aggregate apply result
157
+ * @param totalErrors - Total validation errors encountered
158
+ * @param totalWarnings - Total validation warnings encountered
159
+ * @param componentCount - Number of components considered during deploy
160
+ * @param skippedValidationCount - Number of components skipped from validation
161
+ * @param isDryRun - Whether deploy was running in dry-run mode
162
+ */
163
+ function printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun) {
164
+ const appliedCount = result.applied.length;
165
+ const skippedCount = result.skipped.length;
166
+ const applyErrors = result.errors.length + getStepFailureCount(result);
167
+ const stepErrorCount = result.applied.reduce((sum, a) => sum + (a.stepErrors?.length ?? 0), 0);
168
+ if (isDryRun) {
169
+ note(`Would apply ${appliedCount} component(s)\n` +
170
+ `${result.actions?.length ?? 0} planned action(s)\n` +
171
+ `${applyErrors} apply error(s)\n` +
172
+ `${totalErrors} validation error(s), ${totalWarnings} validation warning(s) across ${componentCount} validated component(s)` +
173
+ (skippedValidationCount > 0
174
+ ? `\nSkipped validation for ${skippedValidationCount} component(s) with apply errors`
175
+ : ''), 'Dry Run Summary');
176
+ }
177
+ else {
178
+ note(`Applied ${appliedCount}, skipped ${skippedCount}\n` +
179
+ `${applyErrors} apply error(s)` +
180
+ (stepErrorCount > 0 ? `, ${stepErrorCount} registration step error(s)` : '') +
181
+ `\n` +
182
+ `${totalErrors} validation error(s), ${totalWarnings} validation warning(s) across ${componentCount} validated component(s)` +
183
+ (skippedValidationCount > 0
184
+ ? `\nSkipped validation for ${skippedValidationCount} component(s) with apply errors`
185
+ : ''), 'Deploy Summary');
186
+ }
187
+ const totalProblems = applyErrors + totalErrors;
188
+ if (totalProblems > 0) {
189
+ throw new FurnaceError(buildDeployFailureMessage(applyErrors, totalErrors, isDryRun));
190
+ }
191
+ outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
192
+ }
193
+ function logApplyResult(result, isDryRun) {
194
+ if (isDryRun && result.actions && result.actions.length > 0) {
195
+ info('Planned actions:');
196
+ for (const action of result.actions) {
197
+ info(` [${action.action}] ${action.component}: ${action.description}`);
198
+ }
199
+ }
200
+ else if (isDryRun) {
201
+ info('No actions would be performed.');
202
+ }
203
+ else {
204
+ for (const applied of result.applied) {
205
+ success(`${applied.name} (${applied.type}) → ${applied.filesAffected.length} files`);
206
+ }
207
+ for (const skipped of result.skipped) {
208
+ info(`${skipped.name} — ${skipped.reason}`);
209
+ }
210
+ for (const applied of result.applied) {
211
+ if (applied.stepErrors && applied.stepErrors.length > 0) {
212
+ for (const stepErr of applied.stepErrors) {
213
+ warn(`${applied.name}: [${stepErr.step}] ${stepErr.error}`);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ for (const err of result.errors) {
219
+ error(`${err.name} — ${err.error}`);
220
+ }
221
+ }
222
+ /**
223
+ * Runs the furnace deploy command: apply components then validate in one step.
224
+ * @param projectRoot - Root directory of the project
225
+ * @param name - Optional component name to deploy (deploys all if omitted)
226
+ * @param options - Command options
227
+ */
228
+ export async function furnaceDeployCommand(projectRoot, name, options = {}) {
229
+ const isDryRun = options.dryRun ?? false;
230
+ intro(isDryRun ? 'Furnace Deploy (dry run)' : 'Furnace Deploy');
231
+ // Verify engine exists
232
+ const paths = getProjectPaths(projectRoot);
233
+ if (!(await pathExists(paths.engine))) {
234
+ throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
235
+ }
236
+ // Load furnace config
237
+ if (!(await furnaceConfigExists(projectRoot))) {
238
+ throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
239
+ }
240
+ const config = await loadFurnaceConfig(projectRoot);
241
+ const furnacePaths = getFurnacePaths(projectRoot);
242
+ const overrideCount = Object.keys(config.overrides).length;
243
+ const customCount = Object.keys(config.custom).length;
244
+ if (overrideCount === 0 && customCount === 0) {
245
+ info('No components to deploy.');
246
+ outro('Done');
247
+ return;
248
+ }
249
+ // --- Step 1: Apply ---
250
+ const applySpinner = spinner(isDryRun ? 'Calculating planned actions...' : 'Applying components to engine...');
251
+ let result;
252
+ if (name) {
253
+ const namedApplyResult = await applyNamedComponent(name, paths.engine, furnacePaths, config, isDryRun);
254
+ if (namedApplyResult === 'stock') {
255
+ applySpinner.stop('Apply skipped');
256
+ info(`"${name}" is a stock component. Stock components are not applied locally.`);
257
+ outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
258
+ return;
259
+ }
260
+ result = namedApplyResult;
261
+ // Persist Furnace state for the deployed component so status/change
262
+ // detection stays accurate after single-component deploys.
263
+ if (!isDryRun &&
264
+ result.errors.length === 0 &&
265
+ getStepFailureCount(result) === 0 &&
266
+ result.applied.length > 0) {
267
+ const applied = result.applied[0];
268
+ await persistSingleComponentState(projectRoot, applied, furnacePaths);
269
+ }
270
+ }
271
+ else {
272
+ result = await applyAllComponents(projectRoot, isDryRun);
273
+ }
274
+ applySpinner.stop(isDryRun ? 'Planned actions calculated' : 'Components applied');
275
+ logApplyResult(result, isDryRun);
276
+ // --- Step 2: Validate (read-only, runs even in dry-run) ---
277
+ const validateSpinner = spinner('Validating components...');
278
+ const failedComponents = getFailedComponentNames(result);
279
+ let totalErrors = 0;
280
+ let totalWarnings = 0;
281
+ let componentCount = 0;
282
+ let skippedValidationCount = 0;
283
+ if (name && failedComponents.has(name)) {
284
+ skippedValidationCount = 1;
285
+ validateSpinner.stop('Validation skipped');
286
+ warn(`Skipping validation for ${name} because apply failed.`);
287
+ }
288
+ else if (name) {
289
+ const target = resolveNamedValidationTarget(name, config, furnacePaths);
290
+ if (target === 'stock') {
291
+ validateSpinner.stop('Validation skipped');
292
+ info(`"${name}" is a stock component. Stock components are not validated locally.`);
293
+ outro(isDryRun ? 'Dry run complete' : 'Deploy complete');
294
+ return;
295
+ }
296
+ if (!(await pathExists(target.componentDir))) {
297
+ validateSpinner.stop('Validation failed');
298
+ throw new FurnaceError(`Component directory not found for "${name}".`, name);
299
+ }
300
+ const issues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
301
+ componentCount = 1;
302
+ validateSpinner.stop('Validation complete');
303
+ if (issues.length === 0) {
304
+ success(`${name} — all checks passed`);
305
+ }
306
+ else {
307
+ const [errors, warnings] = displayValidationIssues(issues);
308
+ totalErrors += errors;
309
+ totalWarnings += warnings;
310
+ }
311
+ }
312
+ else {
313
+ // Validate all components
314
+ const results = await validateAllComponents(projectRoot);
315
+ validateSpinner.stop('Validation complete');
316
+ for (const [componentName, issues] of results) {
317
+ if (failedComponents.has(componentName)) {
318
+ skippedValidationCount++;
319
+ continue;
320
+ }
321
+ componentCount++;
322
+ if (issues.length === 0) {
323
+ success(`${componentName} — all checks passed`);
324
+ }
325
+ else {
326
+ const [errors, warnings] = displayValidationIssues(issues);
327
+ totalErrors += errors;
328
+ totalWarnings += warnings;
329
+ }
330
+ }
331
+ if (skippedValidationCount > 0) {
332
+ warn(`Skipped validation for ${skippedValidationCount} component(s) because their apply step failed.`);
333
+ }
334
+ }
335
+ // --- Step 3: Summary ---
336
+ printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun);
337
+ }
338
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Runs the furnace diff command to show changes vs the Firefox original.
3
+ * Only works for override components.
4
+ * @param projectRoot - Root directory of the project
5
+ * @param name - Component name to diff
6
+ */
7
+ export declare function furnaceDiffCommand(projectRoot: string, name: string): Promise<void>;
@@ -0,0 +1,119 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { getProjectPaths } from '../../core/config.js';
5
+ import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
6
+ import { FurnaceError } from '../../errors/furnace.js';
7
+ import { pathExists, readText } from '../../utils/fs.js';
8
+ import { formatErrorText, formatSuccessText, info, intro, outro } from '../../utils/logger.js';
9
+ /**
10
+ * Computes a simplified line-level diff between two strings.
11
+ * Note: Uses a prefix/suffix matching algorithm that may combine
12
+ * multiple scattered changes into a single change region.
13
+ * For complex diffs with interleaved changes, the output may not
14
+ * be optimal.
15
+ */
16
+ function lineDiff(original, modified) {
17
+ const oldLines = original.split('\n');
18
+ const newLines = modified.split('\n');
19
+ // Remove trailing empty element from trailing newline
20
+ if (oldLines[oldLines.length - 1] === '')
21
+ oldLines.pop();
22
+ if (newLines[newLines.length - 1] === '')
23
+ newLines.pop();
24
+ const output = [];
25
+ // Simple diff: find common prefix, common suffix, then show changed region
26
+ let firstDiff = 0;
27
+ while (firstDiff < oldLines.length && firstDiff < newLines.length) {
28
+ if (oldLines[firstDiff] !== newLines[firstDiff])
29
+ break;
30
+ firstDiff++;
31
+ }
32
+ let lastOldDiff = oldLines.length - 1;
33
+ let lastNewDiff = newLines.length - 1;
34
+ while (lastOldDiff > firstDiff && lastNewDiff > firstDiff) {
35
+ if (oldLines[lastOldDiff] !== newLines[lastNewDiff])
36
+ break;
37
+ lastOldDiff--;
38
+ lastNewDiff--;
39
+ }
40
+ // Context lines before the change
41
+ const contextLines = 3;
42
+ const contextStart = Math.max(0, firstDiff - contextLines);
43
+ const contextEndOld = Math.min(oldLines.length - 1, lastOldDiff + contextLines);
44
+ const contextEndNew = Math.min(newLines.length - 1, lastNewDiff + contextLines);
45
+ // Leading context
46
+ for (let i = contextStart; i < firstDiff; i++) {
47
+ output.push(` ${oldLines[i]}`);
48
+ }
49
+ // Removed lines
50
+ for (let i = firstDiff; i <= lastOldDiff; i++) {
51
+ output.push(formatErrorText(`- ${oldLines[i]}`));
52
+ }
53
+ // Added lines
54
+ for (let i = firstDiff; i <= lastNewDiff; i++) {
55
+ output.push(formatSuccessText(`+ ${newLines[i]}`));
56
+ }
57
+ // Trailing context
58
+ const trailingStart = Math.max(lastOldDiff + 1, lastNewDiff + 1);
59
+ const trailingEnd = Math.max(contextEndOld, contextEndNew);
60
+ // Use the new lines for trailing context (they should match old lines here)
61
+ for (let i = trailingStart; i <= trailingEnd && i < newLines.length; i++) {
62
+ output.push(` ${newLines[i]}`);
63
+ }
64
+ return output;
65
+ }
66
+ /**
67
+ * Runs the furnace diff command to show changes vs the Firefox original.
68
+ * Only works for override components.
69
+ * @param projectRoot - Root directory of the project
70
+ * @param name - Component name to diff
71
+ */
72
+ export async function furnaceDiffCommand(projectRoot, name) {
73
+ intro('Furnace Diff');
74
+ const config = await loadFurnaceConfig(projectRoot);
75
+ const paths = getProjectPaths(projectRoot);
76
+ const furnacePaths = getFurnacePaths(projectRoot);
77
+ // Verify the component is an override
78
+ const overrideConfig = config.overrides[name];
79
+ if (!overrideConfig) {
80
+ throw new FurnaceError(`"${name}" is not an override component. The diff command only works for overrides.`, name);
81
+ }
82
+ const overrideDir = join(furnacePaths.overridesDir, name);
83
+ if (!(await pathExists(overrideDir))) {
84
+ throw new FurnaceError(`Override directory not found: components/overrides/${name}`, name);
85
+ }
86
+ const entries = await readdir(overrideDir, { withFileTypes: true });
87
+ let hasDifferences = false;
88
+ for (const entry of entries) {
89
+ if (!entry.isFile())
90
+ continue;
91
+ if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
92
+ continue;
93
+ const originalPath = join(paths.engine, overrideConfig.basePath, entry.name);
94
+ const modifiedPath = join(overrideDir, entry.name);
95
+ if (!(await pathExists(originalPath))) {
96
+ info(`${entry.name}: original not found in engine (new file)`);
97
+ hasDifferences = true;
98
+ continue;
99
+ }
100
+ const originalContent = await readText(originalPath);
101
+ const modifiedContent = await readText(modifiedPath);
102
+ if (originalContent === modifiedContent) {
103
+ continue;
104
+ }
105
+ hasDifferences = true;
106
+ info(`--- ${overrideConfig.basePath}/${entry.name}`);
107
+ info(`+++ components/overrides/${name}/${entry.name}`);
108
+ const diffLines = lineDiff(originalContent, modifiedContent);
109
+ for (const line of diffLines) {
110
+ info(line);
111
+ }
112
+ info('');
113
+ }
114
+ if (!hasDifferences) {
115
+ info('No modifications found');
116
+ }
117
+ outro('Diff complete');
118
+ }
119
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1,16 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../../types/cli.js';
3
+ import { furnaceApplyCommand } from './apply.js';
4
+ import { furnaceCreateCommand } from './create.js';
5
+ import { furnaceDeployCommand } from './deploy.js';
6
+ import { furnaceDiffCommand } from './diff.js';
7
+ import { furnaceListCommand } from './list.js';
8
+ import { furnaceOverrideCommand } from './override.js';
9
+ import { furnacePreviewCommand } from './preview.js';
10
+ import { furnaceRemoveCommand } from './remove.js';
11
+ import { furnaceScanCommand } from './scan.js';
12
+ import { furnaceStatusCommand } from './status.js';
13
+ import { furnaceValidateCommand } from './validate.js';
14
+ export { furnaceApplyCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRemoveCommand, furnaceScanCommand, furnaceStatusCommand, furnaceValidateCommand, };
15
+ /** Registers the furnace command on the CLI program. */
16
+ export declare function registerFurnace(program: Command, context: CommandContext): void;
@@ -0,0 +1,121 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { Option } from 'commander';
3
+ import { pickDefined } from '../../utils/options.js';
4
+ import { furnaceApplyCommand } from './apply.js';
5
+ import { furnaceCreateCommand } from './create.js';
6
+ import { furnaceDeployCommand } from './deploy.js';
7
+ import { furnaceDiffCommand } from './diff.js';
8
+ import { furnaceListCommand } from './list.js';
9
+ import { furnaceOverrideCommand } from './override.js';
10
+ import { furnacePreviewCommand } from './preview.js';
11
+ import { furnaceRemoveCommand } from './remove.js';
12
+ import { furnaceScanCommand } from './scan.js';
13
+ import { furnaceStatusCommand } from './status.js';
14
+ import { furnaceValidateCommand } from './validate.js';
15
+ export { furnaceApplyCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRemoveCommand, furnaceScanCommand, furnaceStatusCommand, furnaceValidateCommand, };
16
+ /**
17
+ * Registers read-only Furnace commands such as status, apply, deploy, and scan.
18
+ * @param furnace - Parent Furnace command
19
+ * @param context - Shared CLI registration context
20
+ */
21
+ function registerFurnaceInfoCommands(furnace, context) {
22
+ const { getProjectRoot, withErrorHandling } = context;
23
+ furnace
24
+ .command('status [name]')
25
+ .description('Show component status and registration details')
26
+ .action(withErrorHandling(async (name) => {
27
+ await furnaceStatusCommand(getProjectRoot(), name);
28
+ }));
29
+ furnace
30
+ .command('apply')
31
+ .description('Apply all components to the engine')
32
+ .option('--dry-run', 'Show what would be changed without writing')
33
+ .action(withErrorHandling(async (options) => {
34
+ await furnaceApplyCommand(getProjectRoot(), pickDefined(options));
35
+ }));
36
+ furnace
37
+ .command('deploy [name]')
38
+ .description('Apply components and validate in one step')
39
+ .option('--dry-run', 'Show what would be changed without writing')
40
+ .action(withErrorHandling(async (name, options) => {
41
+ await furnaceDeployCommand(getProjectRoot(), name, pickDefined(options ?? {}));
42
+ }));
43
+ furnace
44
+ .command('scan')
45
+ .description('Scan engine for available components')
46
+ .action(withErrorHandling(async () => {
47
+ await furnaceScanCommand(getProjectRoot());
48
+ }));
49
+ furnace
50
+ .command('create [name]')
51
+ .description('Create a new custom component')
52
+ .option('-d, --description <desc>', 'Component description')
53
+ .option('--localized', 'Include Fluent l10n support')
54
+ .option('--no-register', 'Skip customElements.js registration')
55
+ .option('--with-tests', 'Scaffold Mochitest directory and register in moz.build')
56
+ .option('--compose <tags>', 'Stock component tags composed internally (comma-separated)', (val) => val.split(',').map((s) => s.trim()))
57
+ .action(withErrorHandling(async (name, options) => {
58
+ await furnaceCreateCommand(getProjectRoot(), name, options);
59
+ }));
60
+ }
61
+ /**
62
+ * Registers modifying Furnace commands such as override, remove, preview, and diff.
63
+ * @param furnace - Parent Furnace command
64
+ * @param context - Shared CLI registration context
65
+ */
66
+ function registerFurnaceModifyCommands(furnace, context) {
67
+ const { getProjectRoot, withErrorHandling } = context;
68
+ furnace
69
+ .command('override [name]')
70
+ .description('Fork an existing component for modification')
71
+ .addOption(new Option('-t, --type <type>', 'Override type').choices(['css-only', 'full']))
72
+ .option('-d, --description <desc>', 'Description')
73
+ .action(withErrorHandling(async (name, options) => {
74
+ await furnaceOverrideCommand(getProjectRoot(), name, options);
75
+ }));
76
+ furnace
77
+ .command('list')
78
+ .description('List all registered components')
79
+ .action(withErrorHandling(async () => {
80
+ await furnaceListCommand(getProjectRoot());
81
+ }));
82
+ furnace
83
+ .command('remove <name>')
84
+ .description('Remove a component from the workspace')
85
+ .option('-f, --force', 'Skip confirmation')
86
+ .action(withErrorHandling(async (name, options) => {
87
+ await furnaceRemoveCommand(getProjectRoot(), name, options);
88
+ }));
89
+ furnace
90
+ .command('preview')
91
+ .description('Start component preview (Storybook)')
92
+ .option('--install', 'Force reinstall Storybook dependencies')
93
+ .action(withErrorHandling(async (options) => {
94
+ await furnacePreviewCommand(getProjectRoot(), options);
95
+ }));
96
+ furnace
97
+ .command('validate [name]')
98
+ .description('Run accessibility and compatibility checks')
99
+ .action(withErrorHandling(async (name) => {
100
+ await furnaceValidateCommand(getProjectRoot(), name);
101
+ }));
102
+ furnace
103
+ .command('diff <name>')
104
+ .description('Show changes vs Firefox original (overrides only)')
105
+ .action(withErrorHandling(async (name) => {
106
+ await furnaceDiffCommand(getProjectRoot(), name);
107
+ }));
108
+ }
109
+ /** Registers the furnace command on the CLI program. */
110
+ export function registerFurnace(program, context) {
111
+ const { getProjectRoot, withErrorHandling } = context;
112
+ const furnace = program
113
+ .command('furnace')
114
+ .description('Component management (Furnace)')
115
+ .action(withErrorHandling(async () => {
116
+ await furnaceStatusCommand(getProjectRoot());
117
+ }));
118
+ registerFurnaceInfoCommands(furnace, context);
119
+ registerFurnaceModifyCommands(furnace, context);
120
+ }
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Runs the furnace list command to display all registered components.
3
+ * @param projectRoot - Root directory of the project
4
+ */
5
+ export declare function furnaceListCommand(projectRoot: string): Promise<void>;
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
3
+ import { info, intro, note, outro } from '../../utils/logger.js';
4
+ /**
5
+ * Runs the furnace list command to display all registered components.
6
+ * @param projectRoot - Root directory of the project
7
+ */
8
+ export async function furnaceListCommand(projectRoot) {
9
+ intro('Furnace List');
10
+ if (!(await furnaceConfigExists(projectRoot))) {
11
+ info('No components configured. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
12
+ outro('Done');
13
+ return;
14
+ }
15
+ const config = await loadFurnaceConfig(projectRoot);
16
+ const stockCount = config.stock.length;
17
+ const overrideCount = Object.keys(config.overrides).length;
18
+ const customCount = Object.keys(config.custom).length;
19
+ const total = stockCount + overrideCount + customCount;
20
+ if (total === 0) {
21
+ info('No components configured. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
22
+ outro('Done');
23
+ return;
24
+ }
25
+ // --- Stock ---
26
+ if (stockCount > 0) {
27
+ info('Stock:');
28
+ for (const name of config.stock) {
29
+ info(` ${name}`);
30
+ }
31
+ }
32
+ // --- Overrides ---
33
+ if (overrideCount > 0) {
34
+ info('Overrides:');
35
+ for (const [name, entry] of Object.entries(config.overrides)) {
36
+ let line = ` ${name} (${entry.type})`;
37
+ if (entry.description) {
38
+ line += ` — ${entry.description}`;
39
+ }
40
+ info(line);
41
+ }
42
+ }
43
+ // --- Custom ---
44
+ if (customCount > 0) {
45
+ info('Custom:');
46
+ for (const [name, entry] of Object.entries(config.custom)) {
47
+ const flags = [];
48
+ if (entry.localized)
49
+ flags.push('localized');
50
+ if (entry.register)
51
+ flags.push('registered');
52
+ let line = ` ${name}`;
53
+ if (entry.description) {
54
+ line += ` — ${entry.description}`;
55
+ }
56
+ if (flags.length > 0) {
57
+ line += ` [${flags.join(', ')}]`;
58
+ }
59
+ info(line);
60
+ }
61
+ }
62
+ note(`Stock: ${stockCount} Overrides: ${overrideCount} Custom: ${customCount} Total: ${total}`, 'Summary');
63
+ outro('List complete');
64
+ }
65
+ //# sourceMappingURL=list.js.map