@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,91 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { getProjectPaths } from '../core/config.js';
5
+ import { buildArtifactMismatchMessage, hasBuildArtifacts, run } from '../core/mach.js';
6
+ import { GeneralError } from '../errors/base.js';
7
+ import { AmbiguousBuildArtifactsError, BuildError } from '../errors/build.js';
8
+ import { toError } from '../utils/errors.js';
9
+ import { pathExists, removeDir, removeFile } from '../utils/fs.js';
10
+ import { info, intro, verbose } from '../utils/logger.js';
11
+ /**
12
+ * Cleans the dev profile to prevent stale-state startup failures.
13
+ *
14
+ * Removes two things:
15
+ * 1. **startupCache/** — Firefox caches compiled chrome JS bytecode here.
16
+ * When chrome scripts change between builds, the stale cache causes silent
17
+ * crashes on startup.
18
+ * 2. **.parentlock** — A zero-byte lock file that persists if the previous
19
+ * session was killed (Ctrl-C, crash, `kill`). Firefox checks this on
20
+ * startup and silently exits if it exists, assuming another instance owns
21
+ * the profile.
22
+ *
23
+ * @param engineDir - Path to the engine directory
24
+ */
25
+ async function cleanDevProfile(engineDir) {
26
+ try {
27
+ const entries = await readdir(engineDir);
28
+ const objDirs = entries.filter((e) => e.startsWith('obj-'));
29
+ if (objDirs.length === 0) {
30
+ return;
31
+ }
32
+ for (const objDir of objDirs) {
33
+ const profileDir = join(engineDir, objDir, 'tmp', 'profile-default');
34
+ const cachePath = join(profileDir, 'startupCache');
35
+ if (await pathExists(cachePath)) {
36
+ await removeDir(cachePath);
37
+ }
38
+ const lockPath = join(profileDir, '.parentlock');
39
+ if (await pathExists(lockPath)) {
40
+ await removeFile(lockPath);
41
+ }
42
+ }
43
+ }
44
+ catch (error) {
45
+ verbose(`Non-fatal dev profile cleanup failure: ${toError(error).message}`);
46
+ }
47
+ }
48
+ /**
49
+ * Runs the run command to launch the built browser.
50
+ * @param projectRoot - Root directory of the project
51
+ */
52
+ export async function runCommand(projectRoot) {
53
+ intro('FireForge Run');
54
+ const paths = getProjectPaths(projectRoot);
55
+ // Check if engine exists
56
+ if (!(await pathExists(paths.engine))) {
57
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
58
+ }
59
+ const buildCheck = await hasBuildArtifacts(paths.engine);
60
+ if (buildCheck.ambiguous && buildCheck.objDirs && buildCheck.objDirs.length > 0) {
61
+ throw new AmbiguousBuildArtifactsError(buildCheck.objDirs);
62
+ }
63
+ const mismatchMessage = buildArtifactMismatchMessage(paths.engine, buildCheck, 'Run');
64
+ if (mismatchMessage) {
65
+ throw new GeneralError(mismatchMessage);
66
+ }
67
+ if (!buildCheck.exists) {
68
+ const detail = buildCheck.objDir
69
+ ? `Build artifacts incomplete in ${buildCheck.objDir}/`
70
+ : 'No build artifacts found (obj-*/ directory missing)';
71
+ throw new GeneralError(`Run requires a completed build. ${detail}\n\n` +
72
+ "Run 'fireforge build' first, then rerun 'fireforge run'.");
73
+ }
74
+ // Clean stale profile state to prevent silent startup failures
75
+ await cleanDevProfile(paths.engine);
76
+ info('Launching browser...\n');
77
+ const exitCode = await run(paths.engine);
78
+ if (exitCode !== 0 && exitCode !== 130 && exitCode !== 143) {
79
+ throw new BuildError(`Browser exited with code ${exitCode}`, 'mach run');
80
+ }
81
+ }
82
+ /** Registers the run command on the CLI program. */
83
+ export function registerRun(program, { getProjectRoot, withErrorHandling }) {
84
+ program
85
+ .command('run')
86
+ .description('Launch the built browser')
87
+ .action(withErrorHandling(async () => {
88
+ await runCommand(getProjectRoot());
89
+ }));
90
+ }
91
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,23 @@
1
+ import type { SetupOptions } from '../types/commands/index.js';
2
+ import type { FireForgeConfig, FirefoxProduct, ProjectLicense } from '../types/config.js';
3
+ export interface ResolvedSetupInputs {
4
+ finalName: string;
5
+ finalVendor: string;
6
+ finalAppId: string;
7
+ finalBinaryName: string;
8
+ finalFirefoxVersion: string;
9
+ finalProduct: FirefoxProduct;
10
+ finalLicense: ProjectLicense;
11
+ }
12
+ /** Parses an optional Firefox product flag into a typed product value. */
13
+ export declare function parseFirefoxProductOption(product: string | undefined): FirefoxProduct | undefined;
14
+ /** Parses an optional license flag into a validated SPDX identifier. */
15
+ export declare function parseProjectLicenseOption(license: string | undefined): ProjectLicense | undefined;
16
+ /** Validates non-interactive setup options before project scaffolding begins. */
17
+ export declare function validateSetupOptions(options: SetupOptions): void;
18
+ /** Resolves setup inputs from CLI flags and optional interactive prompts. */
19
+ export declare function resolveSetupInputs(options: SetupOptions, isInteractive: boolean): Promise<ResolvedSetupInputs>;
20
+ /** Builds the persisted FireForge config from resolved setup inputs. */
21
+ export declare function buildSetupConfig(inputs: ResolvedSetupInputs): FireForgeConfig;
22
+ /** Writes the initial project files produced by the setup workflow. */
23
+ export declare function writeSetupProjectFiles(projectRoot: string, config: FireForgeConfig): Promise<void>;
@@ -0,0 +1,310 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { cpus } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { group, select, text } from '@clack/prompts';
5
+ import { getProjectPaths, writeConfig } from '../core/config.js';
6
+ import { CancellationError, InvalidArgumentError } from '../errors/base.js';
7
+ import { ensureDir, pathExists, readText, writeText } from '../utils/fs.js';
8
+ import { cancel } from '../utils/logger.js';
9
+ import { getPackageRoot } from '../utils/package-root.js';
10
+ import { inferProductFromVersion, isValidAppId, isValidFirefoxVersion, isValidProjectLicense, } from '../utils/validation.js';
11
+ function getTemplatesDir() {
12
+ return join(getPackageRoot(), 'templates');
13
+ }
14
+ function renderLicenseTemplate(license, template, vendor, now = new Date()) {
15
+ if (license !== '0BSD') {
16
+ return template;
17
+ }
18
+ return template.replace(/\[year\]/g, String(now.getFullYear())).replace(/\[fullname\]/g, vendor);
19
+ }
20
+ function resolveFirefoxProduct(value, field) {
21
+ if (value === 'firefox' || value === 'firefox-esr' || value === 'firefox-beta') {
22
+ return value;
23
+ }
24
+ throw new InvalidArgumentError('Invalid product (use: firefox, firefox-esr, firefox-beta)', field);
25
+ }
26
+ function resolveProjectLicense(value, field) {
27
+ if (typeof value === 'string' && isValidProjectLicense(value)) {
28
+ return value;
29
+ }
30
+ throw new InvalidArgumentError('Invalid license (use: EUPL-1.2, MPL-2.0, 0BSD, GPL-2.0-or-later)', field);
31
+ }
32
+ /** Parses an optional Firefox product flag into a typed product value. */
33
+ export function parseFirefoxProductOption(product) {
34
+ if (product === undefined) {
35
+ return undefined;
36
+ }
37
+ return resolveFirefoxProduct(product, '--product');
38
+ }
39
+ /** Parses an optional license flag into a validated SPDX identifier. */
40
+ export function parseProjectLicenseOption(license) {
41
+ if (license === undefined) {
42
+ return undefined;
43
+ }
44
+ return resolveProjectLicense(license, '--license');
45
+ }
46
+ /** Validates non-interactive setup options before project scaffolding begins. */
47
+ export function validateSetupOptions(options) {
48
+ if (options.name !== undefined) {
49
+ if (!options.name.trim()) {
50
+ throw new InvalidArgumentError('Name is required', '--name');
51
+ }
52
+ if (options.name.length > 50) {
53
+ throw new InvalidArgumentError('Name must be 50 characters or less', '--name');
54
+ }
55
+ }
56
+ if (options.vendor !== undefined && !options.vendor.trim()) {
57
+ throw new InvalidArgumentError('Vendor is required', '--vendor');
58
+ }
59
+ if (options.appId !== undefined && !isValidAppId(options.appId)) {
60
+ throw new InvalidArgumentError('Invalid app ID format (use reverse-domain: org.example.browser)', '--app-id');
61
+ }
62
+ if (options.binaryName !== undefined && !/^[a-z][a-z0-9-]*$/.test(options.binaryName)) {
63
+ throw new InvalidArgumentError('Binary name must start with a letter and contain only lowercase letters, numbers, and hyphens', '--binary-name');
64
+ }
65
+ if (options.firefoxVersion !== undefined && !isValidFirefoxVersion(options.firefoxVersion)) {
66
+ throw new InvalidArgumentError('Invalid Firefox version format (e.g., 146.0, 140.0esr, or 147.0b1)', '--firefox-version');
67
+ }
68
+ if (options.product !== undefined) {
69
+ resolveFirefoxProduct(options.product, '--product');
70
+ }
71
+ if (options.license !== undefined) {
72
+ resolveProjectLicense(options.license, '--license');
73
+ }
74
+ }
75
+ async function promptSetupInputs(options) {
76
+ const project = await group({
77
+ name: () => options.name
78
+ ? Promise.resolve(options.name)
79
+ : text({
80
+ message: 'What is the name of your browser?',
81
+ placeholder: 'MyBrowser',
82
+ validate: (value) => {
83
+ const normalizedValue = value ?? '';
84
+ if (!normalizedValue.trim())
85
+ return 'Name is required';
86
+ if (normalizedValue.length > 50)
87
+ return 'Name must be 50 characters or less';
88
+ return undefined;
89
+ },
90
+ }),
91
+ vendor: () => options.vendor
92
+ ? Promise.resolve(options.vendor)
93
+ : text({
94
+ message: 'What is your vendor/company name?',
95
+ placeholder: 'My Company',
96
+ validate: (value) => {
97
+ if (!(value ?? '').trim())
98
+ return 'Vendor is required';
99
+ return undefined;
100
+ },
101
+ }),
102
+ appId: ({ results }) => options.appId
103
+ ? Promise.resolve(options.appId)
104
+ : text({
105
+ message: 'Application ID (reverse-domain format)',
106
+ placeholder: `org.${(results.name ?? 'browser').toLowerCase().replace(/[^a-z0-9]/g, '')}.browser`,
107
+ validate: (value) => {
108
+ if (value && !isValidAppId(value)) {
109
+ return 'Must be in reverse-domain format (e.g., org.example.browser)';
110
+ }
111
+ return undefined;
112
+ },
113
+ }),
114
+ binaryName: ({ results }) => options.binaryName
115
+ ? Promise.resolve(options.binaryName)
116
+ : text({
117
+ message: 'Binary name (executable name)',
118
+ placeholder: (results.name ?? 'browser').toLowerCase().replace(/[^a-z0-9]/g, ''),
119
+ validate: (value) => {
120
+ if (value && !/^[a-z][a-z0-9-]*$/.test(value)) {
121
+ return 'Must start with a letter and contain only lowercase letters, numbers, and hyphens';
122
+ }
123
+ return undefined;
124
+ },
125
+ }),
126
+ firefoxVersion: () => options.firefoxVersion
127
+ ? Promise.resolve(options.firefoxVersion)
128
+ : text({
129
+ message: 'Firefox version to base on',
130
+ placeholder: '140.0esr',
131
+ validate: (value) => {
132
+ if (value && !isValidFirefoxVersion(value)) {
133
+ return 'Invalid Firefox version format (e.g., 146.0, 140.0esr, or 147.0b1)';
134
+ }
135
+ return undefined;
136
+ },
137
+ }),
138
+ product: ({ results }) => {
139
+ if (options.product) {
140
+ return Promise.resolve(options.product);
141
+ }
142
+ const effectiveVersion = (typeof results.firefoxVersion === 'string' && results.firefoxVersion.trim()) ||
143
+ options.firefoxVersion ||
144
+ '140.0esr';
145
+ const inferredProduct = inferProductFromVersion(effectiveVersion);
146
+ if (inferredProduct) {
147
+ return Promise.resolve(inferredProduct);
148
+ }
149
+ return select({
150
+ message: 'Which Firefox product?',
151
+ options: [
152
+ { value: 'firefox', label: 'Firefox (stable releases)' },
153
+ { value: 'firefox-esr', label: 'Firefox ESR (extended support)' },
154
+ { value: 'firefox-beta', label: 'Firefox Beta (pre-release)' },
155
+ ],
156
+ });
157
+ },
158
+ license: () => options.license
159
+ ? Promise.resolve(options.license)
160
+ : select({
161
+ message: 'Project license',
162
+ options: [
163
+ {
164
+ value: 'EUPL-1.2',
165
+ label: 'EUPL 1.2 (recommended)',
166
+ hint: 'copyleft, MPL-2.0 compatible',
167
+ },
168
+ { value: 'MPL-2.0', label: 'MPL 2.0', hint: 'file-level copyleft' },
169
+ { value: '0BSD', label: '0BSD', hint: 'permissive, no conditions' },
170
+ { value: 'GPL-2.0-or-later', label: 'GPL 2.0+', hint: 'strong copyleft' },
171
+ ],
172
+ initialValue: 'EUPL-1.2',
173
+ }),
174
+ }, {
175
+ onCancel: () => {
176
+ cancel('Setup cancelled');
177
+ throw new CancellationError();
178
+ },
179
+ });
180
+ const sanitizedName = project.name.toLowerCase().replace(/[^a-z0-9]/g, '');
181
+ const finalName = project.name;
182
+ const finalVendor = project.vendor;
183
+ const finalAppId = (typeof project.appId === 'string' ? project.appId.trim() : '') ||
184
+ `org.${sanitizedName}.browser`;
185
+ const finalBinaryName = (typeof project.binaryName === 'string' ? project.binaryName.trim() : '') || sanitizedName;
186
+ const finalFirefoxVersion = (typeof project.firefoxVersion === 'string' ? project.firefoxVersion.trim() : '') || '140.0esr';
187
+ if (!isValidAppId(finalAppId)) {
188
+ throw new InvalidArgumentError(`Derived appId "${finalAppId}" is invalid.`, 'appId');
189
+ }
190
+ if (!isValidFirefoxVersion(finalFirefoxVersion)) {
191
+ throw new InvalidArgumentError(`Default Firefox version "${finalFirefoxVersion}" is invalid.`, 'firefoxVersion');
192
+ }
193
+ return {
194
+ finalName,
195
+ finalVendor,
196
+ finalAppId,
197
+ finalBinaryName,
198
+ finalFirefoxVersion,
199
+ finalProduct: (typeof project.product === 'string'
200
+ ? resolveFirefoxProduct(project.product, 'product')
201
+ : undefined) ??
202
+ inferProductFromVersion(finalFirefoxVersion) ??
203
+ 'firefox',
204
+ finalLicense: resolveProjectLicense(project.license, 'license'),
205
+ };
206
+ }
207
+ /** Resolves setup inputs from CLI flags and optional interactive prompts. */
208
+ export async function resolveSetupInputs(options, isInteractive) {
209
+ if (options.name &&
210
+ options.vendor &&
211
+ options.appId &&
212
+ options.binaryName &&
213
+ options.firefoxVersion) {
214
+ return {
215
+ finalName: options.name,
216
+ finalVendor: options.vendor,
217
+ finalAppId: options.appId,
218
+ finalBinaryName: options.binaryName,
219
+ finalFirefoxVersion: options.firefoxVersion,
220
+ finalProduct: options.product ?? inferProductFromVersion(options.firefoxVersion) ?? 'firefox',
221
+ finalLicense: options.license ?? 'EUPL-1.2',
222
+ };
223
+ }
224
+ if (!isInteractive) {
225
+ throw new InvalidArgumentError('Missing required options for non-interactive mode. Required: --name, --vendor, --app-id, --binary-name, --firefox-version');
226
+ }
227
+ return promptSetupInputs(options);
228
+ }
229
+ /** Builds the persisted FireForge config from resolved setup inputs. */
230
+ export function buildSetupConfig(inputs) {
231
+ return {
232
+ name: inputs.finalName,
233
+ vendor: inputs.finalVendor,
234
+ appId: inputs.finalAppId,
235
+ binaryName: inputs.finalBinaryName,
236
+ license: inputs.finalLicense,
237
+ firefox: {
238
+ version: inputs.finalFirefoxVersion,
239
+ product: inputs.finalProduct,
240
+ },
241
+ build: {
242
+ jobs: Math.max(1, cpus().length),
243
+ },
244
+ };
245
+ }
246
+ /** Writes the initial project files produced by the setup workflow. */
247
+ export async function writeSetupProjectFiles(projectRoot, config) {
248
+ const paths = getProjectPaths(projectRoot);
249
+ await ensureDir(paths.patches);
250
+ await ensureDir(paths.configs);
251
+ await ensureDir(paths.fireforgeDir);
252
+ await writeConfig(projectRoot, config);
253
+ const gitignorePath = join(projectRoot, '.gitignore');
254
+ const requiredIgnores = ['node_modules/', 'dist/', 'engine/', '.fireforge/'];
255
+ if (await pathExists(gitignorePath)) {
256
+ const existingContent = await readText(gitignorePath);
257
+ const lines = existingContent.split('\n').map((line) => line.trim());
258
+ const missing = requiredIgnores.filter((item) => {
259
+ const withoutSlash = item.endsWith('/') ? item.slice(0, -1) : item;
260
+ return !lines.includes(item) && !lines.includes(withoutSlash);
261
+ });
262
+ if (missing.length > 0) {
263
+ const toAppend = (existingContent.endsWith('\n') ? '' : '\n') + missing.join('\n') + '\n';
264
+ await writeText(gitignorePath, existingContent + toAppend);
265
+ }
266
+ }
267
+ else {
268
+ await writeText(gitignorePath, requiredIgnores.join('\n') + '\n');
269
+ }
270
+ const rootPackageJsonPath = join(projectRoot, 'package.json');
271
+ if (!(await pathExists(rootPackageJsonPath))) {
272
+ const rootPackageJson = {
273
+ private: true,
274
+ license: config.license,
275
+ };
276
+ await writeText(rootPackageJsonPath, JSON.stringify(rootPackageJson, null, 2) + '\n');
277
+ }
278
+ const templatesDir = getTemplatesDir();
279
+ if (config.license !== undefined) {
280
+ const licenseTemplatePath = join(templatesDir, 'licenses', `${config.license}.md`);
281
+ if (await pathExists(licenseTemplatePath)) {
282
+ const licenseText = renderLicenseTemplate(config.license, await readText(licenseTemplatePath), config.vendor);
283
+ await writeText(join(projectRoot, 'LICENSE'), licenseText);
284
+ }
285
+ }
286
+ const configsTemplateDir = join(templatesDir, 'configs');
287
+ if (!(await pathExists(configsTemplateDir))) {
288
+ return;
289
+ }
290
+ const configFiles = [
291
+ 'common.mozconfig',
292
+ 'darwin.mozconfig',
293
+ 'linux.mozconfig',
294
+ 'win32.mozconfig',
295
+ ];
296
+ for (const file of configFiles) {
297
+ const srcPath = join(configsTemplateDir, file);
298
+ const destPath = join(paths.configs, file);
299
+ if (!(await pathExists(srcPath))) {
300
+ continue;
301
+ }
302
+ const content = (await readText(srcPath))
303
+ .replace(/\$\{name\}/g, config.name)
304
+ .replace(/\$\{vendor\}/g, config.vendor)
305
+ .replace(/\$\{appId\}/g, config.appId)
306
+ .replace(/\$\{binaryName\}/g, config.binaryName);
307
+ await writeText(destPath, content);
308
+ }
309
+ }
310
+ //# sourceMappingURL=setup-support.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { SetupOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the setup command.
6
+ * @param projectRoot - Root directory for the project
7
+ * @param options - CLI options for non-interactive mode
8
+ */
9
+ export declare function setupCommand(projectRoot: string, options?: SetupOptions): Promise<void>;
10
+ /** Registers the setup command on the CLI program. */
11
+ export declare function registerSetup(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,94 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { confirm } from '@clack/prompts';
3
+ import { Option } from 'commander';
4
+ import { configExists } from '../core/config.js';
5
+ import { ConfigError } from '../errors/config.js';
6
+ import { cancel, intro, isCancel, note, outro, spinner } from '../utils/logger.js';
7
+ import { pickDefined } from '../utils/options.js';
8
+ import { PROJECT_LICENSES } from '../utils/validation.js';
9
+ import { buildSetupConfig, parseFirefoxProductOption, parseProjectLicenseOption, resolveSetupInputs, validateSetupOptions, writeSetupProjectFiles, } from './setup-support.js';
10
+ /**
11
+ * Runs the setup command.
12
+ * @param projectRoot - Root directory for the project
13
+ * @param options - CLI options for non-interactive mode
14
+ */
15
+ export async function setupCommand(projectRoot, options = {}) {
16
+ // Validate any CLI-provided options first
17
+ validateSetupOptions(options);
18
+ // Determine if we can run interactively
19
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
20
+ intro('FireForge Setup');
21
+ // Check if config already exists
22
+ if (await configExists(projectRoot)) {
23
+ if (options.force) {
24
+ // Skip confirmation when --force is provided
25
+ }
26
+ else if (isInteractive) {
27
+ const overwrite = await confirm({
28
+ message: 'A fireforge.json already exists. Overwrite?',
29
+ initialValue: false,
30
+ });
31
+ if (isCancel(overwrite) || !overwrite) {
32
+ cancel('Setup cancelled');
33
+ return;
34
+ }
35
+ }
36
+ else {
37
+ throw new ConfigError('fireforge.json already exists. Use --force to overwrite.');
38
+ }
39
+ }
40
+ const resolved = await resolveSetupInputs(options, isInteractive);
41
+ const config = buildSetupConfig(resolved);
42
+ const s = spinner('Creating project structure...');
43
+ try {
44
+ await writeSetupProjectFiles(projectRoot, config);
45
+ s.stop('Project structure created');
46
+ // Show next steps
47
+ note(`Next steps:\n` +
48
+ ` 1. fireforge download # Download Firefox source\n` +
49
+ ` 2. fireforge bootstrap # Install build dependencies\n` +
50
+ ` 3. fireforge build # Build the browser\n` +
51
+ ` 4. fireforge run # Launch the browser`, 'Getting Started');
52
+ outro(`${config.name} project created successfully!`);
53
+ }
54
+ catch (error) {
55
+ s.error('Failed to create project');
56
+ throw error;
57
+ }
58
+ }
59
+ /** Registers the setup command on the CLI program. */
60
+ export function registerSetup(program, { getProjectRoot, withErrorHandling }) {
61
+ program
62
+ .command('setup')
63
+ .description('Initialize a new FireForge project')
64
+ .option('--name <name>', 'Browser name')
65
+ .option('--vendor <vendor>', 'Vendor/company name')
66
+ .option('--app-id <appId>', 'Application ID (reverse-domain format)')
67
+ .option('--binary-name <binaryName>', 'Binary name (executable name)')
68
+ .option('--firefox-version <version>', 'Firefox version to base on')
69
+ .addOption(new Option('--product <product>', 'Firefox product').choices([
70
+ 'firefox',
71
+ 'firefox-esr',
72
+ 'firefox-beta',
73
+ ]))
74
+ .addOption(new Option('--license <license>', 'Project license').choices([...PROJECT_LICENSES]))
75
+ .option('-f, --force', 'Overwrite existing configuration without prompting')
76
+ .action(withErrorHandling(async (options) => {
77
+ const { product, license, ...rest } = options;
78
+ const setupOptions = { ...pickDefined(rest) };
79
+ if (product !== undefined) {
80
+ const parsedProduct = parseFirefoxProductOption(product);
81
+ if (parsedProduct !== undefined) {
82
+ setupOptions.product = parsedProduct;
83
+ }
84
+ }
85
+ if (license !== undefined) {
86
+ const parsedLicense = parseProjectLicenseOption(license);
87
+ if (parsedLicense !== undefined) {
88
+ setupOptions.license = parsedLicense;
89
+ }
90
+ }
91
+ await setupCommand(getProjectRoot(), setupOptions);
92
+ }));
93
+ }
94
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { StatusOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the status command to show modified files.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Status display options
8
+ */
9
+ export declare function statusCommand(projectRoot: string, options?: StatusOptions): Promise<void>;
10
+ /** Registers the status command on the CLI program. */
11
+ export declare function registerStatus(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;