@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,356 @@
1
+ import { configExists, getProjectPaths, loadConfig, loadState } from '../core/config.js';
2
+ import { getCurrentBranch, getHead, isGitRepository, isMissingHeadError } from '../core/git.js';
3
+ import { ensureGit } from '../core/git-base.js';
4
+ import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
5
+ import { ensureMach, ensurePython } from '../core/mach.js';
6
+ import { countPatches } from '../core/patch-apply.js';
7
+ import { rebuildPatchesManifest, validatePatchesManifestConsistency, validatePatchIntegrity, } from '../core/patch-manifest.js';
8
+ import { ExitCode } from '../errors/codes.js';
9
+ import { toError } from '../utils/errors.js';
10
+ import { pathExists } from '../utils/fs.js';
11
+ import { error, info, intro, outro, success, warn } from '../utils/logger.js';
12
+ /**
13
+ * Runs a doctor check and returns the result.
14
+ */
15
+ async function runCheck(name, check, fix) {
16
+ try {
17
+ await check();
18
+ return { name, passed: true, severity: 'ok', message: 'OK' };
19
+ }
20
+ catch (error) {
21
+ const message = toError(error).message;
22
+ const result = { name, passed: false, severity: 'error', message };
23
+ if (fix !== undefined) {
24
+ result.fix = fix;
25
+ }
26
+ return result;
27
+ }
28
+ }
29
+ function summarizeWorkingTreeChangeCount(changeCount) {
30
+ return `Engine working tree has ${changeCount} local change${changeCount === 1 ? '' : 's'}. Some FireForge commands assume a clean baseline and may behave differently until these are exported, discarded, or committed.`;
31
+ }
32
+ async function collectEngineChecks(paths, state, engineExists) {
33
+ const checks = [];
34
+ if (!engineExists) {
35
+ return checks;
36
+ }
37
+ // Check 6: Engine is a git repository
38
+ const isGitRepo = await isGitRepository(paths.engine);
39
+ checks.push({
40
+ name: 'Engine is git repository',
41
+ passed: isGitRepo,
42
+ severity: isGitRepo ? 'ok' : 'error',
43
+ message: isGitRepo ? 'OK' : 'engine/ is not a git repository',
44
+ ...(!isGitRepo ? { fix: 'Run "fireforge download --force" to reinitialize' } : {}),
45
+ });
46
+ // Only run git-dependent checks if the engine is actually a git repo
47
+ if (isGitRepo) {
48
+ let currentHead;
49
+ let canValidateBranch = true;
50
+ // Engine consistency checks
51
+ if (state.baseCommit) {
52
+ try {
53
+ currentHead = await getHead(paths.engine);
54
+ }
55
+ catch (error) {
56
+ if (!isMissingHeadError(error)) {
57
+ throw error;
58
+ }
59
+ canValidateBranch = false;
60
+ checks.push({
61
+ name: 'Engine state consistency',
62
+ passed: false,
63
+ severity: 'error',
64
+ message: 'Engine repository has no baseline commit yet. A previous "fireforge download" likely stopped after git init but before the initial Firefox commit was created.',
65
+ fix: 'Re-run "fireforge download --force" to recreate the baseline repository cleanly.',
66
+ });
67
+ }
68
+ if (canValidateBranch && currentHead !== state.baseCommit) {
69
+ checks.push({
70
+ name: 'Engine state consistency',
71
+ passed: false,
72
+ severity: 'error',
73
+ message: 'HEAD differs from baseCommit. FireForge expects the engine repository to remain at the downloaded baseline commit; branch switches or commits inside engine/ can break import, resolve, and patch regeneration workflows.',
74
+ fix: 'Reset engine/ to the baseline commit or re-run "fireforge download --force".',
75
+ });
76
+ }
77
+ else if (canValidateBranch) {
78
+ checks.push({
79
+ name: 'Engine state consistency',
80
+ passed: true,
81
+ severity: 'ok',
82
+ message: 'OK',
83
+ });
84
+ }
85
+ }
86
+ const rawStatus = await getWorkingTreeStatus(paths.engine);
87
+ const workingTreeStatus = await expandUntrackedDirectoryEntries(paths.engine, rawStatus);
88
+ if (workingTreeStatus.length > 0) {
89
+ checks.push({
90
+ name: 'Engine working tree',
91
+ passed: true,
92
+ severity: 'warning',
93
+ warning: true,
94
+ message: summarizeWorkingTreeChangeCount(workingTreeStatus.length),
95
+ fix: 'Use "fireforge status" to review changes, then export, discard, or reset them as appropriate.',
96
+ });
97
+ }
98
+ else {
99
+ checks.push({
100
+ name: 'Engine working tree',
101
+ passed: true,
102
+ severity: 'ok',
103
+ message: 'OK',
104
+ });
105
+ }
106
+ let branch;
107
+ if (canValidateBranch) {
108
+ try {
109
+ branch = await getCurrentBranch(paths.engine);
110
+ }
111
+ catch (error) {
112
+ if (!isMissingHeadError(error)) {
113
+ throw error;
114
+ }
115
+ canValidateBranch = false;
116
+ checks.push({
117
+ name: 'Engine branch',
118
+ passed: false,
119
+ severity: 'error',
120
+ message: 'Engine repository has no baseline commit yet. A previous "fireforge download" likely stopped before git created the initial Firefox commit.',
121
+ fix: 'Re-run "fireforge download --force" to recreate the baseline repository cleanly.',
122
+ });
123
+ }
124
+ }
125
+ if (!canValidateBranch &&
126
+ branch === undefined &&
127
+ currentHead === undefined &&
128
+ !state.baseCommit) {
129
+ // An unborn repository can fail branch detection before state.json records baseCommit.
130
+ // The error above already explains the recovery path, so avoid adding extra noise here.
131
+ }
132
+ else if (!canValidateBranch) {
133
+ checks.push({
134
+ name: 'Engine branch',
135
+ passed: true,
136
+ severity: 'warning',
137
+ warning: true,
138
+ message: 'Skipped branch validation because the baseline commit is missing.',
139
+ fix: 'Finish recreating the engine baseline with "fireforge download --force".',
140
+ });
141
+ }
142
+ else if (branch === 'firefox') {
143
+ checks.push({
144
+ name: 'Engine branch',
145
+ passed: true,
146
+ severity: 'ok',
147
+ message: 'OK',
148
+ });
149
+ }
150
+ else if (branch === 'HEAD' && state.baseCommit && currentHead === state.baseCommit) {
151
+ checks.push({
152
+ name: 'Engine branch',
153
+ passed: true,
154
+ severity: 'warning',
155
+ warning: true,
156
+ message: 'Engine is detached at the recorded base commit. This is acceptable for disposable worktrees and audit clones.',
157
+ fix: 'If this is your primary workspace, checkout the "firefox" branch to match FireForge defaults.',
158
+ });
159
+ }
160
+ else {
161
+ checks.push({
162
+ name: 'Engine branch',
163
+ passed: false,
164
+ severity: 'error',
165
+ message: `Engine is on branch "${branch}", but expected "firefox".`,
166
+ });
167
+ }
168
+ }
169
+ // Check 7: mach available
170
+ checks.push(await runCheck('mach available', async () => {
171
+ await ensureMach(paths.engine);
172
+ }, 'Firefox source may be corrupted. Re-download with "fireforge download --force"'));
173
+ return checks;
174
+ }
175
+ function reportDoctorResults(checks) {
176
+ info('');
177
+ let passedCount = 0;
178
+ let warningCount = 0;
179
+ let failedCount = 0;
180
+ for (const check of checks) {
181
+ const severity = check.severity ?? (check.passed ? (check.warning ? 'warning' : 'ok') : 'error');
182
+ if (severity === 'warning') {
183
+ warn(`! ${check.name}: ${check.message}`);
184
+ if (check.fix) {
185
+ warn(` Fix: ${check.fix}`);
186
+ }
187
+ warningCount++;
188
+ }
189
+ else if (severity === 'ok') {
190
+ success(`✓ ${check.name}: ${check.message}`);
191
+ passedCount++;
192
+ }
193
+ else {
194
+ error(`✗ ${check.name}: ${check.message}`);
195
+ if (check.fix) {
196
+ warn(` Fix: ${check.fix}`);
197
+ }
198
+ failedCount++;
199
+ }
200
+ }
201
+ info('');
202
+ if (failedCount === 0 && warningCount === 0) {
203
+ outro(`All ${passedCount} checks passed!`);
204
+ }
205
+ else if (failedCount === 0) {
206
+ outro(`${passedCount} passed, ${warningCount} warning${warningCount === 1 ? '' : 's'}`);
207
+ }
208
+ else {
209
+ outro(`${passedCount} passed, ${warningCount} warning${warningCount === 1 ? '' : 's'}, ${failedCount} failed`);
210
+ return ExitCode.GENERAL_ERROR;
211
+ }
212
+ return ExitCode.SUCCESS;
213
+ }
214
+ async function collectProjectChecks(paths, engineExists, firefoxVersion, options) {
215
+ const checks = [];
216
+ const patchesExist = await pathExists(paths.patches);
217
+ checks.push({
218
+ name: 'Patches directory exists',
219
+ passed: true,
220
+ severity: 'ok',
221
+ message: patchesExist ? 'OK' : 'No patches/ directory (optional)',
222
+ });
223
+ if (patchesExist) {
224
+ const patchCount = await countPatches(paths.patches);
225
+ checks.push({
226
+ name: 'Patches found',
227
+ passed: true,
228
+ severity: 'ok',
229
+ message: `${patchCount} patch${patchCount === 1 ? '' : 'es'} found`,
230
+ });
231
+ const manifestConsistencyIssues = await validatePatchesManifestConsistency(paths.patches);
232
+ if (manifestConsistencyIssues.length > 0) {
233
+ if (options.repairPatchesManifest) {
234
+ try {
235
+ const repairedManifest = await rebuildPatchesManifest(paths.patches, firefoxVersion ?? 'unknown');
236
+ checks.push({
237
+ name: 'Patch manifest consistency',
238
+ passed: true,
239
+ severity: 'warning',
240
+ warning: true,
241
+ message: `Rebuilt patches.json from ${repairedManifest.patches.length} patch` +
242
+ `${repairedManifest.patches.length === 1 ? '' : 'es'}. Review recovered metadata before release.`,
243
+ });
244
+ }
245
+ catch (error) {
246
+ checks.push({
247
+ name: 'Patch manifest consistency',
248
+ passed: false,
249
+ severity: 'error',
250
+ message: toError(error).message,
251
+ fix: 'Repair failed. Fix the underlying patch metadata issue and retry the doctor command.',
252
+ });
253
+ }
254
+ }
255
+ else {
256
+ checks.push({
257
+ name: 'Patch manifest consistency',
258
+ passed: false,
259
+ severity: 'error',
260
+ message: manifestConsistencyIssues.map((issue) => issue.message).join(' '),
261
+ fix: 'Run "fireforge doctor --repair-patches-manifest" to rebuild patches.json from patch files.',
262
+ });
263
+ }
264
+ }
265
+ else {
266
+ checks.push({
267
+ name: 'Patch manifest consistency',
268
+ passed: true,
269
+ severity: 'ok',
270
+ message: 'OK',
271
+ });
272
+ }
273
+ if (engineExists) {
274
+ checks.push(await runCheck('Patch integrity', async () => {
275
+ const issues = await validatePatchIntegrity(paths.patches, paths.engine);
276
+ if (issues.length > 0) {
277
+ const fileList = issues.map((issue) => issue.targetFile).filter(Boolean);
278
+ throw new Error(`${issues.length} patch(es) are modification patches for non-existent files: ${fileList.join(', ')}`);
279
+ }
280
+ }, 'Re-export affected files with "fireforge export <paths...>" to create full-file patches'));
281
+ }
282
+ }
283
+ const configsExist = await pathExists(paths.configs);
284
+ checks.push(await runCheck('Configs directory exists', () => {
285
+ if (!configsExist) {
286
+ throw new Error('configs/ directory not found');
287
+ }
288
+ }, 'Run "fireforge setup" to create configs'));
289
+ return checks;
290
+ }
291
+ /**
292
+ * Runs the doctor command to diagnose issues.
293
+ * @param projectRoot - Root directory of the project
294
+ */
295
+ export async function doctorCommand(projectRoot, options = {}) {
296
+ intro('FireForge Doctor');
297
+ const checks = [];
298
+ const paths = getProjectPaths(projectRoot);
299
+ const state = await loadState(projectRoot);
300
+ let config;
301
+ // Check 1: Git installed
302
+ checks.push(await runCheck('Git installed', async () => {
303
+ await ensureGit();
304
+ }, 'Install git from https://git-scm.com/'));
305
+ // Check 2: Python supported by mach
306
+ checks.push(await runCheck('Python supported by mach', async () => {
307
+ await ensurePython(paths.engine);
308
+ }, 'Install a Python version supported by engine/mach, then re-run "fireforge doctor".'));
309
+ // Check 3: fireforge.json exists
310
+ checks.push(await runCheck('fireforge.json exists', async () => {
311
+ if (!(await configExists(projectRoot))) {
312
+ throw new Error('fireforge.json not found');
313
+ }
314
+ }, 'Run "fireforge setup" to create a project'));
315
+ // Check 4: fireforge.json is valid
316
+ checks.push(await runCheck('fireforge.json is valid', async () => {
317
+ config = await loadConfig(projectRoot);
318
+ }, 'Check fireforge.json for syntax errors or missing fields'));
319
+ // Check 5: Engine directory exists
320
+ const engineExists = await pathExists(paths.engine);
321
+ checks.push(await runCheck('Engine directory exists', () => {
322
+ if (!engineExists) {
323
+ throw new Error('engine/ directory not found');
324
+ }
325
+ }, 'Run "fireforge download" to download Firefox source'));
326
+ // Check: Pending Resolution
327
+ if (state.pendingResolution) {
328
+ checks.push({
329
+ name: 'Pending Resolution',
330
+ passed: false,
331
+ severity: 'error',
332
+ message: `You are currently resolving a conflict for patch ${state.pendingResolution.patchFilename}.`,
333
+ fix: 'Build and Export commands may behave unexpectedly until "fireforge resolve" is completed.',
334
+ });
335
+ }
336
+ // Engine checks (git repo, state consistency, working tree, branch, mach)
337
+ checks.push(...(await collectEngineChecks(paths, state, engineExists)));
338
+ checks.push(...(await collectProjectChecks(paths, engineExists, config?.firefox.version, options)));
339
+ // Display results and return
340
+ const exitCode = reportDoctorResults(checks);
341
+ return { checks, exitCode };
342
+ }
343
+ /** Registers the doctor command on the CLI program. */
344
+ export function registerDoctor(program, { getProjectRoot, withErrorHandling }) {
345
+ program
346
+ .command('doctor')
347
+ .description('Diagnose project issues')
348
+ .option('--repair-patches-manifest', 'Rebuild patches/patches.json from the current patch files before reporting results')
349
+ .action(withErrorHandling(async (options) => {
350
+ const result = await doctorCommand(getProjectRoot(), options);
351
+ if (result.exitCode !== 0) {
352
+ process.exitCode = result.exitCode;
353
+ }
354
+ }));
355
+ }
356
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { DownloadOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the download command.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Download options
8
+ */
9
+ export declare function downloadCommand(projectRoot: string, options: DownloadOptions): Promise<void>;
10
+ /** Registers the download command on the CLI program. */
11
+ export declare function registerDownload(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,127 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { join } from 'node:path';
3
+ import { getProjectPaths, loadConfig, updateState } from '../core/config.js';
4
+ import { downloadFirefoxSource, formatBytes } from '../core/firefox.js';
5
+ import { getHead, initRepository, isGitRepository, isMissingHeadError, resumeRepository, } from '../core/git.js';
6
+ import { EngineExistsError, PartialEngineExistsError } from '../errors/download.js';
7
+ import { ensureDir, pathExists, removeDir } from '../utils/fs.js';
8
+ import { info, intro, outro, spinner, step, warn } from '../utils/logger.js';
9
+ import { pickDefined } from '../utils/options.js';
10
+ /**
11
+ * Runs the download command.
12
+ * @param projectRoot - Root directory of the project
13
+ * @param options - Download options
14
+ */
15
+ export async function downloadCommand(projectRoot, options) {
16
+ intro('FireForge Download');
17
+ // Load configuration
18
+ const config = await loadConfig(projectRoot);
19
+ const paths = getProjectPaths(projectRoot);
20
+ const version = config.firefox.version;
21
+ info(`Firefox version: ${version}`);
22
+ // Check if engine already exists
23
+ if (await pathExists(paths.engine)) {
24
+ if (!options.force) {
25
+ if (await isGitRepository(paths.engine)) {
26
+ try {
27
+ await getHead(paths.engine);
28
+ }
29
+ catch (error) {
30
+ if (isMissingHeadError(error)) {
31
+ // Partial init detected — attempt to resume instead of requiring --force
32
+ info('Detected partially initialized engine. Attempting to resume...');
33
+ const resumeSpinner = spinner('Resuming git repository initialization...');
34
+ try {
35
+ await resumeRepository(paths.engine, {
36
+ onProgress: (message) => {
37
+ resumeSpinner.message(message);
38
+ if (!(process.stdout.isTTY && process.stderr.isTTY)) {
39
+ step(message);
40
+ }
41
+ },
42
+ });
43
+ const baseCommit = await getHead(paths.engine);
44
+ resumeSpinner.stop('Git repository resumed successfully');
45
+ await updateState(projectRoot, {
46
+ downloadedVersion: version,
47
+ baseCommit,
48
+ });
49
+ outro(`Firefox ${version} is ready! (resumed from partial init)`);
50
+ return;
51
+ }
52
+ catch (error) {
53
+ void error;
54
+ resumeSpinner.error('Resume failed');
55
+ throw new PartialEngineExistsError(paths.engine);
56
+ }
57
+ }
58
+ // Re-throw unexpected git errors (e.g. corrupted objects) rather
59
+ // than masking them behind the generic EngineExistsError below.
60
+ throw error;
61
+ }
62
+ }
63
+ throw new EngineExistsError(paths.engine);
64
+ }
65
+ warn('Removing existing engine directory...');
66
+ await removeDir(paths.engine);
67
+ }
68
+ // Ensure cache directory exists
69
+ const cacheDir = join(paths.fireforgeDir, 'cache');
70
+ await ensureDir(cacheDir);
71
+ // Download with progress
72
+ const s = spinner(`Downloading Firefox ${version}...`);
73
+ let lastPercent = 0;
74
+ try {
75
+ await downloadFirefoxSource(version, config.firefox.product, paths.engine, cacheDir, (downloaded, total) => {
76
+ if (total <= 0)
77
+ return;
78
+ const percent = Math.floor((downloaded / total) * 100);
79
+ if (percent !== lastPercent && percent % 5 === 0) {
80
+ s.message(`Downloading Firefox ${version}... ${percent}% (${formatBytes(downloaded)} / ${formatBytes(total)})`);
81
+ lastPercent = percent;
82
+ }
83
+ });
84
+ s.stop(`Firefox ${version} downloaded`);
85
+ }
86
+ catch (error) {
87
+ s.error('Download failed');
88
+ throw error;
89
+ }
90
+ // Initialize git repository
91
+ const gitSpinner = spinner('Initializing git repository (this may take a few minutes)...');
92
+ let baseCommit;
93
+ try {
94
+ await initRepository(paths.engine, 'firefox', {
95
+ onProgress: (message) => {
96
+ gitSpinner.message(message);
97
+ if (!(process.stdout.isTTY && process.stderr.isTTY)) {
98
+ step(message);
99
+ }
100
+ },
101
+ });
102
+ baseCommit = await getHead(paths.engine);
103
+ gitSpinner.stop('Git repository initialized');
104
+ }
105
+ catch (error) {
106
+ gitSpinner.error('Failed to initialize git repository');
107
+ warn('engine/ may now contain a partially initialized git repository. Re-run "fireforge download --force" to recreate the baseline cleanly.');
108
+ throw error;
109
+ }
110
+ // Update state
111
+ await updateState(projectRoot, {
112
+ downloadedVersion: version,
113
+ baseCommit,
114
+ });
115
+ outro(`Firefox ${version} is ready!`);
116
+ }
117
+ /** Registers the download command on the CLI program. */
118
+ export function registerDownload(program, { getProjectRoot, withErrorHandling }) {
119
+ program
120
+ .command('download')
121
+ .description('Download Firefox source')
122
+ .option('-f, --force', 'Force re-download, removing existing source')
123
+ .action(withErrorHandling(async (options) => {
124
+ await downloadCommand(getProjectRoot(), pickDefined(options));
125
+ }));
126
+ }
127
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { ExportOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the export-all command to export all changes as a patch.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Export options
8
+ */
9
+ export declare function exportAllCommand(projectRoot: string, options?: ExportOptions): Promise<void>;
10
+ /** Registers the export-all command on the CLI program. */
11
+ export declare function registerExportAll(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { Option } from 'commander';
3
+ import { isBrandingManagedPath } from '../core/branding.js';
4
+ import { getProjectPaths, loadConfig } from '../core/config.js';
5
+ import { hasChanges, isGitRepository } from '../core/git.js';
6
+ import { getAllDiff } from '../core/git-diff.js';
7
+ import { getWorkingTreeStatus } from '../core/git-status.js';
8
+ import { extractAffectedFiles } from '../core/patch-apply.js';
9
+ import { commitExportedPatch } from '../core/patch-export.js';
10
+ import { GeneralError } from '../errors/base.js';
11
+ import { ensureDir, pathExists } from '../utils/fs.js';
12
+ import { info, intro, outro, spinner } from '../utils/logger.js';
13
+ import { pickDefined } from '../utils/options.js';
14
+ import { PATCH_CATEGORIES } from '../utils/validation.js';
15
+ import { autoFixLicenseHeaders, confirmSupersedePatches, promptExportPatchMetadata, runPatchLint, } from './export-shared.js';
16
+ async function checkBrandingManagedFiles(paths, config) {
17
+ const changedFiles = await getWorkingTreeStatus(paths.engine);
18
+ const brandingManagedFiles = changedFiles
19
+ .flatMap((entry) => [entry.file, entry.originalPath].filter((value) => !!value))
20
+ .filter((file) => isBrandingManagedPath(file, config.binaryName));
21
+ if (brandingManagedFiles.length > 0) {
22
+ throw new GeneralError('Export-all refuses to capture tool-managed branding changes by default.\n\n' +
23
+ 'Review these files with "fireforge status" first. If you intentionally want a branding patch, export the specific branding paths explicitly with "fireforge export ...".');
24
+ }
25
+ }
26
+ /**
27
+ * Runs the export-all command to export all changes as a patch.
28
+ * @param projectRoot - Root directory of the project
29
+ * @param options - Export options
30
+ */
31
+ export async function exportAllCommand(projectRoot, options = {}) {
32
+ intro('FireForge Export All');
33
+ const paths = getProjectPaths(projectRoot);
34
+ // Check if engine exists
35
+ if (!(await pathExists(paths.engine))) {
36
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
37
+ }
38
+ // Check if it's a git repository
39
+ if (!(await isGitRepository(paths.engine))) {
40
+ throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
41
+ }
42
+ // Check for changes
43
+ if (!(await hasChanges(paths.engine))) {
44
+ info('No changes to export');
45
+ outro('Nothing to export');
46
+ return;
47
+ }
48
+ const config = await loadConfig(projectRoot);
49
+ await checkBrandingManagedFiles(paths, config);
50
+ // Get the full diff
51
+ let diff = await getAllDiff(paths.engine);
52
+ if (!diff.trim()) {
53
+ info('No diff content to export');
54
+ outro('Nothing to export');
55
+ return;
56
+ }
57
+ // Check for non-interactive mode
58
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
59
+ // Auto-fix missing license headers on new files (interactive only)
60
+ const headersAdded = await autoFixLicenseHeaders(paths.engine, diff, config, isInteractive);
61
+ if (headersAdded) {
62
+ diff = await getAllDiff(paths.engine);
63
+ }
64
+ const metadata = await promptExportPatchMetadata(options, isInteractive, 'export-all');
65
+ if (!metadata)
66
+ return;
67
+ const { patchName, selectedCategory, description } = metadata;
68
+ // Ensure patches directory exists
69
+ await ensureDir(paths.patches);
70
+ const s = spinner('Exporting all changes...');
71
+ try {
72
+ // Extract affected files from diff
73
+ const filesAffected = extractAffectedFiles(diff);
74
+ await runPatchLint(paths.engine, filesAffected, diff, config, options.skipLint);
75
+ // Check how many existing patches would be superseded
76
+ const shouldProceed = await confirmSupersedePatches(paths.patches, filesAffected, options.supersede, isInteractive, s);
77
+ if (!shouldProceed)
78
+ return;
79
+ // Get Firefox version for metadata
80
+ const { patchFilename, superseded } = await commitExportedPatch({
81
+ patchesDir: paths.patches,
82
+ category: selectedCategory,
83
+ name: patchName,
84
+ description,
85
+ diff,
86
+ filesAffected,
87
+ sourceEsrVersion: config.firefox.version,
88
+ });
89
+ for (const oldPatch of superseded) {
90
+ info(`Superseded: ${oldPatch.filename}`);
91
+ }
92
+ s.stop(`Exported to ${patchFilename}`);
93
+ info(`\nPatch saved to: patches/${patchFilename}`);
94
+ if (filesAffected.length > 0) {
95
+ info(`Files affected: ${filesAffected.length}`);
96
+ }
97
+ outro('Export complete');
98
+ }
99
+ catch (error) {
100
+ s.error('Export failed');
101
+ throw error;
102
+ }
103
+ }
104
+ /** Registers the export-all command on the CLI program. */
105
+ export function registerExportAll(program, { getProjectRoot, withErrorHandling }) {
106
+ program
107
+ .command('export-all')
108
+ .description('Export all changes as a patch')
109
+ .option('--name <name>', 'Name for the patch')
110
+ .addOption(new Option('-c, --category <category>', 'Patch category').choices([...PATCH_CATEGORIES]))
111
+ .option('-d, --description <desc>', 'Description of the patch')
112
+ .option('--supersede', 'Allow superseding multiple existing patches')
113
+ .option('--skip-lint', 'Skip patch lint checks (downgrade errors to warnings)')
114
+ .action(withErrorHandling(async (options) => {
115
+ const { category, ...rest } = options;
116
+ await exportAllCommand(getProjectRoot(), {
117
+ ...pickDefined(rest),
118
+ ...(category !== undefined ? { category: category } : {}),
119
+ });
120
+ }));
121
+ }
122
+ //# sourceMappingURL=export-all.js.map
@@ -0,0 +1,48 @@
1
+ import type { ExportOptions, PatchCategory } from '../types/commands/index.js';
2
+ import type { FireForgeConfig } from '../types/config.js';
3
+ import type { SpinnerHandle } from '../utils/logger.js';
4
+ /**
5
+ * Runs the full patch lint pipeline and reports results.
6
+ * Warnings are always displayed. Errors block the export unless skipLint is true.
7
+ *
8
+ * @param engineDir - Engine root directory
9
+ * @param filesAffected - Files touched by the patch
10
+ * @param diffContent - Raw unified diff string
11
+ * @param config - Project configuration
12
+ * @param skipLint - If true, downgrade errors to warnings
13
+ */
14
+ export declare function runPatchLint(engineDir: string, filesAffected: string[], diffContent: string, config: FireForgeConfig, skipLint?: boolean): Promise<void>;
15
+ /**
16
+ * Resolves patch metadata interactively or from flags, with shared validation.
17
+ * @param options - Export command options
18
+ * @param isInteractive - Whether interactive prompts are allowed
19
+ * @param commandName - Command name for error/help text
20
+ */
21
+ export declare function promptExportPatchMetadata(options: ExportOptions, isInteractive: boolean, commandName: 'export' | 'export-all'): Promise<{
22
+ patchName: string;
23
+ selectedCategory: PatchCategory;
24
+ description: string;
25
+ } | null>;
26
+ /**
27
+ * Confirms whether an export may supersede existing patches.
28
+ * @param patchesDir - Patches directory
29
+ * @param filesAffected - Files touched by the pending export
30
+ * @param supersede - Explicit supersede flag from CLI options
31
+ * @param isInteractive - Whether interactive prompts are allowed
32
+ * @param s - Active spinner handle to stop before prompting
33
+ */
34
+ export declare function confirmSupersedePatches(patchesDir: string, filesAffected: string[], supersede: boolean | undefined, isInteractive: boolean, s: SpinnerHandle): Promise<boolean>;
35
+ /**
36
+ * Detects new files missing license headers and offers to add them.
37
+ *
38
+ * In interactive mode the user is prompted before any files are modified.
39
+ * In non-interactive mode the function is a no-op — the existing lint error
40
+ * will block the export instead.
41
+ *
42
+ * @param engineDir - Absolute path to engine directory
43
+ * @param diffContent - Current unified diff
44
+ * @param config - Project configuration
45
+ * @param isInteractive - Whether interactive prompts are available
46
+ * @returns true if files were modified on disk (caller must regenerate diff)
47
+ */
48
+ export declare function autoFixLicenseHeaders(engineDir: string, diffContent: string, config: FireForgeConfig, isInteractive: boolean): Promise<boolean>;