@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,377 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { multiselect, text } from '@clack/prompts';
|
|
4
|
+
import { getProjectPaths, loadConfig } from '../../core/config.js';
|
|
5
|
+
import { ensureFurnaceConfig, getFurnacePaths, writeFurnaceConfig, } from '../../core/furnace-config.js';
|
|
6
|
+
import { isComponentInEngine } from '../../core/furnace-scanner.js';
|
|
7
|
+
import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
|
|
8
|
+
import { registerTestManifest } from '../../core/manifest-register.js';
|
|
9
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
10
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
11
|
+
import { toError } from '../../utils/errors.js';
|
|
12
|
+
import { ensureDir, pathExists, readText, writeText } from '../../utils/fs.js';
|
|
13
|
+
import { cancel, intro, isCancel, note, outro, success, warn } from '../../utils/logger.js';
|
|
14
|
+
/**
|
|
15
|
+
* Converts a kebab-case tag name to PascalCase class name.
|
|
16
|
+
* e.g. "moz-sidebar-panel" → "MozSidebarPanel"
|
|
17
|
+
*/
|
|
18
|
+
function tagNameToClassName(tagName) {
|
|
19
|
+
return tagName
|
|
20
|
+
.split('-')
|
|
21
|
+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
|
|
22
|
+
.join('');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validates a custom element tag name.
|
|
26
|
+
* @returns Error message if invalid, undefined if valid
|
|
27
|
+
*/
|
|
28
|
+
function validateTagName(name) {
|
|
29
|
+
if (!name.trim())
|
|
30
|
+
return 'Name is required';
|
|
31
|
+
if (!name.includes('-'))
|
|
32
|
+
return 'Custom element names must contain a hyphen (e.g., "my-widget")';
|
|
33
|
+
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(name))
|
|
34
|
+
return 'Name must be lowercase, start with a letter, and use hyphens to separate words (e.g., "my-widget")';
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks if a component name conflicts with existing entries in furnace.json.
|
|
39
|
+
*/
|
|
40
|
+
function checkNameConflict(config, name) {
|
|
41
|
+
if (name in config.custom) {
|
|
42
|
+
return `A custom component named "${name}" already exists in furnace.json`;
|
|
43
|
+
}
|
|
44
|
+
if (name in config.overrides) {
|
|
45
|
+
return `An override component named "${name}" already exists in furnace.json`;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates the .mjs file content for a custom component.
|
|
51
|
+
*/
|
|
52
|
+
function generateMjsContent(name, className, description, localized, header) {
|
|
53
|
+
const connectedCallback = localized
|
|
54
|
+
? `
|
|
55
|
+
connectedCallback() {
|
|
56
|
+
super.connectedCallback();
|
|
57
|
+
this.insertFTLIfNeeded("${name}.ftl");
|
|
58
|
+
}
|
|
59
|
+
`
|
|
60
|
+
: '';
|
|
61
|
+
return `${header}
|
|
62
|
+
|
|
63
|
+
import { html } from "chrome://global/content/vendor/lit.all.mjs";
|
|
64
|
+
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ${description || name}
|
|
68
|
+
*
|
|
69
|
+
* @tagname ${name}
|
|
70
|
+
*/
|
|
71
|
+
class ${className} extends MozLitElement {
|
|
72
|
+
static properties = {};
|
|
73
|
+
|
|
74
|
+
constructor() {
|
|
75
|
+
super();
|
|
76
|
+
}
|
|
77
|
+
${connectedCallback}
|
|
78
|
+
render() {
|
|
79
|
+
return html\`
|
|
80
|
+
<link rel="stylesheet" href="chrome://global/content/elements/${name}.css" />
|
|
81
|
+
<slot></slot>
|
|
82
|
+
\`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
customElements.define("${name}", ${className});
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Generates the .css file content for a custom component.
|
|
90
|
+
*/
|
|
91
|
+
function generateCssContent(header) {
|
|
92
|
+
return `${header}
|
|
93
|
+
|
|
94
|
+
:host {
|
|
95
|
+
display: block;
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generates the .ftl file content for a custom component.
|
|
101
|
+
*/
|
|
102
|
+
function generateFtlContent(name, header) {
|
|
103
|
+
return `${header}
|
|
104
|
+
|
|
105
|
+
## Strings for the ${name} component
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Scaffolds browser mochitest files for a newly created custom component.
|
|
110
|
+
* @param componentName - Custom element tag name
|
|
111
|
+
* @param license - Project license used for generated headers
|
|
112
|
+
* @param forgeConfig - Project config fields needed for test naming
|
|
113
|
+
* @param paths - Resolved project paths used to place test files
|
|
114
|
+
* @returns Relative test filenames created or updated for the component
|
|
115
|
+
*/
|
|
116
|
+
async function scaffoldTestFiles(componentName, license, forgeConfig, paths) {
|
|
117
|
+
const strippedName = componentName.startsWith('moz-') ? componentName.slice(4) : componentName;
|
|
118
|
+
// Avoid double-prefixing: strip binaryName prefix since testDirName already uses it
|
|
119
|
+
const testDirName = forgeConfig.binaryName;
|
|
120
|
+
const withoutBinaryPrefix = strippedName.startsWith(testDirName + '-')
|
|
121
|
+
? strippedName.slice(testDirName.length + 1)
|
|
122
|
+
: strippedName;
|
|
123
|
+
const underscored = withoutBinaryPrefix.replace(/-/g, '_');
|
|
124
|
+
const testFileName = `browser_${testDirName}_${underscored}.js`;
|
|
125
|
+
const testDir = join(paths.engine, 'browser/base/content/test', testDirName);
|
|
126
|
+
await ensureDir(testDir);
|
|
127
|
+
const jsHeader = getLicenseHeader(license, 'js');
|
|
128
|
+
const hashHeader = getLicenseHeader(license, 'hash');
|
|
129
|
+
const testFiles = [];
|
|
130
|
+
// browser.toml — create if missing, append entry if existing
|
|
131
|
+
const tomlPath = join(testDir, 'browser.toml');
|
|
132
|
+
if (await pathExists(tomlPath)) {
|
|
133
|
+
// Append the new test entry if not already present
|
|
134
|
+
const existingToml = await readText(tomlPath);
|
|
135
|
+
if (!existingToml.includes(`["${testFileName}"]`)) {
|
|
136
|
+
await writeText(tomlPath, existingToml.trimEnd() + `\n\n["${testFileName}"]\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const browserToml = `${hashHeader}
|
|
141
|
+
|
|
142
|
+
[DEFAULT]
|
|
143
|
+
support-files = ["head.js"]
|
|
144
|
+
|
|
145
|
+
["${testFileName}"]
|
|
146
|
+
`;
|
|
147
|
+
await writeText(tomlPath, browserToml);
|
|
148
|
+
}
|
|
149
|
+
testFiles.push('browser.toml');
|
|
150
|
+
// head.js — only create if it doesn't exist (shared across components)
|
|
151
|
+
const headPath = join(testDir, 'head.js');
|
|
152
|
+
if (!(await pathExists(headPath))) {
|
|
153
|
+
const headJs = `${jsHeader}
|
|
154
|
+
|
|
155
|
+
"use strict";
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Wait for a custom element to be defined.
|
|
159
|
+
* @param {string} tag - Custom element tag name
|
|
160
|
+
* @returns {Promise<CustomElementConstructor>}
|
|
161
|
+
*/
|
|
162
|
+
async function waitForElement(tag) {
|
|
163
|
+
return customElements.whenDefined(tag);
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
await writeText(headPath, headJs);
|
|
167
|
+
testFiles.push('head.js');
|
|
168
|
+
}
|
|
169
|
+
// browser_{binaryName}_{stripped}.js
|
|
170
|
+
const testJs = `${jsHeader}
|
|
171
|
+
|
|
172
|
+
"use strict";
|
|
173
|
+
|
|
174
|
+
add_task(async function test_${underscored}_defined() {
|
|
175
|
+
const ctor = await waitForElement("${componentName}");
|
|
176
|
+
Assert.ok(ctor, "${componentName} custom element should be defined");
|
|
177
|
+
Assert.equal(typeof ctor, "function", "Constructor should be a function");
|
|
178
|
+
});
|
|
179
|
+
`;
|
|
180
|
+
await writeText(join(testDir, testFileName), testJs);
|
|
181
|
+
testFiles.push(testFileName);
|
|
182
|
+
// Register in moz.build
|
|
183
|
+
try {
|
|
184
|
+
const registerResult = await registerTestManifest(paths.engine, testDirName);
|
|
185
|
+
if (!registerResult.skipped) {
|
|
186
|
+
success(`Registered test manifest in ${registerResult.manifest}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
warn(`Could not register test manifest in moz.build — ${toError(error).message}. Register manually with "fireforge register".`);
|
|
191
|
+
}
|
|
192
|
+
return testFiles;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Resolves the localized and registration feature flags for a new component.
|
|
196
|
+
* @param isInteractive - Whether interactive prompts are available
|
|
197
|
+
* @param options - CLI-provided feature flags
|
|
198
|
+
* @returns Final feature selections, or null when creation is cancelled
|
|
199
|
+
*/
|
|
200
|
+
async function resolveCreateFeatures(isInteractive, options) {
|
|
201
|
+
let localized = options.localized ?? false;
|
|
202
|
+
let register = options.register ?? true;
|
|
203
|
+
if (isInteractive && options.localized === undefined && options.register === undefined) {
|
|
204
|
+
const features = await multiselect({
|
|
205
|
+
message: 'Component features:',
|
|
206
|
+
options: [
|
|
207
|
+
{
|
|
208
|
+
value: 'localized',
|
|
209
|
+
label: 'Fluent localization (data-l10n-id)',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
value: 'register',
|
|
213
|
+
label: 'Register in customElements.js',
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
initialValues: ['register'],
|
|
217
|
+
});
|
|
218
|
+
if (isCancel(features)) {
|
|
219
|
+
cancel('Create cancelled');
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const selected = features;
|
|
223
|
+
localized = selected.includes('localized');
|
|
224
|
+
register = selected.includes('register');
|
|
225
|
+
}
|
|
226
|
+
return { localized, register };
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Writes the scaffolded component source files to disk.
|
|
230
|
+
* @param componentDir - Destination component directory
|
|
231
|
+
* @param componentName - Custom element tag name
|
|
232
|
+
* @param className - Generated component class name
|
|
233
|
+
* @param description - Human-readable component description
|
|
234
|
+
* @param localized - Whether to include a Fluent file
|
|
235
|
+
* @param license - Project license used for generated headers
|
|
236
|
+
* @returns Relative filenames written for the component
|
|
237
|
+
*/
|
|
238
|
+
async function writeComponentFiles(componentDir, componentName, className, description, localized, license) {
|
|
239
|
+
await ensureDir(componentDir);
|
|
240
|
+
const files = [`${componentName}.mjs`, `${componentName}.css`];
|
|
241
|
+
const mjsContent = generateMjsContent(componentName, className, description, localized, getLicenseHeader(license, 'js'));
|
|
242
|
+
await writeText(join(componentDir, `${componentName}.mjs`), mjsContent);
|
|
243
|
+
const cssContent = generateCssContent(getLicenseHeader(license, 'css'));
|
|
244
|
+
await writeText(join(componentDir, `${componentName}.css`), cssContent);
|
|
245
|
+
if (localized) {
|
|
246
|
+
const ftlContent = generateFtlContent(componentName, getLicenseHeader(license, 'hash'));
|
|
247
|
+
await writeText(join(componentDir, `${componentName}.ftl`), ftlContent);
|
|
248
|
+
files.push(`${componentName}.ftl`);
|
|
249
|
+
}
|
|
250
|
+
return files;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Runs the furnace create command to scaffold a new custom component.
|
|
254
|
+
* @param projectRoot - Root directory of the project
|
|
255
|
+
* @param name - Optional component tag name (prompted if not provided)
|
|
256
|
+
* @param options - CLI options for non-interactive mode
|
|
257
|
+
*/
|
|
258
|
+
export async function furnaceCreateCommand(projectRoot, name, options = {}) {
|
|
259
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
260
|
+
intro('Furnace Create');
|
|
261
|
+
// Load or create furnace.json
|
|
262
|
+
const config = await ensureFurnaceConfig(projectRoot);
|
|
263
|
+
const paths = getProjectPaths(projectRoot);
|
|
264
|
+
const forgeConfig = await loadConfig(projectRoot);
|
|
265
|
+
const license = forgeConfig.license ?? DEFAULT_LICENSE;
|
|
266
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
267
|
+
// --- Resolve component name ---
|
|
268
|
+
let componentName = name;
|
|
269
|
+
if (componentName) {
|
|
270
|
+
// Validate CLI-provided name
|
|
271
|
+
const validationError = validateTagName(componentName);
|
|
272
|
+
if (validationError) {
|
|
273
|
+
throw new InvalidArgumentError(validationError, 'name');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else if (isInteractive) {
|
|
277
|
+
const nameResult = await text({
|
|
278
|
+
message: 'Component tag name:',
|
|
279
|
+
placeholder: 'moz-my-widget',
|
|
280
|
+
validate: (value) => validateTagName(value ?? ''),
|
|
281
|
+
});
|
|
282
|
+
if (isCancel(nameResult)) {
|
|
283
|
+
cancel('Create cancelled');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
componentName = String(nameResult);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
throw new InvalidArgumentError('Component name is required in non-interactive mode.\n' +
|
|
290
|
+
'Usage: fireforge furnace create <name> -d "description"', 'name');
|
|
291
|
+
}
|
|
292
|
+
// Check for conflicts
|
|
293
|
+
const conflict = checkNameConflict(config, componentName);
|
|
294
|
+
if (conflict) {
|
|
295
|
+
throw new FurnaceError(conflict, componentName);
|
|
296
|
+
}
|
|
297
|
+
// Check if it already exists in the engine source tree
|
|
298
|
+
if (await pathExists(paths.engine)) {
|
|
299
|
+
if (await isComponentInEngine(paths.engine, componentName)) {
|
|
300
|
+
throw new FurnaceError(`"${componentName}" already exists in the engine source tree. Use "fireforge furnace override" instead.`, componentName);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Warn if name doesn't match componentPrefix
|
|
304
|
+
if (config.componentPrefix && !componentName.startsWith(config.componentPrefix)) {
|
|
305
|
+
warn(`Name "${componentName}" does not start with the configured prefix "${config.componentPrefix}".`);
|
|
306
|
+
}
|
|
307
|
+
// --- Resolve description ---
|
|
308
|
+
let description = options.description ?? '';
|
|
309
|
+
if (!description && isInteractive) {
|
|
310
|
+
const descResult = await text({
|
|
311
|
+
message: 'Description (optional):',
|
|
312
|
+
placeholder: 'A brief description of the component',
|
|
313
|
+
});
|
|
314
|
+
if (!isCancel(descResult)) {
|
|
315
|
+
description = String(descResult);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// --- Resolve features ---
|
|
319
|
+
const featureSelection = await resolveCreateFeatures(isInteractive, options);
|
|
320
|
+
if (!featureSelection) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const { localized, register } = featureSelection;
|
|
324
|
+
// --- Generate component files ---
|
|
325
|
+
const className = tagNameToClassName(componentName);
|
|
326
|
+
const componentDir = join(furnacePaths.customDir, componentName);
|
|
327
|
+
// Check if directory already exists on disk
|
|
328
|
+
if (await pathExists(componentDir)) {
|
|
329
|
+
throw new FurnaceError(`Directory already exists: components/custom/${componentName}`, componentName);
|
|
330
|
+
}
|
|
331
|
+
const files = await writeComponentFiles(componentDir, componentName, className, description, localized, license);
|
|
332
|
+
// --- Validate and process --compose ---
|
|
333
|
+
const composes = options.compose;
|
|
334
|
+
if (composes && composes.length > 0) {
|
|
335
|
+
for (const tag of composes) {
|
|
336
|
+
if (!config.stock.includes(tag)) {
|
|
337
|
+
warn(`Composed tag "${tag}" is not in the stock array of furnace.json.`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// --- Update furnace.json ---
|
|
342
|
+
const customEntry = {
|
|
343
|
+
description,
|
|
344
|
+
targetPath: `toolkit/content/widgets/${componentName}`,
|
|
345
|
+
register,
|
|
346
|
+
localized,
|
|
347
|
+
};
|
|
348
|
+
if (composes && composes.length > 0) {
|
|
349
|
+
customEntry.composes = composes;
|
|
350
|
+
}
|
|
351
|
+
config.custom[componentName] = customEntry;
|
|
352
|
+
await writeFurnaceConfig(projectRoot, config);
|
|
353
|
+
// --- Scaffold tests if requested ---
|
|
354
|
+
const withTests = options.withTests ?? false;
|
|
355
|
+
const testFiles = [];
|
|
356
|
+
if (withTests) {
|
|
357
|
+
const scafFiles = await scaffoldTestFiles(componentName, license, forgeConfig, paths);
|
|
358
|
+
testFiles.push(...scafFiles);
|
|
359
|
+
}
|
|
360
|
+
// --- Success ---
|
|
361
|
+
let noteParts = `Files created in components/custom/${componentName}/:\n` +
|
|
362
|
+
files.map((f) => ` ${f}`).join('\n');
|
|
363
|
+
if (testFiles.length > 0) {
|
|
364
|
+
noteParts +=
|
|
365
|
+
`\n\nTest files in engine/browser/base/content/test/${forgeConfig.binaryName}/:\n` +
|
|
366
|
+
testFiles.map((f) => ` ${f}`).join('\n');
|
|
367
|
+
}
|
|
368
|
+
noteParts +=
|
|
369
|
+
'\n\n' +
|
|
370
|
+
'Next steps:\n' +
|
|
371
|
+
` 1. Edit component files in components/custom/${componentName}/\n` +
|
|
372
|
+
' 2. Run "fireforge furnace preview" to see it\n' +
|
|
373
|
+
' 3. Run "fireforge build" to apply and build';
|
|
374
|
+
note(noteParts, componentName);
|
|
375
|
+
outro('Component created');
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FurnaceDeployOptions } from '../../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the furnace deploy command: apply components then validate in one step.
|
|
4
|
+
* @param projectRoot - Root directory of the project
|
|
5
|
+
* @param name - Optional component name to deploy (deploys all if omitted)
|
|
6
|
+
* @param options - Command options
|
|
7
|
+
*/
|
|
8
|
+
export declare function furnaceDeployCommand(projectRoot: string, name?: string, options?: FurnaceDeployOptions): Promise<void>;
|