@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,180 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { Command } from 'commander';
5
+ import { registerBootstrap } from './commands/bootstrap.js';
6
+ import { registerBuild } from './commands/build.js';
7
+ import { registerConfig } from './commands/config.js';
8
+ import { registerDiscard } from './commands/discard.js';
9
+ import { registerDoctor } from './commands/doctor.js';
10
+ import { registerDownload } from './commands/download.js';
11
+ import { registerExport } from './commands/export.js';
12
+ import { registerExportAll } from './commands/export-all.js';
13
+ import { registerFurnace } from './commands/furnace/index.js';
14
+ import { registerImport } from './commands/import.js';
15
+ import { registerLint } from './commands/lint.js';
16
+ import { registerPackage } from './commands/package.js';
17
+ import { registerReExport } from './commands/re-export.js';
18
+ import { registerRebase } from './commands/rebase.js';
19
+ import { registerRegister } from './commands/register.js';
20
+ import { registerReset } from './commands/reset.js';
21
+ import { registerResolve } from './commands/resolve.js';
22
+ import { registerRun } from './commands/run.js';
23
+ import { registerSetup } from './commands/setup.js';
24
+ import { registerStatus } from './commands/status.js';
25
+ import { registerTest } from './commands/test.js';
26
+ import { registerToken } from './commands/token.js';
27
+ import { registerWatch } from './commands/watch.js';
28
+ import { registerWire } from './commands/wire.js';
29
+ import { CancellationError, CommandError, FireForgeError } from './errors/base.js';
30
+ import { ExitCode } from './errors/codes.js';
31
+ import { toError } from './utils/errors.js';
32
+ import { cancel, error as logError, setVerbose } from './utils/logger.js';
33
+ import { getPackageVersion } from './utils/package-root.js';
34
+ const brokenPipeInstalledKey = Symbol.for('fireforge.cli.brokenPipeHandlerInstalled');
35
+ const brokenPipeListenerKey = Symbol.for('fireforge.cli.brokenPipeHandlerListener');
36
+ function getProcessState() {
37
+ return process;
38
+ }
39
+ function getBrokenPipeHandler(state) {
40
+ const existingHandler = state[brokenPipeListenerKey];
41
+ if (existingHandler) {
42
+ return existingHandler;
43
+ }
44
+ const handler = (error) => {
45
+ if (error.code === 'EPIPE') {
46
+ process.exitCode = 0;
47
+ return;
48
+ }
49
+ throw error;
50
+ };
51
+ state[brokenPipeListenerKey] = handler;
52
+ return handler;
53
+ }
54
+ /**
55
+ * Installs a handler for broken-pipe (EPIPE) errors on stdout/stderr.
56
+ * This is a process-level concern: when output is piped to a process that
57
+ * closes early (e.g. `fireforge status | head`), Node emits EPIPE.
58
+ * We treat this as a clean exit.
59
+ */
60
+ export function installBrokenPipeHandler() {
61
+ const state = getProcessState();
62
+ if (state[brokenPipeInstalledKey]) {
63
+ return;
64
+ }
65
+ const handleStreamError = getBrokenPipeHandler(state);
66
+ process.stdout.on('error', handleStreamError);
67
+ process.stderr.on('error', handleStreamError);
68
+ state[brokenPipeInstalledKey] = true;
69
+ }
70
+ /** Removes the broken-pipe handler installed for CLI tests. */
71
+ export function resetBrokenPipeHandlerForTests() {
72
+ const state = getProcessState();
73
+ const handleStreamError = state[brokenPipeListenerKey];
74
+ if (handleStreamError) {
75
+ process.stdout.off('error', handleStreamError);
76
+ process.stderr.off('error', handleStreamError);
77
+ }
78
+ state[brokenPipeInstalledKey] = undefined;
79
+ state[brokenPipeListenerKey] = undefined;
80
+ }
81
+ /**
82
+ * Gets the project root directory.
83
+ * Walks up from the current working directory until a fireforge.json is found.
84
+ * Falls back to the current working directory when no project root is found.
85
+ */
86
+ export function getProjectRoot() {
87
+ const start = resolve(process.cwd());
88
+ let current = start;
89
+ for (;;) {
90
+ if (existsSync(join(current, 'fireforge.json'))) {
91
+ return current;
92
+ }
93
+ const parent = dirname(current);
94
+ if (parent === current)
95
+ return start;
96
+ current = parent;
97
+ }
98
+ }
99
+ /**
100
+ * Wraps a command handler with error handling.
101
+ *
102
+ * Logs the user-visible error message and throws a {@link CommandError}
103
+ * carrying the appropriate exit code. The actual `process.exit()` call
104
+ * lives in the CLI entrypoint (`bin/fireforge.ts`), keeping shared library
105
+ * code free of process-terminating side effects.
106
+ */
107
+ export function withErrorHandling(handler) {
108
+ return async (...args) => {
109
+ try {
110
+ await handler(...args);
111
+ }
112
+ catch (error) {
113
+ if (error instanceof CancellationError) {
114
+ cancel('Operation cancelled');
115
+ throw new CommandError(ExitCode.GENERAL_ERROR);
116
+ }
117
+ if (error instanceof FireForgeError) {
118
+ logError(error.userMessage);
119
+ throw new CommandError(error.code);
120
+ }
121
+ const normalizedError = toError(error);
122
+ logError(`Unexpected error: ${normalizedError.message}`);
123
+ if (normalizedError.stack) {
124
+ console.error(normalizedError.stack);
125
+ }
126
+ throw new CommandError(ExitCode.GENERAL_ERROR);
127
+ }
128
+ };
129
+ }
130
+ /**
131
+ * Creates and configures the CLI program.
132
+ */
133
+ export function createProgram() {
134
+ const program = new Command();
135
+ program
136
+ .name('fireforge')
137
+ .description('A build tool for customizing Firefox')
138
+ .version(getPackageVersion())
139
+ .option('-v, --verbose', 'Enable debug output')
140
+ .hook('preAction', (thisCommand) => {
141
+ const opts = thisCommand.opts();
142
+ if (opts['verbose']) {
143
+ setVerbose(true);
144
+ }
145
+ });
146
+ const ctx = { getProjectRoot, withErrorHandling };
147
+ registerSetup(program, ctx);
148
+ registerDownload(program, ctx);
149
+ registerBootstrap(program, ctx);
150
+ registerImport(program, ctx);
151
+ registerResolve(program, ctx);
152
+ registerBuild(program, ctx);
153
+ registerRun(program, ctx);
154
+ registerStatus(program, ctx);
155
+ registerReset(program, ctx);
156
+ registerDiscard(program, ctx);
157
+ registerExport(program, ctx);
158
+ registerExportAll(program, ctx);
159
+ registerReExport(program, ctx);
160
+ registerRebase(program, ctx);
161
+ registerPackage(program, ctx);
162
+ registerWatch(program, ctx);
163
+ registerTest(program, ctx);
164
+ registerConfig(program, ctx);
165
+ registerDoctor(program, ctx);
166
+ registerRegister(program, ctx);
167
+ registerWire(program, ctx);
168
+ registerToken(program, ctx);
169
+ registerLint(program, ctx);
170
+ registerFurnace(program, ctx);
171
+ return program;
172
+ }
173
+ /**
174
+ * Main CLI entry point.
175
+ */
176
+ export async function main() {
177
+ const program = createProgram();
178
+ await program.parseAsync(process.argv);
179
+ }
180
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ /**
4
+ * Runs the bootstrap command.
5
+ * @param projectRoot - Root directory of the project
6
+ */
7
+ export declare function bootstrapCommand(projectRoot: string): Promise<void>;
8
+ /** Registers the bootstrap command on the CLI program. */
9
+ export declare function registerBootstrap(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,73 @@
1
+ import { getProjectPaths } from '../core/config.js';
2
+ import { ensureOriginRemote } from '../core/git.js';
3
+ import { bootstrapWithOutput } from '../core/mach.js';
4
+ import { GeneralError } from '../errors/base.js';
5
+ import { BootstrapError } from '../errors/build.js';
6
+ import { pathExists } from '../utils/fs.js';
7
+ import { error, info, intro, outro } from '../utils/logger.js';
8
+ function buildBootstrapFailureMessage(output) {
9
+ const normalized = output.replace(/\r\n/g, '\n');
10
+ const issues = [];
11
+ if (/traceback \(most recent call last\):/i.test(normalized)) {
12
+ issues.push('Bootstrap emitted a Python traceback.');
13
+ }
14
+ if (/\bhttp(?:\s+error)?\s*403\b/i.test(normalized) || /\b403\b.*forbidden/i.test(normalized)) {
15
+ issues.push('Bootstrap hit an HTTP 403 while fetching dependencies.');
16
+ }
17
+ if (/no such remote ['"]origin['"]/i.test(normalized) ||
18
+ /remote ['"]origin['"] does not exist/i.test(normalized) ||
19
+ /missing git remote ['"]origin['"]/i.test(normalized)) {
20
+ issues.push('Bootstrap expected an "origin" git remote in the Firefox source checkout.');
21
+ }
22
+ if (issues.length === 0) {
23
+ return undefined;
24
+ }
25
+ return ('Bootstrap did not complete successfully.\n\n' +
26
+ `${issues.join('\n')}\n\n` +
27
+ 'Review the bootstrap output above, fix the underlying dependency or source-tree issue, and rerun "fireforge bootstrap".');
28
+ }
29
+ /**
30
+ * Runs the bootstrap command.
31
+ * @param projectRoot - Root directory of the project
32
+ */
33
+ export async function bootstrapCommand(projectRoot) {
34
+ intro('FireForge Bootstrap');
35
+ const paths = getProjectPaths(projectRoot);
36
+ // Check if engine exists
37
+ if (!(await pathExists(paths.engine))) {
38
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
39
+ }
40
+ // Ensure the engine repo has an "origin" remote so Firefox's bootstrap
41
+ // scripts don't emit noisy "No such remote" errors.
42
+ await ensureOriginRemote(paths.engine);
43
+ info('Installing Firefox build dependencies...');
44
+ info('This may take a while and require sudo permissions.\n');
45
+ const result = await bootstrapWithOutput(paths.engine);
46
+ if (result.exitCode !== 0) {
47
+ error('Bootstrap failed');
48
+ const failureMessage = buildBootstrapFailureMessage(`${result.stdout}\n${result.stderr}`);
49
+ if (failureMessage) {
50
+ throw new GeneralError(failureMessage);
51
+ }
52
+ throw new BootstrapError();
53
+ }
54
+ // mach bootstrap may exit 0 even when sub-downloads fail (e.g. HTTP 403).
55
+ // Scan output for known failure patterns and surface them as warnings.
56
+ const softFailure = buildBootstrapFailureMessage(`${result.stdout}\n${result.stderr}`);
57
+ if (softFailure) {
58
+ info('');
59
+ info(softFailure);
60
+ info('Bootstrap exited successfully but the issues above may cause build failures.');
61
+ }
62
+ outro('Build dependencies installed successfully!');
63
+ }
64
+ /** Registers the bootstrap command on the CLI program. */
65
+ export function registerBootstrap(program, { getProjectRoot, withErrorHandling }) {
66
+ program
67
+ .command('bootstrap')
68
+ .description('Install Firefox build dependencies')
69
+ .action(withErrorHandling(async () => {
70
+ await bootstrapCommand(getProjectRoot());
71
+ }));
72
+ }
73
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { BuildOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the build command.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param options - Build options
8
+ */
9
+ export declare function buildCommand(projectRoot: string, options: BuildOptions): Promise<void>;
10
+ /** Registers the build command on the CLI program. */
11
+ export declare function registerBuild(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,102 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { InvalidArgumentError as CommanderInvalidArgumentError } from 'commander';
3
+ import { validateBrandOverride } from '../core/brand-validation.js';
4
+ import { prepareBuildEnvironment } from '../core/build-prepare.js';
5
+ import { getProjectPaths, loadConfig } from '../core/config.js';
6
+ import { build, buildArtifactMismatchMessage, buildUI, hasBuildArtifacts } from '../core/mach.js';
7
+ import { GeneralError } from '../errors/base.js';
8
+ import { BuildError } from '../errors/build.js';
9
+ import { pathExists } from '../utils/fs.js';
10
+ import { error, info, intro, outro, verbose } from '../utils/logger.js';
11
+ import { pickDefined } from '../utils/options.js';
12
+ import { isPositiveInteger } from '../utils/validation.js';
13
+ function parseJobCount(value) {
14
+ const parsed = Number(value);
15
+ if (!Number.isSafeInteger(parsed) || parsed < 1) {
16
+ throw new CommanderInvalidArgumentError('jobs must be a positive integer');
17
+ }
18
+ return parsed;
19
+ }
20
+ function resolveJobCount(options, configJobs) {
21
+ const jobs = options.jobs ?? configJobs;
22
+ if (jobs === undefined) {
23
+ return undefined;
24
+ }
25
+ if (!isPositiveInteger(jobs)) {
26
+ throw new GeneralError('Build jobs must be a positive integer');
27
+ }
28
+ return jobs;
29
+ }
30
+ /**
31
+ * Runs the build command.
32
+ * @param projectRoot - Root directory of the project
33
+ * @param options - Build options
34
+ */
35
+ export async function buildCommand(projectRoot, options) {
36
+ const buildType = options.ui ? 'UI-only' : 'Full';
37
+ const brandInfo = options.brand ? ` [${options.brand}]` : '';
38
+ intro(`FireForge Build (${buildType}${brandInfo})`);
39
+ // Load configuration
40
+ const config = await loadConfig(projectRoot);
41
+ const paths = getProjectPaths(projectRoot);
42
+ validateBrandOverride(config.binaryName, options.brand);
43
+ // Check if engine exists
44
+ if (!(await pathExists(paths.engine))) {
45
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
46
+ }
47
+ const buildCheck = await hasBuildArtifacts(paths.engine);
48
+ const mismatchMessage = buildArtifactMismatchMessage(paths.engine, buildCheck, 'Build');
49
+ if (mismatchMessage) {
50
+ throw new GeneralError(mismatchMessage);
51
+ }
52
+ // Log brand info if specified
53
+ if (options.brand) {
54
+ verbose(`Building with brand: ${options.brand}`);
55
+ // Future: Load brand-specific config from fireforge.json brands section
56
+ info(`Brand: ${options.brand}`);
57
+ }
58
+ // Shared pre-flight: branding, Furnace, mozconfig
59
+ await prepareBuildEnvironment(projectRoot, paths, config);
60
+ const jobs = resolveJobCount(options, config.build?.jobs);
61
+ // Run build
62
+ info(`Starting ${buildType.toLowerCase()} build...`);
63
+ if (jobs !== undefined) {
64
+ info(`Using ${jobs} parallel jobs`);
65
+ }
66
+ info(''); // Empty line before build output
67
+ const startTime = Date.now();
68
+ let exitCode;
69
+ try {
70
+ if (options.ui) {
71
+ exitCode = await buildUI(paths.engine);
72
+ }
73
+ else {
74
+ exitCode = await build(paths.engine, jobs);
75
+ }
76
+ }
77
+ catch (error) {
78
+ throw new BuildError('Build process failed to start', options.ui ? 'mach build faster' : 'mach build', error instanceof Error ? error : undefined);
79
+ }
80
+ const duration = Date.now() - startTime;
81
+ const minutes = Math.floor(duration / 60000);
82
+ const seconds = Math.floor((duration % 60000) / 1000);
83
+ const timeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
84
+ if (exitCode !== 0) {
85
+ error(`Build failed after ${timeStr}`);
86
+ throw new BuildError(`Build failed with exit code ${exitCode}`, options.ui ? 'mach build faster' : 'mach build');
87
+ }
88
+ outro(`Build completed in ${timeStr}!`);
89
+ }
90
+ /** Registers the build command on the CLI program. */
91
+ export function registerBuild(program, { getProjectRoot, withErrorHandling }) {
92
+ program
93
+ .command('build')
94
+ .description('Build the browser')
95
+ .option('--ui', 'Fast UI-only rebuild')
96
+ .option('-j, --jobs <n>', 'Number of parallel jobs', parseJobCount)
97
+ .option('--brand <name>', 'Build specific brand')
98
+ .action(withErrorHandling(async (options) => {
99
+ await buildCommand(getProjectRoot(), pickDefined(options));
100
+ }));
101
+ }
102
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ /**
4
+ * Runs the config command to get or set configuration values.
5
+ * @param projectRoot - Root directory of the project
6
+ * @param key - Configuration key (dot notation)
7
+ * @param value - Optional value to set
8
+ */
9
+ export declare function configCommand(projectRoot: string, key: string, value?: string, options?: {
10
+ force?: boolean;
11
+ }): Promise<void>;
12
+ /** Registers the config command on the CLI program. */
13
+ export declare function registerConfig(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,135 @@
1
+ import { configExists, loadConfig, mutateConfig, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, writeConfig, writeConfigDocument, } from '../core/config.js';
2
+ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
3
+ import { toError } from '../utils/errors.js';
4
+ import { info, intro, outro, success, warn } from '../utils/logger.js';
5
+ import { pickDefined } from '../utils/options.js';
6
+ /**
7
+ * Gets a nested value from an object using dot notation.
8
+ * @param obj - Object to traverse
9
+ * @param path - Dot-separated path (e.g., "firefox.version")
10
+ * @returns The value at the path, or undefined if not found
11
+ */
12
+ function getNestedValue(obj, path) {
13
+ const parts = path.split('.');
14
+ let current = obj;
15
+ for (const part of parts) {
16
+ if (current === null || current === undefined) {
17
+ return undefined;
18
+ }
19
+ if (typeof current !== 'object') {
20
+ return undefined;
21
+ }
22
+ current = current[part];
23
+ }
24
+ return current;
25
+ }
26
+ /** Config keys that must always be stored as strings. */
27
+ const STRING_TYPED_KEYS = new Set([
28
+ 'name',
29
+ 'vendor',
30
+ 'appId',
31
+ 'binaryName',
32
+ 'firefox.version',
33
+ 'firefox.product',
34
+ 'license',
35
+ 'wire.subscriptDir',
36
+ ]);
37
+ /**
38
+ * Parses a string value into the appropriate type.
39
+ * Keys listed in STRING_TYPED_KEYS are always stored as strings to prevent
40
+ * accidental type coercion (e.g. `fireforge config firefox.version 128` would
41
+ * otherwise become the number 128 instead of the string "128").
42
+ */
43
+ function parseValue(value, key) {
44
+ // For known string-typed keys, always return as string
45
+ if (key && STRING_TYPED_KEYS.has(key)) {
46
+ return value;
47
+ }
48
+ // Try to parse as JSON first (handles numbers, booleans, arrays, objects).
49
+ try {
50
+ const parsed = JSON.parse(value);
51
+ if (typeof parsed !== 'string') {
52
+ warn(`Value "${value}" was interpreted as ${typeof parsed}. Use '"${value}"' for a string.`);
53
+ }
54
+ return parsed;
55
+ }
56
+ catch (error) {
57
+ void error;
58
+ // Fall back to string
59
+ return value;
60
+ }
61
+ }
62
+ /**
63
+ * Formats a value for display.
64
+ */
65
+ function formatValue(value) {
66
+ if (value === undefined) {
67
+ return '(not set)';
68
+ }
69
+ if (value === null || typeof value === 'object' || typeof value === 'function') {
70
+ return JSON.stringify(value, null, 2);
71
+ }
72
+ return String(value);
73
+ }
74
+ /**
75
+ * Runs the config command to get or set configuration values.
76
+ * @param projectRoot - Root directory of the project
77
+ * @param key - Configuration key (dot notation)
78
+ * @param value - Optional value to set
79
+ */
80
+ export async function configCommand(projectRoot, key, value, options = {}) {
81
+ intro('FireForge Config');
82
+ // Check if config exists
83
+ if (!(await configExists(projectRoot))) {
84
+ throw new GeneralError('No fireforge.json found. Run "fireforge setup" to create a project.');
85
+ }
86
+ const config = await loadConfig(projectRoot);
87
+ if (value === undefined) {
88
+ // Get mode
89
+ const currentValue = getNestedValue(config, key);
90
+ if (currentValue === undefined) {
91
+ throw new InvalidArgumentError(`Unknown config key: ${key}`);
92
+ }
93
+ else {
94
+ info(`${key} = ${formatValue(currentValue)}`);
95
+ }
96
+ }
97
+ else {
98
+ // Set mode — validate key prefix
99
+ const topLevelKey = key.split('.')[0] ?? key;
100
+ if (!SUPPORTED_CONFIG_ROOT_KEYS.includes(topLevelKey) &&
101
+ !options.force) {
102
+ throw new InvalidArgumentError(`Unknown config key prefix: "${topLevelKey}". Known keys: ${SUPPORTED_CONFIG_ROOT_KEYS.join(', ')}. Use --force to set anyway.`);
103
+ }
104
+ if (!SUPPORTED_CONFIG_PATHS.includes(key) && !options.force) {
105
+ throw new InvalidArgumentError(`Unknown config key: "${key}". Known keys: ${SUPPORTED_CONFIG_PATHS.join(', ')}. Use --force to set anyway.`);
106
+ }
107
+ const parsedValue = parseValue(value, key);
108
+ try {
109
+ if (options.force) {
110
+ const updatedConfig = mutateConfig(config, key, parsedValue, true);
111
+ await writeConfigDocument(projectRoot, updatedConfig);
112
+ }
113
+ else {
114
+ const updatedConfig = mutateConfig(config, key, parsedValue);
115
+ await writeConfig(projectRoot, updatedConfig);
116
+ }
117
+ }
118
+ catch (error) {
119
+ throw new InvalidArgumentError(`Invalid value for "${key}": ${toError(error).message}`, key);
120
+ }
121
+ success(`Set ${key} = ${formatValue(parsedValue)}`);
122
+ }
123
+ outro('');
124
+ }
125
+ /** Registers the config command on the CLI program. */
126
+ export function registerConfig(program, { getProjectRoot, withErrorHandling }) {
127
+ program
128
+ .command('config <key> [value]')
129
+ .description('Get or set configuration values')
130
+ .option('-f, --force', 'Allow setting unknown config keys')
131
+ .action(withErrorHandling(async (key, value, options) => {
132
+ await configCommand(getProjectRoot(), key, value, pickDefined(options));
133
+ }));
134
+ }
135
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,12 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { DiscardOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Runs the discard command to revert changes to a specific file.
6
+ * @param projectRoot - Root directory of the project
7
+ * @param file - File path to discard (relative to engine/)
8
+ * @param options - Discard options
9
+ */
10
+ export declare function discardCommand(projectRoot: string, file: string, options?: DiscardOptions): Promise<void>;
11
+ /** Registers the discard command on the CLI program. */
12
+ export declare function registerDiscard(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
@@ -0,0 +1,84 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ import { confirm } from '@clack/prompts';
3
+ import { getProjectPaths } from '../core/config.js';
4
+ import { isGitRepository } from '../core/git.js';
5
+ import { discardStatusEntry } from '../core/git-file-ops.js';
6
+ import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
7
+ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
8
+ import { GitError } from '../errors/git.js';
9
+ import { pathExists } from '../utils/fs.js';
10
+ import { info, intro, isCancel, outro, spinner } from '../utils/logger.js';
11
+ import { pickDefined } from '../utils/options.js';
12
+ /**
13
+ * Runs the discard command to revert changes to a specific file.
14
+ * @param projectRoot - Root directory of the project
15
+ * @param file - File path to discard (relative to engine/)
16
+ * @param options - Discard options
17
+ */
18
+ export async function discardCommand(projectRoot, file, options = {}) {
19
+ intro('FireForge Discard');
20
+ const paths = getProjectPaths(projectRoot);
21
+ // Check if engine exists
22
+ if (!(await pathExists(paths.engine))) {
23
+ throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
24
+ }
25
+ // Check if it's a git repository
26
+ if (!(await isGitRepository(paths.engine))) {
27
+ throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
28
+ }
29
+ // Check if the file has changes
30
+ const statusEntries = await expandUntrackedDirectoryEntries(paths.engine, await getWorkingTreeStatus(paths.engine));
31
+ const statusEntry = statusEntries.find((entry) => entry.file === file || entry.originalPath === file);
32
+ if (!statusEntry) {
33
+ throw new GeneralError(`File "${file}" has no changes to discard.`);
34
+ }
35
+ if (!options.force && !options.dryRun) {
36
+ const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
37
+ if (!isInteractive) {
38
+ throw new InvalidArgumentError('Interactive confirmation not available. Use --force flag to discard without confirmation.', 'Use: fireforge discard <file> --force');
39
+ }
40
+ const confirmed = await confirm({
41
+ message: `Discard all changes to ${statusEntry.file}?`,
42
+ initialValue: false,
43
+ });
44
+ if (isCancel(confirmed) || !confirmed) {
45
+ outro('Discard cancelled');
46
+ return;
47
+ }
48
+ }
49
+ if (options.dryRun) {
50
+ const target = statusEntry.originalPath === file
51
+ ? `${statusEntry.originalPath} -> ${statusEntry.file}`
52
+ : statusEntry.file;
53
+ info(`Would discard changes to: ${target}`);
54
+ outro('Dry run complete — no changes made');
55
+ return;
56
+ }
57
+ const s = spinner(`Discarding changes to ${file}...`);
58
+ try {
59
+ await discardStatusEntry(paths.engine, statusEntry);
60
+ s.stop(`Discarded changes to ${file}`);
61
+ outro('File restored to original state');
62
+ }
63
+ catch (error) {
64
+ s.error('Discard failed');
65
+ if (error instanceof GitError) {
66
+ throw error;
67
+ }
68
+ throw new GitError(`Failed to discard ${file}`, statusEntry.isUntracked
69
+ ? `rm ${statusEntry.file}`
70
+ : `restore --source HEAD --staged --worktree -- ${statusEntry.file}`, error instanceof Error ? error : undefined);
71
+ }
72
+ }
73
+ /** Registers the discard command on the CLI program. */
74
+ export function registerDiscard(program, { getProjectRoot, withErrorHandling }) {
75
+ program
76
+ .command('discard <file>')
77
+ .description('Discard changes to a specific file (deletes untracked files)')
78
+ .option('--dry-run', 'Show what would be discarded without doing it')
79
+ .option('--force', 'Skip confirmation prompt')
80
+ .action(withErrorHandling(async (file, options) => {
81
+ await discardCommand(getProjectRoot(), file, pickDefined(options));
82
+ }));
83
+ }
84
+ //# sourceMappingURL=discard.js.map
@@ -0,0 +1,18 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandContext } from '../types/cli.js';
3
+ import type { DoctorCheck, DoctorOptions } from '../types/commands/index.js';
4
+ /**
5
+ * Result of the doctor command, carrying the exit code so the caller
6
+ * (or test) can inspect it without relying on process.exitCode.
7
+ */
8
+ export interface DoctorResult {
9
+ checks: DoctorCheck[];
10
+ exitCode: number;
11
+ }
12
+ /**
13
+ * Runs the doctor command to diagnose issues.
14
+ * @param projectRoot - Root directory of the project
15
+ */
16
+ export declare function doctorCommand(projectRoot: string, options?: DoctorOptions): Promise<DoctorResult>;
17
+ /** Registers the doctor command on the CLI program. */
18
+ export declare function registerDoctor(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;