@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,338 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProjectPaths } from '../../core/config.js';
|
|
4
|
+
import { applyAllComponents, applyCustomComponent, applyOverrideComponent, computeComponentChecksums, prefixChecksums, } from '../../core/furnace-apply.js';
|
|
5
|
+
import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, updateFurnaceState, } from '../../core/furnace-config.js';
|
|
6
|
+
import { createRollbackJournal, restoreRollbackJournalOrThrow, } from '../../core/furnace-rollback.js';
|
|
7
|
+
import { validateAllComponents, validateComponent } from '../../core/furnace-validate.js';
|
|
8
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
9
|
+
import { toError } from '../../utils/errors.js';
|
|
10
|
+
import { pathExists } from '../../utils/fs.js';
|
|
11
|
+
import { error, info, intro, note, outro, spinner, success, warn } from '../../utils/logger.js';
|
|
12
|
+
import { displayValidationIssues } from './validation-output.js';
|
|
13
|
+
/**
|
|
14
|
+
* Builds the final deploy failure summary from apply and validation error counts.
|
|
15
|
+
* @param applyErrors - Number of component application failures
|
|
16
|
+
* @param validationErrors - Number of validation failures
|
|
17
|
+
* @param isDryRun - Whether deploy was running in dry-run mode
|
|
18
|
+
* @returns User-facing deploy summary message
|
|
19
|
+
*/
|
|
20
|
+
function buildDeployFailureMessage(applyErrors, validationErrors, isDryRun) {
|
|
21
|
+
const mode = isDryRun ? 'Dry run' : 'Deploy';
|
|
22
|
+
if (applyErrors > 0 && validationErrors > 0) {
|
|
23
|
+
return `${mode} completed with ${applyErrors} apply error(s) and ${validationErrors} validation error(s).`;
|
|
24
|
+
}
|
|
25
|
+
if (applyErrors > 0) {
|
|
26
|
+
return `${mode} completed with ${applyErrors} apply error(s).`;
|
|
27
|
+
}
|
|
28
|
+
return `${mode} completed with ${validationErrors} validation error(s).`;
|
|
29
|
+
}
|
|
30
|
+
function getStepFailureCount(result) {
|
|
31
|
+
return result.applied.filter((entry) => (entry.stepErrors?.length ?? 0) > 0).length;
|
|
32
|
+
}
|
|
33
|
+
function getFailedComponentNames(result) {
|
|
34
|
+
const failed = new Set(result.errors.map((entry) => entry.name));
|
|
35
|
+
for (const applied of result.applied) {
|
|
36
|
+
if ((applied.stepErrors?.length ?? 0) > 0) {
|
|
37
|
+
failed.add(applied.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return failed;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Persists checksum state for a successfully applied named component.
|
|
44
|
+
* @param projectRoot - Root directory of the project
|
|
45
|
+
* @param appliedEntry - Applied component descriptor from deploy
|
|
46
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
47
|
+
*/
|
|
48
|
+
async function persistSingleComponentState(projectRoot, appliedEntry, furnacePaths) {
|
|
49
|
+
const componentDir = appliedEntry.type === 'override'
|
|
50
|
+
? join(furnacePaths.overridesDir, appliedEntry.name)
|
|
51
|
+
: join(furnacePaths.customDir, appliedEntry.name);
|
|
52
|
+
const checksums = await computeComponentChecksums(componentDir);
|
|
53
|
+
const prefixed = prefixChecksums(checksums, appliedEntry.type, appliedEntry.name);
|
|
54
|
+
await updateFurnaceState(projectRoot, (current) => ({
|
|
55
|
+
...current,
|
|
56
|
+
appliedChecksums: { ...(current.appliedChecksums ?? {}), ...prefixed },
|
|
57
|
+
lastApply: new Date().toISOString(),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Applies a single named override or custom component in targeted deploy mode.
|
|
62
|
+
* @param name - Component name to apply
|
|
63
|
+
* @param engineDir - Firefox engine source directory
|
|
64
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
65
|
+
* @param config - Loaded Furnace configuration
|
|
66
|
+
* @param isDryRun - Whether file writes should be skipped
|
|
67
|
+
* @returns Apply result for the named component, or `stock` for stock-only entries
|
|
68
|
+
*/
|
|
69
|
+
async function applyNamedComponent(name, engineDir, furnacePaths, config, isDryRun) {
|
|
70
|
+
const rollbackJournal = isDryRun ? undefined : createRollbackJournal();
|
|
71
|
+
const result = {
|
|
72
|
+
applied: [],
|
|
73
|
+
skipped: [],
|
|
74
|
+
errors: [],
|
|
75
|
+
actions: [],
|
|
76
|
+
};
|
|
77
|
+
const overrideConfig = config.overrides[name];
|
|
78
|
+
const customConfig = config.custom[name];
|
|
79
|
+
if (overrideConfig) {
|
|
80
|
+
const componentDir = join(furnacePaths.overridesDir, name);
|
|
81
|
+
if (!(await pathExists(componentDir))) {
|
|
82
|
+
throw new FurnaceError(`Component directory not found: components/overrides/${name}`, name);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const { affectedPaths: filesAffected, actions } = await applyOverrideComponent(engineDir, name, componentDir, overrideConfig, isDryRun, rollbackJournal);
|
|
86
|
+
if (isDryRun && actions) {
|
|
87
|
+
result.actions = actions;
|
|
88
|
+
}
|
|
89
|
+
result.applied.push({ name, type: 'override', filesAffected });
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
result.errors.push({ name, error: toError(error).message });
|
|
93
|
+
}
|
|
94
|
+
if (!isDryRun && result.errors.length > 0 && rollbackJournal) {
|
|
95
|
+
await restoreRollbackJournalOrThrow(rollbackJournal, `Furnace deploy failed for "${name}"`);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
if (customConfig) {
|
|
100
|
+
const componentDir = join(furnacePaths.customDir, name);
|
|
101
|
+
if (!(await pathExists(componentDir))) {
|
|
102
|
+
throw new FurnaceError(`Component directory not found: components/custom/${name}`, name);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const { affectedPaths: filesAffected, stepErrors, actions, } = await applyCustomComponent(engineDir, name, componentDir, customConfig, isDryRun, rollbackJournal);
|
|
106
|
+
if (isDryRun && actions) {
|
|
107
|
+
result.actions = actions;
|
|
108
|
+
}
|
|
109
|
+
result.applied.push({
|
|
110
|
+
name,
|
|
111
|
+
type: 'custom',
|
|
112
|
+
filesAffected,
|
|
113
|
+
...(stepErrors.length > 0 ? { stepErrors } : {}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
result.errors.push({ name, error: toError(error).message });
|
|
118
|
+
}
|
|
119
|
+
if (!isDryRun && getStepFailureCount(result) > 0 && rollbackJournal) {
|
|
120
|
+
await restoreRollbackJournalOrThrow(rollbackJournal, `Furnace deploy failed for "${name}"`);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
if (config.stock.includes(name)) {
|
|
125
|
+
return 'stock';
|
|
126
|
+
}
|
|
127
|
+
throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolves the validation target for a single named component.
|
|
131
|
+
* @param name - Component name to validate
|
|
132
|
+
* @param config - Loaded Furnace configuration
|
|
133
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
134
|
+
* @returns Validation target details, or `stock` for stock-only entries
|
|
135
|
+
*/
|
|
136
|
+
function resolveNamedValidationTarget(name, config, furnacePaths) {
|
|
137
|
+
if (name in config.overrides) {
|
|
138
|
+
return {
|
|
139
|
+
type: 'override',
|
|
140
|
+
componentDir: join(furnacePaths.overridesDir, name),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (name in config.custom) {
|
|
144
|
+
return {
|
|
145
|
+
type: 'custom',
|
|
146
|
+
componentDir: join(furnacePaths.customDir, name),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (config.stock.includes(name)) {
|
|
150
|
+
return 'stock';
|
|
151
|
+
}
|
|
152
|
+
throw new FurnaceError(`Component "${name}" not found in furnace.json.`, name);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Prints the deploy summary after apply and validation complete.
|
|
156
|
+
* @param result - Aggregate apply result
|
|
157
|
+
* @param totalErrors - Total validation errors encountered
|
|
158
|
+
* @param totalWarnings - Total validation warnings encountered
|
|
159
|
+
* @param componentCount - Number of components considered during deploy
|
|
160
|
+
* @param skippedValidationCount - Number of components skipped from validation
|
|
161
|
+
* @param isDryRun - Whether deploy was running in dry-run mode
|
|
162
|
+
*/
|
|
163
|
+
function printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun) {
|
|
164
|
+
const appliedCount = result.applied.length;
|
|
165
|
+
const skippedCount = result.skipped.length;
|
|
166
|
+
const applyErrors = result.errors.length + getStepFailureCount(result);
|
|
167
|
+
const stepErrorCount = result.applied.reduce((sum, a) => sum + (a.stepErrors?.length ?? 0), 0);
|
|
168
|
+
if (isDryRun) {
|
|
169
|
+
note(`Would apply ${appliedCount} component(s)\n` +
|
|
170
|
+
`${result.actions?.length ?? 0} planned action(s)\n` +
|
|
171
|
+
`${applyErrors} apply error(s)\n` +
|
|
172
|
+
`${totalErrors} validation error(s), ${totalWarnings} validation warning(s) across ${componentCount} validated component(s)` +
|
|
173
|
+
(skippedValidationCount > 0
|
|
174
|
+
? `\nSkipped validation for ${skippedValidationCount} component(s) with apply errors`
|
|
175
|
+
: ''), 'Dry Run Summary');
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
note(`Applied ${appliedCount}, skipped ${skippedCount}\n` +
|
|
179
|
+
`${applyErrors} apply error(s)` +
|
|
180
|
+
(stepErrorCount > 0 ? `, ${stepErrorCount} registration step error(s)` : '') +
|
|
181
|
+
`\n` +
|
|
182
|
+
`${totalErrors} validation error(s), ${totalWarnings} validation warning(s) across ${componentCount} validated component(s)` +
|
|
183
|
+
(skippedValidationCount > 0
|
|
184
|
+
? `\nSkipped validation for ${skippedValidationCount} component(s) with apply errors`
|
|
185
|
+
: ''), 'Deploy Summary');
|
|
186
|
+
}
|
|
187
|
+
const totalProblems = applyErrors + totalErrors;
|
|
188
|
+
if (totalProblems > 0) {
|
|
189
|
+
throw new FurnaceError(buildDeployFailureMessage(applyErrors, totalErrors, isDryRun));
|
|
190
|
+
}
|
|
191
|
+
outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
|
|
192
|
+
}
|
|
193
|
+
function logApplyResult(result, isDryRun) {
|
|
194
|
+
if (isDryRun && result.actions && result.actions.length > 0) {
|
|
195
|
+
info('Planned actions:');
|
|
196
|
+
for (const action of result.actions) {
|
|
197
|
+
info(` [${action.action}] ${action.component}: ${action.description}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (isDryRun) {
|
|
201
|
+
info('No actions would be performed.');
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
for (const applied of result.applied) {
|
|
205
|
+
success(`${applied.name} (${applied.type}) → ${applied.filesAffected.length} files`);
|
|
206
|
+
}
|
|
207
|
+
for (const skipped of result.skipped) {
|
|
208
|
+
info(`${skipped.name} — ${skipped.reason}`);
|
|
209
|
+
}
|
|
210
|
+
for (const applied of result.applied) {
|
|
211
|
+
if (applied.stepErrors && applied.stepErrors.length > 0) {
|
|
212
|
+
for (const stepErr of applied.stepErrors) {
|
|
213
|
+
warn(`${applied.name}: [${stepErr.step}] ${stepErr.error}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const err of result.errors) {
|
|
219
|
+
error(`${err.name} — ${err.error}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Runs the furnace deploy command: apply components then validate in one step.
|
|
224
|
+
* @param projectRoot - Root directory of the project
|
|
225
|
+
* @param name - Optional component name to deploy (deploys all if omitted)
|
|
226
|
+
* @param options - Command options
|
|
227
|
+
*/
|
|
228
|
+
export async function furnaceDeployCommand(projectRoot, name, options = {}) {
|
|
229
|
+
const isDryRun = options.dryRun ?? false;
|
|
230
|
+
intro(isDryRun ? 'Furnace Deploy (dry run)' : 'Furnace Deploy');
|
|
231
|
+
// Verify engine exists
|
|
232
|
+
const paths = getProjectPaths(projectRoot);
|
|
233
|
+
if (!(await pathExists(paths.engine))) {
|
|
234
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
235
|
+
}
|
|
236
|
+
// Load furnace config
|
|
237
|
+
if (!(await furnaceConfigExists(projectRoot))) {
|
|
238
|
+
throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
239
|
+
}
|
|
240
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
241
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
242
|
+
const overrideCount = Object.keys(config.overrides).length;
|
|
243
|
+
const customCount = Object.keys(config.custom).length;
|
|
244
|
+
if (overrideCount === 0 && customCount === 0) {
|
|
245
|
+
info('No components to deploy.');
|
|
246
|
+
outro('Done');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// --- Step 1: Apply ---
|
|
250
|
+
const applySpinner = spinner(isDryRun ? 'Calculating planned actions...' : 'Applying components to engine...');
|
|
251
|
+
let result;
|
|
252
|
+
if (name) {
|
|
253
|
+
const namedApplyResult = await applyNamedComponent(name, paths.engine, furnacePaths, config, isDryRun);
|
|
254
|
+
if (namedApplyResult === 'stock') {
|
|
255
|
+
applySpinner.stop('Apply skipped');
|
|
256
|
+
info(`"${name}" is a stock component. Stock components are not applied locally.`);
|
|
257
|
+
outro(isDryRun ? 'Dry run complete (no files modified)' : 'Deploy complete');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
result = namedApplyResult;
|
|
261
|
+
// Persist Furnace state for the deployed component so status/change
|
|
262
|
+
// detection stays accurate after single-component deploys.
|
|
263
|
+
if (!isDryRun &&
|
|
264
|
+
result.errors.length === 0 &&
|
|
265
|
+
getStepFailureCount(result) === 0 &&
|
|
266
|
+
result.applied.length > 0) {
|
|
267
|
+
const applied = result.applied[0];
|
|
268
|
+
await persistSingleComponentState(projectRoot, applied, furnacePaths);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
result = await applyAllComponents(projectRoot, isDryRun);
|
|
273
|
+
}
|
|
274
|
+
applySpinner.stop(isDryRun ? 'Planned actions calculated' : 'Components applied');
|
|
275
|
+
logApplyResult(result, isDryRun);
|
|
276
|
+
// --- Step 2: Validate (read-only, runs even in dry-run) ---
|
|
277
|
+
const validateSpinner = spinner('Validating components...');
|
|
278
|
+
const failedComponents = getFailedComponentNames(result);
|
|
279
|
+
let totalErrors = 0;
|
|
280
|
+
let totalWarnings = 0;
|
|
281
|
+
let componentCount = 0;
|
|
282
|
+
let skippedValidationCount = 0;
|
|
283
|
+
if (name && failedComponents.has(name)) {
|
|
284
|
+
skippedValidationCount = 1;
|
|
285
|
+
validateSpinner.stop('Validation skipped');
|
|
286
|
+
warn(`Skipping validation for ${name} because apply failed.`);
|
|
287
|
+
}
|
|
288
|
+
else if (name) {
|
|
289
|
+
const target = resolveNamedValidationTarget(name, config, furnacePaths);
|
|
290
|
+
if (target === 'stock') {
|
|
291
|
+
validateSpinner.stop('Validation skipped');
|
|
292
|
+
info(`"${name}" is a stock component. Stock components are not validated locally.`);
|
|
293
|
+
outro(isDryRun ? 'Dry run complete' : 'Deploy complete');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!(await pathExists(target.componentDir))) {
|
|
297
|
+
validateSpinner.stop('Validation failed');
|
|
298
|
+
throw new FurnaceError(`Component directory not found for "${name}".`, name);
|
|
299
|
+
}
|
|
300
|
+
const issues = await validateComponent(target.componentDir, name, target.type, config, projectRoot);
|
|
301
|
+
componentCount = 1;
|
|
302
|
+
validateSpinner.stop('Validation complete');
|
|
303
|
+
if (issues.length === 0) {
|
|
304
|
+
success(`${name} — all checks passed`);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const [errors, warnings] = displayValidationIssues(issues);
|
|
308
|
+
totalErrors += errors;
|
|
309
|
+
totalWarnings += warnings;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// Validate all components
|
|
314
|
+
const results = await validateAllComponents(projectRoot);
|
|
315
|
+
validateSpinner.stop('Validation complete');
|
|
316
|
+
for (const [componentName, issues] of results) {
|
|
317
|
+
if (failedComponents.has(componentName)) {
|
|
318
|
+
skippedValidationCount++;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
componentCount++;
|
|
322
|
+
if (issues.length === 0) {
|
|
323
|
+
success(`${componentName} — all checks passed`);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const [errors, warnings] = displayValidationIssues(issues);
|
|
327
|
+
totalErrors += errors;
|
|
328
|
+
totalWarnings += warnings;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (skippedValidationCount > 0) {
|
|
332
|
+
warn(`Skipped validation for ${skippedValidationCount} component(s) because their apply step failed.`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// --- Step 3: Summary ---
|
|
336
|
+
printDeploymentSummary(result, totalErrors, totalWarnings, componentCount, skippedValidationCount, isDryRun);
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs the furnace diff command to show changes vs the Firefox original.
|
|
3
|
+
* Only works for override components.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param name - Component name to diff
|
|
6
|
+
*/
|
|
7
|
+
export declare function furnaceDiffCommand(projectRoot: string, name: string): Promise<void>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getProjectPaths } from '../../core/config.js';
|
|
5
|
+
import { getFurnacePaths, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
6
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
7
|
+
import { pathExists, readText } from '../../utils/fs.js';
|
|
8
|
+
import { formatErrorText, formatSuccessText, info, intro, outro } from '../../utils/logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Computes a simplified line-level diff between two strings.
|
|
11
|
+
* Note: Uses a prefix/suffix matching algorithm that may combine
|
|
12
|
+
* multiple scattered changes into a single change region.
|
|
13
|
+
* For complex diffs with interleaved changes, the output may not
|
|
14
|
+
* be optimal.
|
|
15
|
+
*/
|
|
16
|
+
function lineDiff(original, modified) {
|
|
17
|
+
const oldLines = original.split('\n');
|
|
18
|
+
const newLines = modified.split('\n');
|
|
19
|
+
// Remove trailing empty element from trailing newline
|
|
20
|
+
if (oldLines[oldLines.length - 1] === '')
|
|
21
|
+
oldLines.pop();
|
|
22
|
+
if (newLines[newLines.length - 1] === '')
|
|
23
|
+
newLines.pop();
|
|
24
|
+
const output = [];
|
|
25
|
+
// Simple diff: find common prefix, common suffix, then show changed region
|
|
26
|
+
let firstDiff = 0;
|
|
27
|
+
while (firstDiff < oldLines.length && firstDiff < newLines.length) {
|
|
28
|
+
if (oldLines[firstDiff] !== newLines[firstDiff])
|
|
29
|
+
break;
|
|
30
|
+
firstDiff++;
|
|
31
|
+
}
|
|
32
|
+
let lastOldDiff = oldLines.length - 1;
|
|
33
|
+
let lastNewDiff = newLines.length - 1;
|
|
34
|
+
while (lastOldDiff > firstDiff && lastNewDiff > firstDiff) {
|
|
35
|
+
if (oldLines[lastOldDiff] !== newLines[lastNewDiff])
|
|
36
|
+
break;
|
|
37
|
+
lastOldDiff--;
|
|
38
|
+
lastNewDiff--;
|
|
39
|
+
}
|
|
40
|
+
// Context lines before the change
|
|
41
|
+
const contextLines = 3;
|
|
42
|
+
const contextStart = Math.max(0, firstDiff - contextLines);
|
|
43
|
+
const contextEndOld = Math.min(oldLines.length - 1, lastOldDiff + contextLines);
|
|
44
|
+
const contextEndNew = Math.min(newLines.length - 1, lastNewDiff + contextLines);
|
|
45
|
+
// Leading context
|
|
46
|
+
for (let i = contextStart; i < firstDiff; i++) {
|
|
47
|
+
output.push(` ${oldLines[i]}`);
|
|
48
|
+
}
|
|
49
|
+
// Removed lines
|
|
50
|
+
for (let i = firstDiff; i <= lastOldDiff; i++) {
|
|
51
|
+
output.push(formatErrorText(`- ${oldLines[i]}`));
|
|
52
|
+
}
|
|
53
|
+
// Added lines
|
|
54
|
+
for (let i = firstDiff; i <= lastNewDiff; i++) {
|
|
55
|
+
output.push(formatSuccessText(`+ ${newLines[i]}`));
|
|
56
|
+
}
|
|
57
|
+
// Trailing context
|
|
58
|
+
const trailingStart = Math.max(lastOldDiff + 1, lastNewDiff + 1);
|
|
59
|
+
const trailingEnd = Math.max(contextEndOld, contextEndNew);
|
|
60
|
+
// Use the new lines for trailing context (they should match old lines here)
|
|
61
|
+
for (let i = trailingStart; i <= trailingEnd && i < newLines.length; i++) {
|
|
62
|
+
output.push(` ${newLines[i]}`);
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Runs the furnace diff command to show changes vs the Firefox original.
|
|
68
|
+
* Only works for override components.
|
|
69
|
+
* @param projectRoot - Root directory of the project
|
|
70
|
+
* @param name - Component name to diff
|
|
71
|
+
*/
|
|
72
|
+
export async function furnaceDiffCommand(projectRoot, name) {
|
|
73
|
+
intro('Furnace Diff');
|
|
74
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
75
|
+
const paths = getProjectPaths(projectRoot);
|
|
76
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
77
|
+
// Verify the component is an override
|
|
78
|
+
const overrideConfig = config.overrides[name];
|
|
79
|
+
if (!overrideConfig) {
|
|
80
|
+
throw new FurnaceError(`"${name}" is not an override component. The diff command only works for overrides.`, name);
|
|
81
|
+
}
|
|
82
|
+
const overrideDir = join(furnacePaths.overridesDir, name);
|
|
83
|
+
if (!(await pathExists(overrideDir))) {
|
|
84
|
+
throw new FurnaceError(`Override directory not found: components/overrides/${name}`, name);
|
|
85
|
+
}
|
|
86
|
+
const entries = await readdir(overrideDir, { withFileTypes: true });
|
|
87
|
+
let hasDifferences = false;
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!entry.isFile())
|
|
90
|
+
continue;
|
|
91
|
+
if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
|
|
92
|
+
continue;
|
|
93
|
+
const originalPath = join(paths.engine, overrideConfig.basePath, entry.name);
|
|
94
|
+
const modifiedPath = join(overrideDir, entry.name);
|
|
95
|
+
if (!(await pathExists(originalPath))) {
|
|
96
|
+
info(`${entry.name}: original not found in engine (new file)`);
|
|
97
|
+
hasDifferences = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const originalContent = await readText(originalPath);
|
|
101
|
+
const modifiedContent = await readText(modifiedPath);
|
|
102
|
+
if (originalContent === modifiedContent) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
hasDifferences = true;
|
|
106
|
+
info(`--- ${overrideConfig.basePath}/${entry.name}`);
|
|
107
|
+
info(`+++ components/overrides/${name}/${entry.name}`);
|
|
108
|
+
const diffLines = lineDiff(originalContent, modifiedContent);
|
|
109
|
+
for (const line of diffLines) {
|
|
110
|
+
info(line);
|
|
111
|
+
}
|
|
112
|
+
info('');
|
|
113
|
+
}
|
|
114
|
+
if (!hasDifferences) {
|
|
115
|
+
info('No modifications found');
|
|
116
|
+
}
|
|
117
|
+
outro('Diff complete');
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandContext } from '../../types/cli.js';
|
|
3
|
+
import { furnaceApplyCommand } from './apply.js';
|
|
4
|
+
import { furnaceCreateCommand } from './create.js';
|
|
5
|
+
import { furnaceDeployCommand } from './deploy.js';
|
|
6
|
+
import { furnaceDiffCommand } from './diff.js';
|
|
7
|
+
import { furnaceListCommand } from './list.js';
|
|
8
|
+
import { furnaceOverrideCommand } from './override.js';
|
|
9
|
+
import { furnacePreviewCommand } from './preview.js';
|
|
10
|
+
import { furnaceRemoveCommand } from './remove.js';
|
|
11
|
+
import { furnaceScanCommand } from './scan.js';
|
|
12
|
+
import { furnaceStatusCommand } from './status.js';
|
|
13
|
+
import { furnaceValidateCommand } from './validate.js';
|
|
14
|
+
export { furnaceApplyCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRemoveCommand, furnaceScanCommand, furnaceStatusCommand, furnaceValidateCommand, };
|
|
15
|
+
/** Registers the furnace command on the CLI program. */
|
|
16
|
+
export declare function registerFurnace(program: Command, context: CommandContext): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { Option } from 'commander';
|
|
3
|
+
import { pickDefined } from '../../utils/options.js';
|
|
4
|
+
import { furnaceApplyCommand } from './apply.js';
|
|
5
|
+
import { furnaceCreateCommand } from './create.js';
|
|
6
|
+
import { furnaceDeployCommand } from './deploy.js';
|
|
7
|
+
import { furnaceDiffCommand } from './diff.js';
|
|
8
|
+
import { furnaceListCommand } from './list.js';
|
|
9
|
+
import { furnaceOverrideCommand } from './override.js';
|
|
10
|
+
import { furnacePreviewCommand } from './preview.js';
|
|
11
|
+
import { furnaceRemoveCommand } from './remove.js';
|
|
12
|
+
import { furnaceScanCommand } from './scan.js';
|
|
13
|
+
import { furnaceStatusCommand } from './status.js';
|
|
14
|
+
import { furnaceValidateCommand } from './validate.js';
|
|
15
|
+
export { furnaceApplyCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRemoveCommand, furnaceScanCommand, furnaceStatusCommand, furnaceValidateCommand, };
|
|
16
|
+
/**
|
|
17
|
+
* Registers read-only Furnace commands such as status, apply, deploy, and scan.
|
|
18
|
+
* @param furnace - Parent Furnace command
|
|
19
|
+
* @param context - Shared CLI registration context
|
|
20
|
+
*/
|
|
21
|
+
function registerFurnaceInfoCommands(furnace, context) {
|
|
22
|
+
const { getProjectRoot, withErrorHandling } = context;
|
|
23
|
+
furnace
|
|
24
|
+
.command('status [name]')
|
|
25
|
+
.description('Show component status and registration details')
|
|
26
|
+
.action(withErrorHandling(async (name) => {
|
|
27
|
+
await furnaceStatusCommand(getProjectRoot(), name);
|
|
28
|
+
}));
|
|
29
|
+
furnace
|
|
30
|
+
.command('apply')
|
|
31
|
+
.description('Apply all components to the engine')
|
|
32
|
+
.option('--dry-run', 'Show what would be changed without writing')
|
|
33
|
+
.action(withErrorHandling(async (options) => {
|
|
34
|
+
await furnaceApplyCommand(getProjectRoot(), pickDefined(options));
|
|
35
|
+
}));
|
|
36
|
+
furnace
|
|
37
|
+
.command('deploy [name]')
|
|
38
|
+
.description('Apply components and validate in one step')
|
|
39
|
+
.option('--dry-run', 'Show what would be changed without writing')
|
|
40
|
+
.action(withErrorHandling(async (name, options) => {
|
|
41
|
+
await furnaceDeployCommand(getProjectRoot(), name, pickDefined(options ?? {}));
|
|
42
|
+
}));
|
|
43
|
+
furnace
|
|
44
|
+
.command('scan')
|
|
45
|
+
.description('Scan engine for available components')
|
|
46
|
+
.action(withErrorHandling(async () => {
|
|
47
|
+
await furnaceScanCommand(getProjectRoot());
|
|
48
|
+
}));
|
|
49
|
+
furnace
|
|
50
|
+
.command('create [name]')
|
|
51
|
+
.description('Create a new custom component')
|
|
52
|
+
.option('-d, --description <desc>', 'Component description')
|
|
53
|
+
.option('--localized', 'Include Fluent l10n support')
|
|
54
|
+
.option('--no-register', 'Skip customElements.js registration')
|
|
55
|
+
.option('--with-tests', 'Scaffold Mochitest directory and register in moz.build')
|
|
56
|
+
.option('--compose <tags>', 'Stock component tags composed internally (comma-separated)', (val) => val.split(',').map((s) => s.trim()))
|
|
57
|
+
.action(withErrorHandling(async (name, options) => {
|
|
58
|
+
await furnaceCreateCommand(getProjectRoot(), name, options);
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Registers modifying Furnace commands such as override, remove, preview, and diff.
|
|
63
|
+
* @param furnace - Parent Furnace command
|
|
64
|
+
* @param context - Shared CLI registration context
|
|
65
|
+
*/
|
|
66
|
+
function registerFurnaceModifyCommands(furnace, context) {
|
|
67
|
+
const { getProjectRoot, withErrorHandling } = context;
|
|
68
|
+
furnace
|
|
69
|
+
.command('override [name]')
|
|
70
|
+
.description('Fork an existing component for modification')
|
|
71
|
+
.addOption(new Option('-t, --type <type>', 'Override type').choices(['css-only', 'full']))
|
|
72
|
+
.option('-d, --description <desc>', 'Description')
|
|
73
|
+
.action(withErrorHandling(async (name, options) => {
|
|
74
|
+
await furnaceOverrideCommand(getProjectRoot(), name, options);
|
|
75
|
+
}));
|
|
76
|
+
furnace
|
|
77
|
+
.command('list')
|
|
78
|
+
.description('List all registered components')
|
|
79
|
+
.action(withErrorHandling(async () => {
|
|
80
|
+
await furnaceListCommand(getProjectRoot());
|
|
81
|
+
}));
|
|
82
|
+
furnace
|
|
83
|
+
.command('remove <name>')
|
|
84
|
+
.description('Remove a component from the workspace')
|
|
85
|
+
.option('-f, --force', 'Skip confirmation')
|
|
86
|
+
.action(withErrorHandling(async (name, options) => {
|
|
87
|
+
await furnaceRemoveCommand(getProjectRoot(), name, options);
|
|
88
|
+
}));
|
|
89
|
+
furnace
|
|
90
|
+
.command('preview')
|
|
91
|
+
.description('Start component preview (Storybook)')
|
|
92
|
+
.option('--install', 'Force reinstall Storybook dependencies')
|
|
93
|
+
.action(withErrorHandling(async (options) => {
|
|
94
|
+
await furnacePreviewCommand(getProjectRoot(), options);
|
|
95
|
+
}));
|
|
96
|
+
furnace
|
|
97
|
+
.command('validate [name]')
|
|
98
|
+
.description('Run accessibility and compatibility checks')
|
|
99
|
+
.action(withErrorHandling(async (name) => {
|
|
100
|
+
await furnaceValidateCommand(getProjectRoot(), name);
|
|
101
|
+
}));
|
|
102
|
+
furnace
|
|
103
|
+
.command('diff <name>')
|
|
104
|
+
.description('Show changes vs Firefox original (overrides only)')
|
|
105
|
+
.action(withErrorHandling(async (name) => {
|
|
106
|
+
await furnaceDiffCommand(getProjectRoot(), name);
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
/** Registers the furnace command on the CLI program. */
|
|
110
|
+
export function registerFurnace(program, context) {
|
|
111
|
+
const { getProjectRoot, withErrorHandling } = context;
|
|
112
|
+
const furnace = program
|
|
113
|
+
.command('furnace')
|
|
114
|
+
.description('Component management (Furnace)')
|
|
115
|
+
.action(withErrorHandling(async () => {
|
|
116
|
+
await furnaceStatusCommand(getProjectRoot());
|
|
117
|
+
}));
|
|
118
|
+
registerFurnaceInfoCommands(furnace, context);
|
|
119
|
+
registerFurnaceModifyCommands(furnace, context);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
3
|
+
import { info, intro, note, outro } from '../../utils/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the furnace list command to display all registered components.
|
|
6
|
+
* @param projectRoot - Root directory of the project
|
|
7
|
+
*/
|
|
8
|
+
export async function furnaceListCommand(projectRoot) {
|
|
9
|
+
intro('Furnace List');
|
|
10
|
+
if (!(await furnaceConfigExists(projectRoot))) {
|
|
11
|
+
info('No components configured. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
12
|
+
outro('Done');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
16
|
+
const stockCount = config.stock.length;
|
|
17
|
+
const overrideCount = Object.keys(config.overrides).length;
|
|
18
|
+
const customCount = Object.keys(config.custom).length;
|
|
19
|
+
const total = stockCount + overrideCount + customCount;
|
|
20
|
+
if (total === 0) {
|
|
21
|
+
info('No components configured. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
22
|
+
outro('Done');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// --- Stock ---
|
|
26
|
+
if (stockCount > 0) {
|
|
27
|
+
info('Stock:');
|
|
28
|
+
for (const name of config.stock) {
|
|
29
|
+
info(` ${name}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// --- Overrides ---
|
|
33
|
+
if (overrideCount > 0) {
|
|
34
|
+
info('Overrides:');
|
|
35
|
+
for (const [name, entry] of Object.entries(config.overrides)) {
|
|
36
|
+
let line = ` ${name} (${entry.type})`;
|
|
37
|
+
if (entry.description) {
|
|
38
|
+
line += ` — ${entry.description}`;
|
|
39
|
+
}
|
|
40
|
+
info(line);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// --- Custom ---
|
|
44
|
+
if (customCount > 0) {
|
|
45
|
+
info('Custom:');
|
|
46
|
+
for (const [name, entry] of Object.entries(config.custom)) {
|
|
47
|
+
const flags = [];
|
|
48
|
+
if (entry.localized)
|
|
49
|
+
flags.push('localized');
|
|
50
|
+
if (entry.register)
|
|
51
|
+
flags.push('registered');
|
|
52
|
+
let line = ` ${name}`;
|
|
53
|
+
if (entry.description) {
|
|
54
|
+
line += ` — ${entry.description}`;
|
|
55
|
+
}
|
|
56
|
+
if (flags.length > 0) {
|
|
57
|
+
line += ` [${flags.join(', ')}]`;
|
|
58
|
+
}
|
|
59
|
+
info(line);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
note(`Stock: ${stockCount} Overrides: ${overrideCount} Custom: ${customCount} Total: ${total}`, 'Summary');
|
|
63
|
+
outro('List complete');
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=list.js.map
|