@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,244 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { createHash } from 'node:crypto';
3
+ import { readdir } from 'node:fs/promises';
4
+ import { join, relative } from 'node:path';
5
+ import { FurnaceError } from '../errors/furnace.js';
6
+ import { toError } from '../utils/errors.js';
7
+ import { copyFile, ensureDir, pathExists, readText } from '../utils/fs.js';
8
+ import { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
9
+ import { addCustomElementRegistration, addJarMnEntries } from './furnace-registration.js';
10
+ import { recordCreatedDir, snapshotFile } from './furnace-rollback.js';
11
+ /** Path to the Fluent localization directory for toolkit global components */
12
+ const FTL_DIR = 'toolkit/locales/en-US/toolkit/global';
13
+ function isChecksummedComponentFile(name) {
14
+ return name.endsWith('.mjs') || name.endsWith('.css') || name.endsWith('.ftl');
15
+ }
16
+ function isOverrideCopyCandidate(entryName, type) {
17
+ if (entryName === 'override.json') {
18
+ return false;
19
+ }
20
+ if (type === 'css-only') {
21
+ return entryName.endsWith('.css');
22
+ }
23
+ return entryName.endsWith('.mjs') || entryName.endsWith('.css');
24
+ }
25
+ /** Computes stable checksums for the source files that define a component. */
26
+ export async function computeComponentChecksums(componentDir) {
27
+ const checksums = {};
28
+ const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
29
+ for (const entry of entries) {
30
+ if (!entry.isFile())
31
+ continue;
32
+ if (entry.name === 'override.json')
33
+ continue;
34
+ if (!isChecksummedComponentFile(entry.name))
35
+ continue;
36
+ const content = await readText(join(componentDir, entry.name));
37
+ const normalized = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
38
+ const hash = createHash('sha256').update(normalized).digest('hex');
39
+ checksums[entry.name] = hash;
40
+ }
41
+ return checksums;
42
+ }
43
+ /** Compares current component file checksums against the previously recorded state. */
44
+ export async function hasComponentChanged(componentDir, previousChecksums) {
45
+ const current = await computeComponentChecksums(componentDir);
46
+ const currentKeys = Object.keys(current);
47
+ const previousKeys = Object.keys(previousChecksums);
48
+ if (currentKeys.length !== previousKeys.length) {
49
+ return true;
50
+ }
51
+ for (const key of currentKeys) {
52
+ if (current[key] !== previousChecksums[key]) {
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ async function buildCustomDryRunActions(name, componentDir, engineDir, config, targetDir, entries) {
59
+ const actions = [];
60
+ for (const entry of entries) {
61
+ if (!entry.isFile())
62
+ continue;
63
+ if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
64
+ continue;
65
+ actions.push({
66
+ component: name,
67
+ action: 'copy',
68
+ source: join(componentDir, entry.name),
69
+ target: join(targetDir, entry.name),
70
+ description: `Copy ${entry.name} to ${config.targetPath}`,
71
+ });
72
+ }
73
+ if (config.localized) {
74
+ const ftlFile = `${name}.ftl`;
75
+ const ftlSrc = join(componentDir, ftlFile);
76
+ if (await pathExists(ftlSrc)) {
77
+ actions.push({
78
+ component: name,
79
+ action: 'copy-ftl',
80
+ source: ftlSrc,
81
+ target: join(engineDir, FTL_DIR, ftlFile),
82
+ description: `Copy ${ftlFile} to ${FTL_DIR}`,
83
+ });
84
+ }
85
+ }
86
+ if (config.register) {
87
+ actions.push({
88
+ component: name,
89
+ action: 'register-ce',
90
+ description: `Register ${name} in customElements.js (DOMContentLoaded block)`,
91
+ });
92
+ }
93
+ const copiedFileNames = entries
94
+ .filter((entry) => entry.isFile() && (entry.name.endsWith('.mjs') || entry.name.endsWith('.css')))
95
+ .map((entry) => entry.name);
96
+ if (copiedFileNames.length > 0) {
97
+ actions.push({
98
+ component: name,
99
+ action: 'register-jar',
100
+ description: `Add ${copiedFileNames.join(', ')} to jar.mn`,
101
+ });
102
+ }
103
+ return actions;
104
+ }
105
+ /** Applies a custom component into the engine tree and captures registration step errors. */
106
+ export async function applyCustomComponent(engineDir, name, componentDir, config, dryRun = false, rollbackJournal) {
107
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
108
+ throw new FurnaceError(`Invalid component name "${name}": must match /^[a-z][a-z0-9-]*$/`);
109
+ }
110
+ const targetDir = join(engineDir, config.targetPath);
111
+ const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
112
+ if (dryRun) {
113
+ const actions = await buildCustomDryRunActions(name, componentDir, engineDir, config, targetDir, entries);
114
+ return { affectedPaths: [], stepErrors: [], actions };
115
+ }
116
+ if (rollbackJournal && !(await pathExists(targetDir))) {
117
+ recordCreatedDir(rollbackJournal, targetDir);
118
+ }
119
+ await ensureDir(targetDir);
120
+ const affectedPaths = [];
121
+ const stepErrors = [];
122
+ const copiedFileNames = [];
123
+ for (const entry of entries) {
124
+ if (!entry.isFile())
125
+ continue;
126
+ if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
127
+ continue;
128
+ const src = join(componentDir, entry.name);
129
+ const dest = join(targetDir, entry.name);
130
+ if (rollbackJournal) {
131
+ await snapshotFile(rollbackJournal, dest);
132
+ }
133
+ await copyFile(src, dest);
134
+ affectedPaths.push(relative(engineDir, dest));
135
+ copiedFileNames.push(entry.name);
136
+ }
137
+ if (config.localized) {
138
+ const ftlFile = `${name}.ftl`;
139
+ const ftlSrc = join(componentDir, ftlFile);
140
+ if (await pathExists(ftlSrc)) {
141
+ const ftlDest = join(engineDir, FTL_DIR, ftlFile);
142
+ if (rollbackJournal) {
143
+ await snapshotFile(rollbackJournal, ftlDest);
144
+ }
145
+ await copyFile(ftlSrc, ftlDest);
146
+ affectedPaths.push(relative(engineDir, ftlDest));
147
+ }
148
+ }
149
+ if (config.register) {
150
+ try {
151
+ const modulePath = `chrome://global/content/elements/${name}.mjs`;
152
+ if (rollbackJournal) {
153
+ await snapshotFile(rollbackJournal, join(engineDir, CUSTOM_ELEMENTS_JS));
154
+ }
155
+ await addCustomElementRegistration(engineDir, name, modulePath);
156
+ affectedPaths.push(CUSTOM_ELEMENTS_JS);
157
+ }
158
+ catch (error) {
159
+ stepErrors.push({
160
+ step: 'customElements.js registration',
161
+ error: toError(error).message,
162
+ });
163
+ }
164
+ }
165
+ if (copiedFileNames.length > 0) {
166
+ try {
167
+ if (rollbackJournal) {
168
+ await snapshotFile(rollbackJournal, join(engineDir, JAR_MN));
169
+ }
170
+ await addJarMnEntries(engineDir, name, copiedFileNames);
171
+ affectedPaths.push(JAR_MN);
172
+ }
173
+ catch (error) {
174
+ stepErrors.push({
175
+ step: 'jar.mn registration',
176
+ error: toError(error).message,
177
+ });
178
+ }
179
+ }
180
+ return { affectedPaths, stepErrors };
181
+ }
182
+ /** Applies an override component by copying its matching files onto the engine tree. */
183
+ export async function applyOverrideComponent(engineDir, name, componentDir, config, dryRun = false, rollbackJournal) {
184
+ const targetDir = join(engineDir, config.basePath);
185
+ if (!(await pathExists(targetDir))) {
186
+ throw new FurnaceError(`Override target path not found in engine: ${config.basePath}`, name);
187
+ }
188
+ const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
189
+ if (dryRun) {
190
+ const actions = entries
191
+ .filter((entry) => entry.isFile() && isOverrideCopyCandidate(entry.name, config.type))
192
+ .map((entry) => ({
193
+ component: name,
194
+ action: 'copy',
195
+ source: join(componentDir, entry.name),
196
+ target: join(targetDir, entry.name),
197
+ description: `Override ${entry.name} in ${config.basePath}`,
198
+ }));
199
+ if (actions.length === 0) {
200
+ throw new FurnaceError(`No matching files found in override directory for "${name}"`, name);
201
+ }
202
+ return { affectedPaths: [], actions };
203
+ }
204
+ const affectedPaths = [];
205
+ for (const entry of entries) {
206
+ if (!entry.isFile() || !isOverrideCopyCandidate(entry.name, config.type)) {
207
+ continue;
208
+ }
209
+ const src = join(componentDir, entry.name);
210
+ const dest = join(targetDir, entry.name);
211
+ if (rollbackJournal) {
212
+ await snapshotFile(rollbackJournal, dest);
213
+ }
214
+ await copyFile(src, dest);
215
+ affectedPaths.push(relative(engineDir, dest));
216
+ }
217
+ if (affectedPaths.length === 0) {
218
+ throw new FurnaceError(`No matching files found in override directory for "${name}"`, name);
219
+ }
220
+ return { affectedPaths };
221
+ }
222
+ /** Extracts per-component checksums from the flattened state-file checksum map. */
223
+ export function extractComponentChecksums(allChecksums, type, name) {
224
+ if (!allChecksums)
225
+ return {};
226
+ const prefix = `${type}/${name}/`;
227
+ const result = {};
228
+ for (const [key, value] of Object.entries(allChecksums)) {
229
+ if (key.startsWith(prefix)) {
230
+ result[key.slice(prefix.length)] = value;
231
+ }
232
+ }
233
+ return result;
234
+ }
235
+ /** Prefixes component checksums so they can be stored in the flattened state format. */
236
+ export function prefixChecksums(checksums, type, name) {
237
+ const prefix = `${type}/${name}/`;
238
+ const result = {};
239
+ for (const [key, value] of Object.entries(checksums)) {
240
+ result[`${prefix}${key}`] = value;
241
+ }
242
+ return result;
243
+ }
244
+ //# sourceMappingURL=furnace-apply-helpers.js.map
@@ -0,0 +1,16 @@
1
+ import type { ApplyResult, DryRunAction } from '../types/furnace.js';
2
+ export { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
3
+ /**
4
+ * Applies all override and custom components to the engine source tree.
5
+ *
6
+ * Unchanged components (matching checksums) are skipped. If any component
7
+ * fails, FireForge restores only the engine files touched during this apply
8
+ * attempt and leaves the state file unchanged.
9
+ *
10
+ * @param root - Root directory of the project
11
+ * @param dryRun - If true, enumerate planned actions without writing
12
+ * @returns Summary of applied, skipped, and errored components (with actions when dry-run)
13
+ */
14
+ export declare function applyAllComponents(root: string, dryRun?: boolean): Promise<ApplyResult & {
15
+ actions?: DryRunAction[];
16
+ }>;
@@ -0,0 +1,147 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { FurnaceError } from '../errors/furnace.js';
4
+ import { toError } from '../utils/errors.js';
5
+ import { pathExists } from '../utils/fs.js';
6
+ import { getProjectPaths } from './config.js';
7
+ import { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
8
+ import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, } from './furnace-config.js';
9
+ import { createRollbackJournal, restoreRollbackJournalOrThrow, } from './furnace-rollback.js';
10
+ export { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
11
+ function addMissingComponentError(result, name, directoryPath) {
12
+ result.errors.push({
13
+ name,
14
+ error: `Component directory not found: ${directoryPath}`,
15
+ });
16
+ }
17
+ async function applyOverrideBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal) {
18
+ for (const [name, overrideConfig] of Object.entries(config.overrides)) {
19
+ const componentDir = join(furnacePaths.overridesDir, name);
20
+ if (!(await pathExists(componentDir))) {
21
+ addMissingComponentError(result, name, `components/overrides/${name}`);
22
+ continue;
23
+ }
24
+ if (!dryRun) {
25
+ const previous = extractComponentChecksums(state.appliedChecksums, 'override', name);
26
+ const changed = await hasComponentChanged(componentDir, previous);
27
+ if (!changed) {
28
+ result.skipped.push({ name, reason: 'No changes since last apply' });
29
+ Object.assign(newChecksums, prefixChecksums(previous, 'override', name));
30
+ continue;
31
+ }
32
+ }
33
+ try {
34
+ const { affectedPaths: filesAffected, actions } = await applyOverrideComponent(engineDir, name, componentDir, overrideConfig, dryRun, rollbackJournal);
35
+ if (dryRun && actions) {
36
+ allActions.push(...actions);
37
+ }
38
+ result.applied.push({ name, type: 'override', filesAffected });
39
+ if (!dryRun) {
40
+ const checksums = await computeComponentChecksums(componentDir);
41
+ Object.assign(newChecksums, prefixChecksums(checksums, 'override', name));
42
+ }
43
+ }
44
+ catch (error) {
45
+ result.errors.push({
46
+ name,
47
+ error: toError(error).message,
48
+ });
49
+ }
50
+ }
51
+ }
52
+ async function applyCustomBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal) {
53
+ for (const [name, customConfig] of Object.entries(config.custom)) {
54
+ const componentDir = join(furnacePaths.customDir, name);
55
+ if (!(await pathExists(componentDir))) {
56
+ addMissingComponentError(result, name, `components/custom/${name}`);
57
+ continue;
58
+ }
59
+ if (!dryRun) {
60
+ const previous = extractComponentChecksums(state.appliedChecksums, 'custom', name);
61
+ const changed = await hasComponentChanged(componentDir, previous);
62
+ if (!changed) {
63
+ result.skipped.push({ name, reason: 'No changes since last apply' });
64
+ Object.assign(newChecksums, prefixChecksums(previous, 'custom', name));
65
+ continue;
66
+ }
67
+ }
68
+ try {
69
+ const { affectedPaths: filesAffected, stepErrors, actions, } = await applyCustomComponent(engineDir, name, componentDir, customConfig, dryRun, rollbackJournal);
70
+ if (dryRun && actions) {
71
+ allActions.push(...actions);
72
+ }
73
+ result.applied.push({
74
+ name,
75
+ type: 'custom',
76
+ filesAffected,
77
+ ...(stepErrors.length > 0 ? { stepErrors } : {}),
78
+ });
79
+ // Only store checksums when the component applied without step errors,
80
+ // so that partially failed components are re-applied on the next run.
81
+ if (!dryRun && stepErrors.length === 0) {
82
+ const checksums = await computeComponentChecksums(componentDir);
83
+ Object.assign(newChecksums, prefixChecksums(checksums, 'custom', name));
84
+ }
85
+ }
86
+ catch (error) {
87
+ result.errors.push({
88
+ name,
89
+ error: toError(error).message,
90
+ });
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * Applies all override and custom components to the engine source tree.
96
+ *
97
+ * Unchanged components (matching checksums) are skipped. If any component
98
+ * fails, FireForge restores only the engine files touched during this apply
99
+ * attempt and leaves the state file unchanged.
100
+ *
101
+ * @param root - Root directory of the project
102
+ * @param dryRun - If true, enumerate planned actions without writing
103
+ * @returns Summary of applied, skipped, and errored components (with actions when dry-run)
104
+ */
105
+ export async function applyAllComponents(root, dryRun = false) {
106
+ const config = await loadFurnaceConfig(root);
107
+ const state = await loadFurnaceState(root);
108
+ const { engine: engineDir } = getProjectPaths(root);
109
+ const furnacePaths = getFurnacePaths(root);
110
+ if (!(await pathExists(engineDir))) {
111
+ throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
112
+ }
113
+ const rollbackJournal = dryRun ? undefined : createRollbackJournal();
114
+ const result = {
115
+ applied: [],
116
+ skipped: [],
117
+ errors: [],
118
+ };
119
+ const allActions = [];
120
+ const newChecksums = {};
121
+ await applyOverrideBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal);
122
+ await applyCustomBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal);
123
+ // Check for any partial failures (step errors on applied components).
124
+ const hasStepErrors = result.applied.some((entry) => 'stepErrors' in entry && entry.stepErrors.length > 0);
125
+ // Orphaned components are implicitly cleaned up: newChecksums only
126
+ // contains entries for components that still exist in furnace.json,
127
+ // and it fully replaces state.appliedChecksums below.
128
+ // --- Rollback on failure, persist on success (skip for dry-run) ---
129
+ if (!dryRun) {
130
+ if (result.errors.length > 0 || hasStepErrors) {
131
+ if (rollbackJournal) {
132
+ await restoreRollbackJournalOrThrow(rollbackJournal, 'Furnace apply failed');
133
+ }
134
+ }
135
+ else {
136
+ await updateFurnaceState(root, {
137
+ lastApply: new Date().toISOString(),
138
+ appliedChecksums: newChecksums,
139
+ });
140
+ }
141
+ }
142
+ if (dryRun) {
143
+ result.actions = allActions;
144
+ }
145
+ return result;
146
+ }
147
+ //# sourceMappingURL=furnace-apply.js.map
@@ -0,0 +1,94 @@
1
+ import type { FurnaceConfig, FurnaceState } from '../types/furnace.js';
2
+ /** Name of the furnace configuration file */
3
+ export declare const FURNACE_CONFIG_FILENAME = "furnace.json";
4
+ /** Name of the furnace state file */
5
+ export declare const FURNACE_STATE_FILENAME = "furnace-state.json";
6
+ /** Name of the components directory */
7
+ export declare const COMPONENTS_DIR = "components";
8
+ /** Name of the overrides subdirectory */
9
+ export declare const OVERRIDES_DIR = "overrides";
10
+ /** Name of the custom subdirectory */
11
+ export declare const CUSTOM_DIR = "custom";
12
+ /**
13
+ * Paths for furnace-related files and directories.
14
+ */
15
+ interface FurnacePaths {
16
+ /** Path to furnace.json */
17
+ furnaceConfig: string;
18
+ /** Path to components directory */
19
+ componentsDir: string;
20
+ /** Path to components/overrides directory */
21
+ overridesDir: string;
22
+ /** Path to components/custom directory */
23
+ customDir: string;
24
+ /** Path to .fireforge/furnace-state.json */
25
+ furnaceState: string;
26
+ }
27
+ /**
28
+ * Gets all furnace-related paths based on a root directory.
29
+ * @param root - Root directory of the project
30
+ * @returns All furnace paths
31
+ */
32
+ export declare function getFurnacePaths(root: string): FurnacePaths;
33
+ /**
34
+ * Checks if a furnace.json exists in the given directory.
35
+ * @param root - Root directory to check
36
+ * @returns True if furnace.json exists
37
+ */
38
+ export declare function furnaceConfigExists(root: string): Promise<boolean>;
39
+ /**
40
+ * Validates a raw config object and returns a typed FurnaceConfig.
41
+ * @param data - Raw data to validate
42
+ * @returns Validated FurnaceConfig
43
+ * @throws Error if validation fails
44
+ */
45
+ export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
46
+ /**
47
+ * Validates a parsed furnace state object and returns a typed FurnaceState.
48
+ * @param data - Parsed JSON state data
49
+ * @returns Validated FurnaceState
50
+ */
51
+ export declare function validateFurnaceState(data: unknown): FurnaceState;
52
+ /**
53
+ * Loads and validates the furnace.json configuration.
54
+ * @param root - Root directory of the project
55
+ * @returns Validated FurnaceConfig
56
+ * @throws Error if config doesn't exist or is invalid
57
+ */
58
+ export declare function loadFurnaceConfig(root: string): Promise<FurnaceConfig>;
59
+ /**
60
+ * Writes a furnace configuration to furnace.json.
61
+ * @param root - Root directory of the project
62
+ * @param config - Configuration to write
63
+ */
64
+ export declare function writeFurnaceConfig(root: string, config: FurnaceConfig): Promise<void>;
65
+ /**
66
+ * Creates a default furnace configuration.
67
+ * @returns A valid empty FurnaceConfig
68
+ */
69
+ export declare function createDefaultFurnaceConfig(): FurnaceConfig;
70
+ /**
71
+ * Loads furnace config if it exists, or creates and writes a default config.
72
+ * @param root - Root directory of the project
73
+ * @returns FurnaceConfig (existing or newly created)
74
+ */
75
+ export declare function ensureFurnaceConfig(root: string): Promise<FurnaceConfig>;
76
+ /**
77
+ * Loads the furnace state, or returns defaults if it doesn't exist.
78
+ * @param root - Root directory of the project
79
+ * @returns Furnace state
80
+ */
81
+ export declare function loadFurnaceState(root: string): Promise<FurnaceState>;
82
+ /**
83
+ * Saves the furnace state.
84
+ * @param root - Root directory of the project
85
+ * @param state - State to save
86
+ */
87
+ export declare function saveFurnaceState(root: string, state: FurnaceState): Promise<void>;
88
+ /**
89
+ * Updates furnace state fields transactionally under the state file lock.
90
+ * @param root - Root directory of the project
91
+ * @param updates - Fields to update, or a transactional updater function
92
+ */
93
+ export declare function updateFurnaceState(root: string, updates: Partial<FurnaceState> | ((current: FurnaceState) => FurnaceState)): Promise<void>;
94
+ export {};