@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,244 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readdir } from 'node:fs/promises';
|
|
4
|
+
import { join, relative } from 'node:path';
|
|
5
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
6
|
+
import { toError } from '../utils/errors.js';
|
|
7
|
+
import { copyFile, ensureDir, pathExists, readText } from '../utils/fs.js';
|
|
8
|
+
import { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
|
|
9
|
+
import { addCustomElementRegistration, addJarMnEntries } from './furnace-registration.js';
|
|
10
|
+
import { recordCreatedDir, snapshotFile } from './furnace-rollback.js';
|
|
11
|
+
/** Path to the Fluent localization directory for toolkit global components */
|
|
12
|
+
const FTL_DIR = 'toolkit/locales/en-US/toolkit/global';
|
|
13
|
+
function isChecksummedComponentFile(name) {
|
|
14
|
+
return name.endsWith('.mjs') || name.endsWith('.css') || name.endsWith('.ftl');
|
|
15
|
+
}
|
|
16
|
+
function isOverrideCopyCandidate(entryName, type) {
|
|
17
|
+
if (entryName === 'override.json') {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (type === 'css-only') {
|
|
21
|
+
return entryName.endsWith('.css');
|
|
22
|
+
}
|
|
23
|
+
return entryName.endsWith('.mjs') || entryName.endsWith('.css');
|
|
24
|
+
}
|
|
25
|
+
/** Computes stable checksums for the source files that define a component. */
|
|
26
|
+
export async function computeComponentChecksums(componentDir) {
|
|
27
|
+
const checksums = {};
|
|
28
|
+
const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (!entry.isFile())
|
|
31
|
+
continue;
|
|
32
|
+
if (entry.name === 'override.json')
|
|
33
|
+
continue;
|
|
34
|
+
if (!isChecksummedComponentFile(entry.name))
|
|
35
|
+
continue;
|
|
36
|
+
const content = await readText(join(componentDir, entry.name));
|
|
37
|
+
const normalized = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
|
38
|
+
const hash = createHash('sha256').update(normalized).digest('hex');
|
|
39
|
+
checksums[entry.name] = hash;
|
|
40
|
+
}
|
|
41
|
+
return checksums;
|
|
42
|
+
}
|
|
43
|
+
/** Compares current component file checksums against the previously recorded state. */
|
|
44
|
+
export async function hasComponentChanged(componentDir, previousChecksums) {
|
|
45
|
+
const current = await computeComponentChecksums(componentDir);
|
|
46
|
+
const currentKeys = Object.keys(current);
|
|
47
|
+
const previousKeys = Object.keys(previousChecksums);
|
|
48
|
+
if (currentKeys.length !== previousKeys.length) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
for (const key of currentKeys) {
|
|
52
|
+
if (current[key] !== previousChecksums[key]) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
async function buildCustomDryRunActions(name, componentDir, engineDir, config, targetDir, entries) {
|
|
59
|
+
const actions = [];
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (!entry.isFile())
|
|
62
|
+
continue;
|
|
63
|
+
if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
|
|
64
|
+
continue;
|
|
65
|
+
actions.push({
|
|
66
|
+
component: name,
|
|
67
|
+
action: 'copy',
|
|
68
|
+
source: join(componentDir, entry.name),
|
|
69
|
+
target: join(targetDir, entry.name),
|
|
70
|
+
description: `Copy ${entry.name} to ${config.targetPath}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (config.localized) {
|
|
74
|
+
const ftlFile = `${name}.ftl`;
|
|
75
|
+
const ftlSrc = join(componentDir, ftlFile);
|
|
76
|
+
if (await pathExists(ftlSrc)) {
|
|
77
|
+
actions.push({
|
|
78
|
+
component: name,
|
|
79
|
+
action: 'copy-ftl',
|
|
80
|
+
source: ftlSrc,
|
|
81
|
+
target: join(engineDir, FTL_DIR, ftlFile),
|
|
82
|
+
description: `Copy ${ftlFile} to ${FTL_DIR}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (config.register) {
|
|
87
|
+
actions.push({
|
|
88
|
+
component: name,
|
|
89
|
+
action: 'register-ce',
|
|
90
|
+
description: `Register ${name} in customElements.js (DOMContentLoaded block)`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const copiedFileNames = entries
|
|
94
|
+
.filter((entry) => entry.isFile() && (entry.name.endsWith('.mjs') || entry.name.endsWith('.css')))
|
|
95
|
+
.map((entry) => entry.name);
|
|
96
|
+
if (copiedFileNames.length > 0) {
|
|
97
|
+
actions.push({
|
|
98
|
+
component: name,
|
|
99
|
+
action: 'register-jar',
|
|
100
|
+
description: `Add ${copiedFileNames.join(', ')} to jar.mn`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return actions;
|
|
104
|
+
}
|
|
105
|
+
/** Applies a custom component into the engine tree and captures registration step errors. */
|
|
106
|
+
export async function applyCustomComponent(engineDir, name, componentDir, config, dryRun = false, rollbackJournal) {
|
|
107
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
108
|
+
throw new FurnaceError(`Invalid component name "${name}": must match /^[a-z][a-z0-9-]*$/`);
|
|
109
|
+
}
|
|
110
|
+
const targetDir = join(engineDir, config.targetPath);
|
|
111
|
+
const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
|
|
112
|
+
if (dryRun) {
|
|
113
|
+
const actions = await buildCustomDryRunActions(name, componentDir, engineDir, config, targetDir, entries);
|
|
114
|
+
return { affectedPaths: [], stepErrors: [], actions };
|
|
115
|
+
}
|
|
116
|
+
if (rollbackJournal && !(await pathExists(targetDir))) {
|
|
117
|
+
recordCreatedDir(rollbackJournal, targetDir);
|
|
118
|
+
}
|
|
119
|
+
await ensureDir(targetDir);
|
|
120
|
+
const affectedPaths = [];
|
|
121
|
+
const stepErrors = [];
|
|
122
|
+
const copiedFileNames = [];
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
if (!entry.isFile())
|
|
125
|
+
continue;
|
|
126
|
+
if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
|
|
127
|
+
continue;
|
|
128
|
+
const src = join(componentDir, entry.name);
|
|
129
|
+
const dest = join(targetDir, entry.name);
|
|
130
|
+
if (rollbackJournal) {
|
|
131
|
+
await snapshotFile(rollbackJournal, dest);
|
|
132
|
+
}
|
|
133
|
+
await copyFile(src, dest);
|
|
134
|
+
affectedPaths.push(relative(engineDir, dest));
|
|
135
|
+
copiedFileNames.push(entry.name);
|
|
136
|
+
}
|
|
137
|
+
if (config.localized) {
|
|
138
|
+
const ftlFile = `${name}.ftl`;
|
|
139
|
+
const ftlSrc = join(componentDir, ftlFile);
|
|
140
|
+
if (await pathExists(ftlSrc)) {
|
|
141
|
+
const ftlDest = join(engineDir, FTL_DIR, ftlFile);
|
|
142
|
+
if (rollbackJournal) {
|
|
143
|
+
await snapshotFile(rollbackJournal, ftlDest);
|
|
144
|
+
}
|
|
145
|
+
await copyFile(ftlSrc, ftlDest);
|
|
146
|
+
affectedPaths.push(relative(engineDir, ftlDest));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (config.register) {
|
|
150
|
+
try {
|
|
151
|
+
const modulePath = `chrome://global/content/elements/${name}.mjs`;
|
|
152
|
+
if (rollbackJournal) {
|
|
153
|
+
await snapshotFile(rollbackJournal, join(engineDir, CUSTOM_ELEMENTS_JS));
|
|
154
|
+
}
|
|
155
|
+
await addCustomElementRegistration(engineDir, name, modulePath);
|
|
156
|
+
affectedPaths.push(CUSTOM_ELEMENTS_JS);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
stepErrors.push({
|
|
160
|
+
step: 'customElements.js registration',
|
|
161
|
+
error: toError(error).message,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (copiedFileNames.length > 0) {
|
|
166
|
+
try {
|
|
167
|
+
if (rollbackJournal) {
|
|
168
|
+
await snapshotFile(rollbackJournal, join(engineDir, JAR_MN));
|
|
169
|
+
}
|
|
170
|
+
await addJarMnEntries(engineDir, name, copiedFileNames);
|
|
171
|
+
affectedPaths.push(JAR_MN);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
stepErrors.push({
|
|
175
|
+
step: 'jar.mn registration',
|
|
176
|
+
error: toError(error).message,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { affectedPaths, stepErrors };
|
|
181
|
+
}
|
|
182
|
+
/** Applies an override component by copying its matching files onto the engine tree. */
|
|
183
|
+
export async function applyOverrideComponent(engineDir, name, componentDir, config, dryRun = false, rollbackJournal) {
|
|
184
|
+
const targetDir = join(engineDir, config.basePath);
|
|
185
|
+
if (!(await pathExists(targetDir))) {
|
|
186
|
+
throw new FurnaceError(`Override target path not found in engine: ${config.basePath}`, name);
|
|
187
|
+
}
|
|
188
|
+
const entries = await readdir(componentDir, { withFileTypes: true, encoding: 'utf8' });
|
|
189
|
+
if (dryRun) {
|
|
190
|
+
const actions = entries
|
|
191
|
+
.filter((entry) => entry.isFile() && isOverrideCopyCandidate(entry.name, config.type))
|
|
192
|
+
.map((entry) => ({
|
|
193
|
+
component: name,
|
|
194
|
+
action: 'copy',
|
|
195
|
+
source: join(componentDir, entry.name),
|
|
196
|
+
target: join(targetDir, entry.name),
|
|
197
|
+
description: `Override ${entry.name} in ${config.basePath}`,
|
|
198
|
+
}));
|
|
199
|
+
if (actions.length === 0) {
|
|
200
|
+
throw new FurnaceError(`No matching files found in override directory for "${name}"`, name);
|
|
201
|
+
}
|
|
202
|
+
return { affectedPaths: [], actions };
|
|
203
|
+
}
|
|
204
|
+
const affectedPaths = [];
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (!entry.isFile() || !isOverrideCopyCandidate(entry.name, config.type)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const src = join(componentDir, entry.name);
|
|
210
|
+
const dest = join(targetDir, entry.name);
|
|
211
|
+
if (rollbackJournal) {
|
|
212
|
+
await snapshotFile(rollbackJournal, dest);
|
|
213
|
+
}
|
|
214
|
+
await copyFile(src, dest);
|
|
215
|
+
affectedPaths.push(relative(engineDir, dest));
|
|
216
|
+
}
|
|
217
|
+
if (affectedPaths.length === 0) {
|
|
218
|
+
throw new FurnaceError(`No matching files found in override directory for "${name}"`, name);
|
|
219
|
+
}
|
|
220
|
+
return { affectedPaths };
|
|
221
|
+
}
|
|
222
|
+
/** Extracts per-component checksums from the flattened state-file checksum map. */
|
|
223
|
+
export function extractComponentChecksums(allChecksums, type, name) {
|
|
224
|
+
if (!allChecksums)
|
|
225
|
+
return {};
|
|
226
|
+
const prefix = `${type}/${name}/`;
|
|
227
|
+
const result = {};
|
|
228
|
+
for (const [key, value] of Object.entries(allChecksums)) {
|
|
229
|
+
if (key.startsWith(prefix)) {
|
|
230
|
+
result[key.slice(prefix.length)] = value;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
/** Prefixes component checksums so they can be stored in the flattened state format. */
|
|
236
|
+
export function prefixChecksums(checksums, type, name) {
|
|
237
|
+
const prefix = `${type}/${name}/`;
|
|
238
|
+
const result = {};
|
|
239
|
+
for (const [key, value] of Object.entries(checksums)) {
|
|
240
|
+
result[`${prefix}${key}`] = value;
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=furnace-apply-helpers.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ApplyResult, DryRunAction } from '../types/furnace.js';
|
|
2
|
+
export { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Applies all override and custom components to the engine source tree.
|
|
5
|
+
*
|
|
6
|
+
* Unchanged components (matching checksums) are skipped. If any component
|
|
7
|
+
* fails, FireForge restores only the engine files touched during this apply
|
|
8
|
+
* attempt and leaves the state file unchanged.
|
|
9
|
+
*
|
|
10
|
+
* @param root - Root directory of the project
|
|
11
|
+
* @param dryRun - If true, enumerate planned actions without writing
|
|
12
|
+
* @returns Summary of applied, skipped, and errored components (with actions when dry-run)
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyAllComponents(root: string, dryRun?: boolean): Promise<ApplyResult & {
|
|
15
|
+
actions?: DryRunAction[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
4
|
+
import { toError } from '../utils/errors.js';
|
|
5
|
+
import { pathExists } from '../utils/fs.js';
|
|
6
|
+
import { getProjectPaths } from './config.js';
|
|
7
|
+
import { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
8
|
+
import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, } from './furnace-config.js';
|
|
9
|
+
import { createRollbackJournal, restoreRollbackJournalOrThrow, } from './furnace-rollback.js';
|
|
10
|
+
export { applyCustomComponent, applyOverrideComponent, computeComponentChecksums, extractComponentChecksums, hasComponentChanged, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
11
|
+
function addMissingComponentError(result, name, directoryPath) {
|
|
12
|
+
result.errors.push({
|
|
13
|
+
name,
|
|
14
|
+
error: `Component directory not found: ${directoryPath}`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function applyOverrideBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal) {
|
|
18
|
+
for (const [name, overrideConfig] of Object.entries(config.overrides)) {
|
|
19
|
+
const componentDir = join(furnacePaths.overridesDir, name);
|
|
20
|
+
if (!(await pathExists(componentDir))) {
|
|
21
|
+
addMissingComponentError(result, name, `components/overrides/${name}`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (!dryRun) {
|
|
25
|
+
const previous = extractComponentChecksums(state.appliedChecksums, 'override', name);
|
|
26
|
+
const changed = await hasComponentChanged(componentDir, previous);
|
|
27
|
+
if (!changed) {
|
|
28
|
+
result.skipped.push({ name, reason: 'No changes since last apply' });
|
|
29
|
+
Object.assign(newChecksums, prefixChecksums(previous, 'override', name));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const { affectedPaths: filesAffected, actions } = await applyOverrideComponent(engineDir, name, componentDir, overrideConfig, dryRun, rollbackJournal);
|
|
35
|
+
if (dryRun && actions) {
|
|
36
|
+
allActions.push(...actions);
|
|
37
|
+
}
|
|
38
|
+
result.applied.push({ name, type: 'override', filesAffected });
|
|
39
|
+
if (!dryRun) {
|
|
40
|
+
const checksums = await computeComponentChecksums(componentDir);
|
|
41
|
+
Object.assign(newChecksums, prefixChecksums(checksums, 'override', name));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
result.errors.push({
|
|
46
|
+
name,
|
|
47
|
+
error: toError(error).message,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function applyCustomBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal) {
|
|
53
|
+
for (const [name, customConfig] of Object.entries(config.custom)) {
|
|
54
|
+
const componentDir = join(furnacePaths.customDir, name);
|
|
55
|
+
if (!(await pathExists(componentDir))) {
|
|
56
|
+
addMissingComponentError(result, name, `components/custom/${name}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!dryRun) {
|
|
60
|
+
const previous = extractComponentChecksums(state.appliedChecksums, 'custom', name);
|
|
61
|
+
const changed = await hasComponentChanged(componentDir, previous);
|
|
62
|
+
if (!changed) {
|
|
63
|
+
result.skipped.push({ name, reason: 'No changes since last apply' });
|
|
64
|
+
Object.assign(newChecksums, prefixChecksums(previous, 'custom', name));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const { affectedPaths: filesAffected, stepErrors, actions, } = await applyCustomComponent(engineDir, name, componentDir, customConfig, dryRun, rollbackJournal);
|
|
70
|
+
if (dryRun && actions) {
|
|
71
|
+
allActions.push(...actions);
|
|
72
|
+
}
|
|
73
|
+
result.applied.push({
|
|
74
|
+
name,
|
|
75
|
+
type: 'custom',
|
|
76
|
+
filesAffected,
|
|
77
|
+
...(stepErrors.length > 0 ? { stepErrors } : {}),
|
|
78
|
+
});
|
|
79
|
+
// Only store checksums when the component applied without step errors,
|
|
80
|
+
// so that partially failed components are re-applied on the next run.
|
|
81
|
+
if (!dryRun && stepErrors.length === 0) {
|
|
82
|
+
const checksums = await computeComponentChecksums(componentDir);
|
|
83
|
+
Object.assign(newChecksums, prefixChecksums(checksums, 'custom', name));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
result.errors.push({
|
|
88
|
+
name,
|
|
89
|
+
error: toError(error).message,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Applies all override and custom components to the engine source tree.
|
|
96
|
+
*
|
|
97
|
+
* Unchanged components (matching checksums) are skipped. If any component
|
|
98
|
+
* fails, FireForge restores only the engine files touched during this apply
|
|
99
|
+
* attempt and leaves the state file unchanged.
|
|
100
|
+
*
|
|
101
|
+
* @param root - Root directory of the project
|
|
102
|
+
* @param dryRun - If true, enumerate planned actions without writing
|
|
103
|
+
* @returns Summary of applied, skipped, and errored components (with actions when dry-run)
|
|
104
|
+
*/
|
|
105
|
+
export async function applyAllComponents(root, dryRun = false) {
|
|
106
|
+
const config = await loadFurnaceConfig(root);
|
|
107
|
+
const state = await loadFurnaceState(root);
|
|
108
|
+
const { engine: engineDir } = getProjectPaths(root);
|
|
109
|
+
const furnacePaths = getFurnacePaths(root);
|
|
110
|
+
if (!(await pathExists(engineDir))) {
|
|
111
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first.');
|
|
112
|
+
}
|
|
113
|
+
const rollbackJournal = dryRun ? undefined : createRollbackJournal();
|
|
114
|
+
const result = {
|
|
115
|
+
applied: [],
|
|
116
|
+
skipped: [],
|
|
117
|
+
errors: [],
|
|
118
|
+
};
|
|
119
|
+
const allActions = [];
|
|
120
|
+
const newChecksums = {};
|
|
121
|
+
await applyOverrideBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal);
|
|
122
|
+
await applyCustomBatch(config, furnacePaths, state, engineDir, dryRun, result, allActions, newChecksums, rollbackJournal);
|
|
123
|
+
// Check for any partial failures (step errors on applied components).
|
|
124
|
+
const hasStepErrors = result.applied.some((entry) => 'stepErrors' in entry && entry.stepErrors.length > 0);
|
|
125
|
+
// Orphaned components are implicitly cleaned up: newChecksums only
|
|
126
|
+
// contains entries for components that still exist in furnace.json,
|
|
127
|
+
// and it fully replaces state.appliedChecksums below.
|
|
128
|
+
// --- Rollback on failure, persist on success (skip for dry-run) ---
|
|
129
|
+
if (!dryRun) {
|
|
130
|
+
if (result.errors.length > 0 || hasStepErrors) {
|
|
131
|
+
if (rollbackJournal) {
|
|
132
|
+
await restoreRollbackJournalOrThrow(rollbackJournal, 'Furnace apply failed');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
await updateFurnaceState(root, {
|
|
137
|
+
lastApply: new Date().toISOString(),
|
|
138
|
+
appliedChecksums: newChecksums,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (dryRun) {
|
|
143
|
+
result.actions = allActions;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=furnace-apply.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FurnaceConfig, FurnaceState } from '../types/furnace.js';
|
|
2
|
+
/** Name of the furnace configuration file */
|
|
3
|
+
export declare const FURNACE_CONFIG_FILENAME = "furnace.json";
|
|
4
|
+
/** Name of the furnace state file */
|
|
5
|
+
export declare const FURNACE_STATE_FILENAME = "furnace-state.json";
|
|
6
|
+
/** Name of the components directory */
|
|
7
|
+
export declare const COMPONENTS_DIR = "components";
|
|
8
|
+
/** Name of the overrides subdirectory */
|
|
9
|
+
export declare const OVERRIDES_DIR = "overrides";
|
|
10
|
+
/** Name of the custom subdirectory */
|
|
11
|
+
export declare const CUSTOM_DIR = "custom";
|
|
12
|
+
/**
|
|
13
|
+
* Paths for furnace-related files and directories.
|
|
14
|
+
*/
|
|
15
|
+
interface FurnacePaths {
|
|
16
|
+
/** Path to furnace.json */
|
|
17
|
+
furnaceConfig: string;
|
|
18
|
+
/** Path to components directory */
|
|
19
|
+
componentsDir: string;
|
|
20
|
+
/** Path to components/overrides directory */
|
|
21
|
+
overridesDir: string;
|
|
22
|
+
/** Path to components/custom directory */
|
|
23
|
+
customDir: string;
|
|
24
|
+
/** Path to .fireforge/furnace-state.json */
|
|
25
|
+
furnaceState: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Gets all furnace-related paths based on a root directory.
|
|
29
|
+
* @param root - Root directory of the project
|
|
30
|
+
* @returns All furnace paths
|
|
31
|
+
*/
|
|
32
|
+
export declare function getFurnacePaths(root: string): FurnacePaths;
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a furnace.json exists in the given directory.
|
|
35
|
+
* @param root - Root directory to check
|
|
36
|
+
* @returns True if furnace.json exists
|
|
37
|
+
*/
|
|
38
|
+
export declare function furnaceConfigExists(root: string): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Validates a raw config object and returns a typed FurnaceConfig.
|
|
41
|
+
* @param data - Raw data to validate
|
|
42
|
+
* @returns Validated FurnaceConfig
|
|
43
|
+
* @throws Error if validation fails
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Validates a parsed furnace state object and returns a typed FurnaceState.
|
|
48
|
+
* @param data - Parsed JSON state data
|
|
49
|
+
* @returns Validated FurnaceState
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateFurnaceState(data: unknown): FurnaceState;
|
|
52
|
+
/**
|
|
53
|
+
* Loads and validates the furnace.json configuration.
|
|
54
|
+
* @param root - Root directory of the project
|
|
55
|
+
* @returns Validated FurnaceConfig
|
|
56
|
+
* @throws Error if config doesn't exist or is invalid
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadFurnaceConfig(root: string): Promise<FurnaceConfig>;
|
|
59
|
+
/**
|
|
60
|
+
* Writes a furnace configuration to furnace.json.
|
|
61
|
+
* @param root - Root directory of the project
|
|
62
|
+
* @param config - Configuration to write
|
|
63
|
+
*/
|
|
64
|
+
export declare function writeFurnaceConfig(root: string, config: FurnaceConfig): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Creates a default furnace configuration.
|
|
67
|
+
* @returns A valid empty FurnaceConfig
|
|
68
|
+
*/
|
|
69
|
+
export declare function createDefaultFurnaceConfig(): FurnaceConfig;
|
|
70
|
+
/**
|
|
71
|
+
* Loads furnace config if it exists, or creates and writes a default config.
|
|
72
|
+
* @param root - Root directory of the project
|
|
73
|
+
* @returns FurnaceConfig (existing or newly created)
|
|
74
|
+
*/
|
|
75
|
+
export declare function ensureFurnaceConfig(root: string): Promise<FurnaceConfig>;
|
|
76
|
+
/**
|
|
77
|
+
* Loads the furnace state, or returns defaults if it doesn't exist.
|
|
78
|
+
* @param root - Root directory of the project
|
|
79
|
+
* @returns Furnace state
|
|
80
|
+
*/
|
|
81
|
+
export declare function loadFurnaceState(root: string): Promise<FurnaceState>;
|
|
82
|
+
/**
|
|
83
|
+
* Saves the furnace state.
|
|
84
|
+
* @param root - Root directory of the project
|
|
85
|
+
* @param state - State to save
|
|
86
|
+
*/
|
|
87
|
+
export declare function saveFurnaceState(root: string, state: FurnaceState): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Updates furnace state fields transactionally under the state file lock.
|
|
90
|
+
* @param root - Root directory of the project
|
|
91
|
+
* @param updates - Fields to update, or a transactional updater function
|
|
92
|
+
*/
|
|
93
|
+
export declare function updateFurnaceState(root: string, updates: Partial<FurnaceState> | ((current: FurnaceState) => FurnaceState)): Promise<void>;
|
|
94
|
+
export {};
|