@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,8 @@
|
|
|
1
|
+
import type { FurnaceOverrideOptions } from '../../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the furnace override command to fork an existing engine component.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param name - Optional component tag name (prompted if not provided)
|
|
6
|
+
* @param options - CLI options for non-interactive mode
|
|
7
|
+
*/
|
|
8
|
+
export declare function furnaceOverrideCommand(projectRoot: string, name?: string, options?: FurnaceOverrideOptions): Promise<void>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { select, text } from '@clack/prompts';
|
|
5
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
6
|
+
import { ensureFurnaceConfig, getFurnacePaths, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
7
|
+
import { getComponentDetails, scanWidgetsDirectory } from '../../core/furnace-scanner.js';
|
|
8
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
9
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
10
|
+
import { copyFile, ensureDir, pathExists, writeJson } from '../../utils/fs.js';
|
|
11
|
+
import { cancel, intro, isCancel, note, outro } from '../../utils/logger.js';
|
|
12
|
+
/**
|
|
13
|
+
* Copies the source files needed for a new override into the workspace.
|
|
14
|
+
* @param srcDir - Original component directory in the engine checkout
|
|
15
|
+
* @param destDir - Destination override directory in the workspace
|
|
16
|
+
* @param overrideType - Requested override mode
|
|
17
|
+
* @returns Filenames copied into the override directory
|
|
18
|
+
*/
|
|
19
|
+
async function copyOverrideFiles(srcDir, destDir, overrideType) {
|
|
20
|
+
await ensureDir(destDir);
|
|
21
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
22
|
+
const copiedFiles = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (!entry.isFile())
|
|
25
|
+
continue;
|
|
26
|
+
if (overrideType === 'css-only') {
|
|
27
|
+
// Only copy .css files
|
|
28
|
+
if (entry.name.endsWith('.css')) {
|
|
29
|
+
await copyFile(join(srcDir, entry.name), join(destDir, entry.name));
|
|
30
|
+
copiedFiles.push(entry.name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Full override: copy .mjs and .css files
|
|
35
|
+
if (entry.name.endsWith('.mjs') || entry.name.endsWith('.css')) {
|
|
36
|
+
await copyFile(join(srcDir, entry.name), join(destDir, entry.name));
|
|
37
|
+
copiedFiles.push(entry.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return copiedFiles;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Writes override metadata to disk and updates furnace.json with the new override entry.
|
|
45
|
+
* @param projectRoot - Root directory of the project
|
|
46
|
+
* @param destDir - Override component directory
|
|
47
|
+
* @param componentName - Component tag name
|
|
48
|
+
* @param overrideType - Override mode that was created
|
|
49
|
+
* @param description - Human-readable override description
|
|
50
|
+
* @param details - Source component metadata from the engine scan
|
|
51
|
+
* @param firefoxVersion - Firefox version recorded in the workspace config
|
|
52
|
+
* @param config - Mutable Furnace config object to update
|
|
53
|
+
*/
|
|
54
|
+
async function saveOverrideConfig(projectRoot, destDir, componentName, overrideType, description, details, firefoxVersion, config) {
|
|
55
|
+
const overrideJson = {
|
|
56
|
+
type: overrideType,
|
|
57
|
+
description,
|
|
58
|
+
basePath: details.sourcePath,
|
|
59
|
+
baseVersion: firefoxVersion,
|
|
60
|
+
};
|
|
61
|
+
await writeJson(join(destDir, 'override.json'), overrideJson);
|
|
62
|
+
config.overrides[componentName] = {
|
|
63
|
+
type: overrideType,
|
|
64
|
+
description,
|
|
65
|
+
basePath: details.sourcePath,
|
|
66
|
+
baseVersion: firefoxVersion,
|
|
67
|
+
};
|
|
68
|
+
await writeFurnaceConfig(projectRoot, config);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Runs the furnace override command to fork an existing engine component.
|
|
72
|
+
* @param projectRoot - Root directory of the project
|
|
73
|
+
* @param name - Optional component tag name (prompted if not provided)
|
|
74
|
+
* @param options - CLI options for non-interactive mode
|
|
75
|
+
*/
|
|
76
|
+
export async function furnaceOverrideCommand(projectRoot, name, options = {}) {
|
|
77
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
78
|
+
intro('Furnace Override');
|
|
79
|
+
// Load or create furnace.json
|
|
80
|
+
const config = await ensureFurnaceConfig(projectRoot);
|
|
81
|
+
const paths = getProjectPaths(projectRoot);
|
|
82
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
83
|
+
// Verify engine/ exists
|
|
84
|
+
if (!(await pathExists(paths.engine))) {
|
|
85
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
86
|
+
}
|
|
87
|
+
// --- Resolve component name ---
|
|
88
|
+
let componentName = name;
|
|
89
|
+
if (!componentName && isInteractive) {
|
|
90
|
+
// Scan for available components, filtering out already-overridden ones
|
|
91
|
+
const allComponents = await scanWidgetsDirectory(paths.engine);
|
|
92
|
+
const available = allComponents.filter((c) => !(c.tagName in config.overrides));
|
|
93
|
+
if (available.length === 0) {
|
|
94
|
+
throw new FurnaceError('No components available to override.');
|
|
95
|
+
}
|
|
96
|
+
const selected = await select({
|
|
97
|
+
message: 'Select a component to override:',
|
|
98
|
+
options: available.map((c) => ({
|
|
99
|
+
value: c.tagName,
|
|
100
|
+
label: c.tagName,
|
|
101
|
+
hint: [c.hasCSS && 'CSS', c.hasFTL && 'FTL', c.isRegistered && 'registered']
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.join(', '),
|
|
104
|
+
})),
|
|
105
|
+
});
|
|
106
|
+
if (isCancel(selected)) {
|
|
107
|
+
cancel('Override cancelled');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
componentName = selected;
|
|
111
|
+
}
|
|
112
|
+
else if (!componentName) {
|
|
113
|
+
throw new InvalidArgumentError('Component name is required in non-interactive mode.\n' +
|
|
114
|
+
'Usage: fireforge furnace override <name> -t <type> -d "description"', 'name');
|
|
115
|
+
}
|
|
116
|
+
// Validate component name to prevent path traversal
|
|
117
|
+
if (!/^[a-z][a-z0-9]*-[a-z0-9-]*$/.test(componentName)) {
|
|
118
|
+
throw new InvalidArgumentError(`Invalid component name "${componentName}": must contain a hyphen (required for custom elements), with only lowercase letters, digits, and hyphens.`, 'name');
|
|
119
|
+
}
|
|
120
|
+
// Check for existing override
|
|
121
|
+
if (componentName in config.overrides) {
|
|
122
|
+
throw new FurnaceError(`An override for "${componentName}" already exists in furnace.json`, componentName);
|
|
123
|
+
}
|
|
124
|
+
// Validate the component exists in engine
|
|
125
|
+
const details = await getComponentDetails(paths.engine, componentName);
|
|
126
|
+
if (!details) {
|
|
127
|
+
throw new FurnaceError(`Component "${componentName}" not found in the engine source tree.`, componentName);
|
|
128
|
+
}
|
|
129
|
+
// --- Resolve override type ---
|
|
130
|
+
let overrideType = options.type;
|
|
131
|
+
if (!overrideType && isInteractive) {
|
|
132
|
+
const typeResult = await select({
|
|
133
|
+
message: 'Override type:',
|
|
134
|
+
options: [
|
|
135
|
+
{
|
|
136
|
+
value: 'css-only',
|
|
137
|
+
label: 'CSS only — restyle the component',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
value: 'full',
|
|
141
|
+
label: 'Full override — modify styling and behavior',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
if (isCancel(typeResult)) {
|
|
146
|
+
cancel('Override cancelled');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
overrideType = typeResult;
|
|
150
|
+
}
|
|
151
|
+
else if (!overrideType) {
|
|
152
|
+
throw new InvalidArgumentError('Override type is required in non-interactive mode. Use -t css-only or -t full.', 'type');
|
|
153
|
+
}
|
|
154
|
+
if (overrideType === 'css-only' && !details.hasCSS) {
|
|
155
|
+
throw new FurnaceError(`Component "${componentName}" does not have any CSS files to override with --type css-only.`, componentName);
|
|
156
|
+
}
|
|
157
|
+
// --- Resolve description ---
|
|
158
|
+
let description = options.description ?? '';
|
|
159
|
+
if (!description && isInteractive) {
|
|
160
|
+
const descResult = await text({
|
|
161
|
+
message: 'Description (optional):',
|
|
162
|
+
placeholder: 'What are you changing about this component?',
|
|
163
|
+
});
|
|
164
|
+
if (!isCancel(descResult)) {
|
|
165
|
+
description = String(descResult);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// --- Copy original files ---
|
|
169
|
+
const srcDir = join(paths.engine, details.sourcePath);
|
|
170
|
+
const destDir = join(furnacePaths.overridesDir, componentName);
|
|
171
|
+
if (await pathExists(destDir)) {
|
|
172
|
+
throw new FurnaceError(`Directory already exists: components/overrides/${componentName}`, componentName);
|
|
173
|
+
}
|
|
174
|
+
const forgeConfig = await loadConfig(projectRoot);
|
|
175
|
+
const copiedFiles = await copyOverrideFiles(srcDir, destDir, overrideType);
|
|
176
|
+
await saveOverrideConfig(projectRoot, destDir, componentName, overrideType, description, details, forgeConfig.firefox.version, config);
|
|
177
|
+
// --- Success ---
|
|
178
|
+
note(`Files copied to components/overrides/${componentName}/:\n` +
|
|
179
|
+
copiedFiles.map((f) => ` ${f}`).join('\n') +
|
|
180
|
+
'\n override.json' +
|
|
181
|
+
'\n\n' +
|
|
182
|
+
'Next steps:\n' +
|
|
183
|
+
` 1. Edit the copied files in components/overrides/${componentName}/\n` +
|
|
184
|
+
' 2. Run "fireforge furnace preview" to see changes\n' +
|
|
185
|
+
' 3. Run "fireforge build" to apply and build', componentName);
|
|
186
|
+
outro('Override created');
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=override.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FurnacePreviewOptions } from '../../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the furnace preview command to start Storybook for component preview.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param options - Command options
|
|
6
|
+
*/
|
|
7
|
+
export declare function furnacePreviewCommand(projectRoot: string, options?: FurnacePreviewOptions): Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getProjectPaths } from '../../core/config.js';
|
|
4
|
+
import { furnaceConfigExists, loadFurnaceConfig } from '../../core/furnace-config.js';
|
|
5
|
+
import { cleanStories, syncStories } from '../../core/furnace-stories.js';
|
|
6
|
+
import { runMach, runMachCapture } from '../../core/mach.js';
|
|
7
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
8
|
+
import { pathExists } from '../../utils/fs.js';
|
|
9
|
+
import { info, intro, outro, spinner } from '../../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Builds a targeted Storybook failure message from captured mach output.
|
|
12
|
+
* @param output - Combined stdout and stderr from the Storybook command
|
|
13
|
+
* @param installRequested - Whether the caller requested a dependency reinstall first
|
|
14
|
+
* @returns User-facing guidance for the specific failure mode
|
|
15
|
+
*/
|
|
16
|
+
function buildStorybookFailureMessage(output, installRequested) {
|
|
17
|
+
const installHint = installRequested
|
|
18
|
+
? 'Try running "python3 ./mach storybook upgrade" manually in the engine directory.'
|
|
19
|
+
: 'Run "fireforge furnace preview --install" to bootstrap Storybook dependencies, or run "python3 ./mach storybook upgrade" manually in engine/.';
|
|
20
|
+
if (/(ENOENT|No such file or directory)/i.test(output) && /storybook|backend/i.test(output)) {
|
|
21
|
+
return ('Storybook failed because the Firefox checkout appears to be missing Storybook workspace files or backend dependencies.\n\n' +
|
|
22
|
+
installHint);
|
|
23
|
+
}
|
|
24
|
+
return ('Storybook failed to start. Check the output above for the specific Firefox-side error.\n\n' +
|
|
25
|
+
installHint);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Runs the furnace preview command to start Storybook for component preview.
|
|
29
|
+
* @param projectRoot - Root directory of the project
|
|
30
|
+
* @param options - Command options
|
|
31
|
+
*/
|
|
32
|
+
export async function furnacePreviewCommand(projectRoot, options = {}) {
|
|
33
|
+
intro('Furnace Preview (Storybook)');
|
|
34
|
+
// Verify engine exists
|
|
35
|
+
const paths = getProjectPaths(projectRoot);
|
|
36
|
+
if (!(await pathExists(paths.engine))) {
|
|
37
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
38
|
+
}
|
|
39
|
+
// Load furnace config
|
|
40
|
+
if (!(await furnaceConfigExists(projectRoot))) {
|
|
41
|
+
throw new FurnaceError('No furnace.json found. Run "fireforge furnace create" or "fireforge furnace override" to get started.');
|
|
42
|
+
}
|
|
43
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
44
|
+
const stockCount = config.stock.length;
|
|
45
|
+
const overrideCount = Object.keys(config.overrides).length;
|
|
46
|
+
const customCount = Object.keys(config.custom).length;
|
|
47
|
+
const totalCount = stockCount + overrideCount + customCount;
|
|
48
|
+
if (totalCount === 0) {
|
|
49
|
+
info('No components to preview.');
|
|
50
|
+
outro('Done');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const storybookRoot = join(paths.engine, 'browser', 'components', 'storybook');
|
|
54
|
+
if (!(await pathExists(storybookRoot))) {
|
|
55
|
+
throw new FurnaceError('This Firefox checkout does not contain browser/components/storybook. Furnace preview requires the upstream Storybook workspace to exist before stories can be synced.');
|
|
56
|
+
}
|
|
57
|
+
let previewResult;
|
|
58
|
+
let storiesSynced = false;
|
|
59
|
+
try {
|
|
60
|
+
// Sync story files
|
|
61
|
+
const syncSpinner = spinner('Syncing component stories...');
|
|
62
|
+
const result = await syncStories(projectRoot);
|
|
63
|
+
storiesSynced = true;
|
|
64
|
+
const created = result.created.length;
|
|
65
|
+
const updated = result.updated.length;
|
|
66
|
+
const total = created + updated;
|
|
67
|
+
syncSpinner.stop(`Synced ${total} stories (${created} new, ${updated} updated)`);
|
|
68
|
+
// Force-reinstall Storybook dependencies if requested
|
|
69
|
+
if (options.install) {
|
|
70
|
+
const installSpinner = spinner('Reinstalling Storybook dependencies...');
|
|
71
|
+
const installCode = await runMach(['storybook', 'upgrade'], paths.engine);
|
|
72
|
+
if (installCode !== 0) {
|
|
73
|
+
installSpinner.stop('Failed to reinstall Storybook dependencies');
|
|
74
|
+
throw new FurnaceError('Storybook dependency reinstallation failed. Try running "python3 ./mach storybook upgrade" manually in the engine directory.');
|
|
75
|
+
}
|
|
76
|
+
installSpinner.stop('Storybook dependencies reinstalled');
|
|
77
|
+
}
|
|
78
|
+
// Start Storybook
|
|
79
|
+
info('Starting Storybook...');
|
|
80
|
+
info('Press Ctrl+C to stop\n');
|
|
81
|
+
previewResult = await runMachCapture(['storybook'], paths.engine);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (storiesSynced) {
|
|
85
|
+
await cleanStories(paths.engine);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (previewResult.exitCode !== 0 &&
|
|
89
|
+
previewResult.exitCode !== 130 &&
|
|
90
|
+
previewResult.exitCode !== 143) {
|
|
91
|
+
const combinedOutput = `${previewResult.stdout}\n${previewResult.stderr}`;
|
|
92
|
+
throw new FurnaceError(buildStorybookFailureMessage(combinedOutput, options.install ?? false));
|
|
93
|
+
}
|
|
94
|
+
outro('Storybook stopped');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FurnaceRemoveOptions } from '../../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the furnace remove command to remove a component from the workspace.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param name - Component tag name to remove
|
|
6
|
+
* @param options - CLI options
|
|
7
|
+
*/
|
|
8
|
+
export declare function furnaceRemoveCommand(projectRoot: string, name: string, options?: FurnaceRemoveOptions): Promise<void>;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir, unlink } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { confirm } from '@clack/prompts';
|
|
5
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
6
|
+
import { getFurnacePaths, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
7
|
+
import { removeCustomElementRegistration, removeJarMnEntries, } from '../../core/furnace-registration.js';
|
|
8
|
+
import { deregisterTestManifest } from '../../core/manifest-register.js';
|
|
9
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
10
|
+
import { toError } from '../../utils/errors.js';
|
|
11
|
+
import { pathExists, readText, removeDir, writeText } from '../../utils/fs.js';
|
|
12
|
+
import { cancel, info, intro, isCancel, outro, warn } from '../../utils/logger.js';
|
|
13
|
+
/**
|
|
14
|
+
* Finds which section a component belongs to in the furnace config.
|
|
15
|
+
* @returns The component type, or undefined if not found
|
|
16
|
+
*/
|
|
17
|
+
function findComponentType(config, name) {
|
|
18
|
+
if (config.stock.includes(name))
|
|
19
|
+
return 'stock';
|
|
20
|
+
if (name in config.overrides)
|
|
21
|
+
return 'override';
|
|
22
|
+
if (name in config.custom)
|
|
23
|
+
return 'custom';
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Removes generated browser mochitest files associated with a custom component.
|
|
28
|
+
* @param name - Custom component tag name
|
|
29
|
+
* @param projectRoot - Root directory of the project
|
|
30
|
+
*/
|
|
31
|
+
async function cleanupCustomTestFiles(name, projectRoot) {
|
|
32
|
+
try {
|
|
33
|
+
const forgeConfig = await loadConfig(projectRoot);
|
|
34
|
+
const paths = getProjectPaths(projectRoot);
|
|
35
|
+
const binaryName = forgeConfig.binaryName;
|
|
36
|
+
const strippedName = name.startsWith('moz-') ? name.slice(4) : name;
|
|
37
|
+
const withoutBinaryPrefix = strippedName.startsWith(binaryName + '-')
|
|
38
|
+
? strippedName.slice(binaryName.length + 1)
|
|
39
|
+
: strippedName;
|
|
40
|
+
const underscored = withoutBinaryPrefix.replace(/-/g, '_');
|
|
41
|
+
const testFileName = `browser_${binaryName}_${underscored}.js`;
|
|
42
|
+
const testDir = join(paths.engine, 'browser/base/content/test', binaryName);
|
|
43
|
+
if (await pathExists(testDir)) {
|
|
44
|
+
const testFilePath = join(testDir, testFileName);
|
|
45
|
+
if (await pathExists(testFilePath)) {
|
|
46
|
+
await unlink(testFilePath);
|
|
47
|
+
info(`Deleted test file: ${testFileName}`);
|
|
48
|
+
}
|
|
49
|
+
const tomlPath = join(testDir, 'browser.toml');
|
|
50
|
+
if (await pathExists(tomlPath)) {
|
|
51
|
+
const toml = await readText(tomlPath);
|
|
52
|
+
const entryPattern = `["${testFileName}"]`;
|
|
53
|
+
if (toml.includes(entryPattern)) {
|
|
54
|
+
const updated = toml.replace(new RegExp(`\\n?\\n?\\["${testFileName}"\\]\\n?`), '\n');
|
|
55
|
+
await writeText(tomlPath, updated);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const remaining = await readdir(testDir);
|
|
59
|
+
const hasTests = remaining.some((f) => f.startsWith('browser_') && f.endsWith('.js'));
|
|
60
|
+
if (!hasTests) {
|
|
61
|
+
await removeDir(testDir);
|
|
62
|
+
info(`Deleted empty test directory: browser/base/content/test/${binaryName}/`);
|
|
63
|
+
if (await deregisterTestManifest(paths.engine, binaryName)) {
|
|
64
|
+
info('Deregistered test manifest from browser/base/moz.build');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
warn(`Could not clean up test files — ${toError(error).message}. Remove them manually if needed.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Runs the furnace remove command to remove a component from the workspace.
|
|
75
|
+
* @param projectRoot - Root directory of the project
|
|
76
|
+
* @param name - Component tag name to remove
|
|
77
|
+
* @param options - CLI options
|
|
78
|
+
*/
|
|
79
|
+
export async function furnaceRemoveCommand(projectRoot, name, options = {}) {
|
|
80
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
81
|
+
intro('Furnace Remove');
|
|
82
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
83
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
84
|
+
// Find which section the component belongs to
|
|
85
|
+
const type = findComponentType(config, name);
|
|
86
|
+
if (!type) {
|
|
87
|
+
throw new FurnaceError(`Component "${name}" not found in furnace.json. Run "fireforge furnace list" to see registered components.`, name);
|
|
88
|
+
}
|
|
89
|
+
// Require --force in non-interactive mode to prevent silent removals
|
|
90
|
+
if (!isInteractive && !options.force) {
|
|
91
|
+
throw new FurnaceError(`Cannot remove "${name}" in non-interactive mode without --force flag.`, name);
|
|
92
|
+
}
|
|
93
|
+
// Confirm removal (skip if --force)
|
|
94
|
+
if (!options.force && isInteractive) {
|
|
95
|
+
const confirmed = await confirm({
|
|
96
|
+
message: `Remove ${type} component "${name}"?`,
|
|
97
|
+
});
|
|
98
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
99
|
+
cancel('Remove cancelled');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Delete component directory for override and custom types
|
|
104
|
+
const paths = getProjectPaths(projectRoot);
|
|
105
|
+
if (type === 'override') {
|
|
106
|
+
const dir = join(furnacePaths.overridesDir, name);
|
|
107
|
+
if (await pathExists(dir)) {
|
|
108
|
+
await removeDir(dir);
|
|
109
|
+
info(`Deleted components/overrides/${name}/`);
|
|
110
|
+
}
|
|
111
|
+
// Clean up deployed files in engine
|
|
112
|
+
const overrideConfig = config.overrides[name];
|
|
113
|
+
if (overrideConfig?.basePath) {
|
|
114
|
+
const engineDir = join(paths.engine, overrideConfig.basePath);
|
|
115
|
+
if (await pathExists(engineDir)) {
|
|
116
|
+
warn(`Deployed files may remain in engine/${overrideConfig.basePath}. Run "fireforge reset -f && fireforge import" to clean.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (type === 'custom') {
|
|
121
|
+
const customConfig = config.custom[name];
|
|
122
|
+
if (customConfig?.register) {
|
|
123
|
+
await removeCustomElementRegistration(paths.engine, name);
|
|
124
|
+
info(`Deregistered ${name} from customElements.js`);
|
|
125
|
+
}
|
|
126
|
+
await removeJarMnEntries(paths.engine, name);
|
|
127
|
+
info(`Removed ${name} entries from toolkit/content/jar.mn`);
|
|
128
|
+
const dir = join(furnacePaths.customDir, name);
|
|
129
|
+
if (await pathExists(dir)) {
|
|
130
|
+
await removeDir(dir);
|
|
131
|
+
info(`Deleted components/custom/${name}/`);
|
|
132
|
+
}
|
|
133
|
+
// Clean up deployed files in engine
|
|
134
|
+
if (customConfig?.targetPath) {
|
|
135
|
+
const engineDir = join(paths.engine, customConfig.targetPath);
|
|
136
|
+
if (await pathExists(engineDir)) {
|
|
137
|
+
await removeDir(engineDir);
|
|
138
|
+
info(`Deleted deployed files from engine/${customConfig.targetPath}/`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (type === 'custom') {
|
|
143
|
+
await cleanupCustomTestFiles(name, projectRoot);
|
|
144
|
+
}
|
|
145
|
+
// Remove entry from furnace.json
|
|
146
|
+
if (type === 'stock') {
|
|
147
|
+
config.stock = config.stock.filter((s) => s !== name);
|
|
148
|
+
}
|
|
149
|
+
else if (type === 'override') {
|
|
150
|
+
config.overrides = Object.fromEntries(Object.entries(config.overrides).filter(([key]) => key !== name));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
config.custom = Object.fromEntries(Object.entries(config.custom).filter(([key]) => key !== name));
|
|
154
|
+
}
|
|
155
|
+
await writeFurnaceConfig(projectRoot, config);
|
|
156
|
+
info(`Removed "${name}" from furnace.json`);
|
|
157
|
+
outro('Component removed');
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=remove.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { confirm, multiselect } from '@clack/prompts';
|
|
3
|
+
import { getProjectPaths } from '../../core/config.js';
|
|
4
|
+
import { ensureFurnaceConfig, furnaceConfigExists, loadFurnaceConfig, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
5
|
+
import { scanWidgetsDirectory } from '../../core/furnace-scanner.js';
|
|
6
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
7
|
+
import { pathExists } from '../../utils/fs.js';
|
|
8
|
+
import { cancel, info, intro, isCancel, note, outro, spinner, success, } from '../../utils/logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Prompts the user to add newly discovered stock components to furnace.json.
|
|
11
|
+
* @param components - Components discovered in the engine scan
|
|
12
|
+
* @param tracked - Existing Furnace tracking map keyed by tag name
|
|
13
|
+
* @param projectRoot - Root directory of the project
|
|
14
|
+
*/
|
|
15
|
+
async function promptAddComponents(components, tracked, projectRoot) {
|
|
16
|
+
const untrackedComponents = components.filter((c) => !tracked.has(c.tagName));
|
|
17
|
+
const shouldAdd = await confirm({ message: 'Add components to furnace.json?' });
|
|
18
|
+
if (isCancel(shouldAdd) || !shouldAdd) {
|
|
19
|
+
if (isCancel(shouldAdd)) {
|
|
20
|
+
cancel('Cancelled');
|
|
21
|
+
}
|
|
22
|
+
outro('Scan complete');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const selected = await multiselect({
|
|
26
|
+
message: 'Select components to add as stock',
|
|
27
|
+
options: untrackedComponents.map((c) => {
|
|
28
|
+
const features = [];
|
|
29
|
+
if (c.hasCSS)
|
|
30
|
+
features.push('CSS');
|
|
31
|
+
if (c.hasFTL)
|
|
32
|
+
features.push('FTL');
|
|
33
|
+
if (c.isRegistered)
|
|
34
|
+
features.push('registered');
|
|
35
|
+
const label = features.length > 0 ? `${c.tagName} — ${features.join(', ')}` : c.tagName;
|
|
36
|
+
return { value: c.tagName, label };
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
if (isCancel(selected)) {
|
|
40
|
+
cancel('Cancelled');
|
|
41
|
+
outro('Scan complete');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const config = await ensureFurnaceConfig(projectRoot);
|
|
45
|
+
const toAdd = selected.filter((s) => !config.stock.includes(s));
|
|
46
|
+
config.stock.push(...toAdd);
|
|
47
|
+
await writeFurnaceConfig(projectRoot, config);
|
|
48
|
+
success(`Added ${selected.length} component${selected.length === 1 ? '' : 's'} to furnace.json`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Runs the furnace scan command to discover MozLitElement components.
|
|
52
|
+
* @param projectRoot - Root directory of the project
|
|
53
|
+
*/
|
|
54
|
+
export async function furnaceScanCommand(projectRoot) {
|
|
55
|
+
intro('Furnace Scan');
|
|
56
|
+
const paths = getProjectPaths(projectRoot);
|
|
57
|
+
if (!(await pathExists(paths.engine))) {
|
|
58
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
59
|
+
}
|
|
60
|
+
const s = spinner('Scanning engine for components...');
|
|
61
|
+
const components = await scanWidgetsDirectory(paths.engine);
|
|
62
|
+
s.stop(`Found ${components.length} component${components.length === 1 ? '' : 's'}`);
|
|
63
|
+
// Build tracking info from furnace.json if it exists
|
|
64
|
+
const tracked = new Map();
|
|
65
|
+
if (await furnaceConfigExists(projectRoot)) {
|
|
66
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
67
|
+
for (const name of config.stock) {
|
|
68
|
+
tracked.set(name, 'stock');
|
|
69
|
+
}
|
|
70
|
+
for (const name of Object.keys(config.overrides)) {
|
|
71
|
+
tracked.set(name, 'override');
|
|
72
|
+
}
|
|
73
|
+
for (const name of Object.keys(config.custom)) {
|
|
74
|
+
tracked.set(name, 'custom');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Display each component
|
|
78
|
+
for (const component of components) {
|
|
79
|
+
const features = [];
|
|
80
|
+
if (component.hasCSS)
|
|
81
|
+
features.push('CSS');
|
|
82
|
+
if (component.hasFTL)
|
|
83
|
+
features.push('FTL');
|
|
84
|
+
if (component.isRegistered)
|
|
85
|
+
features.push('registered');
|
|
86
|
+
let line = component.tagName;
|
|
87
|
+
if (features.length > 0) {
|
|
88
|
+
line += ` — ${features.join(', ')}`;
|
|
89
|
+
}
|
|
90
|
+
const type = tracked.get(component.tagName);
|
|
91
|
+
if (type) {
|
|
92
|
+
line += ` [${type}]`;
|
|
93
|
+
}
|
|
94
|
+
info(line);
|
|
95
|
+
}
|
|
96
|
+
// Summary
|
|
97
|
+
let trackedCount = 0;
|
|
98
|
+
for (const component of components) {
|
|
99
|
+
if (tracked.has(component.tagName)) {
|
|
100
|
+
trackedCount++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const untrackedCount = components.length - trackedCount;
|
|
104
|
+
note(`Total: ${components.length} Tracked: ${trackedCount} Untracked: ${untrackedCount}`, 'Summary');
|
|
105
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
106
|
+
if (isInteractive && untrackedCount > 0) {
|
|
107
|
+
await promptAddComponents(components, tracked, projectRoot);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
outro('Scan complete');
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs the furnace status command to show an overview of Furnace state.
|
|
3
|
+
* When a component name is provided, shows detailed registration status.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param name - Optional component name for detailed status
|
|
6
|
+
*/
|
|
7
|
+
export declare function furnaceStatusCommand(projectRoot: string, name?: string): Promise<void>;
|