@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE.md +294 -0
- package/README.md +435 -0
- package/dist/bin/fireforge.d.ts +10 -0
- package/dist/bin/fireforge.js +29 -0
- package/dist/src/cli.d.ts +33 -0
- package/dist/src/cli.js +180 -0
- package/dist/src/commands/bootstrap.d.ts +9 -0
- package/dist/src/commands/bootstrap.js +73 -0
- package/dist/src/commands/build.d.ts +11 -0
- package/dist/src/commands/build.js +102 -0
- package/dist/src/commands/config.d.ts +13 -0
- package/dist/src/commands/config.js +135 -0
- package/dist/src/commands/discard.d.ts +12 -0
- package/dist/src/commands/discard.js +84 -0
- package/dist/src/commands/doctor.d.ts +18 -0
- package/dist/src/commands/doctor.js +356 -0
- package/dist/src/commands/download.d.ts +11 -0
- package/dist/src/commands/download.js +127 -0
- package/dist/src/commands/export-all.d.ts +11 -0
- package/dist/src/commands/export-all.js +122 -0
- package/dist/src/commands/export-shared.d.ts +48 -0
- package/dist/src/commands/export-shared.js +208 -0
- package/dist/src/commands/export.d.ts +13 -0
- package/dist/src/commands/export.js +178 -0
- package/dist/src/commands/furnace/apply.d.ts +7 -0
- package/dist/src/commands/furnace/apply.js +80 -0
- package/dist/src/commands/furnace/create.d.ts +8 -0
- package/dist/src/commands/furnace/create.js +377 -0
- package/dist/src/commands/furnace/deploy.d.ts +8 -0
- package/dist/src/commands/furnace/deploy.js +338 -0
- package/dist/src/commands/furnace/diff.d.ts +7 -0
- package/dist/src/commands/furnace/diff.js +119 -0
- package/dist/src/commands/furnace/index.d.ts +16 -0
- package/dist/src/commands/furnace/index.js +121 -0
- package/dist/src/commands/furnace/list.d.ts +5 -0
- package/dist/src/commands/furnace/list.js +65 -0
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +188 -0
- package/dist/src/commands/furnace/preview.d.ts +7 -0
- package/dist/src/commands/furnace/preview.js +96 -0
- package/dist/src/commands/furnace/remove.d.ts +8 -0
- package/dist/src/commands/furnace/remove.js +159 -0
- package/dist/src/commands/furnace/scan.d.ts +5 -0
- package/dist/src/commands/furnace/scan.js +112 -0
- package/dist/src/commands/furnace/status.d.ts +7 -0
- package/dist/src/commands/furnace/status.js +137 -0
- package/dist/src/commands/furnace/validate.d.ts +6 -0
- package/dist/src/commands/furnace/validate.js +91 -0
- package/dist/src/commands/furnace/validation-output.d.ts +7 -0
- package/dist/src/commands/furnace/validation-output.js +22 -0
- package/dist/src/commands/import.d.ts +11 -0
- package/dist/src/commands/import.js +241 -0
- package/dist/src/commands/lint.d.ts +10 -0
- package/dist/src/commands/lint.js +118 -0
- package/dist/src/commands/package.d.ts +11 -0
- package/dist/src/commands/package.js +80 -0
- package/dist/src/commands/re-export.d.ts +12 -0
- package/dist/src/commands/re-export.js +242 -0
- package/dist/src/commands/rebase/abort.d.ts +7 -0
- package/dist/src/commands/rebase/abort.js +49 -0
- package/dist/src/commands/rebase/confirm.d.ts +18 -0
- package/dist/src/commands/rebase/confirm.js +33 -0
- package/dist/src/commands/rebase/continue.d.ts +7 -0
- package/dist/src/commands/rebase/continue.js +81 -0
- package/dist/src/commands/rebase/index.d.ts +22 -0
- package/dist/src/commands/rebase/index.js +127 -0
- package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
- package/dist/src/commands/rebase/patch-loop.js +135 -0
- package/dist/src/commands/rebase/summary.d.ts +12 -0
- package/dist/src/commands/rebase/summary.js +43 -0
- package/dist/src/commands/rebase.d.ts +4 -0
- package/dist/src/commands/rebase.js +6 -0
- package/dist/src/commands/register.d.ts +13 -0
- package/dist/src/commands/register.js +67 -0
- package/dist/src/commands/reset.d.ts +11 -0
- package/dist/src/commands/reset.js +83 -0
- package/dist/src/commands/resolve.d.ts +9 -0
- package/dist/src/commands/resolve.js +124 -0
- package/dist/src/commands/run.d.ts +9 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/setup-support.d.ts +23 -0
- package/dist/src/commands/setup-support.js +310 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.js +94 -0
- package/dist/src/commands/status.d.ts +11 -0
- package/dist/src/commands/status.js +268 -0
- package/dist/src/commands/test.d.ts +12 -0
- package/dist/src/commands/test.js +182 -0
- package/dist/src/commands/token-coverage.d.ts +5 -0
- package/dist/src/commands/token-coverage.js +57 -0
- package/dist/src/commands/token.d.ts +14 -0
- package/dist/src/commands/token.js +121 -0
- package/dist/src/commands/watch.d.ts +9 -0
- package/dist/src/commands/watch.js +112 -0
- package/dist/src/commands/wire.d.ts +13 -0
- package/dist/src/commands/wire.js +149 -0
- package/dist/src/core/ast-utils.d.ts +47 -0
- package/dist/src/core/ast-utils.js +57 -0
- package/dist/src/core/brand-validation.d.ts +7 -0
- package/dist/src/core/brand-validation.js +15 -0
- package/dist/src/core/branding.d.ts +49 -0
- package/dist/src/core/branding.js +229 -0
- package/dist/src/core/browser-wire.d.ts +40 -0
- package/dist/src/core/browser-wire.js +66 -0
- package/dist/src/core/build-prepare.d.ts +25 -0
- package/dist/src/core/build-prepare.js +93 -0
- package/dist/src/core/config-mutate.d.ts +15 -0
- package/dist/src/core/config-mutate.js +51 -0
- package/dist/src/core/config-paths.d.ts +28 -0
- package/dist/src/core/config-paths.js +65 -0
- package/dist/src/core/config-state.d.ts +28 -0
- package/dist/src/core/config-state.js +152 -0
- package/dist/src/core/config-validate.d.ts +11 -0
- package/dist/src/core/config-validate.js +141 -0
- package/dist/src/core/config.d.ts +39 -0
- package/dist/src/core/config.js +70 -0
- package/dist/src/core/file-lock.d.ts +11 -0
- package/dist/src/core/file-lock.js +80 -0
- package/dist/src/core/firefox-archive.d.ts +40 -0
- package/dist/src/core/firefox-archive.js +63 -0
- package/dist/src/core/firefox-cache.d.ts +23 -0
- package/dist/src/core/firefox-cache.js +134 -0
- package/dist/src/core/firefox-download.d.ts +21 -0
- package/dist/src/core/firefox-download.js +129 -0
- package/dist/src/core/firefox-extract.d.ts +21 -0
- package/dist/src/core/firefox-extract.js +53 -0
- package/dist/src/core/firefox.d.ts +34 -0
- package/dist/src/core/firefox.js +78 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
- package/dist/src/core/furnace-apply-helpers.js +244 -0
- package/dist/src/core/furnace-apply.d.ts +16 -0
- package/dist/src/core/furnace-apply.js +147 -0
- package/dist/src/core/furnace-config.d.ts +94 -0
- package/dist/src/core/furnace-config.js +372 -0
- package/dist/src/core/furnace-constants.d.ts +4 -0
- package/dist/src/core/furnace-constants.js +6 -0
- package/dist/src/core/furnace-registration-ast.d.ts +24 -0
- package/dist/src/core/furnace-registration-ast.js +218 -0
- package/dist/src/core/furnace-registration-remove.d.ts +14 -0
- package/dist/src/core/furnace-registration-remove.js +89 -0
- package/dist/src/core/furnace-registration-validate.d.ts +20 -0
- package/dist/src/core/furnace-registration-validate.js +40 -0
- package/dist/src/core/furnace-registration.d.ts +29 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-rollback.d.ts +20 -0
- package/dist/src/core/furnace-rollback.js +66 -0
- package/dist/src/core/furnace-scanner.d.ts +40 -0
- package/dist/src/core/furnace-scanner.js +143 -0
- package/dist/src/core/furnace-stories.d.ts +37 -0
- package/dist/src/core/furnace-stories.js +185 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-accessibility.js +32 -0
- package/dist/src/core/furnace-validate-checks.d.ts +4 -0
- package/dist/src/core/furnace-validate-checks.js +7 -0
- package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-compatibility.js +57 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
- package/dist/src/core/furnace-validate-helpers.js +129 -0
- package/dist/src/core/furnace-validate-registration.d.ts +37 -0
- package/dist/src/core/furnace-validate-registration.js +220 -0
- package/dist/src/core/furnace-validate-structure.d.ts +6 -0
- package/dist/src/core/furnace-validate-structure.js +66 -0
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +103 -0
- package/dist/src/core/git-base.d.ts +47 -0
- package/dist/src/core/git-base.js +50 -0
- package/dist/src/core/git-diff.d.ts +63 -0
- package/dist/src/core/git-diff.js +246 -0
- package/dist/src/core/git-file-ops.d.ts +65 -0
- package/dist/src/core/git-file-ops.js +141 -0
- package/dist/src/core/git-status.d.ts +65 -0
- package/dist/src/core/git-status.js +163 -0
- package/dist/src/core/git.d.ts +113 -0
- package/dist/src/core/git.js +363 -0
- package/dist/src/core/license-headers.d.ts +36 -0
- package/dist/src/core/license-headers.js +83 -0
- package/dist/src/core/mach-build-artifacts.d.ts +29 -0
- package/dist/src/core/mach-build-artifacts.js +117 -0
- package/dist/src/core/mach-mozconfig.d.ts +17 -0
- package/dist/src/core/mach-mozconfig.js +50 -0
- package/dist/src/core/mach-python.d.ts +16 -0
- package/dist/src/core/mach-python.js +126 -0
- package/dist/src/core/mach.d.ts +106 -0
- package/dist/src/core/mach.js +166 -0
- package/dist/src/core/manifest-helpers.d.ts +25 -0
- package/dist/src/core/manifest-helpers.js +96 -0
- package/dist/src/core/manifest-register.d.ts +30 -0
- package/dist/src/core/manifest-register.js +65 -0
- package/dist/src/core/manifest-rules.d.ts +39 -0
- package/dist/src/core/manifest-rules.js +151 -0
- package/dist/src/core/manifest-tokenizers.d.ts +34 -0
- package/dist/src/core/manifest-tokenizers.js +84 -0
- package/dist/src/core/parser-fallback.d.ts +36 -0
- package/dist/src/core/parser-fallback.js +43 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
- package/dist/src/core/patch-apply-fuzz.js +70 -0
- package/dist/src/core/patch-apply.d.ts +46 -0
- package/dist/src/core/patch-apply.js +235 -0
- package/dist/src/core/patch-export.d.ts +99 -0
- package/dist/src/core/patch-export.js +314 -0
- package/dist/src/core/patch-files.d.ts +11 -0
- package/dist/src/core/patch-files.js +51 -0
- package/dist/src/core/patch-lint.d.ts +72 -0
- package/dist/src/core/patch-lint.js +403 -0
- package/dist/src/core/patch-lock.d.ts +8 -0
- package/dist/src/core/patch-lock.js +29 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
- package/dist/src/core/patch-manifest-consistency.js +135 -0
- package/dist/src/core/patch-manifest-io.d.ts +36 -0
- package/dist/src/core/patch-manifest-io.js +77 -0
- package/dist/src/core/patch-manifest-query.d.ts +48 -0
- package/dist/src/core/patch-manifest-query.js +124 -0
- package/dist/src/core/patch-manifest-validate.d.ts +22 -0
- package/dist/src/core/patch-manifest-validate.js +72 -0
- package/dist/src/core/patch-manifest.d.ts +11 -0
- package/dist/src/core/patch-manifest.js +12 -0
- package/dist/src/core/patch-parse.d.ts +43 -0
- package/dist/src/core/patch-parse.js +143 -0
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +138 -0
- package/dist/src/core/rebase-session.d.ts +47 -0
- package/dist/src/core/rebase-session.js +65 -0
- package/dist/src/core/register-browser-content.d.ts +11 -0
- package/dist/src/core/register-browser-content.js +116 -0
- package/dist/src/core/register-module.d.ts +11 -0
- package/dist/src/core/register-module.js +76 -0
- package/dist/src/core/register-shared-css.d.ts +11 -0
- package/dist/src/core/register-shared-css.js +117 -0
- package/dist/src/core/register-test-manifest.d.ts +18 -0
- package/dist/src/core/register-test-manifest.js +99 -0
- package/dist/src/core/state-file.d.ts +4 -0
- package/dist/src/core/state-file.js +25 -0
- package/dist/src/core/token-coverage.d.ts +12 -0
- package/dist/src/core/token-coverage.js +74 -0
- package/dist/src/core/token-manager.d.ts +55 -0
- package/dist/src/core/token-manager.js +387 -0
- package/dist/src/core/wire-destroy.d.ts +21 -0
- package/dist/src/core/wire-destroy.js +103 -0
- package/dist/src/core/wire-dom-fragment.d.ts +23 -0
- package/dist/src/core/wire-dom-fragment.js +129 -0
- package/dist/src/core/wire-init.d.ts +23 -0
- package/dist/src/core/wire-init.js +201 -0
- package/dist/src/core/wire-subscript.d.ts +20 -0
- package/dist/src/core/wire-subscript.js +134 -0
- package/dist/src/core/wire-targets.d.ts +7 -0
- package/dist/src/core/wire-targets.js +9 -0
- package/dist/src/core/wire-utils.d.ts +88 -0
- package/dist/src/core/wire-utils.js +279 -0
- package/dist/src/errors/base.d.ts +60 -0
- package/dist/src/errors/base.js +87 -0
- package/dist/src/errors/build.d.ts +52 -0
- package/dist/src/errors/build.js +114 -0
- package/dist/src/errors/codes.d.ts +29 -0
- package/dist/src/errors/codes.js +30 -0
- package/dist/src/errors/config.d.ts +31 -0
- package/dist/src/errors/config.js +61 -0
- package/dist/src/errors/download.d.ts +42 -0
- package/dist/src/errors/download.js +95 -0
- package/dist/src/errors/furnace.d.ts +10 -0
- package/dist/src/errors/furnace.js +22 -0
- package/dist/src/errors/git.d.ts +41 -0
- package/dist/src/errors/git.js +99 -0
- package/dist/src/errors/patch.d.ts +10 -0
- package/dist/src/errors/patch.js +26 -0
- package/dist/src/errors/rebase.d.ts +20 -0
- package/dist/src/errors/rebase.js +30 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/types/cli.d.ts +14 -0
- package/dist/src/types/cli.js +2 -0
- package/dist/src/types/commands/index.d.ts +6 -0
- package/dist/src/types/commands/index.js +6 -0
- package/dist/src/types/commands/options.d.ts +239 -0
- package/dist/src/types/commands/options.js +6 -0
- package/dist/src/types/commands/patches.d.ts +89 -0
- package/dist/src/types/commands/patches.js +6 -0
- package/dist/src/types/commands/project.d.ts +71 -0
- package/dist/src/types/commands/project.js +6 -0
- package/dist/src/types/config.d.ts +101 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/furnace.d.ts +158 -0
- package/dist/src/types/furnace.js +2 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/utils/errors.d.ts +2 -0
- package/dist/src/utils/errors.js +15 -0
- package/dist/src/utils/fs.d.ts +72 -0
- package/dist/src/utils/fs.js +179 -0
- package/dist/src/utils/logger.d.ts +58 -0
- package/dist/src/utils/logger.js +120 -0
- package/dist/src/utils/options.d.ts +8 -0
- package/dist/src/utils/options.js +16 -0
- package/dist/src/utils/package-root.d.ts +10 -0
- package/dist/src/utils/package-root.js +53 -0
- package/dist/src/utils/parse.d.ts +110 -0
- package/dist/src/utils/parse.js +200 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +43 -0
- package/dist/src/utils/platform.d.ts +38 -0
- package/dist/src/utils/platform.js +56 -0
- package/dist/src/utils/process.d.ts +80 -0
- package/dist/src/utils/process.js +188 -0
- package/dist/src/utils/regex.d.ts +24 -0
- package/dist/src/utils/regex.js +40 -0
- package/dist/src/utils/validation.d.ts +133 -0
- package/dist/src/utils/validation.js +250 -0
- package/package.json +106 -0
- package/templates/configs/common.mozconfig +24 -0
- package/templates/configs/darwin.mozconfig +10 -0
- package/templates/configs/linux.mozconfig +12 -0
- package/templates/configs/win32.mozconfig +14 -0
- package/templates/licenses/0BSD.md +14 -0
- package/templates/licenses/EUPL-1.2.md +294 -0
- package/templates/licenses/GPL-2.0-or-later.md +339 -0
- package/templates/licenses/MPL-2.0.md +383 -0
package/dist/src/cli.js
ADDED
|
@@ -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;
|