@hominis/fireforge 0.30.1 → 0.31.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 +25 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +5 -15
- package/dist/src/commands/export-flow.d.ts +6 -0
- package/dist/src/commands/export-flow.js +6 -1
- package/dist/src/commands/export-placement-gate.d.ts +38 -0
- package/dist/src/commands/export-placement-gate.js +105 -0
- package/dist/src/commands/export-shared.d.ts +28 -0
- package/dist/src/commands/export-shared.js +36 -0
- package/dist/src/commands/export.js +47 -112
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +0 -13
- package/dist/src/commands/furnace/chrome-doc-templates.js +1 -1
- package/dist/src/commands/furnace/create-dry-run.d.ts +1 -1
- package/dist/src/commands/furnace/create.d.ts +1 -2
- package/dist/src/commands/furnace/deploy.js +36 -114
- package/dist/src/commands/furnace/refresh.js +52 -32
- package/dist/src/commands/furnace/sync.js +2 -0
- package/dist/src/commands/import.js +108 -73
- package/dist/src/commands/lint-per-patch.d.ts +1 -1
- package/dist/src/commands/lint-per-patch.js +110 -81
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +96 -84
- package/dist/src/commands/patch/compact.d.ts +5 -2
- package/dist/src/commands/patch/compact.js +85 -25
- package/dist/src/commands/patch/delete.js +17 -17
- package/dist/src/commands/patch/index.js +2 -0
- package/dist/src/commands/patch/lint-ignore.js +3 -16
- package/dist/src/commands/patch/move-files.js +2 -0
- package/dist/src/commands/patch/patch-context.d.ts +41 -0
- package/dist/src/commands/patch/patch-context.js +53 -0
- package/dist/src/commands/patch/rename.js +10 -15
- package/dist/src/commands/patch/reorder.d.ts +0 -2
- package/dist/src/commands/patch/reorder.js +18 -19
- package/dist/src/commands/patch/split-plan.d.ts +66 -0
- package/dist/src/commands/patch/split-plan.js +178 -0
- package/dist/src/commands/patch/split.d.ts +30 -0
- package/dist/src/commands/patch/split.js +283 -0
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -7
- package/dist/src/commands/patch/staged-dependency.js +4 -17
- package/dist/src/commands/patch/tier.js +4 -17
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/rebase/summary.d.ts +1 -5
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/status-output.js +77 -68
- package/dist/src/commands/test-diagnose.d.ts +23 -0
- package/dist/src/commands/test-diagnose.js +210 -0
- package/dist/src/commands/test-run.d.ts +58 -0
- package/dist/src/commands/test-run.js +88 -0
- package/dist/src/commands/test.js +169 -257
- package/dist/src/commands/token.js +15 -1
- package/dist/src/commands/wire.js +109 -78
- package/dist/src/core/build-audit.d.ts +1 -1
- package/dist/src/core/build-audit.js +2 -46
- package/dist/src/core/build-baseline-types.d.ts +38 -0
- package/dist/src/core/build-baseline-types.js +10 -0
- package/dist/src/core/build-baseline.d.ts +1 -31
- package/dist/src/core/build-prepare.d.ts +1 -1
- package/dist/src/core/build-prepare.js +2 -45
- package/dist/src/core/config-paths.d.ts +0 -8
- package/dist/src/core/config-paths.js +4 -4
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -1
- package/dist/src/core/config-validate-patch-policy.js +12 -13
- package/dist/src/core/config-validate.js +48 -28
- package/dist/src/core/engine-changes.d.ts +24 -0
- package/dist/src/core/engine-changes.js +64 -0
- package/dist/src/core/firefox-cache.d.ts +0 -5
- package/dist/src/core/firefox-cache.js +1 -1
- package/dist/src/core/firefox-download.d.ts +0 -6
- package/dist/src/core/firefox-download.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -8
- package/dist/src/core/furnace-apply-helpers.js +11 -20
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +1 -1
- package/dist/src/core/furnace-checksum-utils.d.ts +7 -0
- package/dist/src/core/furnace-checksum-utils.js +15 -0
- package/dist/src/core/furnace-config-validate.d.ts +31 -0
- package/dist/src/core/furnace-config-validate.js +133 -0
- package/dist/src/core/furnace-config.d.ts +4 -32
- package/dist/src/core/furnace-config.js +15 -111
- package/dist/src/core/furnace-constants.d.ts +0 -10
- package/dist/src/core/furnace-constants.js +2 -2
- package/dist/src/core/furnace-css-fragments.d.ts +79 -0
- package/dist/src/core/furnace-css-fragments.js +243 -0
- package/dist/src/core/furnace-jsconfig.d.ts +63 -0
- package/dist/src/core/furnace-jsconfig.js +171 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +16 -14
- package/dist/src/core/furnace-validate-helpers.js +40 -1
- package/dist/src/core/furnace-validate-registration.js +16 -1
- package/dist/src/core/furnace-validate.js +54 -2
- package/dist/src/core/git-file-ops.d.ts +0 -12
- package/dist/src/core/git-file-ops.js +2 -2
- package/dist/src/core/lint-cache.d.ts +0 -13
- package/dist/src/core/lint-cache.js +5 -5
- package/dist/src/core/mach.d.ts +5 -1
- package/dist/src/core/mach.js +6 -2
- package/dist/src/core/manifest-register.d.ts +5 -16
- package/dist/src/core/manifest-register.js +3 -1
- package/dist/src/core/patch-lint-checkjs.js +53 -7
- package/dist/src/core/patch-lint-jsdoc.js +63 -4
- package/dist/src/core/patch-lint-observer.d.ts +37 -0
- package/dist/src/core/patch-lint-observer.js +168 -0
- package/dist/src/core/patch-lint.js +132 -125
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +44 -2
- package/dist/src/core/patch-manifest-validate.d.ts +1 -8
- package/dist/src/core/patch-manifest-validate.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-policy.d.ts +0 -4
- package/dist/src/core/patch-policy.js +10 -4
- package/dist/src/core/register-browser-content.d.ts +1 -1
- package/dist/src/core/register-module.d.ts +1 -1
- package/dist/src/core/register-result.d.ts +21 -0
- package/dist/src/core/register-result.js +9 -0
- package/dist/src/core/register-shared-css.d.ts +1 -1
- package/dist/src/core/register-test-manifest.d.ts +1 -1
- package/dist/src/core/test-harness-crash.d.ts +61 -0
- package/dist/src/core/test-harness-crash.js +140 -0
- package/dist/src/core/test-stale-check.d.ts +1 -1
- package/dist/src/core/test-stale-check.js +2 -46
- package/dist/src/core/test-xpcshell-retry.d.ts +1 -1
- package/dist/src/core/test-xpcshell-retry.js +4 -2
- package/dist/src/core/token-dark-mode.js +14 -26
- package/dist/src/core/token-manager.d.ts +4 -0
- package/dist/src/core/token-manager.js +70 -16
- package/dist/src/core/typecheck-shim.d.ts +0 -21
- package/dist/src/core/typecheck-shim.js +26 -4
- package/dist/src/core/wire-utils.js +37 -44
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +105 -0
- package/dist/src/types/furnace.d.ts +12 -1
- package/dist/src/utils/elapsed.d.ts +0 -2
- package/dist/src/utils/elapsed.js +1 -1
- package/dist/src/utils/fs.d.ts +0 -5
- package/dist/src/utils/fs.js +1 -1
- package/dist/src/utils/regex.d.ts +0 -6
- package/dist/src/utils/regex.js +3 -3
- package/dist/src/utils/validation.d.ts +0 -8
- package/dist/src/utils/validation.js +2 -2
- package/package.json +6 -4
|
@@ -10,20 +10,13 @@ import { isValidAppId, isValidFirefoxVersion, isValidProjectLicense, PROJECT_LIC
|
|
|
10
10
|
import { SUPPORTED_CONFIG_ROOT_KEYS } from './config-paths.js';
|
|
11
11
|
import { parsePatchPolicyBlock } from './config-validate-patch-policy.js';
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Parses and validates the four required identity fields (`name`,
|
|
14
|
+
* `vendor`, `appId`, `binaryName`): all non-empty strings, with
|
|
15
|
+
* `binaryName` additionally barred from path separators/traversal and
|
|
16
|
+
* `appId` required to be a reverse-domain identifier.
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
rec = parseObject(data, 'Config');
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
throw new ConfigError('Config must be an object');
|
|
25
|
-
}
|
|
26
|
-
// Required string fields. Empty strings would technically pass the
|
|
18
|
+
function parseIdentityFields(rec) {
|
|
19
|
+
// Empty strings would technically pass the
|
|
27
20
|
// typeof-check below but are never valid for any of these identifier
|
|
28
21
|
// fields — rejecting them here prevents downstream code (Firefox build,
|
|
29
22
|
// launcher binary lookup, AppID assertions) from failing with confusing
|
|
@@ -54,7 +47,14 @@ export function validateConfig(data) {
|
|
|
54
47
|
if (!isValidAppId(appId)) {
|
|
55
48
|
throw new ConfigError('Config field "appId" must be a valid reverse-domain identifier (e.g., "org.example.browser")');
|
|
56
49
|
}
|
|
57
|
-
|
|
50
|
+
return { name, vendor, appId, binaryName };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Parses and validates the required `firefox` block: version shape,
|
|
54
|
+
* product allowlist, product/version cross-compatibility, and the
|
|
55
|
+
* optional sha256 digest (normalized to lowercase).
|
|
56
|
+
*/
|
|
57
|
+
function parseFirefoxBlock(rec) {
|
|
58
58
|
let firefoxRec;
|
|
59
59
|
try {
|
|
60
60
|
firefoxRec = rec.object('firefox');
|
|
@@ -80,19 +80,14 @@ export function validateConfig(data) {
|
|
|
80
80
|
if (firefoxSha256 !== undefined && !/^[a-f0-9]{64}$/i.test(firefoxSha256)) {
|
|
81
81
|
throw new ConfigError('Config field "firefox.sha256" must be a 64-character SHA-256 hex digest');
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
appId,
|
|
88
|
-
binaryName,
|
|
89
|
-
firefox: {
|
|
90
|
-
version: firefoxVersion,
|
|
91
|
-
product: firefoxProduct,
|
|
92
|
-
...(firefoxSha256 !== undefined ? { sha256: firefoxSha256.toLowerCase() } : {}),
|
|
93
|
-
},
|
|
83
|
+
return {
|
|
84
|
+
version: firefoxVersion,
|
|
85
|
+
product: firefoxProduct,
|
|
86
|
+
...(firefoxSha256 !== undefined ? { sha256: firefoxSha256.toLowerCase() } : {}),
|
|
94
87
|
};
|
|
95
|
-
|
|
88
|
+
}
|
|
89
|
+
/** Parses the optional `build` block (currently just `build.jobs`). */
|
|
90
|
+
function parseBuildBlock(rec, config) {
|
|
96
91
|
const buildRec = optionalConfigObject(rec, 'build');
|
|
97
92
|
if (buildRec) {
|
|
98
93
|
config.build = {};
|
|
@@ -104,7 +99,9 @@ export function validateConfig(data) {
|
|
|
104
99
|
config.build.jobs = jobs;
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
|
-
|
|
102
|
+
}
|
|
103
|
+
/** Parses the optional `wire` block (currently just `wire.subscriptDir`). */
|
|
104
|
+
function parseWireBlock(rec, config) {
|
|
108
105
|
const wireRec = optionalConfigObject(rec, 'wire');
|
|
109
106
|
if (wireRec) {
|
|
110
107
|
config.wire = {};
|
|
@@ -116,7 +113,9 @@ export function validateConfig(data) {
|
|
|
116
113
|
config.wire.subscriptDir = subscriptDir;
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
|
-
|
|
116
|
+
}
|
|
117
|
+
/** Parses the optional `license` field against the supported-license list. */
|
|
118
|
+
function parseLicenseField(rec, config) {
|
|
120
119
|
const licenseRaw = rec.raw('license');
|
|
121
120
|
if (licenseRaw !== undefined) {
|
|
122
121
|
if (typeof licenseRaw !== 'string') {
|
|
@@ -127,6 +126,27 @@ export function validateConfig(data) {
|
|
|
127
126
|
}
|
|
128
127
|
config.license = licenseRaw;
|
|
129
128
|
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Validates a raw config object and returns a typed FireForgeConfig.
|
|
132
|
+
* @param data - Raw data to validate
|
|
133
|
+
* @returns Validated FireForgeConfig
|
|
134
|
+
* @throws Error if validation fails
|
|
135
|
+
*/
|
|
136
|
+
export function validateConfig(data) {
|
|
137
|
+
let rec;
|
|
138
|
+
try {
|
|
139
|
+
rec = parseObject(data, 'Config');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new ConfigError('Config must be an object');
|
|
143
|
+
}
|
|
144
|
+
const identity = parseIdentityFields(rec);
|
|
145
|
+
const firefox = parseFirefoxBlock(rec);
|
|
146
|
+
const config = { ...identity, firefox };
|
|
147
|
+
parseBuildBlock(rec, config);
|
|
148
|
+
parseWireBlock(rec, config);
|
|
149
|
+
parseLicenseField(rec, config);
|
|
130
150
|
// Marker comment — appended to lines FireForge writes into upstream files.
|
|
131
151
|
const markerComment = parseMarkerComment(rec.raw('markerComment'));
|
|
132
152
|
if (markerComment !== undefined) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared collector for "what changed in the engine tree since the last
|
|
3
|
+
* successful build". Three preflight/audit paths previously carried their
|
|
4
|
+
* own copy of this logic (`build-audit`, `build-prepare`,
|
|
5
|
+
* `test-stale-check`), differing only in the verbose-log label; they all
|
|
6
|
+
* call this module now.
|
|
7
|
+
*/
|
|
8
|
+
import type { BuildBaseline } from './build-baseline-types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Collects engine-relative paths changed since the baseline's HEAD SHA,
|
|
11
|
+
* plus any workdir modifications (tracked and untracked). Defensive — git
|
|
12
|
+
* failures surface as verbose lines and return the files collected so far,
|
|
13
|
+
* so an empty result means "no drift we can prove" rather than "no drift
|
|
14
|
+
* occurred". When the baseline is missing or the engine has no HEAD yet,
|
|
15
|
+
* falls back to workdir-only collection. The result is sorted.
|
|
16
|
+
*
|
|
17
|
+
* @param engineDir - Path to the engine directory
|
|
18
|
+
* @param baseline - Last-build baseline, when one exists
|
|
19
|
+
* @param contextLabel - Prefix for verbose-log lines (e.g. `Audit`,
|
|
20
|
+
* `Auto-configure`, `Stale-build preflight`) so operators can attribute
|
|
21
|
+
* the probe that emitted them
|
|
22
|
+
* @returns Sorted engine-relative POSIX paths
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectChangedEnginePaths(engineDir: string, baseline: BuildBaseline | undefined, contextLabel: string): Promise<string[]>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Shared collector for "what changed in the engine tree since the last
|
|
4
|
+
* successful build". Three preflight/audit paths previously carried their
|
|
5
|
+
* own copy of this logic (`build-audit`, `build-prepare`,
|
|
6
|
+
* `test-stale-check`), differing only in the verbose-log label; they all
|
|
7
|
+
* call this module now.
|
|
8
|
+
*/
|
|
9
|
+
import { toError } from '../utils/errors.js';
|
|
10
|
+
import { verbose } from '../utils/logger.js';
|
|
11
|
+
import { hasChanges, isMissingHeadError } from './git.js';
|
|
12
|
+
import { git } from './git-base.js';
|
|
13
|
+
import { getUntrackedFiles } from './git-status.js';
|
|
14
|
+
/**
|
|
15
|
+
* Collects engine-relative paths changed since the baseline's HEAD SHA,
|
|
16
|
+
* plus any workdir modifications (tracked and untracked). Defensive — git
|
|
17
|
+
* failures surface as verbose lines and return the files collected so far,
|
|
18
|
+
* so an empty result means "no drift we can prove" rather than "no drift
|
|
19
|
+
* occurred". When the baseline is missing or the engine has no HEAD yet,
|
|
20
|
+
* falls back to workdir-only collection. The result is sorted.
|
|
21
|
+
*
|
|
22
|
+
* @param engineDir - Path to the engine directory
|
|
23
|
+
* @param baseline - Last-build baseline, when one exists
|
|
24
|
+
* @param contextLabel - Prefix for verbose-log lines (e.g. `Audit`,
|
|
25
|
+
* `Auto-configure`, `Stale-build preflight`) so operators can attribute
|
|
26
|
+
* the probe that emitted them
|
|
27
|
+
* @returns Sorted engine-relative POSIX paths
|
|
28
|
+
*/
|
|
29
|
+
export async function collectChangedEnginePaths(engineDir, baseline, contextLabel) {
|
|
30
|
+
const collected = new Set();
|
|
31
|
+
if (baseline?.engineHeadSha) {
|
|
32
|
+
try {
|
|
33
|
+
const output = await git(['diff', '--name-only', `${baseline.engineHeadSha}..HEAD`], engineDir);
|
|
34
|
+
for (const line of output.split('\n')) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (trimmed)
|
|
37
|
+
collected.add(trimmed);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!isMissingHeadError(error)) {
|
|
42
|
+
verbose(`${contextLabel}: could not diff engine against baseline — ${toError(error).message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
if (await hasChanges(engineDir)) {
|
|
48
|
+
const worktreeDiff = await git(['diff', '--name-only', 'HEAD'], engineDir);
|
|
49
|
+
for (const line of worktreeDiff.split('\n')) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (trimmed)
|
|
52
|
+
collected.add(trimmed);
|
|
53
|
+
}
|
|
54
|
+
for (const untracked of await getUntrackedFiles(engineDir)) {
|
|
55
|
+
collected.add(untracked);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
verbose(`${contextLabel}: could not enumerate workdir changes — ${toError(error).message}`);
|
|
61
|
+
}
|
|
62
|
+
return [...collected].sort();
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=engine-changes.js.map
|
|
@@ -3,11 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ResolvedArchive } from './firefox-archive.js';
|
|
5
5
|
import type { ProgressCallback } from './firefox-download.js';
|
|
6
|
-
/**
|
|
7
|
-
* Computes the SHA-256 hex digest of a file.
|
|
8
|
-
* @param filePath - Path to the file
|
|
9
|
-
*/
|
|
10
|
-
export declare function sha256File(filePath: string): Promise<string>;
|
|
11
6
|
/**
|
|
12
7
|
* Ensures a valid cached archive exists, downloading it if needed.
|
|
13
8
|
* @param archive - Resolved archive descriptor
|
|
@@ -18,7 +18,7 @@ import { downloadFile } from './firefox-download.js';
|
|
|
18
18
|
* Computes the SHA-256 hex digest of a file.
|
|
19
19
|
* @param filePath - Path to the file
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
async function sha256File(filePath) {
|
|
22
22
|
const hash = createHash('sha256');
|
|
23
23
|
const stream = createReadStream(filePath);
|
|
24
24
|
await pipeline(stream, hash);
|
|
@@ -5,12 +5,6 @@
|
|
|
5
5
|
* Progress callback for download operations.
|
|
6
6
|
*/
|
|
7
7
|
export type ProgressCallback = (downloaded: number, total: number) => void;
|
|
8
|
-
/**
|
|
9
|
-
* Fetches a URL with timeout and bounded retry for transient failures.
|
|
10
|
-
*
|
|
11
|
-
* Non-retryable errors (e.g. 404) are thrown immediately.
|
|
12
|
-
*/
|
|
13
|
-
export declare function fetchWithRetry(url: string): Promise<Response>;
|
|
14
8
|
/**
|
|
15
9
|
* Downloads a file from a URL with progress tracking, timeout, and retry.
|
|
16
10
|
* @param url - URL to download
|
|
@@ -22,7 +22,7 @@ const DOWNLOAD_STALL_TIMEOUT_MS = 30_000;
|
|
|
22
22
|
*
|
|
23
23
|
* Non-retryable errors (e.g. 404) are thrown immediately.
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
async function fetchWithRetry(url) {
|
|
26
26
|
let lastError;
|
|
27
27
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
28
28
|
const controller = new AbortController();
|
|
@@ -29,13 +29,6 @@ export declare function getOverrideEngineTargetPath(engineDir: string, config: O
|
|
|
29
29
|
export declare function restoreOverrideFileToBaseline(engineDir: string, enginePath: string, journal: RollbackJournal): Promise<'restored' | 'removed' | 'noop'>;
|
|
30
30
|
/** Computes stable checksums for the source files that define a component. */
|
|
31
31
|
export declare function computeComponentChecksums(componentDir: string): Promise<Record<string, string>>;
|
|
32
|
-
/**
|
|
33
|
-
* Returns the filenames present in `previous` that are absent from `current`
|
|
34
|
-
* — i.e. files we know we deployed last time but the workspace has since
|
|
35
|
-
* deleted. The order of returned names is intentionally stable
|
|
36
|
-
* (sorted alphabetically) so test snapshots and CLI output are deterministic.
|
|
37
|
-
*/
|
|
38
|
-
export declare function diffDeletedFiles(previous: Record<string, string>, current: Record<string, string>): string[];
|
|
39
32
|
/**
|
|
40
33
|
* Removes engine copies of files that the developer has deleted from a custom
|
|
41
34
|
* component's workspace since the last apply. `.ftl` files live under the
|
|
@@ -110,4 +103,4 @@ export declare function applyOverrideComponent(engineDir: string, name: string,
|
|
|
110
103
|
affectedPaths: string[];
|
|
111
104
|
actions?: DryRunAction[];
|
|
112
105
|
}>;
|
|
113
|
-
export { extractComponentChecksums, prefixChecksums } from './furnace-checksum-utils.js';
|
|
106
|
+
export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
|
|
@@ -8,6 +8,7 @@ import { copyFile, ensureDir, pathExists, readText, removeFile } from '../utils/
|
|
|
8
8
|
import { verbose } from '../utils/logger.js';
|
|
9
9
|
import { applyCustomFtlFile, describeLocaleFtlJarMnRegistration, removeCustomFtlJarMnEntry, } from './furnace-apply-ftl.js';
|
|
10
10
|
import { CUSTOM_ELEMENTS_JS, JAR_MN } from './furnace-constants.js';
|
|
11
|
+
import { deployFileWithFragments, describeFragmentExpansion, SHARED_FRAGMENTS_DIR, } from './furnace-css-fragments.js';
|
|
11
12
|
import { addCustomElementRegistration, addJarMnEntries, validateCustomElementRegistration, validateJarMnInsertionForFiles, } from './furnace-registration.js';
|
|
12
13
|
import { recordCreatedDir, snapshotFile } from './furnace-rollback.js';
|
|
13
14
|
import { checkRegistrationConsistency } from './furnace-validate-registration.js';
|
|
@@ -97,21 +98,6 @@ export async function computeComponentChecksums(componentDir) {
|
|
|
97
98
|
}
|
|
98
99
|
return checksums;
|
|
99
100
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Returns the filenames present in `previous` that are absent from `current`
|
|
102
|
-
* — i.e. files we know we deployed last time but the workspace has since
|
|
103
|
-
* deleted. The order of returned names is intentionally stable
|
|
104
|
-
* (sorted alphabetically) so test snapshots and CLI output are deterministic.
|
|
105
|
-
*/
|
|
106
|
-
export function diffDeletedFiles(previous, current) {
|
|
107
|
-
const deleted = [];
|
|
108
|
-
for (const key of Object.keys(previous)) {
|
|
109
|
-
if (!(key in current)) {
|
|
110
|
-
deleted.push(key);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return deleted.sort();
|
|
114
|
-
}
|
|
115
101
|
/**
|
|
116
102
|
* Removes engine copies of files that the developer has deleted from a custom
|
|
117
103
|
* component's workspace since the last apply. `.ftl` files live under the
|
|
@@ -300,12 +286,13 @@ async function buildCustomDryRunActions(name, componentDir, engineDir, config, t
|
|
|
300
286
|
continue;
|
|
301
287
|
if (!entry.name.endsWith('.mjs') && !entry.name.endsWith('.css'))
|
|
302
288
|
continue;
|
|
289
|
+
const fragmentNote = await describeFragmentExpansion(join(componentDir, entry.name));
|
|
303
290
|
actions.push({
|
|
304
291
|
component: name,
|
|
305
|
-
action: 'copy',
|
|
292
|
+
action: fragmentNote ? 'expand-fragments' : 'copy',
|
|
306
293
|
source: join(componentDir, entry.name),
|
|
307
294
|
target: join(targetDir, entry.name),
|
|
308
|
-
description: `Copy ${entry.name} to ${config.targetPath}`,
|
|
295
|
+
description: `Copy ${entry.name} to ${config.targetPath}${fragmentNote}`,
|
|
309
296
|
});
|
|
310
297
|
}
|
|
311
298
|
// Per-component .ftl handling is skipped when the component opts into a
|
|
@@ -401,11 +388,15 @@ export async function applyCustomComponent(engineDir, name, componentDir, config
|
|
|
401
388
|
await snapshotFile(rollbackJournal, dest);
|
|
402
389
|
}
|
|
403
390
|
}
|
|
404
|
-
// Copy phase (parallel — independent file writes to different paths)
|
|
391
|
+
// Copy phase (parallel — independent file writes to different paths).
|
|
392
|
+
// CSS files carrying @fireforge-include directives are written as their
|
|
393
|
+
// fragment-expanded form (field report D2); the workspace source keeps
|
|
394
|
+
// only the directive, so shared CSS stays single-sourced.
|
|
395
|
+
const sharedDir = join(componentDir, '..', '..', SHARED_FRAGMENTS_DIR);
|
|
405
396
|
await Promise.all(filesToCopy.map(async (entry) => {
|
|
406
397
|
const src = join(componentDir, entry.name);
|
|
407
398
|
const dest = join(targetDir, entry.name);
|
|
408
|
-
await
|
|
399
|
+
await deployFileWithFragments(src, dest, sharedDir);
|
|
409
400
|
affectedPaths.push(relative(engineDir, dest));
|
|
410
401
|
copiedFileNames.push(entry.name);
|
|
411
402
|
}));
|
|
@@ -499,5 +490,5 @@ export async function applyOverrideComponent(engineDir, name, componentDir, conf
|
|
|
499
490
|
}
|
|
500
491
|
return { affectedPaths };
|
|
501
492
|
}
|
|
502
|
-
export { extractComponentChecksums, prefixChecksums } from './furnace-checksum-utils.js';
|
|
493
|
+
export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
|
|
503
494
|
//# sourceMappingURL=furnace-apply-helpers.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ApplyResult, DryRunAction } from '../types/furnace.js';
|
|
2
2
|
import { type FurnaceOperationContext } from './furnace-operation.js';
|
|
3
3
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
4
|
-
export {
|
|
4
|
+
export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
5
5
|
/**
|
|
6
6
|
* Applies all override and custom components to the engine source tree.
|
|
7
7
|
*
|
|
@@ -14,7 +14,7 @@ import { recordFurnaceRollbackFailure } from './furnace-operation.js';
|
|
|
14
14
|
import { addJarMnEntries, removeCustomElementRegistration, removeJarMnEntries, } from './furnace-registration.js';
|
|
15
15
|
import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotFile, } from './furnace-rollback.js';
|
|
16
16
|
import { runPostApplyConsistencyChecks } from './furnace-validate-registration.js';
|
|
17
|
-
export {
|
|
17
|
+
export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
18
18
|
function addMissingComponentError(result, name, directoryPath) {
|
|
19
19
|
result.errors.push({
|
|
20
20
|
name,
|
|
@@ -2,3 +2,10 @@
|
|
|
2
2
|
export declare function extractComponentChecksums(allChecksums: Record<string, string> | undefined, type: string, name: string): Record<string, string>;
|
|
3
3
|
/** Prefixes component checksums so they can be stored in the flattened state format. */
|
|
4
4
|
export declare function prefixChecksums(checksums: Record<string, string>, type: string, name: string): Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the filenames present in `previous` that are absent from `current`
|
|
7
|
+
* — i.e. files we know we deployed last time but the workspace has since
|
|
8
|
+
* deleted. The order of returned names is intentionally stable
|
|
9
|
+
* (sorted alphabetically) so test snapshots and CLI output are deterministic.
|
|
10
|
+
*/
|
|
11
|
+
export declare function diffDeletedFiles(previous: Record<string, string>, current: Record<string, string>): string[];
|
|
@@ -21,4 +21,19 @@ export function prefixChecksums(checksums, type, name) {
|
|
|
21
21
|
}
|
|
22
22
|
return result;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the filenames present in `previous` that are absent from `current`
|
|
26
|
+
* — i.e. files we know we deployed last time but the workspace has since
|
|
27
|
+
* deleted. The order of returned names is intentionally stable
|
|
28
|
+
* (sorted alphabetically) so test snapshots and CLI output are deterministic.
|
|
29
|
+
*/
|
|
30
|
+
export function diffDeletedFiles(previous, current) {
|
|
31
|
+
const deleted = [];
|
|
32
|
+
for (const key of Object.keys(previous)) {
|
|
33
|
+
if (!(key in current)) {
|
|
34
|
+
deleted.push(key);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return deleted.sort();
|
|
38
|
+
}
|
|
24
39
|
//# sourceMappingURL=furnace-checksum-utils.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section validators for `validateFurnaceConfig`, split out of
|
|
3
|
+
* `furnace-config.ts` to keep that module inside the per-file line
|
|
4
|
+
* budget. Each helper owns one shape: the override entry, the stock
|
|
5
|
+
* list, the named-component maps, and the optional-field tail.
|
|
6
|
+
*/
|
|
7
|
+
import type { FurnaceConfig, OverrideComponentConfig } from '../types/furnace.js';
|
|
8
|
+
/**
|
|
9
|
+
* Validates an override component config object.
|
|
10
|
+
* @param data - Raw data to validate
|
|
11
|
+
* @param name - Component name for error messages
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseOverrideConfig(data: Record<string, unknown>, name: string): OverrideComponentConfig;
|
|
14
|
+
/**
|
|
15
|
+
* Parses and validates the `stock` component list: lowercase identifiers,
|
|
16
|
+
* no duplicates.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseStockList(raw: unknown): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Parses one of the named-component maps (`overrides` / `custom`): the
|
|
21
|
+
* map must be an object, every key a lowercase identifier, every value an
|
|
22
|
+
* object handed to the kind-specific parser.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseNamedComponentMap<T>(raw: unknown, kind: 'override' | 'custom', key: 'overrides' | 'custom', parse: (value: Record<string, unknown>, name: string) => T): Record<string, T>;
|
|
25
|
+
/**
|
|
26
|
+
* Copies the validated optional fields (token settings, platform
|
|
27
|
+
* prefixes, ftl/jsconfig/scan paths) from the migrated raw config onto
|
|
28
|
+
* the typed config, re-validating the path-shaped ones against
|
|
29
|
+
* traversal.
|
|
30
|
+
*/
|
|
31
|
+
export declare function applyOptionalFurnaceFields(migrated: Record<string, unknown>, config: FurnaceConfig): void;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Section validators for `validateFurnaceConfig`, split out of
|
|
4
|
+
* `furnace-config.ts` to keep that module inside the per-file line
|
|
5
|
+
* budget. Each helper owns one shape: the override entry, the stock
|
|
6
|
+
* list, the named-component maps, and the optional-field tail.
|
|
7
|
+
*/
|
|
8
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
9
|
+
import { isObject, isString } from '../utils/validation.js';
|
|
10
|
+
import { parseStringArray } from './furnace-config-array-utils.js';
|
|
11
|
+
/**
|
|
12
|
+
* Validates an override component config object.
|
|
13
|
+
* @param data - Raw data to validate
|
|
14
|
+
* @param name - Component name for error messages
|
|
15
|
+
*/
|
|
16
|
+
export function parseOverrideConfig(data, name) {
|
|
17
|
+
const validTypes = ['css-only', 'full'];
|
|
18
|
+
if (!isString(data['type']) || !validTypes.includes(data['type'])) {
|
|
19
|
+
throw new FurnaceError(`Furnace config: override "${name}.type" must be one of: ${validTypes.join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
if (!isString(data['description'])) {
|
|
22
|
+
throw new FurnaceError(`Furnace config: override "${name}.description" must be a string`);
|
|
23
|
+
}
|
|
24
|
+
if (!isString(data['basePath'])) {
|
|
25
|
+
throw new FurnaceError(`Furnace config: override "${name}.basePath" must be a string`);
|
|
26
|
+
}
|
|
27
|
+
if (data['basePath'].includes('..')) {
|
|
28
|
+
throw new FurnaceError(`Furnace config: override "${name}.basePath" must not contain ".." (path traversal)`);
|
|
29
|
+
}
|
|
30
|
+
if (!isString(data['baseVersion'])) {
|
|
31
|
+
throw new FurnaceError(`Furnace config: override "${name}.baseVersion" must be a string`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
type: data['type'] === 'css-only' ? 'css-only' : 'full',
|
|
35
|
+
description: data['description'],
|
|
36
|
+
basePath: data['basePath'],
|
|
37
|
+
baseVersion: data['baseVersion'],
|
|
38
|
+
...(isString(data['baseCommit']) ? { baseCommit: data['baseCommit'] } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parses and validates the `stock` component list: lowercase identifiers,
|
|
43
|
+
* no duplicates.
|
|
44
|
+
*/
|
|
45
|
+
export function parseStockList(raw) {
|
|
46
|
+
const stock = parseStringArray(raw, 'stock');
|
|
47
|
+
const stockSet = new Set();
|
|
48
|
+
for (const name of stock) {
|
|
49
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
50
|
+
throw new FurnaceError(`Furnace config: stock entry "${name}" must match /^[a-z][a-z0-9-]*$/ (lowercase, no path separators)`);
|
|
51
|
+
}
|
|
52
|
+
if (stockSet.has(name)) {
|
|
53
|
+
throw new FurnaceError(`Furnace config: duplicate stock entry "${name}"`);
|
|
54
|
+
}
|
|
55
|
+
stockSet.add(name);
|
|
56
|
+
}
|
|
57
|
+
return stock;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses one of the named-component maps (`overrides` / `custom`): the
|
|
61
|
+
* map must be an object, every key a lowercase identifier, every value an
|
|
62
|
+
* object handed to the kind-specific parser.
|
|
63
|
+
*/
|
|
64
|
+
export function parseNamedComponentMap(raw, kind, key, parse) {
|
|
65
|
+
if (!isObject(raw)) {
|
|
66
|
+
throw new FurnaceError(`Furnace config: "${key}" must be an object`);
|
|
67
|
+
}
|
|
68
|
+
const out = {};
|
|
69
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
70
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
71
|
+
throw new FurnaceError(`Furnace config: ${kind} name "${name}" must match /^[a-z][a-z0-9-]*$/ (lowercase, no path separators)`);
|
|
72
|
+
}
|
|
73
|
+
if (!isObject(value)) {
|
|
74
|
+
throw new FurnaceError(`Furnace config: ${kind} "${name}" must be an object`);
|
|
75
|
+
}
|
|
76
|
+
out[name] = parse(value, name);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Copies the validated optional fields (token settings, platform
|
|
82
|
+
* prefixes, ftl/jsconfig/scan paths) from the migrated raw config onto
|
|
83
|
+
* the typed config, re-validating the path-shaped ones against
|
|
84
|
+
* traversal.
|
|
85
|
+
*/
|
|
86
|
+
export function applyOptionalFurnaceFields(migrated, config) {
|
|
87
|
+
if (migrated['tokenPrefix'] !== undefined && isString(migrated['tokenPrefix'])) {
|
|
88
|
+
config.tokenPrefix = migrated['tokenPrefix'];
|
|
89
|
+
}
|
|
90
|
+
if (migrated['tokenAllowlist'] !== undefined) {
|
|
91
|
+
config.tokenAllowlist = parseStringArray(migrated['tokenAllowlist'], 'tokenAllowlist');
|
|
92
|
+
}
|
|
93
|
+
if (migrated['platformPrefixes'] !== undefined) {
|
|
94
|
+
config.platformPrefixes = parseStringArray(migrated['platformPrefixes'], 'platformPrefixes');
|
|
95
|
+
}
|
|
96
|
+
if (migrated['runtimeVariables'] !== undefined) {
|
|
97
|
+
config.runtimeVariables = parseStringArray(migrated['runtimeVariables'], 'runtimeVariables');
|
|
98
|
+
}
|
|
99
|
+
if (migrated['tokenHostDocuments'] !== undefined) {
|
|
100
|
+
const docs = parseStringArray(migrated['tokenHostDocuments'], 'tokenHostDocuments');
|
|
101
|
+
config.tokenHostDocuments = docs;
|
|
102
|
+
}
|
|
103
|
+
// Validate optional ftlBasePath
|
|
104
|
+
if (migrated['ftlBasePath'] !== undefined) {
|
|
105
|
+
if (!isString(migrated['ftlBasePath'])) {
|
|
106
|
+
throw new FurnaceError('Furnace config: "ftlBasePath" must be a string if provided');
|
|
107
|
+
}
|
|
108
|
+
if (migrated['ftlBasePath'].includes('..')) {
|
|
109
|
+
throw new FurnaceError('Furnace config: "ftlBasePath" must not contain ".." (path traversal)');
|
|
110
|
+
}
|
|
111
|
+
config.ftlBasePath = migrated['ftlBasePath'];
|
|
112
|
+
}
|
|
113
|
+
// Validate optional typecheckJsconfig — consumer-owned jsconfig whose
|
|
114
|
+
// chrome-elements `paths` entries Furnace maintains on deploy.
|
|
115
|
+
if (migrated['typecheckJsconfig'] !== undefined) {
|
|
116
|
+
const jsconfigPath = migrated['typecheckJsconfig'];
|
|
117
|
+
if (!isString(jsconfigPath) || jsconfigPath.includes('..')) {
|
|
118
|
+
throw new FurnaceError('Furnace config: "typecheckJsconfig" must be a string without ".." (path traversal)');
|
|
119
|
+
}
|
|
120
|
+
config.typecheckJsconfig = jsconfigPath;
|
|
121
|
+
}
|
|
122
|
+
// Validate optional scanPaths
|
|
123
|
+
if (migrated['scanPaths'] !== undefined) {
|
|
124
|
+
const paths = parseStringArray(migrated['scanPaths'], 'scanPaths');
|
|
125
|
+
for (const p of paths) {
|
|
126
|
+
if (p.includes('..')) {
|
|
127
|
+
throw new FurnaceError('Furnace config: "scanPaths" entries must not contain ".." (path traversal)');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
config.scanPaths = paths;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=furnace-config-validate.js.map
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { FurnaceConfig, FurnaceState } from '../types/furnace.js';
|
|
2
2
|
import { detectComposesCycles } from './furnace-graph-utils.js';
|
|
3
3
|
export { detectComposesCycles };
|
|
4
|
-
/**
|
|
5
|
-
export declare const
|
|
6
|
-
/** Name of the furnace state file */
|
|
7
|
-
export declare const FURNACE_STATE_FILENAME = "furnace-state.json";
|
|
8
|
-
/** Name of the components directory */
|
|
9
|
-
export declare const COMPONENTS_DIR = "components";
|
|
10
|
-
/** Name of the overrides subdirectory */
|
|
11
|
-
export declare const OVERRIDES_DIR = "overrides";
|
|
12
|
-
/** Name of the custom subdirectory */
|
|
13
|
-
export declare const CUSTOM_DIR = "custom";
|
|
4
|
+
/** Directory name for shared CSS fragments within components/ */
|
|
5
|
+
export declare const SHARED_FRAGMENTS_DIR = "shared";
|
|
14
6
|
/**
|
|
15
7
|
* Paths for furnace-related files and directories.
|
|
16
8
|
*/
|
|
@@ -23,6 +15,8 @@ interface FurnacePaths {
|
|
|
23
15
|
overridesDir: string;
|
|
24
16
|
/** Path to components/custom directory */
|
|
25
17
|
customDir: string;
|
|
18
|
+
/** Path to components/shared directory (CSS fragments) */
|
|
19
|
+
sharedDir: string;
|
|
26
20
|
/** Path to .fireforge/furnace-state.json */
|
|
27
21
|
furnaceState: string;
|
|
28
22
|
}
|
|
@@ -38,22 +32,6 @@ export declare function getFurnacePaths(root: string): FurnacePaths;
|
|
|
38
32
|
* @returns True if furnace.json exists
|
|
39
33
|
*/
|
|
40
34
|
export declare function furnaceConfigExists(root: string): Promise<boolean>;
|
|
41
|
-
/**
|
|
42
|
-
* Migrates a furnace config from an older schema version to the current one.
|
|
43
|
-
* Returns the data unchanged if it is already at the current version.
|
|
44
|
-
*
|
|
45
|
-
* When a future version 2 is introduced, add a `case 1:` that transforms
|
|
46
|
-
* v1 data into v2 shape and falls through to validation. The pattern is:
|
|
47
|
-
*
|
|
48
|
-
* ```
|
|
49
|
-
* case 1:
|
|
50
|
-
* data = migrateV1ToV2(data);
|
|
51
|
-
* // fallthrough
|
|
52
|
-
* case 2:
|
|
53
|
-
* break;
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export declare function migrateFurnaceConfig(data: Record<string, unknown>): Record<string, unknown>;
|
|
57
35
|
/**
|
|
58
36
|
* Validates a raw config object and returns a typed FurnaceConfig.
|
|
59
37
|
* @param data - Raw data to validate
|
|
@@ -61,12 +39,6 @@ export declare function migrateFurnaceConfig(data: Record<string, unknown>): Rec
|
|
|
61
39
|
* @throws Error if validation fails
|
|
62
40
|
*/
|
|
63
41
|
export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
|
|
64
|
-
/**
|
|
65
|
-
* Validates a parsed furnace state object and returns a typed FurnaceState.
|
|
66
|
-
* @param data - Parsed JSON state data
|
|
67
|
-
* @returns Validated FurnaceState
|
|
68
|
-
*/
|
|
69
|
-
export declare function validateFurnaceState(data: unknown): FurnaceState;
|
|
70
42
|
/**
|
|
71
43
|
* Loads and validates the furnace.json configuration.
|
|
72
44
|
* @param root - Root directory of the project
|