@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,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge rebase` — semi-automated ESR version upgrade.
|
|
4
|
+
*
|
|
5
|
+
* Orchestrates the full patch-rebase workflow:
|
|
6
|
+
* 1. Reset engine to baseline
|
|
7
|
+
* 2. Apply each patch with escalating fuzz
|
|
8
|
+
* 3. Pause on failures for manual resolution
|
|
9
|
+
* 4. Re-export successfully applied patches with the new version stamp
|
|
10
|
+
*
|
|
11
|
+
* Supports `--continue` (resume after manual fix) and `--abort` (cancel).
|
|
12
|
+
*/
|
|
13
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
14
|
+
import { getHead, isGitRepository, resetChanges } from '../../core/git.js';
|
|
15
|
+
import { discoverPatches } from '../../core/patch-files.js';
|
|
16
|
+
import { loadPatchesManifest } from '../../core/patch-manifest.js';
|
|
17
|
+
import { hasActiveRebaseSession, saveRebaseSession } from '../../core/rebase-session.js';
|
|
18
|
+
import { GeneralError } from '../../errors/base.js';
|
|
19
|
+
import { RebaseSessionExistsError } from '../../errors/rebase.js';
|
|
20
|
+
import { pathExists } from '../../utils/fs.js';
|
|
21
|
+
import { info, intro, outro, spinner } from '../../utils/logger.js';
|
|
22
|
+
import { pickDefined } from '../../utils/options.js';
|
|
23
|
+
import { handleAbort } from './abort.js';
|
|
24
|
+
import { confirmDirtyEngineReset } from './confirm.js';
|
|
25
|
+
import { handleContinue } from './continue.js';
|
|
26
|
+
import { runPatchLoop } from './patch-loop.js';
|
|
27
|
+
// ── Fresh start ──
|
|
28
|
+
async function handleFreshStart(projectRoot, options) {
|
|
29
|
+
const isDryRun = options.dryRun === true;
|
|
30
|
+
const maxFuzz = options.maxFuzz ?? 3;
|
|
31
|
+
intro(isDryRun ? 'FireForge Rebase (dry run)' : 'FireForge Rebase');
|
|
32
|
+
if (await hasActiveRebaseSession(projectRoot)) {
|
|
33
|
+
throw new RebaseSessionExistsError();
|
|
34
|
+
}
|
|
35
|
+
const paths = getProjectPaths(projectRoot);
|
|
36
|
+
if (!(await pathExists(paths.engine))) {
|
|
37
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
38
|
+
}
|
|
39
|
+
if (!(await isGitRepository(paths.engine))) {
|
|
40
|
+
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
41
|
+
}
|
|
42
|
+
const config = await loadConfig(projectRoot);
|
|
43
|
+
const currentVersion = config.firefox.version;
|
|
44
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
45
|
+
if (!manifest || manifest.patches.length === 0) {
|
|
46
|
+
throw new GeneralError('No patches found in manifest. Nothing to rebase.');
|
|
47
|
+
}
|
|
48
|
+
// Determine the "from" version from the patches
|
|
49
|
+
const patchVersions = new Set(manifest.patches.map((p) => p.sourceEsrVersion));
|
|
50
|
+
const sortedVersions = [...patchVersions].sort();
|
|
51
|
+
const fromVersion = sortedVersions[0] ?? currentVersion;
|
|
52
|
+
if (patchVersions.size === 1 && fromVersion === currentVersion) {
|
|
53
|
+
info('All patches already match the current Firefox version. Nothing to rebase.');
|
|
54
|
+
outro('Rebase not needed');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
info(`Rebasing patches: ${fromVersion} → ${currentVersion}`);
|
|
58
|
+
info(`Found ${manifest.patches.length} patch(es)`);
|
|
59
|
+
info(`Max fuzz factor: ${maxFuzz}`);
|
|
60
|
+
if (isDryRun) {
|
|
61
|
+
info('[dry-run] Would reset engine, apply patches with fuzz, and re-export.');
|
|
62
|
+
outro('Dry run complete');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!(await confirmDirtyEngineReset({
|
|
66
|
+
engineDir: paths.engine,
|
|
67
|
+
force: options.force ?? false,
|
|
68
|
+
nonInteractiveHint: 'Use: fireforge rebase --force',
|
|
69
|
+
warningMessage: 'The engine directory has uncommitted changes that will be lost by the rebase.',
|
|
70
|
+
promptMessage: 'Discard uncommitted changes and start rebase?',
|
|
71
|
+
cancelMessage: 'Rebase cancelled',
|
|
72
|
+
}))) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Record pre-rebase commit for --abort
|
|
76
|
+
const preRebaseCommit = await getHead(paths.engine);
|
|
77
|
+
// Reset engine to baseline
|
|
78
|
+
const resetSpinner = spinner('Resetting engine to baseline...');
|
|
79
|
+
await resetChanges(paths.engine);
|
|
80
|
+
resetSpinner.stop('Engine reset to baseline');
|
|
81
|
+
// Create rebase session
|
|
82
|
+
const allPatches = await discoverPatches(paths.patches);
|
|
83
|
+
const session = {
|
|
84
|
+
startedAt: new Date().toISOString(),
|
|
85
|
+
fromVersion,
|
|
86
|
+
toVersion: currentVersion,
|
|
87
|
+
preRebaseCommit,
|
|
88
|
+
patches: allPatches.map((p) => ({
|
|
89
|
+
filename: p.filename,
|
|
90
|
+
status: 'pending',
|
|
91
|
+
})),
|
|
92
|
+
currentIndex: 0,
|
|
93
|
+
};
|
|
94
|
+
await saveRebaseSession(projectRoot, session);
|
|
95
|
+
// Run the patch loop
|
|
96
|
+
await runPatchLoop(projectRoot, session, paths, maxFuzz);
|
|
97
|
+
}
|
|
98
|
+
// ── Public API ──
|
|
99
|
+
/**
|
|
100
|
+
* Runs the rebase command to orchestrate an ESR version upgrade.
|
|
101
|
+
* @param projectRoot - Root directory of the project
|
|
102
|
+
* @param options - Rebase options
|
|
103
|
+
*/
|
|
104
|
+
export async function rebaseCommand(projectRoot, options = {}) {
|
|
105
|
+
if (options.abort) {
|
|
106
|
+
return handleAbort(projectRoot, options.force);
|
|
107
|
+
}
|
|
108
|
+
if (options.continue) {
|
|
109
|
+
return handleContinue(projectRoot, options.maxFuzz ?? 3);
|
|
110
|
+
}
|
|
111
|
+
return handleFreshStart(projectRoot, options);
|
|
112
|
+
}
|
|
113
|
+
/** Registers the rebase command on the CLI program. */
|
|
114
|
+
export function registerRebase(program, { getProjectRoot, withErrorHandling }) {
|
|
115
|
+
program
|
|
116
|
+
.command('rebase')
|
|
117
|
+
.description('Semi-automated ESR version upgrade — apply patches with fuzz and re-export')
|
|
118
|
+
.option('--continue', 'Resume after manually resolving a failed patch')
|
|
119
|
+
.option('--abort', 'Cancel the rebase and restore engine to pre-rebase state')
|
|
120
|
+
.option('--dry-run', 'Show what would happen without modifying anything')
|
|
121
|
+
.option('--max-fuzz <n>', 'Maximum fuzz factor for git apply (default: 3)', parseInt)
|
|
122
|
+
.option('-f, --force', 'Skip dirty-tree confirmation prompt')
|
|
123
|
+
.action(withErrorHandling(async (options) => {
|
|
124
|
+
await rebaseCommand(getProjectRoot(), pickDefined(options));
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch application loop and re-export flow.
|
|
3
|
+
*/
|
|
4
|
+
import type { getProjectPaths } from '../../core/config.js';
|
|
5
|
+
import type { RebaseSession } from '../../core/rebase-session.js';
|
|
6
|
+
/**
|
|
7
|
+
* Runs the patch application loop, re-exports applied patches, and stamps versions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runPatchLoop(projectRoot: string, session: RebaseSession, paths: ReturnType<typeof getProjectPaths>, maxFuzz: number): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Patch application loop and re-export flow.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { loadState, saveState } from '../../core/config.js';
|
|
7
|
+
import { getDiffForFilesAgainstHead } from '../../core/git-diff.js';
|
|
8
|
+
import { applyPatchWithFuzz } from '../../core/patch-apply-fuzz.js';
|
|
9
|
+
import { updatePatch } from '../../core/patch-export.js';
|
|
10
|
+
import { discoverPatches } from '../../core/patch-files.js';
|
|
11
|
+
import { loadPatchesManifest, stampPatchVersions } from '../../core/patch-manifest.js';
|
|
12
|
+
import { extractConflictingFiles } from '../../core/patch-parse.js';
|
|
13
|
+
import { clearRebaseSession, saveRebaseSession } from '../../core/rebase-session.js';
|
|
14
|
+
import { toError } from '../../utils/errors.js';
|
|
15
|
+
import { pathExists } from '../../utils/fs.js';
|
|
16
|
+
import { error, info, outro, spinner, success, warn } from '../../utils/logger.js';
|
|
17
|
+
import { printSummary } from './summary.js';
|
|
18
|
+
/**
|
|
19
|
+
* Runs the patch application loop, re-exports applied patches, and stamps versions.
|
|
20
|
+
*/
|
|
21
|
+
export async function runPatchLoop(projectRoot, session, paths, maxFuzz) {
|
|
22
|
+
const allPatches = await discoverPatches(paths.patches);
|
|
23
|
+
const s = spinner('Applying patches...');
|
|
24
|
+
for (let i = session.currentIndex; i < session.patches.length; i++) {
|
|
25
|
+
const entry = session.patches[i];
|
|
26
|
+
if (!entry)
|
|
27
|
+
continue;
|
|
28
|
+
const patchFile = allPatches.find((p) => p.filename === entry.filename);
|
|
29
|
+
if (!patchFile) {
|
|
30
|
+
warn(`Patch file not found for ${entry.filename}, skipping.`);
|
|
31
|
+
entry.status = 'skipped';
|
|
32
|
+
session.currentIndex = i + 1;
|
|
33
|
+
await saveRebaseSession(projectRoot, session);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
s.message(`Applying ${entry.filename}...`);
|
|
37
|
+
const result = await applyPatchWithFuzz(patchFile.path, paths.engine, maxFuzz);
|
|
38
|
+
if (result.success) {
|
|
39
|
+
if (result.fuzzFactor === 0) {
|
|
40
|
+
entry.status = 'applied-clean';
|
|
41
|
+
success(` ${entry.filename} — applied cleanly`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
entry.status = 'applied-fuzz';
|
|
45
|
+
entry.fuzzFactor = result.fuzzFactor;
|
|
46
|
+
warn(` ${entry.filename} — applied with fuzz=${result.fuzzFactor}`);
|
|
47
|
+
}
|
|
48
|
+
session.currentIndex = i + 1;
|
|
49
|
+
await saveRebaseSession(projectRoot, session);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
entry.status = 'failed';
|
|
53
|
+
if (result.error) {
|
|
54
|
+
entry.error = result.error;
|
|
55
|
+
}
|
|
56
|
+
entry.conflictingFiles = extractConflictingFiles(result.error);
|
|
57
|
+
session.currentIndex = i;
|
|
58
|
+
await saveRebaseSession(projectRoot, session);
|
|
59
|
+
// Set pendingResolution in state for visibility
|
|
60
|
+
const state = await loadState(projectRoot);
|
|
61
|
+
state.pendingResolution = {
|
|
62
|
+
patchFilename: entry.filename,
|
|
63
|
+
originalError: result.error ?? 'Unknown error',
|
|
64
|
+
};
|
|
65
|
+
await saveState(projectRoot, state);
|
|
66
|
+
s.error(`${entry.filename} failed to apply`);
|
|
67
|
+
if (result.error) {
|
|
68
|
+
error(` Error: ${result.error}`);
|
|
69
|
+
}
|
|
70
|
+
if (result.rejectFiles && result.rejectFiles.length > 0) {
|
|
71
|
+
info(` .rej files created for manual resolution`);
|
|
72
|
+
}
|
|
73
|
+
info('');
|
|
74
|
+
info('Resolution instructions:');
|
|
75
|
+
info(' 1. Manually fix the conflicts in engine/ (look for .rej files)');
|
|
76
|
+
info(' 2. Run "fireforge rebase --continue" to resume');
|
|
77
|
+
info(' 3. Or run "fireforge rebase --abort" to cancel the rebase');
|
|
78
|
+
return; // Stop the loop
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
s.stop('All patches applied');
|
|
82
|
+
// Re-export all successfully applied patches
|
|
83
|
+
await reExportAppliedPatches(session, paths);
|
|
84
|
+
// Stamp versions
|
|
85
|
+
const appliedFilenames = session.patches
|
|
86
|
+
.filter((p) => p.status === 'applied-clean' || p.status === 'applied-fuzz' || p.status === 'resolved')
|
|
87
|
+
.map((p) => p.filename);
|
|
88
|
+
if (appliedFilenames.length > 0) {
|
|
89
|
+
await stampPatchVersions(paths.patches, appliedFilenames, session.toVersion);
|
|
90
|
+
}
|
|
91
|
+
// Print summary and clean up
|
|
92
|
+
printSummary(session);
|
|
93
|
+
await clearRebaseSession(projectRoot);
|
|
94
|
+
// Clear pending resolution if any
|
|
95
|
+
const state = await loadState(projectRoot);
|
|
96
|
+
if (state.pendingResolution) {
|
|
97
|
+
delete state.pendingResolution;
|
|
98
|
+
await saveState(projectRoot, state);
|
|
99
|
+
}
|
|
100
|
+
info('');
|
|
101
|
+
success(`All patches re-exported with sourceEsrVersion=${session.toVersion}`);
|
|
102
|
+
outro('Rebase complete!');
|
|
103
|
+
}
|
|
104
|
+
async function reExportAppliedPatches(session, paths) {
|
|
105
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
106
|
+
if (!manifest)
|
|
107
|
+
return;
|
|
108
|
+
const s = spinner('Re-exporting patches...');
|
|
109
|
+
for (const entry of session.patches) {
|
|
110
|
+
if (entry.status !== 'applied-clean' && entry.status !== 'applied-fuzz')
|
|
111
|
+
continue;
|
|
112
|
+
const meta = manifest.patches.find((p) => p.filename === entry.filename);
|
|
113
|
+
if (!meta)
|
|
114
|
+
continue;
|
|
115
|
+
s.message(`Re-exporting ${entry.filename}...`);
|
|
116
|
+
const existingFiles = [];
|
|
117
|
+
for (const f of meta.filesAffected) {
|
|
118
|
+
if (await pathExists(join(paths.engine, f))) {
|
|
119
|
+
existingFiles.push(f);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const diffContent = await getDiffForFilesAgainstHead(paths.engine, existingFiles);
|
|
124
|
+
if (diffContent.trim()) {
|
|
125
|
+
const patchPath = join(paths.patches, entry.filename);
|
|
126
|
+
await updatePatch(patchPath, diffContent);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
warn(`Failed to re-export ${entry.filename}: ${toError(err).message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
s.stop('Patches re-exported');
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=patch-loop.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebase summary and status label formatting.
|
|
3
|
+
*/
|
|
4
|
+
import type { RebasePatchEntry, RebaseSession } from '../../core/rebase-session.js';
|
|
5
|
+
/**
|
|
6
|
+
* Formats a status label for a rebase patch entry.
|
|
7
|
+
*/
|
|
8
|
+
export declare function statusLabel(status: RebasePatchEntry['status'], fuzzFactor?: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Prints the rebase summary table.
|
|
11
|
+
*/
|
|
12
|
+
export declare function printSummary(session: RebaseSession): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Rebase summary and status label formatting.
|
|
4
|
+
*/
|
|
5
|
+
import { info } from '../../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Formats a status label for a rebase patch entry.
|
|
8
|
+
*/
|
|
9
|
+
export function statusLabel(status, fuzzFactor) {
|
|
10
|
+
switch (status) {
|
|
11
|
+
case 'applied-clean':
|
|
12
|
+
return 'applied cleanly';
|
|
13
|
+
case 'applied-fuzz':
|
|
14
|
+
return `applied with fuzz=${fuzzFactor ?? '?'}`;
|
|
15
|
+
case 'resolved':
|
|
16
|
+
return 'RESOLVED manually';
|
|
17
|
+
case 'failed':
|
|
18
|
+
return 'FAILED';
|
|
19
|
+
case 'skipped':
|
|
20
|
+
return 'skipped';
|
|
21
|
+
case 'pending':
|
|
22
|
+
return 'pending';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Prints the rebase summary table.
|
|
27
|
+
*/
|
|
28
|
+
export function printSummary(session) {
|
|
29
|
+
info('');
|
|
30
|
+
info(`ESR Rebase Summary: ${session.fromVersion} → ${session.toVersion}`);
|
|
31
|
+
info('='.repeat(55));
|
|
32
|
+
for (const patch of session.patches) {
|
|
33
|
+
const label = statusLabel(patch.status, patch.fuzzFactor);
|
|
34
|
+
info(` ${patch.filename} ${'·'.repeat(Math.max(1, 45 - patch.filename.length - label.length))} ${label}`);
|
|
35
|
+
}
|
|
36
|
+
const clean = session.patches.filter((p) => p.status === 'applied-clean').length;
|
|
37
|
+
const fuzz = session.patches.filter((p) => p.status === 'applied-fuzz').length;
|
|
38
|
+
const resolved = session.patches.filter((p) => p.status === 'resolved').length;
|
|
39
|
+
const failed = session.patches.filter((p) => p.status === 'failed').length;
|
|
40
|
+
info('');
|
|
41
|
+
info(`Results: ${clean} clean, ${fuzz} fuzz-applied, ${resolved} manually resolved, ${failed} failed`);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
import type { RegisterOptions } from '../types/commands/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Registers a file in the appropriate build manifest.
|
|
6
|
+
*
|
|
7
|
+
* @param projectRoot - Root directory of the project
|
|
8
|
+
* @param filePath - Path relative to engine/
|
|
9
|
+
* @param options - Command options
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerCommand(projectRoot: string, filePath: string, options?: RegisterOptions): Promise<void>;
|
|
12
|
+
/** Registers the browser content registration command on the CLI program. */
|
|
13
|
+
export declare function registerRegister(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProjectPaths } from '../core/config.js';
|
|
4
|
+
import { registerFile } from '../core/manifest-rules.js';
|
|
5
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
6
|
+
import { pathExists } from '../utils/fs.js';
|
|
7
|
+
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
8
|
+
import { pickDefined } from '../utils/options.js';
|
|
9
|
+
/**
|
|
10
|
+
* Registers a file in the appropriate build manifest.
|
|
11
|
+
*
|
|
12
|
+
* @param projectRoot - Root directory of the project
|
|
13
|
+
* @param filePath - Path relative to engine/
|
|
14
|
+
* @param options - Command options
|
|
15
|
+
*/
|
|
16
|
+
export async function registerCommand(projectRoot, filePath, options = {}) {
|
|
17
|
+
intro('Register');
|
|
18
|
+
// Verify the file exists in engine/ (skip for dry-run)
|
|
19
|
+
if (!options.dryRun) {
|
|
20
|
+
const paths = getProjectPaths(projectRoot);
|
|
21
|
+
const fullPath = join(paths.engine, filePath);
|
|
22
|
+
if (!(await pathExists(fullPath))) {
|
|
23
|
+
throw new InvalidArgumentError(`File not found in engine: ${filePath}`, 'path');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const result = await registerFile(projectRoot, filePath, options.dryRun, options.after);
|
|
27
|
+
if (options.dryRun) {
|
|
28
|
+
info(`[dry-run] Would register ${filePath}`);
|
|
29
|
+
info(` manifest: ${result.manifest}`);
|
|
30
|
+
info(` entry: ${result.entry}`);
|
|
31
|
+
if (result.previousEntry) {
|
|
32
|
+
info(` insert after: ${result.previousEntry}`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
info(' insert at: start of matching section');
|
|
36
|
+
}
|
|
37
|
+
if (result.afterFallback) {
|
|
38
|
+
warn(`--after target "${options.after}" not found, falling back to alphabetical order`);
|
|
39
|
+
}
|
|
40
|
+
outro('Dry run complete');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (result.skipped) {
|
|
44
|
+
info(`Already registered: ${filePath} in ${result.manifest}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (result.afterFallback) {
|
|
48
|
+
warn(`--after target "${options.after}" not found, falling back to alphabetical order`);
|
|
49
|
+
}
|
|
50
|
+
const position = result.previousEntry ? ` (after ${result.previousEntry})` : '';
|
|
51
|
+
success(`Registered ${filePath} in ${result.manifest}${position}`);
|
|
52
|
+
info("hint: Run 'fireforge build --ui' to make the new module available at runtime");
|
|
53
|
+
}
|
|
54
|
+
outro('Done');
|
|
55
|
+
}
|
|
56
|
+
/** Registers the browser content registration command on the CLI program. */
|
|
57
|
+
export function registerRegister(program, { getProjectRoot, withErrorHandling }) {
|
|
58
|
+
program
|
|
59
|
+
.command('register <path>')
|
|
60
|
+
.description('Register a file in the appropriate build manifest')
|
|
61
|
+
.option('--dry-run', 'Show what would be changed without writing')
|
|
62
|
+
.option('--after <entry>', 'Place entry after line containing this substring (instead of alphabetical)')
|
|
63
|
+
.action(withErrorHandling(async (path, options) => {
|
|
64
|
+
await registerCommand(getProjectRoot(), path, pickDefined(options));
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
import type { ResetOptions } from '../types/commands/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the reset command to restore clean Firefox state.
|
|
6
|
+
* @param projectRoot - Root directory of the project
|
|
7
|
+
* @param options - Reset options
|
|
8
|
+
*/
|
|
9
|
+
export declare function resetCommand(projectRoot: string, options: ResetOptions): Promise<void>;
|
|
10
|
+
/** Registers the reset command on the CLI program. */
|
|
11
|
+
export declare function registerReset(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { confirm } from '@clack/prompts';
|
|
3
|
+
import { getProjectPaths } from '../core/config.js';
|
|
4
|
+
import { hasChanges, isGitRepository, resetChanges } from '../core/git.js';
|
|
5
|
+
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
|
|
6
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
7
|
+
import { pathExists } from '../utils/fs.js';
|
|
8
|
+
import { cancel, info, intro, isCancel, outro, spinner, warn } from '../utils/logger.js';
|
|
9
|
+
import { pickDefined } from '../utils/options.js';
|
|
10
|
+
/**
|
|
11
|
+
* Runs the reset command to restore clean Firefox state.
|
|
12
|
+
* @param projectRoot - Root directory of the project
|
|
13
|
+
* @param options - Reset options
|
|
14
|
+
*/
|
|
15
|
+
export async function resetCommand(projectRoot, options) {
|
|
16
|
+
intro('FireForge Reset');
|
|
17
|
+
const paths = getProjectPaths(projectRoot);
|
|
18
|
+
// Check if engine exists
|
|
19
|
+
if (!(await pathExists(paths.engine))) {
|
|
20
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
21
|
+
}
|
|
22
|
+
// Check if it's a git repository
|
|
23
|
+
if (!(await isGitRepository(paths.engine))) {
|
|
24
|
+
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
25
|
+
}
|
|
26
|
+
// Check for changes
|
|
27
|
+
const hasUncommittedChanges = await hasChanges(paths.engine);
|
|
28
|
+
if (!hasUncommittedChanges) {
|
|
29
|
+
info('No changes to reset');
|
|
30
|
+
outro('Working tree already clean');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Dry-run: show what would be reset
|
|
34
|
+
if (options.dryRun) {
|
|
35
|
+
const statusEntries = await expandUntrackedDirectoryEntries(paths.engine, await getWorkingTreeStatus(paths.engine));
|
|
36
|
+
info(`Would reset ${statusEntries.length} file${statusEntries.length === 1 ? '' : 's'}:`);
|
|
37
|
+
for (const entry of statusEntries) {
|
|
38
|
+
const label = entry.originalPath ? `${entry.originalPath} -> ${entry.file}` : entry.file;
|
|
39
|
+
info(` ${label}`);
|
|
40
|
+
}
|
|
41
|
+
outro('Dry run complete — no changes made');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Confirm reset unless --force is specified
|
|
45
|
+
if (!options.force) {
|
|
46
|
+
// Check for non-interactive mode
|
|
47
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
48
|
+
if (!isInteractive) {
|
|
49
|
+
throw new InvalidArgumentError('Interactive confirmation not available. Use --force flag to reset without confirmation.', 'Use: fireforge reset --force');
|
|
50
|
+
}
|
|
51
|
+
warn('This will discard all uncommitted changes in the engine directory, including staged additions and untracked files.');
|
|
52
|
+
const confirmed = await confirm({
|
|
53
|
+
message: 'Are you sure you want to reset?',
|
|
54
|
+
initialValue: false,
|
|
55
|
+
});
|
|
56
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
57
|
+
cancel('Reset cancelled');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const s = spinner('Resetting changes...');
|
|
62
|
+
try {
|
|
63
|
+
await resetChanges(paths.engine);
|
|
64
|
+
s.stop('Changes reset');
|
|
65
|
+
outro('Working tree restored to clean state');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
s.error('Reset failed');
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Registers the reset command on the CLI program. */
|
|
73
|
+
export function registerReset(program, { getProjectRoot, withErrorHandling }) {
|
|
74
|
+
program
|
|
75
|
+
.command('reset')
|
|
76
|
+
.description('Reset engine/ to clean state')
|
|
77
|
+
.option('-f, --force', 'Skip confirmation prompt (required for scripts/CI)')
|
|
78
|
+
.option('--dry-run', 'Show what would be reset without doing it')
|
|
79
|
+
.action(withErrorHandling(async (options) => {
|
|
80
|
+
await resetCommand(getProjectRoot(), pickDefined(options));
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=reset.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs the resolve command to fix broken patches.
|
|
5
|
+
* @param projectRoot - Root directory of the project
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveCommand(projectRoot: string): Promise<void>;
|
|
8
|
+
/** Registers the resolve command on the CLI program. */
|
|
9
|
+
export declare function registerResolve(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { confirm } from '@clack/prompts';
|
|
4
|
+
import { getProjectPaths, loadConfig, loadState, saveState } from '../core/config.js';
|
|
5
|
+
import { isGitRepository } from '../core/git.js';
|
|
6
|
+
import { getStagedDiffForFiles } from '../core/git-diff.js';
|
|
7
|
+
import { stageFiles, unstageFiles } from '../core/git-file-ops.js';
|
|
8
|
+
import { updatePatch, updatePatchMetadata } from '../core/patch-export.js';
|
|
9
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
10
|
+
import { GeneralError, ResolutionError } from '../errors/base.js';
|
|
11
|
+
import { toError } from '../utils/errors.js';
|
|
12
|
+
import { pathExists } from '../utils/fs.js';
|
|
13
|
+
import { error as logError, info, intro, isCancel, outro, spinner, success, } from '../utils/logger.js';
|
|
14
|
+
/**
|
|
15
|
+
* Runs the resolve command to fix broken patches.
|
|
16
|
+
* @param projectRoot - Root directory of the project
|
|
17
|
+
*/
|
|
18
|
+
export async function resolveCommand(projectRoot) {
|
|
19
|
+
intro('FireForge Resolve');
|
|
20
|
+
const paths = getProjectPaths(projectRoot);
|
|
21
|
+
const state = await loadState(projectRoot);
|
|
22
|
+
if (!state.pendingResolution) {
|
|
23
|
+
info('No patch resolution currently required.');
|
|
24
|
+
outro('Resolution complete');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const { patchFilename } = state.pendingResolution;
|
|
28
|
+
info(`Resolving conflict for patch: ${patchFilename}`);
|
|
29
|
+
// Check if engine exists
|
|
30
|
+
if (!(await pathExists(paths.engine))) {
|
|
31
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
32
|
+
}
|
|
33
|
+
// Check if it's a git repository
|
|
34
|
+
if (!(await isGitRepository(paths.engine))) {
|
|
35
|
+
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
36
|
+
}
|
|
37
|
+
if (!process.stdin.isTTY) {
|
|
38
|
+
throw new GeneralError('Cannot run "fireforge resolve" in non-interactive mode. Use a terminal with TTY support.');
|
|
39
|
+
}
|
|
40
|
+
const finished = await confirm({
|
|
41
|
+
message: 'Have you finished manually fixing the files in engine/?',
|
|
42
|
+
initialValue: true,
|
|
43
|
+
});
|
|
44
|
+
if (isCancel(finished) || !finished) {
|
|
45
|
+
info('Please fix the conflicts and run "fireforge resolve" again.');
|
|
46
|
+
outro('Resolution paused');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
50
|
+
if (!manifest) {
|
|
51
|
+
throw new GeneralError('Patches manifest not found.');
|
|
52
|
+
}
|
|
53
|
+
const patchMetadata = manifest.patches.find((p) => p.filename === patchFilename);
|
|
54
|
+
if (!patchMetadata) {
|
|
55
|
+
throw new ResolutionError(`Patch ${patchFilename} not found in manifest.`);
|
|
56
|
+
}
|
|
57
|
+
const s = spinner(`Updating ${patchFilename}...`);
|
|
58
|
+
try {
|
|
59
|
+
const existingFiles = patchMetadata.filesAffected;
|
|
60
|
+
// Verify all affected files exist in engine/
|
|
61
|
+
const missingFiles = [];
|
|
62
|
+
for (const file of existingFiles) {
|
|
63
|
+
const filePath = join(paths.engine, file);
|
|
64
|
+
if (!(await pathExists(filePath))) {
|
|
65
|
+
missingFiles.push(file);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (missingFiles.length === existingFiles.length) {
|
|
69
|
+
throw new ResolutionError(`All affected files for ${patchFilename} are missing.`);
|
|
70
|
+
}
|
|
71
|
+
// Filter to only existing files
|
|
72
|
+
const activeFiles = existingFiles.filter((f) => !missingFiles.includes(f));
|
|
73
|
+
// Stage, diff, unstage
|
|
74
|
+
let diffContent;
|
|
75
|
+
let staged = false;
|
|
76
|
+
try {
|
|
77
|
+
await stageFiles(paths.engine, activeFiles);
|
|
78
|
+
staged = true;
|
|
79
|
+
diffContent = await getStagedDiffForFiles(paths.engine, activeFiles);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (staged) {
|
|
83
|
+
await unstageFiles(paths.engine, activeFiles);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!diffContent.trim()) {
|
|
87
|
+
s.stop(`No patch update generated for ${patchFilename}`);
|
|
88
|
+
info('No patch update was generated from the staged diff. Pending resolution was left intact so you can retry. To discard the resolution state, delete the "pendingResolution" key from state.json.');
|
|
89
|
+
outro('Resolution unchanged');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Write the new diff content to the patch file
|
|
93
|
+
const patchPath = join(paths.patches, patchFilename);
|
|
94
|
+
await updatePatch(patchPath, diffContent);
|
|
95
|
+
// Update metadata (preserve original createdAt)
|
|
96
|
+
const config = await loadConfig(projectRoot);
|
|
97
|
+
await updatePatchMetadata(paths.patches, patchFilename, {
|
|
98
|
+
...(activeFiles.length < existingFiles.length ? { filesAffected: activeFiles } : {}),
|
|
99
|
+
sourceEsrVersion: config.firefox.version,
|
|
100
|
+
});
|
|
101
|
+
// Cleanup: Clear pendingResolution from state.json
|
|
102
|
+
delete state.pendingResolution;
|
|
103
|
+
await saveState(projectRoot, state);
|
|
104
|
+
s.stop(`Updated ${patchFilename}`);
|
|
105
|
+
success('Patch updated successfully and resolution state cleared.');
|
|
106
|
+
info('Run "fireforge import" to apply the remaining patches.');
|
|
107
|
+
outro('Resolution complete');
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
s.error(`Resolution failed for ${patchFilename}`);
|
|
111
|
+
logError(toError(error).message);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Registers the resolve command on the CLI program. */
|
|
116
|
+
export function registerResolve(program, { getProjectRoot, withErrorHandling }) {
|
|
117
|
+
program
|
|
118
|
+
.command('resolve')
|
|
119
|
+
.description('Update a broken patch with manual fixes and continue')
|
|
120
|
+
.action(withErrorHandling(async () => {
|
|
121
|
+
await resolveCommand(getProjectRoot());
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs the run command to launch the built browser.
|
|
5
|
+
* @param projectRoot - Root directory of the project
|
|
6
|
+
*/
|
|
7
|
+
export declare function runCommand(projectRoot: string): Promise<void>;
|
|
8
|
+
/** Registers the run command on the CLI program. */
|
|
9
|
+
export declare function registerRun(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|