@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,89 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Removal of custom element registrations from customElements.js.
4
+ * Supports three removal strategies: standalone callback, single-line array, multi-line array.
5
+ */
6
+ import { join } from 'node:path';
7
+ import { pathExists, readText, writeText } from '../utils/fs.js';
8
+ import { CUSTOM_ELEMENTS_JS } from './furnace-constants.js';
9
+ /**
10
+ * Removes a custom element registration from customElements.js.
11
+ *
12
+ * This operation is idempotent — if the tag is not registered or the file does
13
+ * not exist, nothing happens.
14
+ *
15
+ * @param engineDir - Path to the Firefox engine source root
16
+ * @param tagName - Custom element tag name to remove
17
+ */
18
+ export async function removeCustomElementRegistration(engineDir, tagName) {
19
+ const filePath = join(engineDir, CUSTOM_ELEMENTS_JS);
20
+ if (!(await pathExists(filePath))) {
21
+ return;
22
+ }
23
+ let content = await readText(filePath);
24
+ const lines = content.split('\n');
25
+ // Strategy 1: Remove standalone callback block (setElementCreationCallback("tagName" …))
26
+ const callbackLine = lines.findIndex((l) => l.includes(`setElementCreationCallback("${tagName}"`));
27
+ if (callbackLine !== -1) {
28
+ let endLine = callbackLine;
29
+ for (let i = callbackLine + 1; i < lines.length; i++) {
30
+ const line = lines[i];
31
+ if (line === undefined)
32
+ continue;
33
+ if (/^\s*\}\);/.test(line)) {
34
+ endLine = i;
35
+ break;
36
+ }
37
+ }
38
+ let startLine = callbackLine;
39
+ const precedingLine = lines[startLine - 1];
40
+ if (startLine > 0 && precedingLine !== undefined && precedingLine.trim() === '') {
41
+ startLine--;
42
+ }
43
+ lines.splice(startLine, endLine - startLine + 1);
44
+ content = lines.join('\n');
45
+ await writeText(filePath, content);
46
+ return;
47
+ }
48
+ // Strategy 2: Remove single-line array entry ["tagName", "..."],
49
+ const singleLineIdx = lines.findIndex((l) => new RegExp(`^\\s*\\["${tagName}",\\s*"[^"]*"\\],?\\s*$`).test(l));
50
+ if (singleLineIdx !== -1) {
51
+ lines.splice(singleLineIdx, 1);
52
+ content = lines.join('\n');
53
+ await writeText(filePath, content);
54
+ return;
55
+ }
56
+ // Strategy 3: Remove multi-line array entry where "tagName", is on its own line
57
+ const multiLineTagIdx = lines.findIndex((l) => new RegExp(`^\\s*"${tagName}",\\s*$`).test(l));
58
+ if (multiLineTagIdx !== -1) {
59
+ // Scan backwards from the tag line to find the opening [ (bounded to 20 lines)
60
+ let startLine = multiLineTagIdx;
61
+ const scanLimit = Math.max(0, multiLineTagIdx - 20);
62
+ for (let i = multiLineTagIdx - 1; i >= scanLimit; i--) {
63
+ const line = lines[i];
64
+ if (line !== undefined && /^\s*\[$/.test(line)) {
65
+ startLine = i;
66
+ break;
67
+ }
68
+ }
69
+ const openIndent = (lines[startLine] ?? '').match(/^(\s*)/)?.[1]?.length ?? 0;
70
+ // Scan forwards from the tag line to find the closing ],
71
+ let endLine = multiLineTagIdx;
72
+ for (let i = multiLineTagIdx + 1; i < lines.length; i++) {
73
+ const line = lines[i];
74
+ if (line !== undefined && /^\s*\],?\s*$/.test(line)) {
75
+ const closeIndent = line.match(/^(\s*)/)?.[1]?.length ?? 0;
76
+ if (closeIndent === openIndent) {
77
+ endLine = i;
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ lines.splice(startLine, endLine - startLine + 1);
83
+ content = lines.join('\n');
84
+ await writeText(filePath, content);
85
+ return;
86
+ }
87
+ // Tag not found in any form — idempotent no-op
88
+ }
89
+ //# sourceMappingURL=furnace-registration-remove.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared validation for furnace custom element registration placement.
3
+ * Used after both AST and legacy code paths to avoid duplicating logic.
4
+ */
5
+ /** Regex for valid custom element tag names. */
6
+ export declare const CUSTOM_ELEMENT_TAG_PATTERN: RegExp;
7
+ /**
8
+ * Validates that a tag name conforms to custom element naming requirements.
9
+ * @throws FurnaceError if the tag name is invalid
10
+ */
11
+ export declare function validateTagName(tagName: string): void;
12
+ /**
13
+ * Validates that a registration entry landed in the correct block
14
+ * (Pattern A = loadSubScript, Pattern B = DOMContentLoaded/importESModule).
15
+ *
16
+ * @param result - The full file content after insertion
17
+ * @param tagName - The tag that was inserted
18
+ * @param isESModule - Whether the module uses ESM (Pattern B) or not (Pattern A)
19
+ */
20
+ export declare function validateRegistrationPlacement(result: string, tagName: string, isESModule: boolean): void;
@@ -0,0 +1,40 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Shared validation for furnace custom element registration placement.
4
+ * Used after both AST and legacy code paths to avoid duplicating logic.
5
+ */
6
+ import { FurnaceError } from '../errors/furnace.js';
7
+ /** Regex for valid custom element tag names. */
8
+ export const CUSTOM_ELEMENT_TAG_PATTERN = /^[a-z][a-z0-9]*-[a-z0-9-]*$/;
9
+ /**
10
+ * Validates that a tag name conforms to custom element naming requirements.
11
+ * @throws FurnaceError if the tag name is invalid
12
+ */
13
+ export function validateTagName(tagName) {
14
+ if (!CUSTOM_ELEMENT_TAG_PATTERN.test(tagName)) {
15
+ throw new FurnaceError(`Invalid tag name "${tagName}": must contain a hyphen and match /^[a-z][a-z0-9]*-[a-z0-9-]*$/`, tagName);
16
+ }
17
+ }
18
+ /**
19
+ * Validates that a registration entry landed in the correct block
20
+ * (Pattern A = loadSubScript, Pattern B = DOMContentLoaded/importESModule).
21
+ *
22
+ * @param result - The full file content after insertion
23
+ * @param tagName - The tag that was inserted
24
+ * @param isESModule - Whether the module uses ESM (Pattern B) or not (Pattern A)
25
+ */
26
+ export function validateRegistrationPlacement(result, tagName, isESModule) {
27
+ const dclPattern = /document\.addEventListener\(\s*["']DOMContentLoaded["']/;
28
+ const insertedPos = result.lastIndexOf(`"${tagName}"`);
29
+ if (insertedPos === -1)
30
+ return;
31
+ const contentBeforeTag = result.slice(0, insertedPos);
32
+ const hasDCLBefore = dclPattern.test(contentBeforeTag);
33
+ if (isESModule && !hasDCLBefore) {
34
+ throw new FurnaceError(`${tagName} was registered in the loadSubScript block (Pattern A) instead of the DOMContentLoaded/importESModule block (Pattern B). This will cause the component to fail at runtime. The customElements.js file structure may have changed upstream — manual intervention required.`, tagName);
35
+ }
36
+ if (!isESModule && hasDCLBefore) {
37
+ throw new FurnaceError(`${tagName} was registered in the DOMContentLoaded/importESModule block (Pattern B) instead of the loadSubScript block (Pattern A). This will cause the component to fail at runtime. The customElements.js file structure may have changed upstream — manual intervention required.`, tagName);
38
+ }
39
+ }
40
+ //# sourceMappingURL=furnace-registration-validate.js.map
@@ -0,0 +1,29 @@
1
+ export { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
2
+ export { addCustomElementRegistration, removeCustomElementRegistration, } from './furnace-registration-ast.js';
3
+ /**
4
+ * Adds jar.mn entries that map chrome:// URIs to on-disk paths for a
5
+ * component's files.
6
+ *
7
+ * Entry format (3-space indent, spaces not tabs):
8
+ * ```
9
+ * content/global/elements/{file} (widgets/{tagName}/{file})
10
+ * ```
11
+ *
12
+ * New entries are inserted in alphabetical order relative to existing
13
+ * `content/global/elements/` entries. The operation is idempotent.
14
+ *
15
+ * @param engineDir - Path to the Firefox engine source root
16
+ * @param tagName - Custom element tag name
17
+ * @param files - Filenames to register (e.g. ["moz-widget.mjs", "moz-widget.css"])
18
+ */
19
+ export declare function addJarMnEntries(engineDir: string, tagName: string, files: string[]): Promise<void>;
20
+ /**
21
+ * Removes all jar.mn entries for a given tag name.
22
+ *
23
+ * This operation is idempotent — if no entries exist or the file is missing,
24
+ * nothing happens.
25
+ *
26
+ * @param engineDir - Path to the Firefox engine source root
27
+ * @param tagName - Custom element tag name whose entries should be removed
28
+ */
29
+ export declare function removeJarMnEntries(engineDir: string, tagName: string): Promise<void>;
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { FurnaceError } from '../errors/furnace.js';
4
+ import { pathExists, readText, writeText } from '../utils/fs.js';
5
+ // Re-export everything from the AST module so existing imports keep working
6
+ export { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
7
+ export { addCustomElementRegistration, removeCustomElementRegistration, } from './furnace-registration-ast.js';
8
+ import { JAR_MN } from './furnace-constants.js';
9
+ /**
10
+ * Adds jar.mn entries that map chrome:// URIs to on-disk paths for a
11
+ * component's files.
12
+ *
13
+ * Entry format (3-space indent, spaces not tabs):
14
+ * ```
15
+ * content/global/elements/{file} (widgets/{tagName}/{file})
16
+ * ```
17
+ *
18
+ * New entries are inserted in alphabetical order relative to existing
19
+ * `content/global/elements/` entries. The operation is idempotent.
20
+ *
21
+ * @param engineDir - Path to the Firefox engine source root
22
+ * @param tagName - Custom element tag name
23
+ * @param files - Filenames to register (e.g. ["moz-widget.mjs", "moz-widget.css"])
24
+ */
25
+ export async function addJarMnEntries(engineDir, tagName, files) {
26
+ const filePath = join(engineDir, JAR_MN);
27
+ if (!(await pathExists(filePath))) {
28
+ throw new FurnaceError('jar.mn not found in engine', tagName);
29
+ }
30
+ let content = await readText(filePath);
31
+ const lines = content.split('\n');
32
+ // Filter to files not already registered
33
+ const newFiles = files.filter((f) => !content.includes(`content/global/elements/${f}`));
34
+ if (newFiles.length === 0)
35
+ return;
36
+ // Build new entry lines
37
+ const newEntries = newFiles.map((f) => ` content/global/elements/${f} (widgets/${tagName}/${f})`);
38
+ // Find insertion point among existing content/global/elements/ lines
39
+ const elementLinePattern = /^\s+content\/global\/elements\/([^.]+)\./;
40
+ let insertIndex = -1;
41
+ for (let i = 0; i < lines.length; i++) {
42
+ const line = lines[i];
43
+ if (line === undefined)
44
+ continue;
45
+ const match = elementLinePattern.exec(line);
46
+ if (match) {
47
+ const existingTag = match[1] ?? '';
48
+ if (existingTag > tagName) {
49
+ insertIndex = i;
50
+ break;
51
+ }
52
+ // Track last element entry line as fallback (insert after it)
53
+ insertIndex = i + 1;
54
+ }
55
+ }
56
+ if (insertIndex === -1) {
57
+ // Fallback: find last content/global/ line
58
+ for (let i = lines.length - 1; i >= 0; i--) {
59
+ const line = lines[i];
60
+ if (line !== undefined && /^\s+content\/global\//.test(line)) {
61
+ insertIndex = i + 1;
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ if (insertIndex === -1) {
67
+ throw new FurnaceError('Could not find insertion point in jar.mn for element entries', tagName);
68
+ }
69
+ lines.splice(insertIndex, 0, ...newEntries);
70
+ content = lines.join('\n');
71
+ await writeText(filePath, content);
72
+ }
73
+ /**
74
+ * Removes all jar.mn entries for a given tag name.
75
+ *
76
+ * This operation is idempotent — if no entries exist or the file is missing,
77
+ * nothing happens.
78
+ *
79
+ * @param engineDir - Path to the Firefox engine source root
80
+ * @param tagName - Custom element tag name whose entries should be removed
81
+ */
82
+ export async function removeJarMnEntries(engineDir, tagName) {
83
+ const filePath = join(engineDir, JAR_MN);
84
+ if (!(await pathExists(filePath))) {
85
+ return;
86
+ }
87
+ let content = await readText(filePath);
88
+ const lines = content.split('\n');
89
+ const pattern = `content/global/elements/${tagName}.`;
90
+ const filtered = lines.filter((line) => !line.includes(pattern));
91
+ if (filtered.length === lines.length)
92
+ return;
93
+ content = filtered.join('\n');
94
+ await writeText(filePath, content);
95
+ }
96
+ //# sourceMappingURL=furnace-registration.js.map
@@ -0,0 +1,20 @@
1
+ interface FileSnapshot {
2
+ existed: boolean;
3
+ content?: Uint8Array;
4
+ mode?: number;
5
+ }
6
+ export interface RollbackJournal {
7
+ files: Map<string, FileSnapshot>;
8
+ createdDirs: Set<string>;
9
+ }
10
+ /** Creates an empty rollback journal for tracking touched files and created directories. */
11
+ export declare function createRollbackJournal(): RollbackJournal;
12
+ /** Records a directory that should be removed if the operation later rolls back. */
13
+ export declare function recordCreatedDir(journal: RollbackJournal, dirPath: string): void;
14
+ /** Snapshots a file once so rollback can restore its previous contents or absence. */
15
+ export declare function snapshotFile(journal: RollbackJournal, filePath: string): Promise<void>;
16
+ /** Restores all snapshotted files and removes directories created during the operation. */
17
+ export declare function restoreRollbackJournal(journal: RollbackJournal): Promise<void>;
18
+ /** Restores a rollback journal and wraps rollback failures in a FurnaceError. */
19
+ export declare function restoreRollbackJournalOrThrow(journal: RollbackJournal, context: string): Promise<void>;
20
+ export {};
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ import { FurnaceError } from '../errors/furnace.js';
5
+ import { toError } from '../utils/errors.js';
6
+ import { pathExists } from '../utils/fs.js';
7
+ /** Creates an empty rollback journal for tracking touched files and created directories. */
8
+ export function createRollbackJournal() {
9
+ return {
10
+ files: new Map(),
11
+ createdDirs: new Set(),
12
+ };
13
+ }
14
+ /** Records a directory that should be removed if the operation later rolls back. */
15
+ export function recordCreatedDir(journal, dirPath) {
16
+ journal.createdDirs.add(dirPath);
17
+ }
18
+ /** Snapshots a file once so rollback can restore its previous contents or absence. */
19
+ export async function snapshotFile(journal, filePath) {
20
+ if (journal.files.has(filePath)) {
21
+ return;
22
+ }
23
+ if (!(await pathExists(filePath))) {
24
+ journal.files.set(filePath, { existed: false });
25
+ return;
26
+ }
27
+ const [content, fileStat] = await Promise.all([readFile(filePath), stat(filePath)]);
28
+ journal.files.set(filePath, {
29
+ existed: true,
30
+ content,
31
+ mode: fileStat.mode,
32
+ });
33
+ }
34
+ async function restoreFile(filePath, snapshot) {
35
+ if (!snapshot.existed) {
36
+ await rm(filePath, { force: true });
37
+ return;
38
+ }
39
+ await mkdir(dirname(filePath), { recursive: true });
40
+ await writeFile(filePath, snapshot.content ?? new Uint8Array());
41
+ if (snapshot.mode !== undefined) {
42
+ await chmod(filePath, snapshot.mode);
43
+ }
44
+ }
45
+ /** Restores all snapshotted files and removes directories created during the operation. */
46
+ export async function restoreRollbackJournal(journal) {
47
+ const fileEntries = [...journal.files.entries()].sort(([left], [right]) => right.length - left.length);
48
+ for (const [filePath, snapshot] of fileEntries) {
49
+ await restoreFile(filePath, snapshot);
50
+ }
51
+ const createdDirs = [...journal.createdDirs].sort((left, right) => right.length - left.length);
52
+ for (const dirPath of createdDirs) {
53
+ await rm(dirPath, { recursive: true, force: true });
54
+ }
55
+ }
56
+ /** Restores a rollback journal and wraps rollback failures in a FurnaceError. */
57
+ export async function restoreRollbackJournalOrThrow(journal, context) {
58
+ try {
59
+ await restoreRollbackJournal(journal);
60
+ }
61
+ catch (error) {
62
+ const message = toError(error).message;
63
+ throw new FurnaceError(`${context}; automatic rollback failed: ${message}`);
64
+ }
65
+ }
66
+ //# sourceMappingURL=furnace-rollback.js.map
@@ -0,0 +1,40 @@
1
+ import type { ScannedComponent } from '../types/furnace.js';
2
+ /**
3
+ * Parses customElements.js to extract tag-to-module mappings.
4
+ *
5
+ * Looks for registration patterns like:
6
+ * ```
7
+ * lazy.customElements.setElementCreationCallback("moz-button", () => {
8
+ * import("chrome://global/content/elements/moz-button.mjs");
9
+ * });
10
+ * ```
11
+ *
12
+ * @param engineDir - Path to the Firefox engine source root
13
+ * @returns Map of tagName to module path
14
+ */
15
+ export declare function scanCustomElementsRegistrations(engineDir: string): Promise<Map<string, string>>;
16
+ /**
17
+ * Scans the widgets directory to discover all MozLitElement custom elements.
18
+ *
19
+ * Each subdirectory starting with `moz-` that contains a `.mjs` file is
20
+ * considered a component. For each component, checks whether it has associated
21
+ * CSS, Fluent localization, and customElements.js registration.
22
+ *
23
+ * @param engineDir - Path to the Firefox engine source root
24
+ * @returns Array of discovered components
25
+ */
26
+ export declare function scanWidgetsDirectory(engineDir: string): Promise<ScannedComponent[]>;
27
+ /**
28
+ * Gets detailed information about a single component by tag name.
29
+ * @param engineDir - Path to the Firefox engine source root
30
+ * @param tagName - Component tag name (e.g., "moz-button")
31
+ * @returns Component details, or null if not found in the source tree
32
+ */
33
+ export declare function getComponentDetails(engineDir: string, tagName: string): Promise<ScannedComponent | null>;
34
+ /**
35
+ * Checks whether a component directory exists in the engine source tree.
36
+ * @param engineDir - Path to the Firefox engine source root
37
+ * @param tagName - Component tag name (e.g., "moz-button")
38
+ * @returns True if the component directory exists
39
+ */
40
+ export declare function isComponentInEngine(engineDir: string, tagName: string): Promise<boolean>;
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { pathExists, readText } from '../utils/fs.js';
5
+ import { CUSTOM_ELEMENTS_JS } from './furnace-constants.js';
6
+ /** Path to the widgets directory within the engine source tree */
7
+ const WIDGETS_DIR = 'toolkit/content/widgets';
8
+ /** Path to the Fluent localization directory for toolkit global components */
9
+ const FTL_DIR = 'toolkit/locales/en-US/toolkit/global';
10
+ /**
11
+ * Parses customElements.js to extract tag-to-module mappings.
12
+ *
13
+ * Looks for registration patterns like:
14
+ * ```
15
+ * lazy.customElements.setElementCreationCallback("moz-button", () => {
16
+ * import("chrome://global/content/elements/moz-button.mjs");
17
+ * });
18
+ * ```
19
+ *
20
+ * @param engineDir - Path to the Firefox engine source root
21
+ * @returns Map of tagName to module path
22
+ */
23
+ export async function scanCustomElementsRegistrations(engineDir) {
24
+ const registrations = new Map();
25
+ const filePath = join(engineDir, CUSTOM_ELEMENTS_JS);
26
+ if (!(await pathExists(filePath))) {
27
+ return registrations;
28
+ }
29
+ const content = await readText(filePath);
30
+ const lines = content.split('\n');
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const line = lines[i];
33
+ if (line === undefined)
34
+ continue;
35
+ const callbackMatch = /setElementCreationCallback\(\s*"([^"]+)"/.exec(line);
36
+ if (!callbackMatch?.[1])
37
+ continue;
38
+ const tagName = callbackMatch[1];
39
+ let modulePath = '';
40
+ // Search the following lines for an import() statement
41
+ const searchEnd = Math.min(i + 5, lines.length);
42
+ for (let j = i + 1; j < searchEnd; j++) {
43
+ const importLine = lines[j];
44
+ if (importLine === undefined)
45
+ continue;
46
+ const importMatch = /import\(\s*"([^"]+)"/.exec(importLine);
47
+ if (importMatch?.[1]) {
48
+ modulePath = importMatch[1];
49
+ break;
50
+ }
51
+ }
52
+ if (!modulePath) {
53
+ // No module path found in the lookahead lines; skip this entry
54
+ continue;
55
+ }
56
+ registrations.set(tagName, modulePath);
57
+ }
58
+ return registrations;
59
+ }
60
+ /**
61
+ * Scans the widgets directory to discover all MozLitElement custom elements.
62
+ *
63
+ * Each subdirectory starting with `moz-` that contains a `.mjs` file is
64
+ * considered a component. For each component, checks whether it has associated
65
+ * CSS, Fluent localization, and customElements.js registration.
66
+ *
67
+ * @param engineDir - Path to the Firefox engine source root
68
+ * @returns Array of discovered components
69
+ */
70
+ export async function scanWidgetsDirectory(engineDir) {
71
+ const widgetsPath = join(engineDir, WIDGETS_DIR);
72
+ if (!(await pathExists(widgetsPath))) {
73
+ return [];
74
+ }
75
+ const entries = await readdir(widgetsPath, { withFileTypes: true });
76
+ const registrations = await scanCustomElementsRegistrations(engineDir);
77
+ const components = [];
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory() || !entry.name.startsWith('moz-')) {
80
+ continue;
81
+ }
82
+ const tagName = entry.name;
83
+ const componentDir = join(widgetsPath, tagName);
84
+ const componentEntries = await readdir(componentDir, { withFileTypes: true });
85
+ // Only include directories that contain a .mjs file
86
+ const hasMjs = componentEntries.some((e) => e.isFile() && e.name.endsWith('.mjs'));
87
+ if (!hasMjs) {
88
+ continue;
89
+ }
90
+ const hasCSS = componentEntries.some((e) => e.isFile() && e.name.endsWith('.css'));
91
+ const ftlPath = join(engineDir, FTL_DIR, `${tagName}.ftl`);
92
+ const hasFTL = await pathExists(ftlPath);
93
+ const isRegistered = registrations.has(tagName);
94
+ components.push({
95
+ tagName,
96
+ sourcePath: join(WIDGETS_DIR, tagName),
97
+ hasCSS,
98
+ hasFTL,
99
+ isRegistered,
100
+ });
101
+ }
102
+ return components;
103
+ }
104
+ /**
105
+ * Gets detailed information about a single component by tag name.
106
+ * @param engineDir - Path to the Firefox engine source root
107
+ * @param tagName - Component tag name (e.g., "moz-button")
108
+ * @returns Component details, or null if not found in the source tree
109
+ */
110
+ export async function getComponentDetails(engineDir, tagName) {
111
+ const componentDir = join(engineDir, WIDGETS_DIR, tagName);
112
+ if (!(await pathExists(componentDir))) {
113
+ return null;
114
+ }
115
+ const entries = await readdir(componentDir, { withFileTypes: true });
116
+ const hasMjs = entries.some((e) => e.isFile() && e.name.endsWith('.mjs'));
117
+ if (!hasMjs) {
118
+ return null;
119
+ }
120
+ const hasCSS = entries.some((e) => e.isFile() && e.name.endsWith('.css'));
121
+ const ftlPath = join(engineDir, FTL_DIR, `${tagName}.ftl`);
122
+ const hasFTL = await pathExists(ftlPath);
123
+ const registrations = await scanCustomElementsRegistrations(engineDir);
124
+ const isRegistered = registrations.has(tagName);
125
+ return {
126
+ tagName,
127
+ sourcePath: join(WIDGETS_DIR, tagName),
128
+ hasCSS,
129
+ hasFTL,
130
+ isRegistered,
131
+ };
132
+ }
133
+ /**
134
+ * Checks whether a component directory exists in the engine source tree.
135
+ * @param engineDir - Path to the Firefox engine source root
136
+ * @param tagName - Component tag name (e.g., "moz-button")
137
+ * @returns True if the component directory exists
138
+ */
139
+ export async function isComponentInEngine(engineDir, tagName) {
140
+ const componentDir = join(engineDir, WIDGETS_DIR, tagName);
141
+ return pathExists(componentDir);
142
+ }
143
+ //# sourceMappingURL=furnace-scanner.js.map
@@ -0,0 +1,37 @@
1
+ import type { ComponentType, SyncResult } from '../types/furnace.js';
2
+ /**
3
+ * Generates the full content of a Storybook `.stories.mjs` file for a
4
+ * Furnace component.
5
+ *
6
+ * @param tagName - Component tag name (e.g. "moz-button")
7
+ * @param displayName - Human-readable name (e.g. "Button")
8
+ * @param type - Component classification (stock, override, custom)
9
+ * @returns Complete story file content
10
+ */
11
+ export declare function generateStoryContent(tagName: string, displayName: string, type: ComponentType, licenseHeader?: string, modulePath?: string): string;
12
+ /**
13
+ * Returns the path to the Storybook stories directory in the engine.
14
+ *
15
+ * @param engineDir - Path to the Firefox engine source root
16
+ * @returns Absolute path to `browser/components/storybook/stories/`
17
+ */
18
+ export declare function getStoriesDir(engineDir: string): string;
19
+ /**
20
+ * Synchronises Storybook story files for all Furnace-managed components.
21
+ *
22
+ * - Stock components: story created only if not already present.
23
+ * - Override components: story always regenerated.
24
+ * - Custom components: story always regenerated.
25
+ * - Stale story files (for components no longer in furnace.json) are removed.
26
+ *
27
+ * @param root - Root directory of the project
28
+ * @returns Summary of created, updated, and removed story files
29
+ */
30
+ export declare function syncStories(root: string): Promise<SyncResult>;
31
+ /**
32
+ * Removes the entire `stories/furnace/` directory from the engine.
33
+ *
34
+ * @param engineDir - Path to the Firefox engine source root
35
+ * @returns Number of files that were removed
36
+ */
37
+ export declare function cleanStories(engineDir: string): Promise<number>;