@hominis/fireforge 0.15.2 → 0.15.4
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 +30 -1
- package/README.md +55 -1
- package/dist/src/commands/build.js +29 -2
- package/dist/src/commands/doctor-furnace.js +8 -1
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +49 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +151 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +34 -0
- package/dist/src/commands/furnace/chrome-doc.js +168 -0
- package/dist/src/commands/furnace/create-mochikit.d.ts +30 -0
- package/dist/src/commands/furnace/create-mochikit.js +70 -0
- package/dist/src/commands/furnace/create-templates.d.ts +32 -0
- package/dist/src/commands/furnace/create-templates.js +69 -0
- package/dist/src/commands/furnace/create.d.ts +17 -0
- package/dist/src/commands/furnace/create.js +54 -16
- package/dist/src/commands/furnace/index.d.ts +2 -1
- package/dist/src/commands/furnace/index.js +20 -3
- package/dist/src/commands/lint.d.ts +13 -1
- package/dist/src/commands/lint.js +33 -7
- package/dist/src/commands/wire.js +59 -6
- package/dist/src/core/browser-wire.d.ts +8 -0
- package/dist/src/core/browser-wire.js +2 -2
- package/dist/src/core/build-audit.d.ts +46 -0
- package/dist/src/core/build-audit.js +251 -0
- package/dist/src/core/build-baseline.d.ts +59 -0
- package/dist/src/core/build-baseline.js +83 -0
- package/dist/src/core/build-prepare.d.ts +20 -1
- package/dist/src/core/build-prepare.js +89 -4
- package/dist/src/core/furnace-operation.d.ts +2 -1
- package/dist/src/core/furnace-operation.js +13 -7
- package/dist/src/core/mach-error-hints.d.ts +29 -0
- package/dist/src/core/mach-error-hints.js +43 -0
- package/dist/src/core/mach.d.ts +5 -2
- package/dist/src/core/mach.js +31 -4
- package/dist/src/core/patch-lint-diff-tag.d.ts +33 -0
- package/dist/src/core/patch-lint-diff-tag.js +83 -0
- package/dist/src/core/wire-dom-fragment.d.ts +16 -4
- package/dist/src/core/wire-dom-fragment.js +32 -17
- package/dist/src/types/commands/options.d.ts +22 -0
- package/dist/src/types/commands/patches.d.ts +9 -0
- package/dist/src/types/furnace.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MochiKit (chrome://mochikit) test-harness scaffolder for
|
|
3
|
+
* `fireforge furnace create --test-style=mochikit`.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: browser-chrome mochitests require a `tabbrowser` to exist in
|
|
6
|
+
* the top-level chrome document. Forks with a bespoke chrome document
|
|
7
|
+
* (e.g. `mybrowser.xhtml`) that deliberately omits tabbrowser cannot run
|
|
8
|
+
* browser-chrome tests today. MochiKit tests load the component module
|
|
9
|
+
* directly via `chrome://global/` and assert against `customElements`, so
|
|
10
|
+
* they work against any fork that registers the upstream toolkit test
|
|
11
|
+
* manifest tree — including those without a tabbrowser.
|
|
12
|
+
*/
|
|
13
|
+
import { type RollbackJournal } from '../../core/furnace-rollback.js';
|
|
14
|
+
import type { ProjectLicense } from '../../types/config.js';
|
|
15
|
+
/**
|
|
16
|
+
* Scaffolds a MochiKit test for a newly created custom component under
|
|
17
|
+
* `engine/toolkit/content/tests/widgets/`. Mirrors the layout stock
|
|
18
|
+
* Firefox widgets (moz-button, moz-toggle, etc.) use, so an operator who
|
|
19
|
+
* already added the `widgets/` tree to their test-manifest registration
|
|
20
|
+
* picks the new test up automatically.
|
|
21
|
+
*
|
|
22
|
+
* Appends a per-test entry to the existing `chrome.toml` when present,
|
|
23
|
+
* writes a fresh `[DEFAULT]`-headed one otherwise. The caller is still
|
|
24
|
+
* responsible for ensuring the `toolkit/content/tests/widgets/chrome.toml`
|
|
25
|
+
* path is registered somewhere in the moz.build tree; most forks inherit
|
|
26
|
+
* this from upstream via `TEST_HARNESS_FILES += [...]`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scaffoldMochikitTestFiles(componentName: string, license: ProjectLicense, paths: {
|
|
29
|
+
engine: string;
|
|
30
|
+
}, journal?: RollbackJournal): Promise<string[]>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* MochiKit (chrome://mochikit) test-harness scaffolder for
|
|
4
|
+
* `fireforge furnace create --test-style=mochikit`.
|
|
5
|
+
*
|
|
6
|
+
* Motivation: browser-chrome mochitests require a `tabbrowser` to exist in
|
|
7
|
+
* the top-level chrome document. Forks with a bespoke chrome document
|
|
8
|
+
* (e.g. `mybrowser.xhtml`) that deliberately omits tabbrowser cannot run
|
|
9
|
+
* browser-chrome tests today. MochiKit tests load the component module
|
|
10
|
+
* directly via `chrome://global/` and assert against `customElements`, so
|
|
11
|
+
* they work against any fork that registers the upstream toolkit test
|
|
12
|
+
* manifest tree — including those without a tabbrowser.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
16
|
+
import { getLicenseHeader } from '../../core/license-headers.js';
|
|
17
|
+
import { ensureDir, pathExists, readText, writeText } from '../../utils/fs.js';
|
|
18
|
+
import { warn } from '../../utils/logger.js';
|
|
19
|
+
import { generateMochikitChromeTomlEntry, generateMochikitChromeTomlSkeleton, generateMochikitTestContent, mochikitTestFileName, } from './create-templates.js';
|
|
20
|
+
/**
|
|
21
|
+
* Scaffolds a MochiKit test for a newly created custom component under
|
|
22
|
+
* `engine/toolkit/content/tests/widgets/`. Mirrors the layout stock
|
|
23
|
+
* Firefox widgets (moz-button, moz-toggle, etc.) use, so an operator who
|
|
24
|
+
* already added the `widgets/` tree to their test-manifest registration
|
|
25
|
+
* picks the new test up automatically.
|
|
26
|
+
*
|
|
27
|
+
* Appends a per-test entry to the existing `chrome.toml` when present,
|
|
28
|
+
* writes a fresh `[DEFAULT]`-headed one otherwise. The caller is still
|
|
29
|
+
* responsible for ensuring the `toolkit/content/tests/widgets/chrome.toml`
|
|
30
|
+
* path is registered somewhere in the moz.build tree; most forks inherit
|
|
31
|
+
* this from upstream via `TEST_HARNESS_FILES += [...]`.
|
|
32
|
+
*/
|
|
33
|
+
export async function scaffoldMochikitTestFiles(componentName, license, paths, journal) {
|
|
34
|
+
const testDir = join(paths.engine, 'toolkit/content/tests/widgets');
|
|
35
|
+
if (journal && !(await pathExists(testDir))) {
|
|
36
|
+
recordCreatedDir(journal, testDir);
|
|
37
|
+
}
|
|
38
|
+
await ensureDir(testDir);
|
|
39
|
+
const hashHeader = getLicenseHeader(license, 'hash');
|
|
40
|
+
const writtenFiles = [];
|
|
41
|
+
const testFileName = mochikitTestFileName(componentName);
|
|
42
|
+
const testFilePath = join(testDir, testFileName);
|
|
43
|
+
if (journal)
|
|
44
|
+
await snapshotFile(journal, testFilePath);
|
|
45
|
+
await writeText(testFilePath, generateMochikitTestContent(componentName));
|
|
46
|
+
writtenFiles.push(testFileName);
|
|
47
|
+
// chrome.toml — append entry if the file already exists, otherwise write
|
|
48
|
+
// a fresh skeleton + entry. Idempotency: if the entry is already present
|
|
49
|
+
// the manifest is left untouched so re-runs don't double-register.
|
|
50
|
+
const manifestPath = join(testDir, 'chrome.toml');
|
|
51
|
+
const entry = generateMochikitChromeTomlEntry(componentName);
|
|
52
|
+
if (await pathExists(manifestPath)) {
|
|
53
|
+
const existing = await readText(manifestPath);
|
|
54
|
+
if (!existing.includes(`["${testFileName}"]`)) {
|
|
55
|
+
if (journal)
|
|
56
|
+
await snapshotFile(journal, manifestPath);
|
|
57
|
+
await writeText(manifestPath, existing.trimEnd() + '\n\n' + entry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (journal)
|
|
62
|
+
await snapshotFile(journal, manifestPath);
|
|
63
|
+
await writeText(manifestPath, generateMochikitChromeTomlSkeleton(hashHeader) + entry);
|
|
64
|
+
writtenFiles.push('chrome.toml');
|
|
65
|
+
}
|
|
66
|
+
warn(`MochiKit scaffold written under toolkit/content/tests/widgets/. ` +
|
|
67
|
+
'Ensure `toolkit/content/tests/widgets/chrome.toml` is reachable from an existing test-harness registration (upstream TEST_HARNESS_FILES entries handle this by default).');
|
|
68
|
+
return writtenFiles;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=create-mochikit.js.map
|
|
@@ -45,3 +45,35 @@ export declare function generateXpcshellTestContent(name: string, header: string
|
|
|
45
45
|
* component actually touches (Services.storage, observer topics, etc.).
|
|
46
46
|
*/
|
|
47
47
|
export declare function generateXpcshellManifestContent(name: string, header: string): string;
|
|
48
|
+
/** Returns the canonical mochikit test file basename for a component. */
|
|
49
|
+
export declare function mochikitTestFileName(name: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Generates a MochiKit (chrome://mochikit) test for a custom component.
|
|
52
|
+
*
|
|
53
|
+
* MochiKit tests load the component module directly via the global chrome
|
|
54
|
+
* URI and assert that `customElements.get(<tag>)` returns a constructor.
|
|
55
|
+
* They run on forks whose top-level chrome document lacks a `tabbrowser`
|
|
56
|
+
* (the class of bug that forces `--xpcshell` for storage code) because
|
|
57
|
+
* they do not traverse `URILoadingHelper.openLinkIn`.
|
|
58
|
+
*
|
|
59
|
+
* The scaffold here is a smoke test — the component is defined and the
|
|
60
|
+
* constructor is a function. Real UI assertions (render output, l10n
|
|
61
|
+
* wiring, keyboard interactions) are intentionally left out because they
|
|
62
|
+
* depend on the component's shape; operators can extend the test using
|
|
63
|
+
* the same SimpleTest APIs upstream toolkit widgets (moz-button, etc.)
|
|
64
|
+
* rely on.
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateMochikitTestContent(name: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Generates the `chrome.toml` entry block to append for a newly scaffolded
|
|
69
|
+
* mochikit test. When the manifest already exists the caller appends this
|
|
70
|
+
* snippet; when absent, the caller writes a file that starts with a
|
|
71
|
+
* `[DEFAULT]` stanza followed by this block.
|
|
72
|
+
*/
|
|
73
|
+
export declare function generateMochikitChromeTomlEntry(name: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Generates the minimal `chrome.toml` used when the file does not yet
|
|
76
|
+
* exist in the tree. Keeps the `[DEFAULT]` stanza empty so each scaffold
|
|
77
|
+
* adds its own per-test entry, matching the stock Firefox convention.
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateMochikitChromeTomlSkeleton(header: string): string;
|
|
@@ -130,6 +130,75 @@ export function generateXpcshellManifestContent(name, header) {
|
|
|
130
130
|
head = ""
|
|
131
131
|
|
|
132
132
|
["${xpcshellTestFileName(name)}"]
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
/** Returns the canonical mochikit test file basename for a component. */
|
|
136
|
+
export function mochikitTestFileName(name) {
|
|
137
|
+
return `test_${name}.html`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generates a MochiKit (chrome://mochikit) test for a custom component.
|
|
141
|
+
*
|
|
142
|
+
* MochiKit tests load the component module directly via the global chrome
|
|
143
|
+
* URI and assert that `customElements.get(<tag>)` returns a constructor.
|
|
144
|
+
* They run on forks whose top-level chrome document lacks a `tabbrowser`
|
|
145
|
+
* (the class of bug that forces `--xpcshell` for storage code) because
|
|
146
|
+
* they do not traverse `URILoadingHelper.openLinkIn`.
|
|
147
|
+
*
|
|
148
|
+
* The scaffold here is a smoke test — the component is defined and the
|
|
149
|
+
* constructor is a function. Real UI assertions (render output, l10n
|
|
150
|
+
* wiring, keyboard interactions) are intentionally left out because they
|
|
151
|
+
* depend on the component's shape; operators can extend the test using
|
|
152
|
+
* the same SimpleTest APIs upstream toolkit widgets (moz-button, etc.)
|
|
153
|
+
* rely on.
|
|
154
|
+
*/
|
|
155
|
+
export function generateMochikitTestContent(name) {
|
|
156
|
+
return `<!DOCTYPE html>
|
|
157
|
+
<html>
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="utf-8" />
|
|
160
|
+
<title>Test the ${name} custom element</title>
|
|
161
|
+
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
|
162
|
+
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
|
163
|
+
</head>
|
|
164
|
+
<body>
|
|
165
|
+
<p id="display"></p>
|
|
166
|
+
<div id="content" style="display: none"></div>
|
|
167
|
+
<pre id="test"></pre>
|
|
168
|
+
<script type="module">
|
|
169
|
+
import "chrome://global/content/elements/${name}.mjs";
|
|
170
|
+
|
|
171
|
+
SimpleTest.waitForExplicitFinish();
|
|
172
|
+
|
|
173
|
+
add_task(async function test_${name.replace(/-/g, '_')}_defined() {
|
|
174
|
+
const ctor = await customElements.whenDefined("${name}");
|
|
175
|
+
ok(ctor, "${name} custom element should be defined");
|
|
176
|
+
is(typeof ctor, "function", "Constructor should be a function");
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Generates the `chrome.toml` entry block to append for a newly scaffolded
|
|
185
|
+
* mochikit test. When the manifest already exists the caller appends this
|
|
186
|
+
* snippet; when absent, the caller writes a file that starts with a
|
|
187
|
+
* `[DEFAULT]` stanza followed by this block.
|
|
188
|
+
*/
|
|
189
|
+
export function generateMochikitChromeTomlEntry(name) {
|
|
190
|
+
return `["${mochikitTestFileName(name)}"]\n`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generates the minimal `chrome.toml` used when the file does not yet
|
|
194
|
+
* exist in the tree. Keeps the `[DEFAULT]` stanza empty so each scaffold
|
|
195
|
+
* adds its own per-test entry, matching the stock Firefox convention.
|
|
196
|
+
*/
|
|
197
|
+
export function generateMochikitChromeTomlSkeleton(header) {
|
|
198
|
+
return `${header}
|
|
199
|
+
|
|
200
|
+
[DEFAULT]
|
|
201
|
+
|
|
133
202
|
`;
|
|
134
203
|
}
|
|
135
204
|
//# sourceMappingURL=create-templates.js.map
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import type { FurnaceCreateOptions } from '../../types/commands/index.js';
|
|
2
|
+
/** Resolved test-harness selection for a `furnace create` run. */
|
|
3
|
+
export type ResolvedTestStyle = 'mochikit' | 'browser-chrome' | 'xpcshell' | 'none';
|
|
4
|
+
/**
|
|
5
|
+
* Collapses `--with-tests`, `--xpcshell`, and `--test-style` into the single
|
|
6
|
+
* scaffold dispatch used inside the mutation phase.
|
|
7
|
+
*
|
|
8
|
+
* Backwards-compat invariants:
|
|
9
|
+
* - `--xpcshell` alone is equivalent to `--test-style=xpcshell`.
|
|
10
|
+
* - `--with-tests` alone (no `--test-style`) now defaults to `mochikit`
|
|
11
|
+
* (previously it defaulted to browser-chrome; the dogfooding pass
|
|
12
|
+
* flagged browser-chrome as unrunnable against non-tabbrowser chrome).
|
|
13
|
+
* Operators who need the old behavior can pass
|
|
14
|
+
* `--with-tests --test-style=browser-chrome`.
|
|
15
|
+
* - `--xpcshell --with-tests` is rejected as ambiguous.
|
|
16
|
+
* @throws InvalidArgumentError when flags conflict.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveTestStyle(options: FurnaceCreateOptions): ResolvedTestStyle;
|
|
2
19
|
/**
|
|
3
20
|
* Runs the furnace create command to scaffold a new custom component.
|
|
4
21
|
* @param projectRoot - Root directory of the project
|
|
@@ -15,6 +15,7 @@ import { FurnaceError } from '../../errors/furnace.js';
|
|
|
15
15
|
import { toError } from '../../utils/errors.js';
|
|
16
16
|
import { ensureDir, pathExists, readText, writeText } from '../../utils/fs.js';
|
|
17
17
|
import { cancel, intro, isCancel, note, outro, success, warn } from '../../utils/logger.js';
|
|
18
|
+
import { scaffoldMochikitTestFiles } from './create-mochikit.js';
|
|
18
19
|
import { generateCssContent, generateFtlContent, generateMjsContent } from './create-templates.js';
|
|
19
20
|
import { scaffoldXpcshellTestFiles } from './create-xpcshell.js';
|
|
20
21
|
async function loadAuthoringFurnaceConfig(projectRoot) {
|
|
@@ -224,6 +225,35 @@ async function writeComponentFiles(componentDir, componentName, className, descr
|
|
|
224
225
|
}
|
|
225
226
|
return files;
|
|
226
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Collapses `--with-tests`, `--xpcshell`, and `--test-style` into the single
|
|
230
|
+
* scaffold dispatch used inside the mutation phase.
|
|
231
|
+
*
|
|
232
|
+
* Backwards-compat invariants:
|
|
233
|
+
* - `--xpcshell` alone is equivalent to `--test-style=xpcshell`.
|
|
234
|
+
* - `--with-tests` alone (no `--test-style`) now defaults to `mochikit`
|
|
235
|
+
* (previously it defaulted to browser-chrome; the dogfooding pass
|
|
236
|
+
* flagged browser-chrome as unrunnable against non-tabbrowser chrome).
|
|
237
|
+
* Operators who need the old behavior can pass
|
|
238
|
+
* `--with-tests --test-style=browser-chrome`.
|
|
239
|
+
* - `--xpcshell --with-tests` is rejected as ambiguous.
|
|
240
|
+
* @throws InvalidArgumentError when flags conflict.
|
|
241
|
+
*/
|
|
242
|
+
export function resolveTestStyle(options) {
|
|
243
|
+
const xpcshellFlag = options.xpcshell ?? false;
|
|
244
|
+
const withTests = options.withTests ?? false;
|
|
245
|
+
const explicit = options.testStyle;
|
|
246
|
+
if (xpcshellFlag && explicit && explicit !== 'xpcshell') {
|
|
247
|
+
throw new InvalidArgumentError(`--xpcshell cannot be combined with --test-style=${explicit}; choose one.`, 'testStyle');
|
|
248
|
+
}
|
|
249
|
+
if (explicit)
|
|
250
|
+
return explicit;
|
|
251
|
+
if (xpcshellFlag)
|
|
252
|
+
return 'xpcshell';
|
|
253
|
+
if (withTests)
|
|
254
|
+
return 'mochikit';
|
|
255
|
+
return 'none';
|
|
256
|
+
}
|
|
227
257
|
/**
|
|
228
258
|
* Performs the transactional mutation phase of furnace create. All file
|
|
229
259
|
* writes and the config update are recorded in a rollback journal so a
|
|
@@ -259,14 +289,18 @@ async function performCreateMutations(args) {
|
|
|
259
289
|
args.config.custom[args.componentName] = customEntry;
|
|
260
290
|
await snapshotFile(journal, args.furnacePaths.furnaceConfig);
|
|
261
291
|
await writeFurnaceConfig(args.projectRoot, args.config);
|
|
262
|
-
if (args.
|
|
292
|
+
if (args.testStyle === 'browser-chrome') {
|
|
263
293
|
const scafFiles = await scaffoldTestFiles(args.componentName, args.license, args.forgeConfig, args.paths, journal);
|
|
264
294
|
testFiles.push(...scafFiles);
|
|
265
295
|
}
|
|
266
|
-
if (args.
|
|
296
|
+
else if (args.testStyle === 'xpcshell') {
|
|
267
297
|
const xpcshellFiles = await scaffoldXpcshellTestFiles(args.componentName, args.license, args.forgeConfig, args.paths, journal);
|
|
268
298
|
testFiles.push(...xpcshellFiles);
|
|
269
299
|
}
|
|
300
|
+
else if (args.testStyle === 'mochikit') {
|
|
301
|
+
const mochikitFiles = await scaffoldMochikitTestFiles(args.componentName, args.license, args.paths, journal);
|
|
302
|
+
testFiles.push(...mochikitFiles);
|
|
303
|
+
}
|
|
270
304
|
}
|
|
271
305
|
catch (error) {
|
|
272
306
|
try {
|
|
@@ -400,15 +434,13 @@ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
|
|
|
400
434
|
return;
|
|
401
435
|
}
|
|
402
436
|
const { localized, register } = featureSelection;
|
|
403
|
-
// --with-tests
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if ((withTests || xpcshellTests) && !(await pathExists(paths.engine))) {
|
|
411
|
-
throw new FurnaceError('Engine directory not found. Run "fireforge download" first to use --with-tests or --xpcshell.', componentName);
|
|
437
|
+
// Collapse --with-tests / --xpcshell / --test-style into the single
|
|
438
|
+
// scaffold selection used by the mutation phase. The resolver validates
|
|
439
|
+
// incompatible combinations up-front so a bad flag set never strands a
|
|
440
|
+
// partial mutation behind.
|
|
441
|
+
const testStyle = resolveTestStyle(options);
|
|
442
|
+
if (testStyle !== 'none' && !(await pathExists(paths.engine))) {
|
|
443
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first to use --with-tests, --xpcshell, or --test-style.', componentName);
|
|
412
444
|
}
|
|
413
445
|
// --- Generate component files ---
|
|
414
446
|
const className = tagNameToClassName(componentName);
|
|
@@ -444,8 +476,7 @@ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
|
|
|
444
476
|
forgeConfig,
|
|
445
477
|
paths,
|
|
446
478
|
license,
|
|
447
|
-
|
|
448
|
-
xpcshellTests,
|
|
479
|
+
testStyle,
|
|
449
480
|
ftlChromeSubPath,
|
|
450
481
|
operationContext: ctx,
|
|
451
482
|
}));
|
|
@@ -453,9 +484,16 @@ export async function furnaceCreateCommand(projectRoot, name, options = {}) {
|
|
|
453
484
|
let noteParts = `Files created in components/custom/${componentName}/:\n` +
|
|
454
485
|
files.map((f) => ` ${f}`).join('\n');
|
|
455
486
|
if (testFiles.length > 0) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
487
|
+
let testRoot;
|
|
488
|
+
if (testStyle === 'xpcshell') {
|
|
489
|
+
testRoot = `engine/browser/base/content/test/${forgeConfig.binaryName}-xpcshell/${componentName}/`;
|
|
490
|
+
}
|
|
491
|
+
else if (testStyle === 'mochikit') {
|
|
492
|
+
testRoot = 'engine/toolkit/content/tests/widgets/';
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
testRoot = `engine/browser/base/content/test/${forgeConfig.binaryName}/`;
|
|
496
|
+
}
|
|
459
497
|
noteParts += `\n\nTest files in ${testRoot}:\n` + testFiles.map((f) => ` ${f}`).join('\n');
|
|
460
498
|
}
|
|
461
499
|
noteParts +=
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { CommandContext } from '../../types/cli.js';
|
|
3
3
|
import { furnaceApplyCommand } from './apply.js';
|
|
4
|
+
import { furnaceChromeDocCreateCommand } from './chrome-doc.js';
|
|
4
5
|
import { furnaceCreateCommand } from './create.js';
|
|
5
6
|
import { furnaceDeployCommand } from './deploy.js';
|
|
6
7
|
import { furnaceDiffCommand } from './diff.js';
|
|
@@ -15,6 +16,6 @@ import { furnaceScanCommand } from './scan.js';
|
|
|
15
16
|
import { furnaceStatusCommand } from './status.js';
|
|
16
17
|
import { furnaceSyncCommand } from './sync.js';
|
|
17
18
|
import { furnaceValidateCommand } from './validate.js';
|
|
18
|
-
export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
|
|
19
|
+
export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceChromeDocCreateCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
|
|
19
20
|
/** Registers the furnace command on the CLI program. */
|
|
20
21
|
export declare function registerFurnace(program: Command, context: CommandContext): void;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Option } from 'commander';
|
|
3
3
|
import { pickDefined } from '../../utils/options.js';
|
|
4
4
|
import { furnaceApplyCommand } from './apply.js';
|
|
5
|
+
import { furnaceChromeDocCreateCommand } from './chrome-doc.js';
|
|
5
6
|
import { furnaceCreateCommand } from './create.js';
|
|
6
7
|
import { furnaceDeployCommand } from './deploy.js';
|
|
7
8
|
import { furnaceDiffCommand } from './diff.js';
|
|
@@ -16,7 +17,7 @@ import { furnaceScanCommand } from './scan.js';
|
|
|
16
17
|
import { furnaceStatusCommand } from './status.js';
|
|
17
18
|
import { furnaceSyncCommand } from './sync.js';
|
|
18
19
|
import { furnaceValidateCommand } from './validate.js';
|
|
19
|
-
export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
|
|
20
|
+
export { furnaceApplyCommand, furnaceBatchOverrideCommand, furnaceChromeDocCreateCommand, furnaceCreateCommand, furnaceDeployCommand, furnaceDiffCommand, furnaceInitCommand, furnaceListCommand, furnaceOverrideCommand, furnacePreviewCommand, furnaceRefreshCommand, furnaceRemoveCommand, furnaceRenameCommand, furnaceScanCommand, furnaceStatusCommand, furnaceSyncCommand, furnaceValidateCommand, };
|
|
20
21
|
/**
|
|
21
22
|
* Registers Furnace commands for querying component state: status, scan,
|
|
22
23
|
* and action commands like apply, deploy, and create.
|
|
@@ -71,12 +72,28 @@ function registerFurnaceInfoCommands(furnace, context) {
|
|
|
71
72
|
.option('-d, --description <desc>', 'Component description')
|
|
72
73
|
.option('--localized', 'Include Fluent l10n support')
|
|
73
74
|
.option('--no-register', 'Skip customElements.js registration')
|
|
74
|
-
.option('--with-tests', 'Scaffold
|
|
75
|
-
.option('--xpcshell', 'Scaffold an xpcshell test harness (for storage-layer code on forks without tabbrowser)')
|
|
75
|
+
.option('--with-tests', 'Scaffold a test harness (defaults to MochiKit; see --test-style)')
|
|
76
|
+
.option('--xpcshell', 'Scaffold an xpcshell test harness (for storage-layer code on forks without tabbrowser); equivalent to --test-style=xpcshell')
|
|
77
|
+
.option('--test-style <style>', "Override the harness written by --with-tests: mochikit (default, runs against non-tabbrowser chrome), browser-chrome (today's scaffold, needs tabbrowser), or xpcshell (headless)", (value) => {
|
|
78
|
+
if (value !== 'mochikit' && value !== 'browser-chrome' && value !== 'xpcshell') {
|
|
79
|
+
throw new Error(`--test-style must be one of: mochikit, browser-chrome, xpcshell. Got: "${value}".`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
})
|
|
76
83
|
.option('--compose <tags>', 'Record stock tags composed internally (metadata only, comma-separated)', (val) => val.split(',').map((s) => s.trim()))
|
|
77
84
|
.action(withErrorHandling(async (name, options) => {
|
|
78
85
|
await furnaceCreateCommand(getProjectRoot(), name, options);
|
|
79
86
|
}));
|
|
87
|
+
const chromeDoc = furnace
|
|
88
|
+
.command('chrome-doc')
|
|
89
|
+
.description('Scaffold top-level chrome documents (xhtml + js + css + ftl + jar.mn)');
|
|
90
|
+
chromeDoc
|
|
91
|
+
.command('create <name>')
|
|
92
|
+
.description('Scaffold a new top-level chrome document')
|
|
93
|
+
.option('--no-titlebar', 'Frameless overlay-style document (omits titlebar-buttonbox)')
|
|
94
|
+
.action(withErrorHandling(async (name, options) => {
|
|
95
|
+
await furnaceChromeDocCreateCommand(getProjectRoot(), name, pickDefined(options));
|
|
96
|
+
}));
|
|
80
97
|
}
|
|
81
98
|
/**
|
|
82
99
|
* Registers Furnace commands for authoring, inspection, and maintenance:
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { CommandContext } from '../types/cli.js';
|
|
3
|
+
/** Options controlling how the lint command filters and tags its output. */
|
|
4
|
+
export interface LintCommandOptions {
|
|
5
|
+
/**
|
|
6
|
+
* When set, tag each issue as `introduced` or `cumulative` based on
|
|
7
|
+
* whether its file changed since this git revision (e.g. `HEAD`, a
|
|
8
|
+
* branch name, or a SHA). Issues are not filtered — the full set still
|
|
9
|
+
* prints and the exit code is unchanged — but a diff-scoped summary
|
|
10
|
+
* makes it trivial to see which errors the current task introduced.
|
|
11
|
+
*/
|
|
12
|
+
since?: string;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
15
|
* Runs the lint command to check engine changes against patch quality rules.
|
|
5
16
|
* @param projectRoot - Root directory of the project
|
|
6
17
|
* @param files - Optional file/directory paths to lint (relative to engine/)
|
|
18
|
+
* @param options - Additional lint options such as `--since` diff-scoping
|
|
7
19
|
*/
|
|
8
|
-
export declare function lintCommand(projectRoot: string, files: string[]): Promise<void>;
|
|
20
|
+
export declare function lintCommand(projectRoot: string, files: string[], options?: LintCommandOptions): Promise<void>;
|
|
9
21
|
/** Registers the lint command on the CLI program. */
|
|
10
22
|
export declare function registerLint(program: Command, { getProjectRoot, withErrorHandling }: CommandContext): void;
|
|
@@ -7,6 +7,7 @@ import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
|
7
7
|
import { getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, } from '../core/git-status.js';
|
|
8
8
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
9
9
|
import { buildPatchQueueContext, lintExportedPatch, lintPatchQueue } from '../core/patch-lint.js';
|
|
10
|
+
import { collectDiffFilePaths, tagLintIssues } from '../core/patch-lint-diff-tag.js';
|
|
10
11
|
import { GeneralError } from '../errors/base.js';
|
|
11
12
|
import { pathExists } from '../utils/fs.js';
|
|
12
13
|
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
@@ -14,8 +15,9 @@ import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
|
14
15
|
* Runs the lint command to check engine changes against patch quality rules.
|
|
15
16
|
* @param projectRoot - Root directory of the project
|
|
16
17
|
* @param files - Optional file/directory paths to lint (relative to engine/)
|
|
18
|
+
* @param options - Additional lint options such as `--since` diff-scoping
|
|
17
19
|
*/
|
|
18
|
-
export async function lintCommand(projectRoot, files) {
|
|
20
|
+
export async function lintCommand(projectRoot, files, options = {}) {
|
|
19
21
|
intro('FireForge Lint');
|
|
20
22
|
const paths = getProjectPaths(projectRoot);
|
|
21
23
|
if (!(await pathExists(paths.engine))) {
|
|
@@ -105,19 +107,38 @@ export async function lintCommand(projectRoot, files) {
|
|
|
105
107
|
outro('Lint passed');
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
110
|
+
// Diff-scoping: tag each issue as introduced-in-current-task vs
|
|
111
|
+
// cumulative-pre-existing-drift. Never filters — full set still prints
|
|
112
|
+
// and exit code semantics are unchanged — but the per-line prefix and
|
|
113
|
+
// summary make triage trivial on a large patch series.
|
|
114
|
+
const sinceActive = Boolean(options.since);
|
|
115
|
+
if (options.since) {
|
|
116
|
+
const diffFiles = await collectDiffFilePaths(paths.engine, options.since);
|
|
117
|
+
tagLintIssues(issues, diffFiles);
|
|
118
|
+
}
|
|
108
119
|
const errors = issues.filter((i) => i.severity === 'error');
|
|
109
120
|
const warnings = issues.filter((i) => i.severity === 'warning');
|
|
110
121
|
const notices = issues.filter((i) => i.severity === 'notice');
|
|
122
|
+
const tagPrefix = (issue) => sinceActive && issue.tag ? `[${issue.tag}] ` : '';
|
|
111
123
|
for (const issue of notices) {
|
|
112
|
-
info(
|
|
124
|
+
info(`${tagPrefix(issue)}NOTICE [${issue.check}] ${issue.file}: ${issue.message}`);
|
|
113
125
|
}
|
|
114
126
|
for (const issue of warnings) {
|
|
115
|
-
warn(
|
|
127
|
+
warn(`${tagPrefix(issue)}[${issue.check}] ${issue.file}: ${issue.message}`);
|
|
116
128
|
}
|
|
117
129
|
for (const issue of errors) {
|
|
118
|
-
warn(
|
|
130
|
+
warn(`${tagPrefix(issue)}ERROR [${issue.check}] ${issue.file}: ${issue.message}`);
|
|
131
|
+
}
|
|
132
|
+
if (sinceActive) {
|
|
133
|
+
const introducedErrors = errors.filter((i) => i.tag === 'introduced').length;
|
|
134
|
+
const introducedWarnings = warnings.filter((i) => i.tag === 'introduced').length;
|
|
135
|
+
const cumulativeErrors = errors.length - introducedErrors;
|
|
136
|
+
const cumulativeWarnings = warnings.length - introducedWarnings;
|
|
137
|
+
info(`\nLint: ${introducedErrors} introduced error(s), ${introducedWarnings} introduced warning(s); ${cumulativeErrors} cumulative error(s), ${cumulativeWarnings} cumulative warning(s)`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
info(`\nLint: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
119
141
|
}
|
|
120
|
-
info(`\nLint: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
121
142
|
if (errors.length > 0) {
|
|
122
143
|
outro('Lint failed');
|
|
123
144
|
throw new GeneralError(`Patch lint found ${errors.length} error(s). Fix these before exporting.`);
|
|
@@ -129,8 +150,13 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
|
|
|
129
150
|
program
|
|
130
151
|
.command('lint [paths...]')
|
|
131
152
|
.description('Lint engine changes against patch quality rules')
|
|
132
|
-
.
|
|
133
|
-
|
|
153
|
+
.option('--since <git-rev>', 'Tag issues as [introduced] or [cumulative] based on whether the file changed since <git-rev> (e.g. HEAD, a branch, a SHA)')
|
|
154
|
+
.action(withErrorHandling(async (paths, options) => {
|
|
155
|
+
const lintOptions = {};
|
|
156
|
+
if (options.since !== undefined) {
|
|
157
|
+
lintOptions.since = options.since;
|
|
158
|
+
}
|
|
159
|
+
await lintCommand(getProjectRoot(), paths, lintOptions);
|
|
134
160
|
}));
|
|
135
161
|
}
|
|
136
162
|
//# sourceMappingURL=lint.js.map
|