@hominis/fireforge 0.30.1 → 0.32.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 +36 -0
- package/README.md +22 -0
- package/dist/src/commands/export-all.js +9 -16
- 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 +46 -1
- package/dist/src/commands/export.js +52 -113
- 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 +3 -1
- package/dist/src/commands/lint-per-patch.js +265 -74
- package/dist/src/commands/lint.d.ts +1 -58
- package/dist/src/commands/lint.js +193 -88
- 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-files.js +4 -1
- package/dist/src/commands/re-export-scan.js +8 -1
- package/dist/src/commands/re-export.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 +68 -0
- package/dist/src/commands/test-run.js +97 -0
- package/dist/src/commands/test.js +214 -263
- 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 +74 -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 +191 -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-base.d.ts +15 -0
- package/dist/src/core/git-base.js +32 -0
- package/dist/src/core/git-diff.d.ts +8 -0
- package/dist/src/core/git-diff.js +224 -59
- package/dist/src/core/git-file-ops.d.ts +39 -12
- package/dist/src/core/git-file-ops.js +84 -3
- 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 +22 -1
- package/dist/src/core/mach.js +27 -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.d.ts +75 -21
- package/dist/src/core/patch-lint-checkjs.js +263 -71
- package/dist/src/core/patch-lint-css.d.ts +23 -0
- package/dist/src/core/patch-lint-css.js +172 -0
- 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.d.ts +34 -11
- package/dist/src/core/patch-lint.js +24 -161
- 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 +9 -2
- package/dist/src/core/test-xpcshell-retry.js +10 -3
- 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 +3 -22
- package/dist/src/core/typecheck-shim.js +69 -7
- 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 +122 -0
- package/dist/src/types/config.d.ts +11 -2
- 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
|
@@ -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
|
|
@@ -10,20 +10,23 @@ import { parseStringArray } from './furnace-config-array-utils.js';
|
|
|
10
10
|
import { parseCustomConfig } from './furnace-config-custom.js';
|
|
11
11
|
import { orderFurnaceConfigForWrite } from './furnace-config-order.js';
|
|
12
12
|
import { validateRuntimeVariables, validateTokenHostDocuments } from './furnace-config-tokens.js';
|
|
13
|
+
import { applyOptionalFurnaceFields, parseNamedComponentMap, parseOverrideConfig, parseStockList, } from './furnace-config-validate.js';
|
|
13
14
|
import { resolveFtlDir } from './furnace-constants.js';
|
|
14
15
|
import { detectComposesCycles, validateComposesReferences } from './furnace-graph-utils.js';
|
|
15
16
|
import { quarantineStateFile, withStateFileLock } from './state-file.js';
|
|
16
17
|
export { detectComposesCycles };
|
|
17
18
|
/** Name of the furnace configuration file */
|
|
18
|
-
|
|
19
|
+
const FURNACE_CONFIG_FILENAME = 'furnace.json';
|
|
19
20
|
/** Name of the furnace state file */
|
|
20
|
-
|
|
21
|
+
const FURNACE_STATE_FILENAME = 'furnace-state.json';
|
|
21
22
|
/** Name of the components directory */
|
|
22
|
-
|
|
23
|
+
const COMPONENTS_DIR = 'components';
|
|
23
24
|
/** Name of the overrides subdirectory */
|
|
24
|
-
|
|
25
|
+
const OVERRIDES_DIR = 'overrides';
|
|
25
26
|
/** Name of the custom subdirectory */
|
|
26
|
-
|
|
27
|
+
const CUSTOM_DIR = 'custom';
|
|
28
|
+
/** Directory name for shared CSS fragments within components/ */
|
|
29
|
+
export const SHARED_FRAGMENTS_DIR = 'shared';
|
|
27
30
|
/**
|
|
28
31
|
* Gets all furnace-related paths based on a root directory.
|
|
29
32
|
* @param root - Root directory of the project
|
|
@@ -36,6 +39,7 @@ export function getFurnacePaths(root) {
|
|
|
36
39
|
componentsDir,
|
|
37
40
|
overridesDir: join(componentsDir, OVERRIDES_DIR),
|
|
38
41
|
customDir: join(componentsDir, CUSTOM_DIR),
|
|
42
|
+
sharedDir: join(componentsDir, SHARED_FRAGMENTS_DIR),
|
|
39
43
|
furnaceState: join(root, FIREFORGE_DIR, FURNACE_STATE_FILENAME),
|
|
40
44
|
};
|
|
41
45
|
}
|
|
@@ -48,36 +52,6 @@ export async function furnaceConfigExists(root) {
|
|
|
48
52
|
const paths = getFurnacePaths(root);
|
|
49
53
|
return pathExists(paths.furnaceConfig);
|
|
50
54
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Validates an override component config object.
|
|
53
|
-
* @param data - Raw data to validate
|
|
54
|
-
* @param name - Component name for error messages
|
|
55
|
-
*/
|
|
56
|
-
function parseOverrideConfig(data, name) {
|
|
57
|
-
const validTypes = ['css-only', 'full'];
|
|
58
|
-
if (!isString(data['type']) || !validTypes.includes(data['type'])) {
|
|
59
|
-
throw new FurnaceError(`Furnace config: override "${name}.type" must be one of: ${validTypes.join(', ')}`);
|
|
60
|
-
}
|
|
61
|
-
if (!isString(data['description'])) {
|
|
62
|
-
throw new FurnaceError(`Furnace config: override "${name}.description" must be a string`);
|
|
63
|
-
}
|
|
64
|
-
if (!isString(data['basePath'])) {
|
|
65
|
-
throw new FurnaceError(`Furnace config: override "${name}.basePath" must be a string`);
|
|
66
|
-
}
|
|
67
|
-
if (data['basePath'].includes('..')) {
|
|
68
|
-
throw new FurnaceError(`Furnace config: override "${name}.basePath" must not contain ".." (path traversal)`);
|
|
69
|
-
}
|
|
70
|
-
if (!isString(data['baseVersion'])) {
|
|
71
|
-
throw new FurnaceError(`Furnace config: override "${name}.baseVersion" must be a string`);
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
type: data['type'] === 'css-only' ? 'css-only' : 'full',
|
|
75
|
-
description: data['description'],
|
|
76
|
-
basePath: data['basePath'],
|
|
77
|
-
baseVersion: data['baseVersion'],
|
|
78
|
-
...(isString(data['baseCommit']) ? { baseCommit: data['baseCommit'] } : {}),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
55
|
/** The current (and only) config schema version. */
|
|
82
56
|
const CURRENT_CONFIG_VERSION = 1;
|
|
83
57
|
/**
|
|
@@ -95,7 +69,7 @@ const CURRENT_CONFIG_VERSION = 1;
|
|
|
95
69
|
* break;
|
|
96
70
|
* ```
|
|
97
71
|
*/
|
|
98
|
-
|
|
72
|
+
function migrateFurnaceConfig(data) {
|
|
99
73
|
const version = data['version'];
|
|
100
74
|
if (typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
|
|
101
75
|
throw new FurnaceError(`Furnace config: "version" must be a positive integer (got ${JSON.stringify(version)}). ` +
|
|
@@ -141,45 +115,9 @@ export function validateFurnaceConfig(data) {
|
|
|
141
115
|
// Validate optional tokenHostDocuments — list of chrome XHTMLs that the
|
|
142
116
|
// `missing-token-link` validator scans for the tokens CSS link.
|
|
143
117
|
validateTokenHostDocuments(migrated['tokenHostDocuments']);
|
|
144
|
-
const stock =
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
148
|
-
throw new FurnaceError(`Furnace config: stock entry "${name}" must match /^[a-z][a-z0-9-]*$/ (lowercase, no path separators)`);
|
|
149
|
-
}
|
|
150
|
-
if (stockSet.has(name)) {
|
|
151
|
-
throw new FurnaceError(`Furnace config: duplicate stock entry "${name}"`);
|
|
152
|
-
}
|
|
153
|
-
stockSet.add(name);
|
|
154
|
-
}
|
|
155
|
-
// Validate overrides
|
|
156
|
-
if (!isObject(migrated['overrides'])) {
|
|
157
|
-
throw new FurnaceError('Furnace config: "overrides" must be an object');
|
|
158
|
-
}
|
|
159
|
-
const overrides = {};
|
|
160
|
-
for (const [name, value] of Object.entries(migrated['overrides'])) {
|
|
161
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
162
|
-
throw new FurnaceError(`Furnace config: override name "${name}" must match /^[a-z][a-z0-9-]*$/ (lowercase, no path separators)`);
|
|
163
|
-
}
|
|
164
|
-
if (!isObject(value)) {
|
|
165
|
-
throw new FurnaceError(`Furnace config: override "${name}" must be an object`);
|
|
166
|
-
}
|
|
167
|
-
overrides[name] = parseOverrideConfig(value, name);
|
|
168
|
-
}
|
|
169
|
-
// Validate custom
|
|
170
|
-
if (!isObject(migrated['custom'])) {
|
|
171
|
-
throw new FurnaceError('Furnace config: "custom" must be an object');
|
|
172
|
-
}
|
|
173
|
-
const custom = {};
|
|
174
|
-
for (const [name, value] of Object.entries(migrated['custom'])) {
|
|
175
|
-
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
176
|
-
throw new FurnaceError(`Furnace config: custom name "${name}" must match /^[a-z][a-z0-9-]*$/ (lowercase, no path separators)`);
|
|
177
|
-
}
|
|
178
|
-
if (!isObject(value)) {
|
|
179
|
-
throw new FurnaceError(`Furnace config: custom "${name}" must be an object`);
|
|
180
|
-
}
|
|
181
|
-
custom[name] = parseCustomConfig(value, name);
|
|
182
|
-
}
|
|
118
|
+
const stock = parseStockList(migrated['stock']);
|
|
119
|
+
const overrides = parseNamedComponentMap(migrated['overrides'], 'override', 'overrides', parseOverrideConfig);
|
|
120
|
+
const custom = parseNamedComponentMap(migrated['custom'], 'custom', 'custom', parseCustomConfig);
|
|
183
121
|
// Detect circular composes references among custom components.
|
|
184
122
|
detectComposesCycles(custom);
|
|
185
123
|
// Validate that every composes reference points to a known component.
|
|
@@ -191,41 +129,7 @@ export function validateFurnaceConfig(data) {
|
|
|
191
129
|
overrides,
|
|
192
130
|
custom,
|
|
193
131
|
};
|
|
194
|
-
|
|
195
|
-
config.tokenPrefix = migrated['tokenPrefix'];
|
|
196
|
-
if (migrated['tokenAllowlist'] !== undefined) {
|
|
197
|
-
config.tokenAllowlist = parseStringArray(migrated['tokenAllowlist'], 'tokenAllowlist');
|
|
198
|
-
}
|
|
199
|
-
if (migrated['platformPrefixes'] !== undefined) {
|
|
200
|
-
config.platformPrefixes = parseStringArray(migrated['platformPrefixes'], 'platformPrefixes');
|
|
201
|
-
}
|
|
202
|
-
if (migrated['runtimeVariables'] !== undefined) {
|
|
203
|
-
config.runtimeVariables = parseStringArray(migrated['runtimeVariables'], 'runtimeVariables');
|
|
204
|
-
}
|
|
205
|
-
if (migrated['tokenHostDocuments'] !== undefined) {
|
|
206
|
-
const docs = parseStringArray(migrated['tokenHostDocuments'], 'tokenHostDocuments');
|
|
207
|
-
config.tokenHostDocuments = docs;
|
|
208
|
-
}
|
|
209
|
-
// Validate optional ftlBasePath
|
|
210
|
-
if (migrated['ftlBasePath'] !== undefined) {
|
|
211
|
-
if (!isString(migrated['ftlBasePath'])) {
|
|
212
|
-
throw new FurnaceError('Furnace config: "ftlBasePath" must be a string if provided');
|
|
213
|
-
}
|
|
214
|
-
if (migrated['ftlBasePath'].includes('..')) {
|
|
215
|
-
throw new FurnaceError('Furnace config: "ftlBasePath" must not contain ".." (path traversal)');
|
|
216
|
-
}
|
|
217
|
-
config.ftlBasePath = migrated['ftlBasePath'];
|
|
218
|
-
}
|
|
219
|
-
// Validate optional scanPaths
|
|
220
|
-
if (migrated['scanPaths'] !== undefined) {
|
|
221
|
-
const paths = parseStringArray(migrated['scanPaths'], 'scanPaths');
|
|
222
|
-
for (const p of paths) {
|
|
223
|
-
if (p.includes('..')) {
|
|
224
|
-
throw new FurnaceError('Furnace config: "scanPaths" entries must not contain ".." (path traversal)');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
config.scanPaths = paths;
|
|
228
|
-
}
|
|
132
|
+
applyOptionalFurnaceFields(migrated, config);
|
|
229
133
|
return config;
|
|
230
134
|
}
|
|
231
135
|
/**
|
|
@@ -233,7 +137,7 @@ export function validateFurnaceConfig(data) {
|
|
|
233
137
|
* @param data - Parsed JSON state data
|
|
234
138
|
* @returns Validated FurnaceState
|
|
235
139
|
*/
|
|
236
|
-
|
|
140
|
+
function validateFurnaceState(data) {
|
|
237
141
|
const result = sanitizeFurnaceState(data);
|
|
238
142
|
if (result.issues.length > 0) {
|
|
239
143
|
throw new FurnaceError(`Invalid furnace state: ${result.issues.join('; ')}`);
|
|
@@ -4,22 +4,12 @@ export declare const CUSTOM_ELEMENTS_JS = "toolkit/content/customElements.js";
|
|
|
4
4
|
export declare const JAR_MN = "toolkit/content/jar.mn";
|
|
5
5
|
/** Default Fluent localization directory for toolkit global components, relative to engine root */
|
|
6
6
|
export declare const FTL_DIR = "toolkit/locales/en-US/toolkit/global";
|
|
7
|
-
/**
|
|
8
|
-
* Suffix for the per-binary xpcshell scaffold parent directory. Components
|
|
9
|
-
* created with `furnace create --with-tests --xpcshell` land at
|
|
10
|
-
* `browser/base/content/test/<binaryName>${XPCSHELL_TEST_DIR_SUFFIX}/<component>/`.
|
|
11
|
-
* Centralised so `create` / `remove` / `rename` / `validate` all agree on
|
|
12
|
-
* the path template (2026-04-24 eval Finding 5).
|
|
13
|
-
*/
|
|
14
|
-
export declare const XPCSHELL_TEST_DIR_SUFFIX = "-xpcshell";
|
|
15
7
|
/**
|
|
16
8
|
* Returns the engine-relative directory that holds xpcshell scaffolds for
|
|
17
9
|
* a given binary. Matches the form `create-xpcshell.ts` writes and the
|
|
18
10
|
* path `remove.ts` / `rename.ts` / `validate.ts` must clean up.
|
|
19
11
|
*/
|
|
20
12
|
export declare function xpcshellTestParentDir(binaryName: string): string;
|
|
21
|
-
/** File extensions that constitute a Furnace component's source files. */
|
|
22
|
-
export declare const COMPONENT_FILE_EXTENSIONS: readonly [".mjs", ".css", ".ftl"];
|
|
23
13
|
/** Returns true when `fileName` has one of the standard component file extensions. */
|
|
24
14
|
export declare function isComponentSourceFile(fileName: string): boolean;
|
|
25
15
|
/**
|
|
@@ -12,7 +12,7 @@ export const FTL_DIR = 'toolkit/locales/en-US/toolkit/global';
|
|
|
12
12
|
* Centralised so `create` / `remove` / `rename` / `validate` all agree on
|
|
13
13
|
* the path template (2026-04-24 eval Finding 5).
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
const XPCSHELL_TEST_DIR_SUFFIX = '-xpcshell';
|
|
16
16
|
/**
|
|
17
17
|
* Returns the engine-relative directory that holds xpcshell scaffolds for
|
|
18
18
|
* a given binary. Matches the form `create-xpcshell.ts` writes and the
|
|
@@ -22,7 +22,7 @@ export function xpcshellTestParentDir(binaryName) {
|
|
|
22
22
|
return `browser/base/content/test/${binaryName}${XPCSHELL_TEST_DIR_SUFFIX}`;
|
|
23
23
|
}
|
|
24
24
|
/** File extensions that constitute a Furnace component's source files. */
|
|
25
|
-
|
|
25
|
+
const COMPONENT_FILE_EXTENSIONS = ['.mjs', '.css', '.ftl'];
|
|
26
26
|
/** Returns true when `fileName` has one of the standard component file extensions. */
|
|
27
27
|
export function isComponentSourceFile(fileName) {
|
|
28
28
|
return COMPONENT_FILE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CSS fragments for Furnace widgets (field report D2).
|
|
3
|
+
*
|
|
4
|
+
* Shadow-DOM isolation forces each widget stylesheet to carry its own copy
|
|
5
|
+
* of genuinely shared CSS (keyframes, resets). Hand-syncing those copies
|
|
6
|
+
* drifts. Instead, a workspace stylesheet declares an include directive
|
|
7
|
+
* (a CSS block comment on its own line):
|
|
8
|
+
*
|
|
9
|
+
* @fireforge-include shared-anims.css
|
|
10
|
+
*
|
|
11
|
+
* naming a fragment file in `components/shared/`. `furnace deploy` expands
|
|
12
|
+
* the fragment into the *deployed* copy only — the workspace source stays
|
|
13
|
+
* DRY — fencing the expansion between the directive line and a matching
|
|
14
|
+
* `@fireforge-end-include` marker so re-deploys can refresh it idempotently.
|
|
15
|
+
*
|
|
16
|
+
* Drift contract: the apply fast-path and `furnace validate` compare the
|
|
17
|
+
* *expanded* workspace source against the engine copy, so editing a
|
|
18
|
+
* fragment surfaces as ordinary component drift and the next deploy
|
|
19
|
+
* refreshes every consuming widget.
|
|
20
|
+
*/
|
|
21
|
+
import type { ValidationIssue } from '../types/furnace.js';
|
|
22
|
+
export { SHARED_FRAGMENTS_DIR } from './furnace-config.js';
|
|
23
|
+
/** Returns the fragment names referenced by `@fireforge-include` directives. */
|
|
24
|
+
export declare function listFragmentIncludes(css: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Collapses fenced fragment expansions back to their bare directives.
|
|
27
|
+
* Inverse of {@link expandCssFragments}; used to compare a deployed file
|
|
28
|
+
* against its workspace source and to re-expand idempotently.
|
|
29
|
+
*/
|
|
30
|
+
export declare function stripExpandedFragments(css: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Expands every `@fireforge-include` directive in `css` with the current
|
|
33
|
+
* content of its fragment file from `sharedDir`, fencing each expansion
|
|
34
|
+
* with an end marker. Existing expansions are stripped first, so the
|
|
35
|
+
* operation is idempotent and refreshes stale content.
|
|
36
|
+
*
|
|
37
|
+
* @param css - Stylesheet source (workspace or previously expanded)
|
|
38
|
+
* @param sharedDir - Absolute path to the shared fragments directory
|
|
39
|
+
* @returns Expanded stylesheet and the fragment names it consumed
|
|
40
|
+
* @throws FurnaceError when a fragment is missing or itself contains an
|
|
41
|
+
* include directive (nesting is not supported)
|
|
42
|
+
*/
|
|
43
|
+
export declare function expandCssFragments(css: string, sharedDir: string): Promise<{
|
|
44
|
+
expanded: string;
|
|
45
|
+
includes: string[];
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Extracts the fenced expansion bodies of a previously expanded stylesheet,
|
|
49
|
+
* keyed by fragment name. Used by validate to compare a deployed expansion
|
|
50
|
+
* against the current fragment source without re-deploying.
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractExpandedFragmentBodies(css: string): Map<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* Deploys one component file: CSS sources carrying include directives are
|
|
55
|
+
* written as their fragment-expanded form; everything else is a plain
|
|
56
|
+
* copy. Extracted here so `applyCustomComponent` stays inside the
|
|
57
|
+
* per-file line budget.
|
|
58
|
+
*
|
|
59
|
+
* @returns True when fragment expansion was applied
|
|
60
|
+
*/
|
|
61
|
+
export declare function deployFileWithFragments(src: string, dest: string, sharedDir: string): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Builds the dry-run description suffix for a component file copy,
|
|
64
|
+
* naming the fragments an expansion would inline. Empty for plain copies.
|
|
65
|
+
*/
|
|
66
|
+
export declare function describeFragmentExpansion(src: string): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Validates fragment usage for one custom component: every directive must
|
|
69
|
+
* name an existing fragment (`missing-fragment`, error), and a deployed
|
|
70
|
+
* stylesheet's fenced expansion must match the current fragment source
|
|
71
|
+
* (`stale-fragment-expansion`, warning → redeploy refreshes it).
|
|
72
|
+
*
|
|
73
|
+
* @param componentDir - Workspace directory of the component
|
|
74
|
+
* @param tagName - Component tag name for issue attribution
|
|
75
|
+
* @param sharedDir - Shared fragments directory
|
|
76
|
+
* @param engineTargetDir - Deployed directory in the engine (optional —
|
|
77
|
+
* pre-deploy validation skips the staleness check)
|
|
78
|
+
*/
|
|
79
|
+
export declare function validateCssFragments(componentDir: string, tagName: string, sharedDir: string, engineTargetDir?: string): Promise<ValidationIssue[]>;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Shared CSS fragments for Furnace widgets (field report D2).
|
|
4
|
+
*
|
|
5
|
+
* Shadow-DOM isolation forces each widget stylesheet to carry its own copy
|
|
6
|
+
* of genuinely shared CSS (keyframes, resets). Hand-syncing those copies
|
|
7
|
+
* drifts. Instead, a workspace stylesheet declares an include directive
|
|
8
|
+
* (a CSS block comment on its own line):
|
|
9
|
+
*
|
|
10
|
+
* @fireforge-include shared-anims.css
|
|
11
|
+
*
|
|
12
|
+
* naming a fragment file in `components/shared/`. `furnace deploy` expands
|
|
13
|
+
* the fragment into the *deployed* copy only — the workspace source stays
|
|
14
|
+
* DRY — fencing the expansion between the directive line and a matching
|
|
15
|
+
* `@fireforge-end-include` marker so re-deploys can refresh it idempotently.
|
|
16
|
+
*
|
|
17
|
+
* Drift contract: the apply fast-path and `furnace validate` compare the
|
|
18
|
+
* *expanded* workspace source against the engine copy, so editing a
|
|
19
|
+
* fragment surfaces as ordinary component drift and the next deploy
|
|
20
|
+
* refreshes every consuming widget.
|
|
21
|
+
*/
|
|
22
|
+
import { readdir } from 'node:fs/promises';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
25
|
+
import { copyFile, pathExists, readText, writeText } from '../utils/fs.js';
|
|
26
|
+
export { SHARED_FRAGMENTS_DIR } from './furnace-config.js';
|
|
27
|
+
// Local copy of the directory name for message text — importing the
|
|
28
|
+
// binding for value use keeps a single source of truth.
|
|
29
|
+
import { SHARED_FRAGMENTS_DIR } from './furnace-config.js';
|
|
30
|
+
const INCLUDE_PATTERN = /^\s*\/\*\s*@fireforge-include\s+([\w./-]+)\s*\*\/\s*$/;
|
|
31
|
+
const END_INCLUDE_PATTERN = /^\s*\/\*\s*@fireforge-end-include\s+([\w./-]+)\s*\*\/\s*$/;
|
|
32
|
+
/** Returns the fragment names referenced by `@fireforge-include` directives. */
|
|
33
|
+
export function listFragmentIncludes(css) {
|
|
34
|
+
const names = [];
|
|
35
|
+
for (const line of css.split('\n')) {
|
|
36
|
+
const m = INCLUDE_PATTERN.exec(line);
|
|
37
|
+
if (m?.[1] && !names.includes(m[1]))
|
|
38
|
+
names.push(m[1]);
|
|
39
|
+
}
|
|
40
|
+
return names;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Collapses fenced fragment expansions back to their bare directives.
|
|
44
|
+
* Inverse of {@link expandCssFragments}; used to compare a deployed file
|
|
45
|
+
* against its workspace source and to re-expand idempotently.
|
|
46
|
+
*/
|
|
47
|
+
export function stripExpandedFragments(css) {
|
|
48
|
+
const lines = css.split('\n');
|
|
49
|
+
const out = [];
|
|
50
|
+
for (let i = 0; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i] ?? '';
|
|
52
|
+
out.push(line);
|
|
53
|
+
const inc = INCLUDE_PATTERN.exec(line);
|
|
54
|
+
if (!inc?.[1])
|
|
55
|
+
continue;
|
|
56
|
+
// A bare directive (workspace file) has no fence to strip. Only skip
|
|
57
|
+
// the expansion body when a matching end marker actually follows —
|
|
58
|
+
// otherwise an unterminated fence would silently eat the rest of the
|
|
59
|
+
// file.
|
|
60
|
+
let endIndex = -1;
|
|
61
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
62
|
+
if (END_INCLUDE_PATTERN.exec(lines[j] ?? '')?.[1] === inc[1]) {
|
|
63
|
+
endIndex = j;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (endIndex !== -1)
|
|
68
|
+
i = endIndex;
|
|
69
|
+
}
|
|
70
|
+
return out.join('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Expands every `@fireforge-include` directive in `css` with the current
|
|
74
|
+
* content of its fragment file from `sharedDir`, fencing each expansion
|
|
75
|
+
* with an end marker. Existing expansions are stripped first, so the
|
|
76
|
+
* operation is idempotent and refreshes stale content.
|
|
77
|
+
*
|
|
78
|
+
* @param css - Stylesheet source (workspace or previously expanded)
|
|
79
|
+
* @param sharedDir - Absolute path to the shared fragments directory
|
|
80
|
+
* @returns Expanded stylesheet and the fragment names it consumed
|
|
81
|
+
* @throws FurnaceError when a fragment is missing or itself contains an
|
|
82
|
+
* include directive (nesting is not supported)
|
|
83
|
+
*/
|
|
84
|
+
export async function expandCssFragments(css, sharedDir) {
|
|
85
|
+
const stripped = stripExpandedFragments(css);
|
|
86
|
+
const includes = listFragmentIncludes(stripped);
|
|
87
|
+
if (includes.length === 0)
|
|
88
|
+
return { expanded: stripped, includes };
|
|
89
|
+
const fragments = new Map();
|
|
90
|
+
for (const name of includes) {
|
|
91
|
+
const fragmentPath = join(sharedDir, name);
|
|
92
|
+
if (!(await pathExists(fragmentPath))) {
|
|
93
|
+
throw new FurnaceError(`CSS fragment "${name}" not found in components/${SHARED_FRAGMENTS_DIR}/. ` +
|
|
94
|
+
'Create the fragment file or remove the @fireforge-include directive.');
|
|
95
|
+
}
|
|
96
|
+
const content = await readText(fragmentPath);
|
|
97
|
+
if (listFragmentIncludes(content).length > 0) {
|
|
98
|
+
throw new FurnaceError(`CSS fragment "${name}" contains an @fireforge-include directive of its own; ` +
|
|
99
|
+
'nested fragment includes are not supported.');
|
|
100
|
+
}
|
|
101
|
+
fragments.set(name, content.replace(/\n$/, ''));
|
|
102
|
+
}
|
|
103
|
+
const out = [];
|
|
104
|
+
for (const line of stripped.split('\n')) {
|
|
105
|
+
out.push(line);
|
|
106
|
+
const inc = INCLUDE_PATTERN.exec(line);
|
|
107
|
+
if (inc?.[1]) {
|
|
108
|
+
out.push(fragments.get(inc[1]) ?? '');
|
|
109
|
+
out.push(`/* @fireforge-end-include ${inc[1]} */`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { expanded: out.join('\n'), includes };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Extracts the fenced expansion bodies of a previously expanded stylesheet,
|
|
116
|
+
* keyed by fragment name. Used by validate to compare a deployed expansion
|
|
117
|
+
* against the current fragment source without re-deploying.
|
|
118
|
+
*/
|
|
119
|
+
export function extractExpandedFragmentBodies(css) {
|
|
120
|
+
const bodies = new Map();
|
|
121
|
+
const lines = css.split('\n');
|
|
122
|
+
let current = null;
|
|
123
|
+
let buffer = [];
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
if (current !== null) {
|
|
126
|
+
if (END_INCLUDE_PATTERN.exec(line)?.[1] === current) {
|
|
127
|
+
bodies.set(current, buffer.join('\n'));
|
|
128
|
+
current = null;
|
|
129
|
+
buffer = [];
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
buffer.push(line);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const inc = INCLUDE_PATTERN.exec(line);
|
|
136
|
+
if (inc?.[1]) {
|
|
137
|
+
current = inc[1];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return bodies;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Deploys one component file: CSS sources carrying include directives are
|
|
144
|
+
* written as their fragment-expanded form; everything else is a plain
|
|
145
|
+
* copy. Extracted here so `applyCustomComponent` stays inside the
|
|
146
|
+
* per-file line budget.
|
|
147
|
+
*
|
|
148
|
+
* @returns True when fragment expansion was applied
|
|
149
|
+
*/
|
|
150
|
+
export async function deployFileWithFragments(src, dest, sharedDir) {
|
|
151
|
+
if (src.endsWith('.css')) {
|
|
152
|
+
const content = await readText(src);
|
|
153
|
+
if (listFragmentIncludes(content).length > 0) {
|
|
154
|
+
const { expanded } = await expandCssFragments(content, sharedDir);
|
|
155
|
+
await writeText(dest, expanded);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await copyFile(src, dest);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Builds the dry-run description suffix for a component file copy,
|
|
164
|
+
* naming the fragments an expansion would inline. Empty for plain copies.
|
|
165
|
+
*/
|
|
166
|
+
export async function describeFragmentExpansion(src) {
|
|
167
|
+
if (!src.endsWith('.css'))
|
|
168
|
+
return '';
|
|
169
|
+
const includes = listFragmentIncludes(await readText(src));
|
|
170
|
+
if (includes.length === 0)
|
|
171
|
+
return '';
|
|
172
|
+
return ` (expanding fragment${includes.length === 1 ? '' : 's'}: ${includes.join(', ')})`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validates fragment usage for one custom component: every directive must
|
|
176
|
+
* name an existing fragment (`missing-fragment`, error), and a deployed
|
|
177
|
+
* stylesheet's fenced expansion must match the current fragment source
|
|
178
|
+
* (`stale-fragment-expansion`, warning → redeploy refreshes it).
|
|
179
|
+
*
|
|
180
|
+
* @param componentDir - Workspace directory of the component
|
|
181
|
+
* @param tagName - Component tag name for issue attribution
|
|
182
|
+
* @param sharedDir - Shared fragments directory
|
|
183
|
+
* @param engineTargetDir - Deployed directory in the engine (optional —
|
|
184
|
+
* pre-deploy validation skips the staleness check)
|
|
185
|
+
*/
|
|
186
|
+
export async function validateCssFragments(componentDir, tagName, sharedDir, engineTargetDir) {
|
|
187
|
+
const issues = [];
|
|
188
|
+
if (!(await pathExists(componentDir)))
|
|
189
|
+
return issues;
|
|
190
|
+
// Graceful degradation like the other validators: an unreadable
|
|
191
|
+
// component directory must not cascade into a validation crash.
|
|
192
|
+
let entries;
|
|
193
|
+
try {
|
|
194
|
+
entries = await readdir(componentDir);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return issues;
|
|
198
|
+
}
|
|
199
|
+
for (const fileName of entries) {
|
|
200
|
+
if (!fileName.endsWith('.css'))
|
|
201
|
+
continue;
|
|
202
|
+
const source = await readText(join(componentDir, fileName));
|
|
203
|
+
const includes = listFragmentIncludes(source);
|
|
204
|
+
if (includes.length === 0)
|
|
205
|
+
continue;
|
|
206
|
+
let deployedBodies = null;
|
|
207
|
+
if (engineTargetDir) {
|
|
208
|
+
const destPath = join(engineTargetDir, fileName);
|
|
209
|
+
if (await pathExists(destPath)) {
|
|
210
|
+
deployedBodies = extractExpandedFragmentBodies(await readText(destPath));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (const include of includes) {
|
|
214
|
+
const fragmentPath = join(sharedDir, include);
|
|
215
|
+
if (!(await pathExists(fragmentPath))) {
|
|
216
|
+
issues.push({
|
|
217
|
+
component: tagName,
|
|
218
|
+
severity: 'error',
|
|
219
|
+
check: 'missing-fragment',
|
|
220
|
+
message: `${fileName} includes CSS fragment "${include}", but components/${SHARED_FRAGMENTS_DIR}/${include} does not exist. ` +
|
|
221
|
+
'Create the fragment or remove the @fireforge-include directive.',
|
|
222
|
+
});
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (deployedBodies === null)
|
|
226
|
+
continue;
|
|
227
|
+
const fragmentContent = (await readText(fragmentPath)).replace(/\n$/, '');
|
|
228
|
+
const deployed = deployedBodies.get(include);
|
|
229
|
+
if (deployed === undefined || deployed !== fragmentContent) {
|
|
230
|
+
issues.push({
|
|
231
|
+
component: tagName,
|
|
232
|
+
severity: 'warning',
|
|
233
|
+
check: 'stale-fragment-expansion',
|
|
234
|
+
message: deployed === undefined
|
|
235
|
+
? `Deployed ${fileName} has no expansion for fragment "${include}". Run "fireforge furnace deploy ${tagName}".`
|
|
236
|
+
: `Deployed ${fileName} carries a stale expansion of fragment "${include}" — the fragment source changed since the last deploy. Run "fireforge furnace deploy ${tagName}".`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return issues;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=furnace-css-fragments.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maintains `compilerOptions.paths` entries in a consumer-owned jsconfig
|
|
3
|
+
* so typed cross-module imports of multi-file Furnace components work
|
|
4
|
+
* (field report D3).
|
|
5
|
+
*
|
|
6
|
+
* When a main widget imports a sibling helper via its deployed chrome URL
|
|
7
|
+
* (`chrome://global/content/elements/<helper>.mjs`), a wildcard module
|
|
8
|
+
* shim swallows the import: value imports degrade to `any` and
|
|
9
|
+
* `import(...).SomeType` typedefs fail with TS2694. The fix is a `paths`
|
|
10
|
+
* mapping from the chrome URL to the real workspace source. Furnace
|
|
11
|
+
* already owns the jar.mn side of that mapping, so it can maintain the
|
|
12
|
+
* jsconfig side automatically on every deploy.
|
|
13
|
+
*
|
|
14
|
+
* Ownership contract: only entries whose key starts with
|
|
15
|
+
* `chrome://global/content/elements/` AND whose mapped path resolves into
|
|
16
|
+
* the Furnace custom-components workspace are managed (added, updated,
|
|
17
|
+
* pruned). Everything else in the jsconfig — including hand-written
|
|
18
|
+
* `paths` entries pointing elsewhere — is preserved verbatim. No
|
|
19
|
+
* `baseUrl` is required or written: relative `paths` resolve against the
|
|
20
|
+
* config file's directory.
|
|
21
|
+
*/
|
|
22
|
+
import type { FurnaceConfig } from '../types/furnace.js';
|
|
23
|
+
/** Result summary of a jsconfig paths sync. */
|
|
24
|
+
export interface JsconfigSyncResult {
|
|
25
|
+
/** Keys newly added to compilerOptions.paths. */
|
|
26
|
+
added: string[];
|
|
27
|
+
/** Managed keys whose mapped path changed. */
|
|
28
|
+
updated: string[];
|
|
29
|
+
/** Managed keys removed because their component/file is gone. */
|
|
30
|
+
pruned: string[];
|
|
31
|
+
/** True when the file was (or would be) rewritten. */
|
|
32
|
+
changed: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reconciles the managed `compilerOptions.paths` entries of the configured
|
|
36
|
+
* jsconfig against the current Furnace workspace. Idempotent; writes only
|
|
37
|
+
* when something actually changes; dry-run returns the diff without
|
|
38
|
+
* writing.
|
|
39
|
+
*
|
|
40
|
+
* The consumer owns the jsconfig file: a missing file is an error with
|
|
41
|
+
* guidance rather than a silent scaffold, and JSONC (comments/trailing
|
|
42
|
+
* commas) is unsupported for the managed file — `readJson` is a strict
|
|
43
|
+
* JSON parser, so the error message says so explicitly.
|
|
44
|
+
*
|
|
45
|
+
* @param root - Project root directory
|
|
46
|
+
* @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
|
|
47
|
+
* @param options - `dryRun` skips the write but still reports the diff
|
|
48
|
+
*/
|
|
49
|
+
export declare function syncFurnaceJsconfigPaths(root: string, config: FurnaceConfig, options?: {
|
|
50
|
+
dryRun?: boolean;
|
|
51
|
+
}): Promise<JsconfigSyncResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Computes jsconfig `paths` drift for `furnace validate`: managed entries
|
|
54
|
+
* that are missing or stale relative to the current workspace. Read-only —
|
|
55
|
+
* delegates to {@link syncFurnaceJsconfigPaths} in dry-run mode.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findJsconfigPathsDrift(root: string, config: FurnaceConfig): Promise<JsconfigSyncResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Runs the jsconfig paths sync after a successful deploy/sync and reports
|
|
60
|
+
* the diff. No-op when `typecheckJsconfig` is unset. Shared by
|
|
61
|
+
* `furnace deploy` and `furnace sync` so both report identically.
|
|
62
|
+
*/
|
|
63
|
+
export declare function reportJsconfigPathsSync(root: string, config: FurnaceConfig, dryRun: boolean): Promise<void>;
|