@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,220 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { createHash } from 'node:crypto';
3
+ import { readdir } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { toError } from '../utils/errors.js';
6
+ import { pathExists, readText } from '../utils/fs.js';
7
+ import { warn } from '../utils/logger.js';
8
+ import { stripJsComments } from '../utils/regex.js';
9
+ import { getProjectPaths, loadConfig } from './config.js';
10
+ import { getFurnacePaths } from './furnace-config.js';
11
+ import { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
12
+ import { getTokensCssPath } from './token-manager.js';
13
+ /**
14
+ * Validates that all Furnace-managed .mjs components are registered in the
15
+ * DOMContentLoaded/importESModule block (Pattern B), not the loadSubScript
16
+ * block (Pattern A).
17
+ *
18
+ * @param root - Project root directory
19
+ * @param config - Furnace configuration
20
+ * @returns Array of validation issues for mis-placed registrations
21
+ */
22
+ export async function validateRegistrationPatterns(root, config) {
23
+ const issues = [];
24
+ const { engine: engineDir } = getProjectPaths(root);
25
+ const filePath = join(engineDir, CUSTOM_ELEMENTS_JS);
26
+ if (!(await pathExists(filePath))) {
27
+ return issues;
28
+ }
29
+ const content = await readText(filePath);
30
+ // Find the DOMContentLoaded block boundary (handles multi-line addEventListener)
31
+ const dclMatch = /document\.addEventListener\(\s*["']DOMContentLoaded["']/.exec(content);
32
+ if (!dclMatch) {
33
+ return issues;
34
+ }
35
+ const domContentLoadedIdx = dclMatch.index;
36
+ // Get all custom component tag names that use .mjs (all custom components do)
37
+ for (const [name, customConfig] of Object.entries(config.custom)) {
38
+ if (!customConfig.register)
39
+ continue;
40
+ // Check if this tag is referenced before the DOMContentLoaded block
41
+ const contentBeforeDCL = stripJsComments(content.slice(0, domContentLoadedIdx));
42
+ const tagPattern = new RegExp(`"${name}"`);
43
+ if (tagPattern.test(contentBeforeDCL)) {
44
+ issues.push({
45
+ component: name,
46
+ severity: 'error',
47
+ check: 'wrong-registration-pattern',
48
+ message: `${name} is registered in the loadSubScript block (Pattern A) instead of the DOMContentLoaded/importESModule block (Pattern B). .mjs components must use Pattern B or they will fail at runtime.`,
49
+ });
50
+ }
51
+ }
52
+ return issues;
53
+ }
54
+ /**
55
+ * Checks registration consistency for a single custom component.
56
+ *
57
+ * Compares source files, engine target files, jar.mn entries, and
58
+ * customElements.js registration for a given component.
59
+ *
60
+ * @param root - Project root directory
61
+ * @param name - Component tag name
62
+ * @param config - Custom component configuration
63
+ * @returns Registration status with per-check booleans and drift info
64
+ */
65
+ export async function checkRegistrationConsistency(root, name, config) {
66
+ const { engine: engineDir } = getProjectPaths(root);
67
+ const furnacePaths = getFurnacePaths(root);
68
+ const componentDir = join(furnacePaths.customDir, name);
69
+ const status = {
70
+ sourceExists: false,
71
+ targetExists: false,
72
+ filesInSync: true,
73
+ jarMnCss: false,
74
+ jarMnMjs: false,
75
+ customElementsPresent: false,
76
+ customElementsCorrectBlock: false,
77
+ driftedFiles: [],
78
+ missingTargetFiles: [],
79
+ };
80
+ // Check source directory
81
+ status.sourceExists = await pathExists(componentDir);
82
+ if (!status.sourceExists)
83
+ return status;
84
+ // Check target directory
85
+ const targetDir = join(engineDir, config.targetPath);
86
+ status.targetExists = await pathExists(targetDir);
87
+ // Compare files (sourceExists is guaranteed true — we early-returned above)
88
+ if (status.targetExists) {
89
+ const entries = await readdir(componentDir, { withFileTypes: true });
90
+ for (const entry of entries) {
91
+ if (!entry.isFile())
92
+ continue;
93
+ if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
94
+ continue;
95
+ const srcPath = join(componentDir, entry.name);
96
+ const destPath = join(targetDir, entry.name);
97
+ if (!(await pathExists(destPath))) {
98
+ status.missingTargetFiles.push(entry.name);
99
+ status.filesInSync = false;
100
+ continue;
101
+ }
102
+ const srcContent = await readText(srcPath);
103
+ const destContent = await readText(destPath);
104
+ const srcHash = createHash('sha256').update(srcContent).digest('hex');
105
+ const destHash = createHash('sha256').update(destContent).digest('hex');
106
+ if (srcHash !== destHash) {
107
+ status.driftedFiles.push(entry.name);
108
+ status.filesInSync = false;
109
+ }
110
+ }
111
+ }
112
+ else {
113
+ status.filesInSync = false;
114
+ }
115
+ // Check jar.mn entries
116
+ const jarMnPath = join(engineDir, JAR_MN);
117
+ if (await pathExists(jarMnPath)) {
118
+ const jarContent = await readText(jarMnPath);
119
+ status.jarMnCss = jarContent.includes(`content/global/elements/${name}.css`);
120
+ status.jarMnMjs = jarContent.includes(`content/global/elements/${name}.mjs`);
121
+ }
122
+ // Check customElements.js registration
123
+ const cePath = join(engineDir, CUSTOM_ELEMENTS_JS);
124
+ if (await pathExists(cePath)) {
125
+ const ceContent = await readText(cePath);
126
+ status.customElementsPresent =
127
+ ceContent.includes(`"${name}"`) || ceContent.includes(`'${name}'`);
128
+ if (status.customElementsPresent) {
129
+ // Check it's in the correct block (after DOMContentLoaded)
130
+ const dclMatch = /document\.addEventListener\(\s*["']DOMContentLoaded["']/.exec(ceContent);
131
+ if (dclMatch) {
132
+ const afterDcl = ceContent.slice(dclMatch.index);
133
+ status.customElementsCorrectBlock =
134
+ afterDcl.includes(`"${name}"`) || afterDcl.includes(`'${name}'`);
135
+ }
136
+ }
137
+ }
138
+ return status;
139
+ }
140
+ /**
141
+ * Validates that each custom component with `register: true` has its .mjs and
142
+ * .css entries in jar.mn.
143
+ *
144
+ * @param root - Project root directory
145
+ * @param config - Furnace configuration
146
+ * @returns Array of validation issues for missing jar.mn entries
147
+ */
148
+ export async function validateJarMnEntries(root, config) {
149
+ const issues = [];
150
+ const { engine: engineDir } = getProjectPaths(root);
151
+ const jarMnPath = join(engineDir, JAR_MN);
152
+ if (!(await pathExists(jarMnPath))) {
153
+ return issues;
154
+ }
155
+ const jarContent = await readText(jarMnPath);
156
+ for (const [name, customConfig] of Object.entries(config.custom)) {
157
+ if (!customConfig.register)
158
+ continue;
159
+ if (!jarContent.includes(`content/global/elements/${name}.mjs`)) {
160
+ issues.push({
161
+ component: name,
162
+ severity: 'error',
163
+ check: 'missing-jar-mn-mjs',
164
+ message: `${name}.mjs is not registered in jar.mn. Run "fireforge furnace deploy" to register.`,
165
+ });
166
+ }
167
+ if (!jarContent.includes(`content/global/elements/${name}.css`)) {
168
+ issues.push({
169
+ component: name,
170
+ severity: 'warning',
171
+ check: 'missing-jar-mn-css',
172
+ message: `${name}.css is not registered in jar.mn.`,
173
+ });
174
+ }
175
+ }
176
+ return issues;
177
+ }
178
+ /**
179
+ * Validates that components using design tokens have the tokens CSS
180
+ * linked in browser.xhtml. Without the link, tokens silently resolve to nothing.
181
+ */
182
+ export async function validateTokenLink(componentDir, tagName, root, tokenPrefix) {
183
+ const issues = [];
184
+ const cssPath = join(componentDir, `${tagName}.css`);
185
+ if (!(await pathExists(cssPath)))
186
+ return issues;
187
+ if (!tokenPrefix)
188
+ return issues;
189
+ const cssContent = await readText(cssPath);
190
+ // Check if the component CSS references any tokens with the configured prefix
191
+ if (!cssContent.includes(tokenPrefix))
192
+ return issues;
193
+ // Check if browser.xhtml links the token CSS file
194
+ const { engine: engineDir } = getProjectPaths(root);
195
+ const browserXhtmlPath = join(engineDir, 'browser/base/content/browser.xhtml');
196
+ if (!(await pathExists(browserXhtmlPath)))
197
+ return issues;
198
+ let tokensCssFile;
199
+ try {
200
+ const forgeConfig = await loadConfig(root);
201
+ const segments = getTokensCssPath(forgeConfig.binaryName).split('/');
202
+ tokensCssFile = segments[segments.length - 1] ?? '';
203
+ }
204
+ catch (error) {
205
+ const reason = toError(error).message;
206
+ warn(`Could not resolve token CSS link target for ${tagName} during validation: ${reason}`);
207
+ return issues;
208
+ }
209
+ const xhtmlContent = await readText(browserXhtmlPath);
210
+ if (!xhtmlContent.includes(tokensCssFile)) {
211
+ issues.push({
212
+ component: tagName,
213
+ severity: 'warning',
214
+ check: 'missing-token-link',
215
+ message: `Component uses ${tokenPrefix}* tokens but browser.xhtml does not link ${tokensCssFile}. Tokens will silently resolve to nothing.`,
216
+ });
217
+ }
218
+ return issues;
219
+ }
220
+ //# sourceMappingURL=furnace-validate-registration.js.map
@@ -0,0 +1,6 @@
1
+ import type { ComponentType, ValidationIssue } from '../types/furnace.js';
2
+ /**
3
+ * Validates the file structure of a component directory.
4
+ * Checks for required files and naming conventions.
5
+ */
6
+ export declare function validateStructure(componentDir: string, tagName: string, type: ComponentType): Promise<ValidationIssue[]>;
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { pathExists } from '../utils/fs.js';
5
+ /**
6
+ * Validates the file structure of a component directory.
7
+ * Checks for required files and naming conventions.
8
+ */
9
+ export async function validateStructure(componentDir, tagName, type) {
10
+ const issues = [];
11
+ const mjsPath = join(componentDir, `${tagName}.mjs`);
12
+ const cssPath = join(componentDir, `${tagName}.css`);
13
+ // .mjs must exist for custom components
14
+ if (type === 'custom' && !(await pathExists(mjsPath))) {
15
+ issues.push({
16
+ component: tagName,
17
+ severity: 'error',
18
+ check: 'missing-mjs',
19
+ message: `Required file ${tagName}.mjs not found.`,
20
+ });
21
+ }
22
+ // .css should exist
23
+ if (!(await pathExists(cssPath))) {
24
+ issues.push({
25
+ component: tagName,
26
+ severity: 'warning',
27
+ check: 'missing-css',
28
+ message: `No ${tagName}.css found. Consider adding styles.`,
29
+ });
30
+ }
31
+ // File names should match tag name
32
+ const entries = await readdir(componentDir, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ if (!entry.isFile())
35
+ continue;
36
+ if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
37
+ continue;
38
+ const fileName = entry.name;
39
+ if (/\.(test|spec|stories)\./.test(fileName))
40
+ continue;
41
+ const expectedPrefix = tagName;
42
+ const nameWithoutExt = entry.name.replace(/\.(mjs|css)$/, '');
43
+ if (nameWithoutExt !== expectedPrefix && !nameWithoutExt.startsWith(expectedPrefix + '-')) {
44
+ issues.push({
45
+ component: tagName,
46
+ severity: 'error',
47
+ check: 'filename-mismatch',
48
+ message: `File "${entry.name}" does not match expected naming convention "${tagName}.*".`,
49
+ });
50
+ }
51
+ }
52
+ // override.json must exist for overrides
53
+ if (type === 'override') {
54
+ const overrideJsonPath = join(componentDir, 'override.json');
55
+ if (!(await pathExists(overrideJsonPath))) {
56
+ issues.push({
57
+ component: tagName,
58
+ severity: 'error',
59
+ check: 'missing-override-json',
60
+ message: 'Required file override.json not found for override component.',
61
+ });
62
+ }
63
+ }
64
+ return issues;
65
+ }
66
+ //# sourceMappingURL=furnace-validate-structure.js.map
@@ -0,0 +1,16 @@
1
+ import type { ComponentType, FurnaceConfig, ValidationIssue } from '../types/furnace.js';
2
+ /**
3
+ * Runs all validation checks on a single component.
4
+ * @param componentDir - Path to the component directory
5
+ * @param tagName - Component tag name
6
+ * @param type - Component type (stock, override, custom)
7
+ * @returns Combined list of validation issues
8
+ */
9
+ export declare function validateComponent(componentDir: string, tagName: string, type: ComponentType, config?: FurnaceConfig, root?: string): Promise<ValidationIssue[]>;
10
+ /**
11
+ * Validates all components registered in furnace.json.
12
+ * Stock components are skipped (no local files to validate).
13
+ * @param root - Project root directory
14
+ * @returns Map of component name to its validation issues
15
+ */
16
+ export declare function validateAllComponents(root: string): Promise<Map<string, ValidationIssue[]>>;
@@ -0,0 +1,103 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { pathExists } from '../utils/fs.js';
4
+ import { getFurnacePaths, loadFurnaceConfig } from './furnace-config.js';
5
+ import { validateAccessibility, validateCompatibility, validateJarMnEntries, validateRegistrationPatterns, validateStructure, validateTokenLink, } from './furnace-validate-checks.js';
6
+ // ---------------------------------------------------------------------------
7
+ // Aggregate validators
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Runs all validation checks on a single component.
11
+ * @param componentDir - Path to the component directory
12
+ * @param tagName - Component tag name
13
+ * @param type - Component type (stock, override, custom)
14
+ * @returns Combined list of validation issues
15
+ */
16
+ export async function validateComponent(componentDir, tagName, type, config, root) {
17
+ const issues = [];
18
+ issues.push(...(await validateStructure(componentDir, tagName, type)));
19
+ issues.push(...(await validateAccessibility(componentDir, tagName)));
20
+ issues.push(...(await validateCompatibility(componentDir, tagName, type, config, root)));
21
+ // Check for missing token link in browser.xhtml
22
+ if (root) {
23
+ issues.push(...(await validateTokenLink(componentDir, tagName, root, config?.tokenPrefix)));
24
+ }
25
+ // When root is provided and this is a custom component with registration,
26
+ // also run registration pattern and jar.mn validation for this component.
27
+ if (root && config && type === 'custom') {
28
+ const customConfig = config.custom[tagName];
29
+ if (customConfig?.register) {
30
+ const singleConfig = {
31
+ ...config,
32
+ custom: { [tagName]: customConfig },
33
+ };
34
+ issues.push(...(await validateRegistrationPatterns(root, singleConfig)));
35
+ issues.push(...(await validateJarMnEntries(root, singleConfig)));
36
+ }
37
+ }
38
+ return issues;
39
+ }
40
+ /**
41
+ * Validates all components registered in furnace.json.
42
+ * Stock components are skipped (no local files to validate).
43
+ * @param root - Project root directory
44
+ * @returns Map of component name to its validation issues
45
+ */
46
+ export async function validateAllComponents(root) {
47
+ const config = await loadFurnaceConfig(root);
48
+ const furnacePaths = getFurnacePaths(root);
49
+ const results = new Map();
50
+ // Override components
51
+ for (const name of Object.keys(config.overrides)) {
52
+ const componentDir = join(furnacePaths.overridesDir, name);
53
+ if (!(await pathExists(componentDir))) {
54
+ results.set(name, [
55
+ {
56
+ component: name,
57
+ severity: 'error',
58
+ check: 'missing-component-dir',
59
+ message: `Component directory not found: components/overrides/${name}`,
60
+ },
61
+ ]);
62
+ continue;
63
+ }
64
+ const issues = await validateComponent(componentDir, name, 'override', config, root);
65
+ results.set(name, issues);
66
+ }
67
+ // Custom components
68
+ for (const name of Object.keys(config.custom)) {
69
+ const componentDir = join(furnacePaths.customDir, name);
70
+ if (!(await pathExists(componentDir))) {
71
+ results.set(name, [
72
+ {
73
+ component: name,
74
+ severity: 'error',
75
+ check: 'missing-component-dir',
76
+ message: `Component directory not found: components/custom/${name}`,
77
+ },
78
+ ]);
79
+ continue;
80
+ }
81
+ // Pass root so that per-component token link validation runs.
82
+ // Per-component registration/jar.mn checks are also included, but that's
83
+ // acceptable as the aggregate validators below deduplicate by component name.
84
+ const issues = await validateComponent(componentDir, name, 'custom', config, root);
85
+ results.set(name, issues);
86
+ }
87
+ // Registration pattern validation (customElements.js Pattern A vs B)
88
+ const registrationIssues = await validateRegistrationPatterns(root, config);
89
+ for (const issue of registrationIssues) {
90
+ const existing = results.get(issue.component) ?? [];
91
+ existing.push(issue);
92
+ results.set(issue.component, existing);
93
+ }
94
+ // jar.mn entry validation
95
+ const jarMnIssues = await validateJarMnEntries(root, config);
96
+ for (const issue of jarMnIssues) {
97
+ const existing = results.get(issue.component) ?? [];
98
+ existing.push(issue);
99
+ results.set(issue.component, existing);
100
+ }
101
+ return results;
102
+ }
103
+ //# sourceMappingURL=furnace-validate.js.map
@@ -0,0 +1,47 @@
1
+ /** Default timeout for `git add -A` on large trees (10 minutes). */
2
+ export declare const GIT_ADD_TIMEOUT_MS: number;
3
+ /** Timeout for chunked `git add` per top-level directory (20 minutes). */
4
+ export declare const GIT_ADD_CHUNK_TIMEOUT_MS: number;
5
+ /**
6
+ * Structured git status entry derived from `git status --porcelain=v1 -z`.
7
+ */
8
+ export interface GitStatusEntry {
9
+ /** Two-character XY status as reported by porcelain output. */
10
+ status: string;
11
+ /** Index status character. */
12
+ indexStatus: string;
13
+ /** Worktree status character. */
14
+ worktreeStatus: string;
15
+ /** Canonical current path for the entry. */
16
+ file: string;
17
+ /** Original path for rename/copy entries. */
18
+ originalPath?: string | undefined;
19
+ /** True when the entry is an untracked path. */
20
+ isUntracked: boolean;
21
+ /** True when the entry represents a rename or copy. */
22
+ isRenameOrCopy: boolean;
23
+ /** True when the entry represents a deletion in either index or worktree. */
24
+ isDeleted: boolean;
25
+ }
26
+ /**
27
+ * Ensures git is available in the system.
28
+ * @throws GitNotFoundError if git is not installed
29
+ */
30
+ export declare function ensureGit(): Promise<void>;
31
+ /**
32
+ * Runs a git command in the specified directory.
33
+ * @param args - Git command arguments
34
+ * @param cwd - Working directory
35
+ * @returns Command output
36
+ */
37
+ export declare function git(args: string[], cwd: string, options?: {
38
+ timeout?: number;
39
+ env?: Record<string, string>;
40
+ }): Promise<string>;
41
+ /**
42
+ * Configures git performance settings for large trees.
43
+ * Enables index preloading, untracked cache, and the manyFiles feature
44
+ * flag which significantly reduces `git add` / `git status` time on
45
+ * repositories with hundreds of thousands of files.
46
+ */
47
+ export declare function configureGitPerformance(repoDir: string): Promise<void>;
@@ -0,0 +1,50 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { GitError, GitNotFoundError } from '../errors/git.js';
3
+ import { exec, executableExists } from '../utils/process.js';
4
+ /** Default timeout for `git add -A` on large trees (10 minutes). */
5
+ export const GIT_ADD_TIMEOUT_MS = 10 * 60_000;
6
+ /** Timeout for chunked `git add` per top-level directory (20 minutes). */
7
+ export const GIT_ADD_CHUNK_TIMEOUT_MS = 20 * 60_000;
8
+ /**
9
+ * Ensures git is available in the system.
10
+ * @throws GitNotFoundError if git is not installed
11
+ */
12
+ export async function ensureGit() {
13
+ if (!(await executableExists('git'))) {
14
+ throw new GitNotFoundError();
15
+ }
16
+ }
17
+ /**
18
+ * Runs a git command in the specified directory.
19
+ * @param args - Git command arguments
20
+ * @param cwd - Working directory
21
+ * @returns Command output
22
+ */
23
+ export async function git(args, cwd, options) {
24
+ const execOptions = { cwd };
25
+ if (options?.timeout !== undefined) {
26
+ execOptions.timeout = options.timeout;
27
+ }
28
+ if (options?.env !== undefined) {
29
+ execOptions.env = options.env;
30
+ }
31
+ const result = await exec('git', args, execOptions);
32
+ if (result.exitCode !== 0) {
33
+ throw new GitError(result.stderr.trim() || 'Git command failed', args.join(' '));
34
+ }
35
+ return result.stdout;
36
+ }
37
+ /**
38
+ * Configures git performance settings for large trees.
39
+ * Enables index preloading, untracked cache, and the manyFiles feature
40
+ * flag which significantly reduces `git add` / `git status` time on
41
+ * repositories with hundreds of thousands of files.
42
+ */
43
+ export async function configureGitPerformance(repoDir) {
44
+ await git(['config', 'core.preloadindex', 'true'], repoDir);
45
+ await git(['config', 'core.untrackedCache', 'true'], repoDir);
46
+ // Explicitly disable fsmonitor to avoid daemon issues on freshly-created repos
47
+ await git(['config', 'core.fsmonitor', 'false'], repoDir);
48
+ await git(['config', 'feature.manyFiles', 'true'], repoDir);
49
+ }
50
+ //# sourceMappingURL=git-base.js.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Gets the diff for a specific file.
3
+ * @param repoDir - Repository directory
4
+ * @param filePath - Path to the file (relative to repo)
5
+ * @returns Diff content
6
+ */
7
+ export declare function getFileDiff(repoDir: string, filePath: string): Promise<string>;
8
+ /**
9
+ * Generates a unified diff for a new (untracked) file.
10
+ * @param repoDir - Repository directory
11
+ * @param filePath - Path to the file (relative to repo)
12
+ * @returns Diff content in unified diff format
13
+ */
14
+ export declare function generateNewFileDiff(repoDir: string, filePath: string): Promise<string>;
15
+ /**
16
+ * Generates a patch for a file.
17
+ * If the file is tracked in HEAD, it generates a standard contextual diff.
18
+ * If the file is untracked (new), it generates a "new file" format patch (snapshot).
19
+ * This ensures standard 3-way mergeable context diffs for existing Mozilla files.
20
+ * @param repoDir - Repository directory
21
+ * @param filePath - Path to the file (relative to repo)
22
+ * @returns Diff content in unified diff format
23
+ */
24
+ export declare function generateFullFilePatch(repoDir: string, filePath: string): Promise<string>;
25
+ /**
26
+ * Generates a unified diff between base content and current file content.
27
+ * @param repoDir - Repository directory
28
+ * @param filePath - Path to the file (relative to repo)
29
+ * @param baseContent - The base content to diff against
30
+ * @returns Unified diff in git format
31
+ */
32
+ export declare function generateModificationDiff(repoDir: string, filePath: string, baseContent: string): Promise<string>;
33
+ /**
34
+ * Gets the diff for all modified files, including untracked (new) files.
35
+ * @param repoDir - Repository directory
36
+ * @returns Diff content
37
+ */
38
+ export declare function getAllDiff(repoDir: string): Promise<string>;
39
+ /**
40
+ * Builds a combined diff against HEAD for the provided files without touching
41
+ * the real git index. Tracked files use `git diff HEAD`; untracked files use
42
+ * synthesized new-file diffs.
43
+ * @param repoDir - Repository directory
44
+ * @param files - File paths to diff (relative to repo root)
45
+ * @returns Combined diff content
46
+ */
47
+ export declare function getDiffForFilesAgainstHead(repoDir: string, files: string[]): Promise<string>;
48
+ /**
49
+ * Generates a combined diff for staged files against HEAD.
50
+ * @param repoDir - Repository directory
51
+ * @param files - File paths to diff (relative to repo)
52
+ * @returns Diff content for the staged files
53
+ */
54
+ export declare function getStagedDiffForFiles(repoDir: string, files: string[]): Promise<string>;
55
+ /**
56
+ * Generates a GIT binary patch for a binary file.
57
+ * For tracked files, uses `git diff --binary HEAD`.
58
+ * For untracked files, temporarily stages with `--intent-to-add` to produce a diff.
59
+ * @param repoDir - Repository directory
60
+ * @param filePath - File path (relative to repo root)
61
+ * @returns The binary diff string, or empty string if no diff
62
+ */
63
+ export declare function generateBinaryFilePatch(repoDir: string, filePath: string): Promise<string>;