@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
|
@@ -4,14 +4,72 @@
|
|
|
4
4
|
* story cleanup, branding setup, Furnace component application, and mozconfig generation.
|
|
5
5
|
*/
|
|
6
6
|
import { FurnaceError } from '../errors/furnace.js';
|
|
7
|
+
import { toError } from '../utils/errors.js';
|
|
7
8
|
import { pathExists } from '../utils/fs.js';
|
|
8
|
-
import { info, spinner, warn } from '../utils/logger.js';
|
|
9
|
+
import { info, spinner, verbose, warn } from '../utils/logger.js';
|
|
9
10
|
import { isBrandingSetup, setupBranding } from './branding.js';
|
|
10
11
|
import { applyAllComponents } from './furnace-apply.js';
|
|
11
12
|
import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceState, } from './furnace-config.js';
|
|
12
13
|
import { runFurnaceMutation } from './furnace-operation.js';
|
|
13
14
|
import { cleanStories } from './furnace-stories.js';
|
|
14
|
-
import {
|
|
15
|
+
import { hasChanges, isMissingHeadError } from './git.js';
|
|
16
|
+
import { git } from './git-base.js';
|
|
17
|
+
import { getUntrackedFiles } from './git-status.js';
|
|
18
|
+
import { generateMozconfig, runMach } from './mach.js';
|
|
19
|
+
/** Path fragments of files whose edits invalidate the recursive-make backend. */
|
|
20
|
+
const BACKEND_INVALIDATING_SUFFIXES = ['moz.build', 'moz.configure', 'Makefile.in'];
|
|
21
|
+
/**
|
|
22
|
+
* Returns true when the file path matches a pattern that forces
|
|
23
|
+
* `mach configure` to regenerate the backend. Exported for testing.
|
|
24
|
+
*/
|
|
25
|
+
export function isBackendInvalidatingFile(path) {
|
|
26
|
+
for (const suffix of BACKEND_INVALIDATING_SUFFIXES) {
|
|
27
|
+
if (path === suffix || path.endsWith(`/${suffix}`))
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Collects engine-relative paths of files changed since the baseline's HEAD
|
|
34
|
+
* SHA plus any workdir modifications. Defensive — git failures surface as
|
|
35
|
+
* verbose lines and return the files collected so far. An empty result
|
|
36
|
+
* means "no drift we can prove" rather than "no drift occurred".
|
|
37
|
+
*/
|
|
38
|
+
async function collectBackendRelevantChanges(engineDir, baseline) {
|
|
39
|
+
const collected = new Set();
|
|
40
|
+
if (baseline.engineHeadSha) {
|
|
41
|
+
try {
|
|
42
|
+
const diff = await git(['diff', '--name-only', `${baseline.engineHeadSha}..HEAD`], engineDir);
|
|
43
|
+
for (const line of diff.split('\n')) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (trimmed)
|
|
46
|
+
collected.add(trimmed);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (!isMissingHeadError(error)) {
|
|
51
|
+
verbose(`Auto-configure: could not diff engine against baseline — ${toError(error).message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
if (await hasChanges(engineDir)) {
|
|
57
|
+
const worktreeDiff = await git(['diff', '--name-only', 'HEAD'], engineDir);
|
|
58
|
+
for (const line of worktreeDiff.split('\n')) {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (trimmed)
|
|
61
|
+
collected.add(trimmed);
|
|
62
|
+
}
|
|
63
|
+
for (const file of await getUntrackedFiles(engineDir)) {
|
|
64
|
+
collected.add(file);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
verbose(`Auto-configure: could not enumerate workdir changes — ${toError(error).message}`);
|
|
70
|
+
}
|
|
71
|
+
return [...collected];
|
|
72
|
+
}
|
|
15
73
|
/**
|
|
16
74
|
* Runs the shared pre-flight steps for build and package commands:
|
|
17
75
|
* 1. Cleans Furnace stories from engine (prevents leaking into production)
|
|
@@ -24,7 +82,7 @@ import { generateMozconfig } from './mach.js';
|
|
|
24
82
|
* @param config - Loaded FireForge configuration
|
|
25
83
|
* @returns Preparation results
|
|
26
84
|
*/
|
|
27
|
-
export async function prepareBuildEnvironment(projectRoot, paths, config) {
|
|
85
|
+
export async function prepareBuildEnvironment(projectRoot, paths, config, options = {}) {
|
|
28
86
|
// Block the build if Furnace has an unresolved repair marker. This prevents
|
|
29
87
|
// building against an engine that may be in an inconsistent state after a
|
|
30
88
|
// failed rollback.
|
|
@@ -36,6 +94,33 @@ export async function prepareBuildEnvironment(projectRoot, paths, config) {
|
|
|
36
94
|
'Run "fireforge doctor --repair-furnace" to reconcile engine state before building.');
|
|
37
95
|
}
|
|
38
96
|
}
|
|
97
|
+
// Auto-configure: if any backend-invalidating file (moz.build, moz.configure,
|
|
98
|
+
// Makefile.in) changed since the last successful build, run `mach configure`
|
|
99
|
+
// before the build step. Prevents incremental builds from silently skipping
|
|
100
|
+
// work against a stale recursive-make backend.
|
|
101
|
+
let reconfigured = false;
|
|
102
|
+
if (options.previousBaseline) {
|
|
103
|
+
const changed = await collectBackendRelevantChanges(paths.engine, options.previousBaseline);
|
|
104
|
+
const invalidating = changed.filter(isBackendInvalidatingFile);
|
|
105
|
+
if (invalidating.length > 0) {
|
|
106
|
+
info(`Backend config changed; running mach configure first... (${invalidating.length} file${invalidating.length === 1 ? '' : 's'} touched)`);
|
|
107
|
+
const configureSpinner = spinner('Running mach configure...');
|
|
108
|
+
try {
|
|
109
|
+
const exitCode = await runMach(['configure'], paths.engine);
|
|
110
|
+
if (exitCode !== 0) {
|
|
111
|
+
configureSpinner.error('mach configure exited non-zero; continuing with build anyway');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
configureSpinner.stop('Backend regenerated');
|
|
115
|
+
reconfigured = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
configureSpinner.error('mach configure failed; continuing with build anyway');
|
|
120
|
+
verbose(`Auto-configure error: ${toError(error).message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
39
124
|
// Clean stories before build to ensure they don't leak into production binary
|
|
40
125
|
await cleanStories(paths.engine);
|
|
41
126
|
// Set up custom branding directory and patch moz.configure
|
|
@@ -117,6 +202,6 @@ export async function prepareBuildEnvironment(projectRoot, paths, config) {
|
|
|
117
202
|
mozconfigSpinner.error('Failed to generate mozconfig');
|
|
118
203
|
throw error;
|
|
119
204
|
}
|
|
120
|
-
return { furnaceApplied };
|
|
205
|
+
return { furnaceApplied, reconfigured };
|
|
121
206
|
}
|
|
122
207
|
//# sourceMappingURL=build-prepare.js.map
|
|
@@ -4,7 +4,8 @@ import { type RollbackJournal } from './furnace-rollback.js';
|
|
|
4
4
|
* The signal names the lifecycle wrapper knows how to react to. Spelled out
|
|
5
5
|
* as a literal union (rather than `NodeJS.Signals`) so the public type
|
|
6
6
|
* surface does not depend on the NodeJS global namespace — consumers of
|
|
7
|
-
*
|
|
7
|
+
* FireForge's published scoped npm package may compile against tsconfigs
|
|
8
|
+
* that omit `@types/node`.
|
|
8
9
|
*/
|
|
9
10
|
export type FurnaceShutdownSignal = 'SIGINT' | 'SIGTERM';
|
|
10
11
|
/**
|
|
@@ -52,16 +52,22 @@ function withTimeout(promise, ms, label) {
|
|
|
52
52
|
* timeout so a stuck I/O operation cannot hang the process indefinitely.
|
|
53
53
|
*/
|
|
54
54
|
export async function rollbackActiveOperationsForSignal(signal) {
|
|
55
|
+
// Snapshot the active operations so we don't race with `runFurnaceMutation`
|
|
56
|
+
// clearing slots during normal completion. Filter completed bodies so a
|
|
57
|
+
// body sitting in its finally-block cleanup window is not counted as live
|
|
58
|
+
// work — this would mis-trigger the rollback banner for plain `fireforge
|
|
59
|
+
// run` (which never registers a mutation but can receive SIGTERM).
|
|
60
|
+
const snapshot = [...activeOperations.values()].filter((op) => !op.completed);
|
|
61
|
+
if (snapshot.length === 0) {
|
|
62
|
+
// Nothing to roll back. Stay silent so commands that never mutated (run,
|
|
63
|
+
// watch, test, doctor) don't print an alarming "rolling back mutations"
|
|
64
|
+
// line on Ctrl+C / SIGTERM. Leave `signalRollbackInFlight` false so a
|
|
65
|
+
// subsequent registrant can still trigger the full path.
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
55
68
|
signalRollbackInFlight = true;
|
|
56
69
|
warn(`Received ${signal}; rolling back in-flight furnace mutations…`);
|
|
57
|
-
// Snapshot the active operations so we don't race with `runFurnaceMutation`
|
|
58
|
-
// clearing slots during normal completion.
|
|
59
|
-
const snapshot = [...activeOperations.values()];
|
|
60
70
|
for (const op of snapshot) {
|
|
61
|
-
// If the body completed successfully and is in its finally-block cleanup
|
|
62
|
-
// (deleting the token), skip rollback — the mutation committed cleanly.
|
|
63
|
-
if (op.completed)
|
|
64
|
-
continue;
|
|
65
71
|
const cleanupErrors = [];
|
|
66
72
|
// Run extra cleanup callbacks first (e.g. preview's cleanStories), so the
|
|
67
73
|
// engine is in its tidiest possible shape before the journal restore
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern-based translator for cryptic mozbuild / mach errors.
|
|
3
|
+
*
|
|
4
|
+
* Each entry maps a stderr regex to an actionable hint. The goal is not to
|
|
5
|
+
* parse every mach failure — it's to convert the handful of errors whose
|
|
6
|
+
* message is non-obvious into a one-line "here's what to change". New
|
|
7
|
+
* entries should only be added when a concrete diagnosis of the cryptic
|
|
8
|
+
* output has been established; low-confidence hints would train operators
|
|
9
|
+
* to ignore the translator.
|
|
10
|
+
*/
|
|
11
|
+
/** A single translator entry. */
|
|
12
|
+
export interface MachErrorHint {
|
|
13
|
+
/** Pattern to search within the captured mach stderr. */
|
|
14
|
+
pattern: RegExp;
|
|
15
|
+
/** Actionable, one-line hint to surface alongside the raw mach output. */
|
|
16
|
+
hint: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Registered hint patterns. Order-sensitive: the first match wins per
|
|
20
|
+
* pattern, but multiple distinct patterns may fire for the same stderr.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MACH_ERROR_HINTS: MachErrorHint[];
|
|
23
|
+
/**
|
|
24
|
+
* Scans captured stderr for known mach errors and returns matching hints.
|
|
25
|
+
* Pure function — safe to call on any string; never throws.
|
|
26
|
+
* @param stderr Captured mach stderr.
|
|
27
|
+
* @returns Ordered, de-duplicated list of hint strings. Empty when nothing matches.
|
|
28
|
+
*/
|
|
29
|
+
export declare function explainMachError(stderr: string): string[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Pattern-based translator for cryptic mozbuild / mach errors.
|
|
4
|
+
*
|
|
5
|
+
* Each entry maps a stderr regex to an actionable hint. The goal is not to
|
|
6
|
+
* parse every mach failure — it's to convert the handful of errors whose
|
|
7
|
+
* message is non-obvious into a one-line "here's what to change". New
|
|
8
|
+
* entries should only be added when a concrete diagnosis of the cryptic
|
|
9
|
+
* output has been established; low-confidence hints would train operators
|
|
10
|
+
* to ignore the translator.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Registered hint patterns. Order-sensitive: the first match wins per
|
|
14
|
+
* pattern, but multiple distinct patterns may fire for the same stderr.
|
|
15
|
+
*/
|
|
16
|
+
export const MACH_ERROR_HINTS = [
|
|
17
|
+
{
|
|
18
|
+
pattern: /mozbuild\.preprocessor\.Preprocessor\.Error[\s\S]*?no preprocessor directives found/,
|
|
19
|
+
hint: 'A file registered under JS_PREFERENCE_PP_FILES contains no preprocessor directives. ' +
|
|
20
|
+
'Use JS_PREFERENCE_FILES instead, or add at least one #filter / #expand directive to the file.',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Scans captured stderr for known mach errors and returns matching hints.
|
|
25
|
+
* Pure function — safe to call on any string; never throws.
|
|
26
|
+
* @param stderr Captured mach stderr.
|
|
27
|
+
* @returns Ordered, de-duplicated list of hint strings. Empty when nothing matches.
|
|
28
|
+
*/
|
|
29
|
+
export function explainMachError(stderr) {
|
|
30
|
+
if (!stderr) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
const hits = [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
for (const { pattern, hint } of MACH_ERROR_HINTS) {
|
|
36
|
+
if (pattern.test(stderr) && !seen.has(hint)) {
|
|
37
|
+
seen.add(hint);
|
|
38
|
+
hits.push(hint);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return hits;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=mach-error-hints.js.map
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -57,14 +57,17 @@ export declare function bootstrap(engineDir: string): Promise<number>;
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function bootstrapWithOutput(engineDir: string): Promise<MachCommandResult>;
|
|
59
59
|
/**
|
|
60
|
-
* Runs a full mach build.
|
|
60
|
+
* Runs a full mach build. On a non-zero exit, any matched error hints are
|
|
61
|
+
* surfaced on top of the raw mach output so operators get an actionable
|
|
62
|
+
* nudge alongside the cryptic mozbuild traceback.
|
|
61
63
|
* @param engineDir - Path to the engine directory
|
|
62
64
|
* @param jobs - Number of parallel jobs (optional)
|
|
63
65
|
* @returns Exit code
|
|
64
66
|
*/
|
|
65
67
|
export declare function build(engineDir: string, jobs?: number): Promise<number>;
|
|
66
68
|
/**
|
|
67
|
-
* Runs a fast UI-only build.
|
|
69
|
+
* Runs a fast UI-only build. On a non-zero exit, any matched error hints are
|
|
70
|
+
* surfaced on top of the raw mach output.
|
|
68
71
|
* @param engineDir - Path to the engine directory
|
|
69
72
|
* @returns Exit code
|
|
70
73
|
*/
|
package/dist/src/core/mach.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { MachNotFoundError } from '../errors/build.js';
|
|
4
4
|
import { pathExists } from '../utils/fs.js';
|
|
5
|
+
import { warn } from '../utils/logger.js';
|
|
5
6
|
import { exec, execInherit, execInheritCapture, execStream } from '../utils/process.js';
|
|
7
|
+
import { explainMachError } from './mach-error-hints.js';
|
|
6
8
|
import { getPython } from './mach-python.js';
|
|
7
9
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
8
10
|
export { buildArtifactMismatchMessage, hasBuildArtifacts, } from './mach-build-artifacts.js';
|
|
@@ -109,7 +111,23 @@ export async function bootstrapWithOutput(engineDir) {
|
|
|
109
111
|
return runMachInheritCapture(['bootstrap', '--application-choice', 'browser'], engineDir);
|
|
110
112
|
}
|
|
111
113
|
/**
|
|
112
|
-
*
|
|
114
|
+
* Prints any matched {@link MachErrorHint} hints for the captured stderr.
|
|
115
|
+
* No-op when nothing matches. Always called before a non-zero exit propagates
|
|
116
|
+
* so the hint sits immediately below the raw mach error in the operator's
|
|
117
|
+
* terminal.
|
|
118
|
+
*/
|
|
119
|
+
function surfaceMachErrorHints(stderr) {
|
|
120
|
+
const hints = explainMachError(stderr);
|
|
121
|
+
if (hints.length === 0)
|
|
122
|
+
return;
|
|
123
|
+
for (const hint of hints) {
|
|
124
|
+
warn(`Hint: ${hint}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Runs a full mach build. On a non-zero exit, any matched error hints are
|
|
129
|
+
* surfaced on top of the raw mach output so operators get an actionable
|
|
130
|
+
* nudge alongside the cryptic mozbuild traceback.
|
|
113
131
|
* @param engineDir - Path to the engine directory
|
|
114
132
|
* @param jobs - Number of parallel jobs (optional)
|
|
115
133
|
* @returns Exit code
|
|
@@ -119,15 +137,24 @@ export async function build(engineDir, jobs) {
|
|
|
119
137
|
if (jobs !== undefined) {
|
|
120
138
|
args.push('-j', String(jobs));
|
|
121
139
|
}
|
|
122
|
-
|
|
140
|
+
const result = await runMachInheritCapture(args, engineDir);
|
|
141
|
+
if (result.exitCode !== 0) {
|
|
142
|
+
surfaceMachErrorHints(result.stderr);
|
|
143
|
+
}
|
|
144
|
+
return result.exitCode;
|
|
123
145
|
}
|
|
124
146
|
/**
|
|
125
|
-
* Runs a fast UI-only build.
|
|
147
|
+
* Runs a fast UI-only build. On a non-zero exit, any matched error hints are
|
|
148
|
+
* surfaced on top of the raw mach output.
|
|
126
149
|
* @param engineDir - Path to the engine directory
|
|
127
150
|
* @returns Exit code
|
|
128
151
|
*/
|
|
129
152
|
export async function buildUI(engineDir) {
|
|
130
|
-
|
|
153
|
+
const result = await runMachInheritCapture(['build', 'faster'], engineDir);
|
|
154
|
+
if (result.exitCode !== 0) {
|
|
155
|
+
surfaceMachErrorHints(result.stderr);
|
|
156
|
+
}
|
|
157
|
+
return result.exitCode;
|
|
131
158
|
}
|
|
132
159
|
/**
|
|
133
160
|
* Runs the built browser.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff-scoping for `fireforge lint`.
|
|
3
|
+
*
|
|
4
|
+
* Pre-existing patch-state errors and errors introduced by the current task
|
|
5
|
+
* print identically today, so triaging "is the diff I just produced clean?"
|
|
6
|
+
* requires mentally subtracting the pre-existing noise. This module
|
|
7
|
+
* classifies each lint issue as either `introduced` (the file was touched
|
|
8
|
+
* since the user-supplied git revision) or `cumulative` (pre-existing
|
|
9
|
+
* drift), without changing what the underlying rules emit.
|
|
10
|
+
*/
|
|
11
|
+
import type { PatchLintIssue } from '../types/commands/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Collects engine-relative paths that changed between `rev` and `HEAD`,
|
|
14
|
+
* plus any workdir modifications and untracked files. An empty set means
|
|
15
|
+
* "no diff we can prove" — downstream treats every issue as `cumulative`
|
|
16
|
+
* in that case (operator ran `lint --since HEAD` with no pending work).
|
|
17
|
+
* @param engineDir Path to the engine git repository.
|
|
18
|
+
* @param rev Git revision to diff against (e.g. `HEAD`, a branch, a SHA).
|
|
19
|
+
*/
|
|
20
|
+
export declare function collectDiffFilePaths(engineDir: string, rev: string): Promise<Set<string>>;
|
|
21
|
+
/**
|
|
22
|
+
* Annotates a list of lint issues with `introduced` / `cumulative` tags
|
|
23
|
+
* based on whether the issue's file is part of the supplied diff set.
|
|
24
|
+
* Mutates each issue in place AND returns the list for chaining.
|
|
25
|
+
*
|
|
26
|
+
* Issues with no file (`issue.file === ''`) — e.g. cross-patch rules that
|
|
27
|
+
* describe queue-wide state — are always `cumulative` under `--since`
|
|
28
|
+
* because they describe drift accumulated across many commits, not a
|
|
29
|
+
* single current-task edit.
|
|
30
|
+
* @param issues Issues returned by the lint orchestrator.
|
|
31
|
+
* @param diffFiles File paths touched since the user's revision.
|
|
32
|
+
*/
|
|
33
|
+
export declare function tagLintIssues(issues: PatchLintIssue[], diffFiles: Set<string>): PatchLintIssue[];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Diff-scoping for `fireforge lint`.
|
|
4
|
+
*
|
|
5
|
+
* Pre-existing patch-state errors and errors introduced by the current task
|
|
6
|
+
* print identically today, so triaging "is the diff I just produced clean?"
|
|
7
|
+
* requires mentally subtracting the pre-existing noise. This module
|
|
8
|
+
* classifies each lint issue as either `introduced` (the file was touched
|
|
9
|
+
* since the user-supplied git revision) or `cumulative` (pre-existing
|
|
10
|
+
* drift), without changing what the underlying rules emit.
|
|
11
|
+
*/
|
|
12
|
+
import { toError } from '../utils/errors.js';
|
|
13
|
+
import { verbose } from '../utils/logger.js';
|
|
14
|
+
import { isMissingHeadError } from './git.js';
|
|
15
|
+
import { git } from './git-base.js';
|
|
16
|
+
import { getUntrackedFiles } from './git-status.js';
|
|
17
|
+
/**
|
|
18
|
+
* Collects engine-relative paths that changed between `rev` and `HEAD`,
|
|
19
|
+
* plus any workdir modifications and untracked files. An empty set means
|
|
20
|
+
* "no diff we can prove" — downstream treats every issue as `cumulative`
|
|
21
|
+
* in that case (operator ran `lint --since HEAD` with no pending work).
|
|
22
|
+
* @param engineDir Path to the engine git repository.
|
|
23
|
+
* @param rev Git revision to diff against (e.g. `HEAD`, a branch, a SHA).
|
|
24
|
+
*/
|
|
25
|
+
export async function collectDiffFilePaths(engineDir, rev) {
|
|
26
|
+
const files = new Set();
|
|
27
|
+
try {
|
|
28
|
+
const commitDiff = await git(['diff', '--name-only', `${rev}...HEAD`], engineDir);
|
|
29
|
+
for (const line of commitDiff.split('\n')) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
if (trimmed)
|
|
32
|
+
files.add(trimmed);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!isMissingHeadError(error)) {
|
|
37
|
+
verbose(`lint --since: could not diff against ${rev} — ${toError(error).message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const worktreeDiff = await git(['diff', '--name-only', 'HEAD'], engineDir);
|
|
42
|
+
for (const line of worktreeDiff.split('\n')) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (trimmed)
|
|
45
|
+
files.add(trimmed);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
verbose(`lint --since: could not enumerate workdir diff — ${toError(error).message}`);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
for (const file of await getUntrackedFiles(engineDir)) {
|
|
53
|
+
files.add(file);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
verbose(`lint --since: could not list untracked files — ${toError(error).message}`);
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Annotates a list of lint issues with `introduced` / `cumulative` tags
|
|
63
|
+
* based on whether the issue's file is part of the supplied diff set.
|
|
64
|
+
* Mutates each issue in place AND returns the list for chaining.
|
|
65
|
+
*
|
|
66
|
+
* Issues with no file (`issue.file === ''`) — e.g. cross-patch rules that
|
|
67
|
+
* describe queue-wide state — are always `cumulative` under `--since`
|
|
68
|
+
* because they describe drift accumulated across many commits, not a
|
|
69
|
+
* single current-task edit.
|
|
70
|
+
* @param issues Issues returned by the lint orchestrator.
|
|
71
|
+
* @param diffFiles File paths touched since the user's revision.
|
|
72
|
+
*/
|
|
73
|
+
export function tagLintIssues(issues, diffFiles) {
|
|
74
|
+
for (const issue of issues) {
|
|
75
|
+
if (!issue.file) {
|
|
76
|
+
issue.tag = 'cumulative';
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
issue.tag = diffFiles.has(issue.file) ? 'introduced' : 'cumulative';
|
|
80
|
+
}
|
|
81
|
+
return issues;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=patch-lint-diff-tag.js.map
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Top-level chrome document — DOM fragment insertion.
|
|
3
|
+
*
|
|
4
|
+
* Default target is `browser/base/content/browser.xhtml`. Forks that replace
|
|
5
|
+
* browser.xhtml with a custom top-level chrome document pass the replacement
|
|
6
|
+
* path in via `targetPath`; the insertion logic is shape-agnostic (looks for
|
|
7
|
+
* `#include browser-sets.inc`, then falls back to `<html:body>`), so any
|
|
8
|
+
* browser.xhtml-shaped xhtml works.
|
|
3
9
|
*/
|
|
10
|
+
export declare const DEFAULT_DOM_TARGET = "browser/base/content/browser.xhtml";
|
|
4
11
|
/**
|
|
5
12
|
* Tokenizer-based implementation for DOM fragment insertion.
|
|
6
13
|
*/
|
|
@@ -10,14 +17,19 @@ export declare function addDomFragmentTokenized(content: string, includeDirectiv
|
|
|
10
17
|
*/
|
|
11
18
|
export declare function legacyAddDomFragment(content: string, includeDirective: string): string;
|
|
12
19
|
/**
|
|
13
|
-
* Inserts a `#include` directive for an `.inc.xhtml` file into
|
|
14
|
-
*
|
|
20
|
+
* Inserts a `#include` directive for an `.inc.xhtml` file into the top-level
|
|
21
|
+
* chrome document (default: `browser/base/content/browser.xhtml`), before
|
|
22
|
+
* `#include browser-sets.inc`.
|
|
15
23
|
*
|
|
16
24
|
* If the file's content was previously inlined (detected by root element id=),
|
|
17
25
|
* the inlined block is automatically replaced with the `#include` directive.
|
|
18
26
|
*
|
|
19
27
|
* @param engineDir - Engine source root
|
|
20
28
|
* @param domFilePath - Path to the `.inc.xhtml` file relative to engine root
|
|
29
|
+
* @param targetPath - Chrome document to insert into, relative to engine
|
|
30
|
+
* root. Defaults to {@link DEFAULT_DOM_TARGET}. Forks that replace
|
|
31
|
+
* browser.xhtml with a custom top-level chrome document pass the
|
|
32
|
+
* replacement path here.
|
|
21
33
|
* @returns true if inserted, false if already present
|
|
22
34
|
*/
|
|
23
|
-
export declare function addDomFragment(engineDir: string, domFilePath: string): Promise<boolean>;
|
|
35
|
+
export declare function addDomFragment(engineDir: string, domFilePath: string, targetPath?: string): Promise<boolean>;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Top-level chrome document — DOM fragment insertion.
|
|
4
|
+
*
|
|
5
|
+
* Default target is `browser/base/content/browser.xhtml`. Forks that replace
|
|
6
|
+
* browser.xhtml with a custom top-level chrome document pass the replacement
|
|
7
|
+
* path in via `targetPath`; the insertion logic is shape-agnostic (looks for
|
|
8
|
+
* `#include browser-sets.inc`, then falls back to `<html:body>`), so any
|
|
9
|
+
* browser.xhtml-shaped xhtml works.
|
|
4
10
|
*/
|
|
5
|
-
import { join, relative } from 'node:path';
|
|
11
|
+
import { dirname, join, relative } from 'node:path';
|
|
6
12
|
import { GeneralError } from '../errors/base.js';
|
|
7
13
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
14
|
import { toRootRelativePath } from '../utils/paths.js';
|
|
9
15
|
import { escapeRegex } from '../utils/regex.js';
|
|
10
16
|
import { withParserFallback } from './parser-fallback.js';
|
|
11
17
|
import { tokenizeXhtml } from './wire-utils.js';
|
|
12
|
-
const
|
|
18
|
+
export const DEFAULT_DOM_TARGET = 'browser/base/content/browser.xhtml';
|
|
13
19
|
/**
|
|
14
20
|
* Tokenizer-based implementation for DOM fragment insertion.
|
|
15
21
|
*/
|
|
@@ -36,7 +42,7 @@ export function addDomFragmentTokenized(content, includeDirective) {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
if (insertIndex === -1) {
|
|
39
|
-
throw new GeneralError('Could not find insertion point in
|
|
45
|
+
throw new GeneralError('Could not find insertion point in chrome document');
|
|
40
46
|
}
|
|
41
47
|
lines.splice(insertIndex, 0, includeDirective);
|
|
42
48
|
return lines.join('\n');
|
|
@@ -64,32 +70,41 @@ export function legacyAddDomFragment(content, includeDirective) {
|
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
if (insertIndex === -1) {
|
|
67
|
-
throw new GeneralError('Could not find insertion point in
|
|
73
|
+
throw new GeneralError('Could not find insertion point in chrome document');
|
|
68
74
|
}
|
|
69
75
|
lines.splice(insertIndex, 0, includeDirective);
|
|
70
76
|
return lines.join('\n');
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
|
-
* Inserts a `#include` directive for an `.inc.xhtml` file into
|
|
74
|
-
*
|
|
79
|
+
* Inserts a `#include` directive for an `.inc.xhtml` file into the top-level
|
|
80
|
+
* chrome document (default: `browser/base/content/browser.xhtml`), before
|
|
81
|
+
* `#include browser-sets.inc`.
|
|
75
82
|
*
|
|
76
83
|
* If the file's content was previously inlined (detected by root element id=),
|
|
77
84
|
* the inlined block is automatically replaced with the `#include` directive.
|
|
78
85
|
*
|
|
79
86
|
* @param engineDir - Engine source root
|
|
80
87
|
* @param domFilePath - Path to the `.inc.xhtml` file relative to engine root
|
|
88
|
+
* @param targetPath - Chrome document to insert into, relative to engine
|
|
89
|
+
* root. Defaults to {@link DEFAULT_DOM_TARGET}. Forks that replace
|
|
90
|
+
* browser.xhtml with a custom top-level chrome document pass the
|
|
91
|
+
* replacement path here.
|
|
81
92
|
* @returns true if inserted, false if already present
|
|
82
93
|
*/
|
|
83
|
-
export async function addDomFragment(engineDir, domFilePath) {
|
|
84
|
-
const
|
|
94
|
+
export async function addDomFragment(engineDir, domFilePath, targetPath = DEFAULT_DOM_TARGET) {
|
|
95
|
+
const targetAbsPath = join(engineDir, targetPath);
|
|
85
96
|
const safeDomFilePath = toRootRelativePath(engineDir, domFilePath);
|
|
86
|
-
if (!(await pathExists(
|
|
87
|
-
throw new GeneralError(`${
|
|
97
|
+
if (!(await pathExists(targetAbsPath))) {
|
|
98
|
+
throw new GeneralError(`${targetPath} not found in engine`);
|
|
88
99
|
}
|
|
89
|
-
// Compute include path relative to
|
|
90
|
-
|
|
100
|
+
// Compute include path relative to the target's directory — the `#include`
|
|
101
|
+
// directive is resolved by the preprocessor relative to the file that
|
|
102
|
+
// contains it, so this must track the chrome doc's location, not a
|
|
103
|
+
// hardcoded `browser/base/content/`.
|
|
104
|
+
const targetDir = dirname(targetPath);
|
|
105
|
+
const includePath = relative(targetDir, safeDomFilePath).replace(/\\/g, '/');
|
|
91
106
|
const includeDirective = `#include ${includePath}`;
|
|
92
|
-
let content = await readText(
|
|
107
|
+
let content = await readText(targetAbsPath);
|
|
93
108
|
// Idempotency: check if the #include directive already exists (line-anchored to avoid substring matches)
|
|
94
109
|
if (new RegExp(`^${escapeRegex(includeDirective)}$`, 'm').test(content)) {
|
|
95
110
|
return false;
|
|
@@ -116,14 +131,14 @@ export async function addDomFragment(engineDir, domFilePath) {
|
|
|
116
131
|
}
|
|
117
132
|
lines.splice(startIdx, endIdx - startIdx, includeDirective);
|
|
118
133
|
content = lines.join('\n');
|
|
119
|
-
await writeText(
|
|
134
|
+
await writeText(targetAbsPath, content);
|
|
120
135
|
return true;
|
|
121
136
|
}
|
|
122
137
|
}
|
|
123
138
|
}
|
|
124
139
|
// Normal insertion
|
|
125
|
-
const { value } = withParserFallback(() => addDomFragmentTokenized(content, includeDirective), () => legacyAddDomFragment(content, includeDirective),
|
|
126
|
-
await writeText(
|
|
140
|
+
const { value } = withParserFallback(() => addDomFragmentTokenized(content, includeDirective), () => legacyAddDomFragment(content, includeDirective), targetPath);
|
|
141
|
+
await writeText(targetAbsPath, value);
|
|
127
142
|
return true;
|
|
128
143
|
}
|
|
129
144
|
//# sourceMappingURL=wire-dom-fragment.js.map
|
|
@@ -281,6 +281,21 @@ export interface FurnaceCreateOptions {
|
|
|
281
281
|
* `XPCSHELL_TESTS_MANIFESTS`).
|
|
282
282
|
*/
|
|
283
283
|
xpcshell?: boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Test harness style to scaffold when `--with-tests` is set.
|
|
286
|
+
*
|
|
287
|
+
* - `mochikit` (default when `--with-tests` is set alone) — a MochiKit
|
|
288
|
+
* test at `engine/toolkit/content/tests/widgets/test_<tag>.html` that
|
|
289
|
+
* loads the component module directly via `chrome://global/` and
|
|
290
|
+
* asserts against `customElements`. Runs today on forks whose
|
|
291
|
+
* top-level chrome document (e.g. `mybrowser.xhtml`) lacks a
|
|
292
|
+
* `tabbrowser`, because it doesn't go through `URILoadingHelper`.
|
|
293
|
+
* - `browser-chrome` — today's browser-mochitest scaffold, requires a
|
|
294
|
+
* working tabbrowser. Use for components that talk to the browser
|
|
295
|
+
* window or open URLs.
|
|
296
|
+
* - `xpcshell` — equivalent to setting `--xpcshell`; headless, storage-only.
|
|
297
|
+
*/
|
|
298
|
+
testStyle?: 'mochikit' | 'browser-chrome' | 'xpcshell';
|
|
284
299
|
/** Stock component tag names composed internally by this component */
|
|
285
300
|
compose?: string[];
|
|
286
301
|
}
|
|
@@ -294,6 +309,13 @@ export interface WireOptions {
|
|
|
294
309
|
dryRun?: boolean;
|
|
295
310
|
after?: string;
|
|
296
311
|
subscriptDir?: string;
|
|
312
|
+
/**
|
|
313
|
+
* Chrome document the DOM fragment's `#include` is inserted into, relative
|
|
314
|
+
* to engine/. Defaults to the first entry of
|
|
315
|
+
* `furnace.json.tokenHostDocuments` when set, otherwise
|
|
316
|
+
* `browser/base/content/browser.xhtml`.
|
|
317
|
+
*/
|
|
318
|
+
target?: string;
|
|
297
319
|
}
|
|
298
320
|
/**
|
|
299
321
|
* Options for the register command.
|
|
@@ -96,4 +96,13 @@ export interface PatchLintIssue {
|
|
|
96
96
|
message: string;
|
|
97
97
|
/** Severity: errors block export, warnings are advisory, notices are informational (not counted) */
|
|
98
98
|
severity: 'error' | 'warning' | 'notice';
|
|
99
|
+
/**
|
|
100
|
+
* Diff-scoping tag populated by `lint --since <rev>`. Absent when the
|
|
101
|
+
* caller did not request diff-scoping.
|
|
102
|
+
*
|
|
103
|
+
* - `introduced` — the issue's file was touched in the diff since `<rev>`.
|
|
104
|
+
* - `cumulative` — the issue is pre-existing patch-state drift not
|
|
105
|
+
* introduced by the current task.
|
|
106
|
+
*/
|
|
107
|
+
tag?: 'introduced' | 'cumulative';
|
|
99
108
|
}
|
|
@@ -106,7 +106,7 @@ export interface FurnaceConfig {
|
|
|
106
106
|
* state before clearing authoring markers. The string is surfaced in doctor's
|
|
107
107
|
* failure message verbatim, so new entries should be self-explanatory.
|
|
108
108
|
*/
|
|
109
|
-
export type FurnacePendingRepairOperation = 'preview-teardown' | 'apply-rollback' | 'deploy-rollback' | 'remove-rollback' | 'create-rollback' | 'override-rollback' | 'scan-rollback' | 'rename-rollback' | 'refresh-rollback';
|
|
109
|
+
export type FurnacePendingRepairOperation = 'preview-teardown' | 'apply-rollback' | 'deploy-rollback' | 'remove-rollback' | 'create-rollback' | 'override-rollback' | 'scan-rollback' | 'rename-rollback' | 'refresh-rollback' | 'chrome-doc-rollback';
|
|
110
110
|
/**
|
|
111
111
|
* Marker persisted into `.fireforge/furnace-state.json` when a furnace
|
|
112
112
|
* mutation failed to roll back cleanly. Its presence tells the next
|