@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
package/README.md ADDED
@@ -0,0 +1,435 @@
1
+ # FireForge
2
+
3
+ **Build and maintain your own Firefox-based browser with a patch-first workflow.**
4
+
5
+ FireForge gives you a toolkit for forking Firefox: download a specific ESR release, manage your customisations as an ordered stack of contextual patches, survive version upgrades with semi-automated rebase, wire custom code into Mozilla's startup paths, and build the result. It also ships **Furnace**, a component system for creating and overriding Firefox custom elements.
6
+
7
+ Inspired by [fern.js](https://github.com/nicktrosper/user-agent-desktop?tab=readme-ov-file#user-agent-desktop) and [Melon](https://github.com/nicktrosper/nicktrosper-melon).
8
+
9
+ ---
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ mkdir mybrowser && cd mybrowser
15
+ npm init -y
16
+ npm install --save-dev fireforge
17
+
18
+ npx fireforge setup # interactive project init
19
+ npx fireforge download # fetch Firefox source (~1 GB)
20
+ npx fireforge bootstrap # install build deps (may need sudo)
21
+ npx fireforge import # apply your patches (if any exist)
22
+ npx fireforge build # build the browser
23
+ npx fireforge run # launch it
24
+ ```
25
+
26
+ Your project now has `fireforge.json`, an `engine/` directory with Firefox source, and a `patches/` directory ready for your first customisation.
27
+
28
+ A few things worth noting here: `bootstrap` may prompt for elevated permissions depending on your platform and what build dependencies are already present. This is not something we can avoid, since Mozilla's own `mach bootstrap` requires it, and wrapping that in our own sudo logic would be worse in every way that matters.
29
+
30
+ ---
31
+
32
+ ## Core Workflow
33
+
34
+ ```bash
35
+ # 1. Make changes inside engine/
36
+ # Edit browser/base/content/browser.js, add CSS, create new modules...
37
+
38
+ # 2. Export your changes as a patch
39
+ npx fireforge export browser/base/content/browser.js \
40
+ --name "custom-toolbar" --category ui
41
+
42
+ # 3. Your patch is now in patches/001-ui-custom-toolbar.patch
43
+ # with metadata tracked in patches/patches.json
44
+
45
+ # 4. Later, reset and replay to verify everything applies cleanly
46
+ npx fireforge reset --force
47
+ npx fireforge import
48
+
49
+ # 5. When Firefox releases a new ESR, update fireforge.json, re-download, and rebase
50
+ npx fireforge download --force
51
+ npx fireforge rebase
52
+ ```
53
+
54
+ The reason your customisations live as patches rather than as a permanent fork branch is, in fairness, a trade-off. Branches are more familiar, but they make upstream merges progressively harder as your changes grow, and they obscure which modifications are intentional versus which are artefacts of merge resolution. Patches are explicit. Each one is a discrete, portable unit of intent. The cost is that you need tooling to manage the stack, which is what FireForge exists to provide.
55
+
56
+ ---
57
+
58
+ ## What You Get
59
+
60
+ - **Patch-based fork management.** Your customisations live as portable, ordered `.patch` files. Export single files, multiple paths, or everything at once. Contextual diffs mean upstream security fixes are not silently dropped when you rebase. This matters more then it might seem at first, because silent patch drift in a browser fork is the sort of bug that only surfaces when someone audits your security posture six months later.
61
+
62
+ - **Semi-automated ESR rebase.** `fireforge rebase` replays your patch stack onto new Firefox source with escalating fuzz matching. When a patch fails, you fix it manually and `--continue`. The full stack gets re-exported with updated version stamps. It would be cleaner to make this fully automatic, of course, but patches that touch the same code as upstream changes genuinely require human judgement about which intent should win. Pretending otherwise would be worse.
63
+
64
+ - **Wiring and registration.** `fireforge wire` and `fireforge register` inject your code into Mozilla's startup paths, build manifests, and JAR files with a single command. The injection is AST-based (via Acorn), not regex-based, which means it survives the formatting changes that Mozilla applies between versions. This is less about elegance then about not breaking on every minor release.
65
+
66
+ - **Furnace component system.** Override existing Firefox custom elements (CSS-only or full fork) or create new ones. Storybook preview included. The component types are `stock` (tracked for preview, no local files), `override` (CSS-only restyle or full behavioural fork), and `custom` (entirely new elements).
67
+
68
+ - **Design token management.** Track CSS custom property coverage across your modified files. This exists because raw colour values in a browser fork become a maintenance problem faster then you would expect, and catching them at export time is considerably cheaper then catching them during a visual regression review.
69
+
70
+ - **Quality checks.** `fireforge lint` catches fork-specific issues (raw colours, missing licence headers, relative imports, large patches) before you export. `fireforge doctor` diagnoses project health. These are not redundant with Mozilla's own `./mach lint`, which does not know about your fork-specific conventions.
71
+
72
+ - **Tested against real Firefox source.** We run end-to-end test passes against actual Firefox ESR 140 source code as part of development. The in-repo test suite is derived from those real-world runs, reflecting actual developer scenarios (multi-file patches, conflict resolution, manifest recovery, binary assets) without requiring 30 GB of Firefox source to execute. 1400+ tests run in under 15 seconds.
73
+
74
+ ---
75
+
76
+ ## Requirements
77
+
78
+ - **Node.js 20+.** Version 18 will appear to work initially, but the native fetch usage in the request layer will fail silently in certain edge cases, which is the sort of thing you would rather discover now.
79
+ - **Python 3** (required by Firefox's `mach` build system).
80
+ - **Git.**
81
+ - Platform build tools: Xcode on macOS, `build-essential` on Linux, Visual Studio on Windows. If you are on an M-series Mac and encounter native module compilation errors, this is almost certainly the `sharp` dependency in your broader toolchain, not FireForge itself.
82
+
83
+ ---
84
+
85
+ ## Patch Workflow
86
+
87
+ Patches live in `patches/`, applied by numeric filename prefix, and tracked in `patches/patches.json`:
88
+
89
+ ```
90
+ patches/
91
+ 001-branding-custom-logo.patch
92
+ 002-privacy-disable-telemetry.patch
93
+ 003-ui-sidebar-tweaks.patch
94
+ patches.json
95
+ ```
96
+
97
+ **Categories:** `branding` | `ui` | `privacy` | `security` | `infra`
98
+
99
+ The category system is intentionally coarse. Finer-grained categorisation sounds appealing in theory, but in practice it creates more classification arguments then it resolves, and the numeric ordering already handles sequencing.
100
+
101
+ ### Exporting changes
102
+
103
+ ```bash
104
+ # Single file
105
+ fireforge export browser/base/content/browser.js
106
+
107
+ # Multiple paths with metadata
108
+ fireforge export browser/modules/mybrowser/*.sys.mjs \
109
+ --name "storage-infra" --category infra
110
+
111
+ # Everything at once
112
+ fireforge export-all --name "all-changes" --category ui
113
+
114
+ # Regenerate patches after further edits
115
+ fireforge re-export --all --scan
116
+ ```
117
+
118
+ ### Rebasing onto a new Firefox version
119
+
120
+ 1. Update `firefox.version` in `fireforge.json`
121
+ 2. `fireforge download --force`
122
+ 3. `fireforge rebase`
123
+ 4. Fix any rejects, then `fireforge rebase --continue`
124
+ 5. If stuck, `fireforge rebase --abort` to restore the pre-rebase state
125
+
126
+ Mind you, the rebase process is deliberately conservative: it will stop at the first patch that cannot be applied cleanly rather then guessing at a resolution and potentially corrupting your intent. This is slower but considerably safer, especially for security-sensitive patches where a misapplied hunk could silently undo a fix.
127
+
128
+ ### Resolving conflicts
129
+
130
+ When `fireforge import` fails on a patch, fix the `.rej` files in `engine/`, then:
131
+
132
+ ```bash
133
+ fireforge resolve
134
+ ```
135
+
136
+ This re-exports the fixed patch and continues applying the remaining stack.
137
+
138
+ <details>
139
+ <summary>Patch manifest format</summary>
140
+
141
+ `patches/patches.json` is updated automatically by `export` and `re-export`:
142
+
143
+ ```json
144
+ {
145
+ "version": 1,
146
+ "patches": [
147
+ {
148
+ "filename": "001-branding-custom-logo.patch",
149
+ "order": 1,
150
+ "category": "branding",
151
+ "name": "custom-logo",
152
+ "description": "Replaces default Firefox branding with custom logo",
153
+ "createdAt": "2025-01-15T10:30:00Z",
154
+ "sourceEsrVersion": "140.0esr",
155
+ "filesAffected": ["browser/branding/official/logo.png"]
156
+ }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ If the manifest drifts after an interrupted export or manual edits, `fireforge import` will stop rather then silently applying a stale stack. Use `fireforge doctor --repair-patches-manifest` to rebuild it from disk. The rebuild is deterministic: it reads patch headers, not cached state, so the result is always consistent with what is actually on the filesystem.
162
+
163
+ </details>
164
+
165
+ <details>
166
+ <summary>Patch lint checks</summary>
167
+
168
+ `fireforge lint` runs automatically during export. Use `--skip-lint` to downgrade errors to warnings, though I would recommend against making that a habit.
169
+
170
+ | Check | Scope | Severity |
171
+ | ------------------------------ | ------------------------------- | -------- |
172
+ | `missing-license-header` | New files (JS/CSS/FTL) | error |
173
+ | `relative-import` | JS/MJS files | error |
174
+ | `token-prefix-violation` | CSS files (with furnace) | error |
175
+ | `raw-color-value` | CSS files | warning |
176
+ | `missing-modification-comment` | Modified upstream JS/MJS | warning |
177
+ | `file-too-large` | New files >650 lines | warning |
178
+ | `missing-jsdoc` | Exports in new `.sys.mjs` | warning |
179
+ | `observer-topic-naming` | Observer topics with binaryName | warning |
180
+ | `large-patch-files` | Patches affecting >5 files | warning |
181
+ | `large-patch-lines` | Patches >300 lines | warning |
182
+
183
+ These catch fork-specific issues that Mozilla's `./mach lint` does not cover. The severity levels are not arbitrary: errors block export because they indicate structural problems that will cause failures downstream, while warnings flag things that are suboptimal but not immediately dangerous.
184
+
185
+ </details>
186
+
187
+ ---
188
+
189
+ ## Wiring Custom Code
190
+
191
+ ```bash
192
+ # Wire a subscript with init/destroy lifecycle
193
+ fireforge wire my-widget --init "MyWidget.init()" --destroy "MyWidget.destroy()"
194
+
195
+ # Register a file in the correct build manifest
196
+ fireforge register browser/modules/mybrowser/MyStore.sys.mjs
197
+
198
+ # Both support --dry-run to preview changes
199
+ ```
200
+
201
+ <details>
202
+ <summary>Wire options</summary>
203
+
204
+ - **Subscript** (always): Adds `loadSubScript` call to `browser-main.js`
205
+ - **`--init <expr>`**: Adds init expression to `gBrowserInit.onLoad()` in `browser-init.js`
206
+ - **`--destroy <expr>`**: Adds destroy expression to `onUnload()` (LIFO ordering, which matters because destroy handlers that run in the wrong order can leave dangling references)
207
+ - **`--after <name>`**: Controls ordering between dependent subscripts
208
+ - **`--dom <file>`**: Inserts `#include` directive for `.inc.xhtml` into `browser.xhtml`
209
+ - **`--subscript-dir <dir>`**: Override the subscript directory (default: `browser/base/content`)
210
+
211
+ </details>
212
+
213
+ <details>
214
+ <summary>Supported register patterns</summary>
215
+
216
+ | File pattern | Manifest | Entry format |
217
+ | ------------------------------------------ | ------------------------------------- | ----------------------------------- |
218
+ | `browser/themes/shared/*.css` | `browser/themes/shared/jar.inc.mn` | `skin/classic/browser/{name}.css` |
219
+ | `browser/base/content/*.{js,mjs}` | `browser/base/jar.mn` | `content/browser/{file}` |
220
+ | `browser/base/content/test/*/browser.toml` | `browser/base/moz.build` | `"content/test/{dir}/browser.toml"` |
221
+ | `browser/modules/mybrowser/*.sys.mjs` | `browser/modules/mybrowser/moz.build` | `"{name}.sys.mjs"` |
222
+ | `toolkit/content/widgets/*/*.{mjs,css}` | `toolkit/content/jar.mn` | `content/global/elements/{file}` |
223
+
224
+ </details>
225
+
226
+ ---
227
+
228
+ ## Furnace (Component System)
229
+
230
+ ```bash
231
+ fireforge furnace scan # discover available components
232
+ fireforge furnace override moz-button -t css-only # fork an existing one
233
+ fireforge furnace create moz-my-widget # create a new one
234
+ fireforge furnace deploy --dry-run # preview
235
+ fireforge furnace deploy # apply + validate
236
+ ```
237
+
238
+ Furnace manages Firefox custom elements (`MozLitElement`). Override stock components with CSS-only restyles or full forks, or scaffold entirely new ones. Changes are applied to `engine/` and then captured by the patch system, which means Furnace is not a separate persistence layer; it feeds into the same patch workflow as everything else.
239
+
240
+ <details>
241
+ <summary>Component types</summary>
242
+
243
+ | Type | Description | Local files |
244
+ | ------------ | ----------------------------------------------------------------- | ------------------------------ |
245
+ | **Stock** | Engine components tracked for Storybook preview | None |
246
+ | **Override** | Forked copies: `css-only` (restyle) or `full` (behaviour + style) | `components/overrides/<name>/` |
247
+ | **Custom** | New elements that do not exist in Firefox | `components/custom/<name>/` |
248
+
249
+ </details>
250
+
251
+ <details>
252
+ <summary>Validation checks</summary>
253
+
254
+ Furnace validates components on deploy. Errors block apply; warnings are advisory. The distinction is not about severity in the abstract but about whether a violation will cause a runtime failure versus a maintenance headache.
255
+
256
+ | Check | Severity | Description |
257
+ | ------------------------ | -------- | ------------------------------------------- |
258
+ | `missing-mjs` | error | Custom component missing `.mjs` file |
259
+ | `missing-css` | warning | No `.css` file |
260
+ | `filename-mismatch` | error | File name does not match tag name |
261
+ | `missing-override-json` | error | Override missing `override.json` |
262
+ | `no-aria-role` | warning | No ARIA role found |
263
+ | `no-keyboard-handler` | warning | Has `@click` but no keyboard handler |
264
+ | `relative-import` | error | Imports must use `chrome://` URIs |
265
+ | `raw-color-value` | error | Raw hex/rgb/hsl (use CSS custom properties) |
266
+ | `token-prefix-violation` | error | CSS variable does not match `tokenPrefix` |
267
+
268
+ </details>
269
+
270
+ <details>
271
+ <summary>furnace.json schema</summary>
272
+
273
+ ```jsonc
274
+ {
275
+ "version": 1,
276
+ "componentPrefix": "moz-",
277
+ "stock": ["moz-button", "moz-toggle"],
278
+ "overrides": {
279
+ "moz-button": {
280
+ "type": "css-only",
281
+ "description": "Custom button styles",
282
+ "basePath": "toolkit/content/widgets/moz-button",
283
+ "baseVersion": "134.0",
284
+ },
285
+ },
286
+ "custom": {
287
+ "moz-my-widget": {
288
+ "description": "A new widget",
289
+ "targetPath": "toolkit/content/widgets/moz-my-widget",
290
+ "register": true,
291
+ "localized": false,
292
+ "composes": ["moz-button"],
293
+ },
294
+ },
295
+ }
296
+ ```
297
+
298
+ </details>
299
+
300
+ ---
301
+
302
+ ## Configuration
303
+
304
+ `fireforge.json` at your project root:
305
+
306
+ ```json
307
+ {
308
+ "name": "MyBrowser",
309
+ "vendor": "My Company",
310
+ "appId": "org.example.mybrowser",
311
+ "binaryName": "mybrowser",
312
+ "license": "EUPL-1.2",
313
+ "firefox": {
314
+ "version": "140.0esr",
315
+ "product": "firefox-esr"
316
+ },
317
+ "build": { "jobs": 8 },
318
+ "wire": { "subscriptDir": "browser/components/mybrowser" }
319
+ }
320
+ ```
321
+
322
+ Use `fireforge config <key> [value]` to read or update values. Run `fireforge --help` and `fireforge <command> --help` for the full option reference. The `jobs` value defaults to your CPU core count, which is usually reasonable but not always optimal; Firefox's build system has enough sequential bottlenecks that doubling your core count does not halve your build time, for what it is worth.
323
+
324
+ ---
325
+
326
+ ## Testing Methodology
327
+
328
+ FireForge's test suite is designed around a constraint that, at least in my experience, does not get enough attention: realistic tests do not have to be slow.
329
+
330
+ ### Real Firefox validation
331
+
332
+ We run full end-to-end test passes against real Firefox ESR 140 source code in a production fork setup. These validate the entire workflow: setup, download, bootstrap, build, export, import, discard, and recovery.
333
+
334
+ ### Derived in-repo tests
335
+
336
+ The 1400+ in-repo tests are not idealised mocks. They are derived from those real Firefox runs. Every fixture, edge case, and scenario was first observed against actual Firefox source, then distilled into a deterministic test that runs in seconds. Examples:
337
+
338
+ - CSS design tokens with `light-dark(#hex)` from a real 348-line tokens file
339
+ - BrowserGlue lazy import with `// BRAND:` markers from a real 2-hunk modification
340
+ - Multi-file theme patches spanning CSS + manifest + build system from real patches
341
+ - Observer topic regex edge cases from a real `notifyObservers` call
342
+
343
+ This means the fast test suite covers the same behavioural surface as the full-tree runs, without requiring 30 GB of Firefox source. It would be fair to call the fixtures "synthetic" in the sense that they are not the original files, but the scenarios they encode are not invented; they are reproductions of real behaviour we observed during development.
344
+
345
+ <details>
346
+ <summary>Running the full-tree suite</summary>
347
+
348
+ The opt-in full-tree suite exercises a connected workflow against a prepared Firefox project:
349
+
350
+ ```bash
351
+ FIREFORGE_FULL_PROJECT_ROOT=/path/to/project npm run test:firefox-full
352
+ ```
353
+
354
+ Optional environment variables:
355
+
356
+ - `FIREFORGE_FULL_BUILD_MODE=ui|full` (defaults to `ui`)
357
+ - `FIREFORGE_FULL_TARGET_FILE=browser/base/content/browser.js` (override the target file for export/import)
358
+ - `FIREFORGE_FULL_KEEP_PATCH=1` (keep the temporary patch instead of cleaning up)
359
+ - `FIREFORGE_FULL_SKIP_SETUP=1` (skip `setup --force` for an already-prepared project)
360
+
361
+ Each run writes artefacts under `.fireforge/full-integration-artifacts/<timestamp>/` in the target project.
362
+
363
+ </details>
364
+
365
+ ---
366
+
367
+ <details>
368
+ <summary>Programmatic API</summary>
369
+
370
+ > **Pre-1.0 stability notice.** FireForge is at v0.9.x. The programmatic API
371
+ > exported from the main package entry point is functional and tested, but
372
+ > may change between minor versions until 1.0. Pin your dependency to an
373
+ > exact version if you rely on it. I would rather be honest about this then
374
+ > pretend the API surface is frozen when it is not.
375
+
376
+ FireForge can be used as a library in addition to the CLI:
377
+
378
+ ```typescript
379
+ import { loadConfig, validateConfig, applyAllComponents, loadFurnaceConfig } from 'fireforge';
380
+ ```
381
+
382
+ ### Exported functions
383
+
384
+ | Function | Module | Purpose |
385
+ | ----------------------- | ---------------- | ------------------------------------------ |
386
+ | `loadConfig` | config | Load and parse `fireforge.json` |
387
+ | `validateConfig` | config | Validate a config object |
388
+ | `applyAllComponents` | furnace-apply | Apply all Furnace components to the engine |
389
+ | `ensureFurnaceConfig` | furnace-config | Create `furnace.json` if missing |
390
+ | `loadFurnaceConfig` | furnace-config | Load and parse `furnace.json` |
391
+ | `loadFurnaceState` | furnace-config | Load Furnace runtime state |
392
+ | `saveFurnaceState` | furnace-config | Persist Furnace runtime state |
393
+ | `validateFurnaceConfig` | furnace-config | Validate a Furnace config |
394
+ | `validateAllComponents` | furnace-validate | Validate all registered components |
395
+ | `validateComponent` | furnace-validate | Validate a single component |
396
+ | `addToken` | token-manager | Add a design token |
397
+ | `getTokensCssPath` | token-manager | Get the path to the tokens CSS file |
398
+ | `validateTokenAdd` | token-manager | Validate a token before adding |
399
+
400
+ ### Exported types
401
+
402
+ All configuration and result types are exported (`FireForgeConfig`, `FurnaceConfig`, `BuildConfig`, `ApplyResult`, `PatchInfo`, etc.). See `src/types/index.ts` for the full list.
403
+
404
+ ### Error classes
405
+
406
+ All error classes extend `FireForgeError`:
407
+
408
+ - `CancellationError` (user-initiated cancellation)
409
+ - `CommandError` (CLI command failure)
410
+ - `GeneralError` (catch-all for unexpected failures)
411
+ - `InvalidArgumentError` (bad input)
412
+ - `ResolutionError` (dependency resolution failure)
413
+
414
+ Use `ExitCode` for programmatic exit code handling.
415
+
416
+ </details>
417
+
418
+ ---
419
+
420
+ ## Roadmap
421
+
422
+ These are planned but not yet implemented. I mention them here for transparency rather then as a promise, since priorities shift and some of these may prove harder then they look.
423
+
424
+ - **Docker builds.** Reproducible builds using Docker containers. The main challenge is keeping the image size reasonable given Firefox's build dependency tree.
425
+ - **CI mode.** Automated setup for continuous integration pipelines.
426
+ - **Update manifests.** Generate update server manifests for auto-updates.
427
+ - **Nightly support.** This requires `hg clone` from mozilla-central rather then the archive download path, which is a meaningfully different code path.
428
+
429
+ ---
430
+
431
+ ## Licence
432
+
433
+ [EUPL-1.2](LICENSE.md). Firefox source in `engine/` is under [MPL-2.0](https://www.mozilla.org/en-US/MPL/2.0/) and is not distributed by this repository.
434
+
435
+ During `fireforge setup`, you choose a licence for your project files. Options: EUPL-1.2 (default), MPL-2.0, 0BSD, GPL-2.0-or-later. Firefox-derived files from Furnace always carry MPL-2.0 headers, because that is what the upstream licence requires regardless of your project-level choice.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FireForge CLI entry point.
4
+ *
5
+ * This is the only file that should call process.exit().
6
+ * All shared library code propagates errors via CommandError or
7
+ * FireForgeError — never by terminating the process directly.
8
+ *
9
+ */
10
+ export {};
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: EUPL-1.2
3
+ /**
4
+ * FireForge CLI entry point.
5
+ *
6
+ * This is the only file that should call process.exit().
7
+ * All shared library code propagates errors via CommandError or
8
+ * FireForgeError — never by terminating the process directly.
9
+ *
10
+ */
11
+ import { installBrokenPipeHandler, main } from '../src/cli.js';
12
+ import { CommandError } from '../src/errors/base.js';
13
+ installBrokenPipeHandler();
14
+ process.on('unhandledRejection', (reason) => {
15
+ console.error('Fatal error (unhandled rejection):', reason instanceof Error ? reason.message : reason);
16
+ if (reason instanceof Error && reason.stack) {
17
+ console.error(reason.stack);
18
+ }
19
+ process.exit(1);
20
+ });
21
+ main().catch((error) => {
22
+ if (error instanceof CommandError) {
23
+ process.exit(error.exitCode);
24
+ }
25
+ // Truly unexpected — CommandError should have been thrown by withErrorHandling
26
+ console.error('Fatal error:', error);
27
+ process.exit(1);
28
+ });
29
+ //# sourceMappingURL=fireforge.js.map
@@ -0,0 +1,33 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Installs a handler for broken-pipe (EPIPE) errors on stdout/stderr.
4
+ * This is a process-level concern: when output is piped to a process that
5
+ * closes early (e.g. `fireforge status | head`), Node emits EPIPE.
6
+ * We treat this as a clean exit.
7
+ */
8
+ export declare function installBrokenPipeHandler(): void;
9
+ /** Removes the broken-pipe handler installed for CLI tests. */
10
+ export declare function resetBrokenPipeHandlerForTests(): void;
11
+ /**
12
+ * Gets the project root directory.
13
+ * Walks up from the current working directory until a fireforge.json is found.
14
+ * Falls back to the current working directory when no project root is found.
15
+ */
16
+ export declare function getProjectRoot(): string;
17
+ /**
18
+ * Wraps a command handler with error handling.
19
+ *
20
+ * Logs the user-visible error message and throws a {@link CommandError}
21
+ * carrying the appropriate exit code. The actual `process.exit()` call
22
+ * lives in the CLI entrypoint (`bin/fireforge.ts`), keeping shared library
23
+ * code free of process-terminating side effects.
24
+ */
25
+ export declare function withErrorHandling<T extends unknown[]>(handler: (...args: T) => Promise<void>): (...args: T) => Promise<void>;
26
+ /**
27
+ * Creates and configures the CLI program.
28
+ */
29
+ export declare function createProgram(): Command;
30
+ /**
31
+ * Main CLI entry point.
32
+ */
33
+ export declare function main(): Promise<void>;