@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
|
@@ -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>;
|