@hominis/fireforge 0.15.6 → 0.15.7
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 +46 -0
- package/README.md +68 -5
- package/dist/src/commands/build.js +60 -3
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +17 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +18 -0
- package/dist/src/commands/furnace/chrome-doc-tests.d.ts +23 -0
- package/dist/src/commands/furnace/chrome-doc-tests.js +120 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +11 -0
- package/dist/src/commands/furnace/chrome-doc.js +37 -4
- package/dist/src/commands/furnace/create-dry-run.d.ts +31 -0
- package/dist/src/commands/furnace/create-dry-run.js +95 -0
- package/dist/src/commands/furnace/create-templates.js +14 -0
- package/dist/src/commands/furnace/create.js +28 -24
- package/dist/src/commands/furnace/index.js +3 -1
- package/dist/src/commands/lint.d.ts +17 -2
- package/dist/src/commands/lint.js +25 -2
- package/dist/src/commands/register.d.ts +1 -1
- package/dist/src/commands/register.js +30 -7
- package/dist/src/commands/test.js +16 -1
- package/dist/src/core/build-audit-registration.d.ts +80 -0
- package/dist/src/core/build-audit-registration.js +187 -0
- package/dist/src/core/build-audit-transforms.d.ts +23 -0
- package/dist/src/core/build-audit-transforms.js +94 -0
- package/dist/src/core/build-audit.js +107 -7
- package/dist/src/core/furnace-validate-registration.d.ts +6 -4
- package/dist/src/core/furnace-validate-registration.js +66 -6
- package/dist/src/core/mach-build-artifacts.d.ts +44 -0
- package/dist/src/core/mach-build-artifacts.js +104 -3
- package/dist/src/core/mach.d.ts +1 -1
- package/dist/src/core/mach.js +1 -1
- package/dist/src/core/test-stale-check.d.ts +42 -0
- package/dist/src/core/test-stale-check.js +114 -0
- package/dist/src/types/commands/options.d.ts +16 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
3
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { toError } from '../utils/errors.js';
|
|
5
|
-
import { pathExists, readJson } from '../utils/fs.js';
|
|
5
|
+
import { pathExists, readJson, writeJson } from '../utils/fs.js';
|
|
6
6
|
import { verbose } from '../utils/logger.js';
|
|
7
7
|
import { isObject, isString } from '../utils/validation.js';
|
|
8
8
|
function validateBuildMozinfo(data) {
|
|
@@ -112,6 +112,107 @@ export function buildArtifactMismatchMessage(engineDir, buildCheck, commandName)
|
|
|
112
112
|
}
|
|
113
113
|
return (`${commandName} cannot use copied or relocated build artifacts whose metadata still points at a different Firefox workspace.\n\n` +
|
|
114
114
|
`${details.join('\n')}\n\n` +
|
|
115
|
-
'Delete the stale obj-* directory in this workspace and run "fireforge build" again so mach regenerates build metadata for the current checkout
|
|
115
|
+
'Delete the stale obj-* directory in this workspace and run "fireforge build" again so mach regenerates build metadata for the current checkout.\n' +
|
|
116
|
+
'If the workspace was simply moved (same tree, different prefix), "fireforge build --rewrite-mozinfo" will patch mozinfo.json paths in place and run mach configure instead of scrubbing the whole tree.');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Safe-relocation rewriter for mozinfo.json under the active obj-* tree.
|
|
120
|
+
*
|
|
121
|
+
* Firefox build artefacts bake the topsrcdir into many generated files
|
|
122
|
+
* (Makefiles, config.status, backend.mk, .deps dependency files — anything
|
|
123
|
+
* produced by `mach configure`). A fresh `mach configure` rebuilds those
|
|
124
|
+
* from the top, so the rewriter only needs to patch the one file `mach`
|
|
125
|
+
* reads to learn where its checkout actually lives. Once mozinfo.json
|
|
126
|
+
* agrees with the on-disk layout, `mach configure` regenerates the rest.
|
|
127
|
+
*
|
|
128
|
+
* Safety rules — the rewrite is refused when any of them are violated:
|
|
129
|
+
* - `topsrcdir` and `topobjdir` must both be present and non-empty.
|
|
130
|
+
* - `topobjdir` must resolve to `<topsrcdir>/<objDir>`; a non-in-tree
|
|
131
|
+
* objdir means the previous workspace was configured differently,
|
|
132
|
+
* so a blind prefix-rewrite could point mach at the wrong tree.
|
|
133
|
+
* - The computed new `topobjdir` must be `<engineDir>/<objDir>`; if it
|
|
134
|
+
* is not, the objDir name itself changed and we cannot prove safety.
|
|
135
|
+
*
|
|
136
|
+
* When any rule trips, the caller should fall back to the clean-rebuild
|
|
137
|
+
* instruction — that's always a correct (if expensive) recovery path.
|
|
138
|
+
*
|
|
139
|
+
* @param engineDir Absolute path to the current engine checkout.
|
|
140
|
+
* @param objDir Name of the obj-* directory to rewrite against.
|
|
141
|
+
* @returns Result object; callers inspect `rewritten` and surface `reason`.
|
|
142
|
+
*/
|
|
143
|
+
export async function attemptMozinfoRewrite(engineDir, objDir) {
|
|
144
|
+
const mozinfoPath = join(engineDir, objDir, 'mozinfo.json');
|
|
145
|
+
if (!(await pathExists(mozinfoPath))) {
|
|
146
|
+
return { rewritten: false, reason: 'mozinfo.json not found in obj directory' };
|
|
147
|
+
}
|
|
148
|
+
let raw;
|
|
149
|
+
try {
|
|
150
|
+
raw = await readJson(mozinfoPath);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return { rewritten: false, reason: `mozinfo.json is unreadable: ${toError(error).message}` };
|
|
154
|
+
}
|
|
155
|
+
if (!isObject(raw)) {
|
|
156
|
+
return { rewritten: false, reason: 'mozinfo.json is not a JSON object' };
|
|
157
|
+
}
|
|
158
|
+
let mozinfo;
|
|
159
|
+
try {
|
|
160
|
+
mozinfo = validateBuildMozinfo(raw);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return { rewritten: false, reason: toError(error).message };
|
|
164
|
+
}
|
|
165
|
+
const oldSrc = mozinfo.topsrcdir;
|
|
166
|
+
const oldObj = mozinfo.topobjdir;
|
|
167
|
+
if (!oldSrc || !oldObj) {
|
|
168
|
+
return {
|
|
169
|
+
rewritten: false,
|
|
170
|
+
reason: 'mozinfo.json is missing topsrcdir or topobjdir; cannot rewrite safely',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const oldSrcResolved = resolve(oldSrc);
|
|
174
|
+
const oldObjResolved = resolve(oldObj);
|
|
175
|
+
const insideTree = oldObjResolved === oldSrcResolved ||
|
|
176
|
+
oldObjResolved.startsWith(oldSrcResolved + sep) ||
|
|
177
|
+
oldObjResolved.startsWith(oldSrcResolved + '/');
|
|
178
|
+
if (!insideTree) {
|
|
179
|
+
return {
|
|
180
|
+
rewritten: false,
|
|
181
|
+
reason: `topobjdir (${oldObjResolved}) is not inside topsrcdir (${oldSrcResolved}) — rewrite would change workspace layout`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const relativeObj = relative(oldSrcResolved, oldObjResolved).split(sep).join('/');
|
|
185
|
+
if (relativeObj !== objDir) {
|
|
186
|
+
return {
|
|
187
|
+
rewritten: false,
|
|
188
|
+
reason: `mozinfo objdir "${relativeObj}" does not match detected objdir "${objDir}" — rewrite would change the obj directory name`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const newSrc = resolve(engineDir);
|
|
192
|
+
const newObj = resolve(engineDir, objDir);
|
|
193
|
+
const patched = { ...raw, topsrcdir: newSrc, topobjdir: newObj };
|
|
194
|
+
let newMozconfig;
|
|
195
|
+
if (mozinfo.mozconfig) {
|
|
196
|
+
const oldMozconfigResolved = resolve(mozinfo.mozconfig);
|
|
197
|
+
if (oldMozconfigResolved === oldSrcResolved ||
|
|
198
|
+
oldMozconfigResolved.startsWith(oldSrcResolved + sep) ||
|
|
199
|
+
oldMozconfigResolved.startsWith(oldSrcResolved + '/')) {
|
|
200
|
+
const rel = relative(oldSrcResolved, oldMozconfigResolved);
|
|
201
|
+
newMozconfig = resolve(newSrc, rel);
|
|
202
|
+
patched['mozconfig'] = newMozconfig;
|
|
203
|
+
}
|
|
204
|
+
// A mozconfig living outside the old topsrcdir is left as-is — it
|
|
205
|
+
// probably points at a shared configuration file the user kept in
|
|
206
|
+
// place across the relocation. A relocated checkout that also moved
|
|
207
|
+
// its mozconfig will still fail configure; operator can re-point
|
|
208
|
+
// with `MOZCONFIG=…` or run a full clean rebuild.
|
|
209
|
+
}
|
|
210
|
+
await writeJson(mozinfoPath, patched);
|
|
211
|
+
return {
|
|
212
|
+
rewritten: true,
|
|
213
|
+
newTopsrcdir: newSrc,
|
|
214
|
+
newTopobjdir: newObj,
|
|
215
|
+
...(newMozconfig ? { newMozconfig } : {}),
|
|
216
|
+
};
|
|
116
217
|
}
|
|
117
218
|
//# sourceMappingURL=mach-build-artifacts.js.map
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { type BuildArtifactCheck, buildArtifactMismatchMessage, hasBuildArtifacts, } from './mach-build-artifacts.js';
|
|
1
|
+
export { attemptMozinfoRewrite, type BuildArtifactCheck, buildArtifactMismatchMessage, hasBuildArtifacts, type MozinfoRewriteResult, } from './mach-build-artifacts.js';
|
|
2
2
|
export { generateMozconfig, type MozconfigVariables } from './mach-mozconfig.js';
|
|
3
3
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
4
4
|
/**
|
package/dist/src/core/mach.js
CHANGED
|
@@ -7,7 +7,7 @@ import { exec, execInherit, execInheritCapture, execStream } from '../utils/proc
|
|
|
7
7
|
import { explainMachError } from './mach-error-hints.js';
|
|
8
8
|
import { getPython } from './mach-python.js';
|
|
9
9
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
10
|
-
export { buildArtifactMismatchMessage, hasBuildArtifacts, } from './mach-build-artifacts.js';
|
|
10
|
+
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, } from './mach-build-artifacts.js';
|
|
11
11
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
12
12
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
13
13
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BuildBaseline } from './build-baseline.js';
|
|
2
|
+
/** Result of the stale-build preflight probe. */
|
|
3
|
+
export interface StaleBuildResult {
|
|
4
|
+
/** True when at least one packageable engine file changed since the baseline. */
|
|
5
|
+
stale: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Engine-relative paths that would have been packaged but appear to have
|
|
8
|
+
* changed since the baseline. Sorted and deduplicated. Truncated at
|
|
9
|
+
* {@link STALE_PATHS_LIMIT} entries for rendering; consult
|
|
10
|
+
* {@link StaleBuildResult.truncated} to know when to append a `(+N more)`
|
|
11
|
+
* tail to the warning.
|
|
12
|
+
*/
|
|
13
|
+
changedPaths: string[];
|
|
14
|
+
/**
|
|
15
|
+
* How many paths were dropped from `changedPaths` due to the render cap.
|
|
16
|
+
* Callers render this as `(+N more)` in the warning body.
|
|
17
|
+
*/
|
|
18
|
+
truncated: number;
|
|
19
|
+
/**
|
|
20
|
+
* The baseline that anchored the diff, or undefined when no previous
|
|
21
|
+
* successful build exists. A missing baseline is treated as "not stale"
|
|
22
|
+
* — we have nothing to compare against and a warning would mislead.
|
|
23
|
+
*/
|
|
24
|
+
baseline: BuildBaseline | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Probes the engine tree for packageable changes since the last successful
|
|
28
|
+
* `fireforge build`. Returns a summary the `fireforge test` handler renders
|
|
29
|
+
* as an up-front warning when `--build` was NOT passed. The probe never
|
|
30
|
+
* throws; git failures and a missing baseline both degrade to `stale: false`
|
|
31
|
+
* so a broken probe cannot block a test run.
|
|
32
|
+
*
|
|
33
|
+
* @param projectRoot Root directory of the project.
|
|
34
|
+
* @param engineDir Path to the engine directory.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkStaleBuildForTest(projectRoot: string, engineDir: string): Promise<StaleBuildResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Formats a human-readable warning body from a {@link StaleBuildResult}.
|
|
39
|
+
* Kept separate from the probe so test code can assert on the structured
|
|
40
|
+
* result without matching the rendered copy.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatStaleBuildWarning(result: StaleBuildResult): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/*
|
|
3
|
+
* Stale-build preflight for `fireforge test`.
|
|
4
|
+
*
|
|
5
|
+
* Without this preflight, an operator who edits engine chrome / packaged
|
|
6
|
+
* resources (`jar.mn` entries, `.xhtml`/`.mjs`/`.css` under chrome trees,
|
|
7
|
+
* pref files) and then runs `fireforge test <path>` only discovers the
|
|
8
|
+
* build is stale AFTER xpcshell / mach test starts and errors out with
|
|
9
|
+
* `NS_ERROR_FILE_NOT_FOUND` against a `chrome://browser/content/…` URI
|
|
10
|
+
* — which reads as a test bug, not a rebuild prompt. The motivating case
|
|
11
|
+
* was scaffolding a new top-level chrome document + BrowserGlue-style
|
|
12
|
+
* xpcshell test: the test file existed, the manifests were registered,
|
|
13
|
+
* but `dist/` still held the pre-edit bundle and chrome URIs resolved
|
|
14
|
+
* to nothing.
|
|
15
|
+
*
|
|
16
|
+
* This preflight diffs engine HEAD (or workdir) against the last-build
|
|
17
|
+
* baseline (`.fireforge/last-build.json`), filters to paths that imply
|
|
18
|
+
* packaging, and returns a compact summary. `fireforge test` prints a
|
|
19
|
+
* warning up-front so the operator sees "you edited X, Y, Z since the
|
|
20
|
+
* last build — rerun with `--build` to refresh" BEFORE mach test
|
|
21
|
+
* launches. Detection stays advisory (warn-only) because a fork that
|
|
22
|
+
* rebuilds out-of-band (a separate `./mach build` invocation, an IDE
|
|
23
|
+
* plugin, etc.) can legitimately have a fresh `dist/` with no
|
|
24
|
+
* FireForge-recorded baseline update.
|
|
25
|
+
*/
|
|
26
|
+
import { toError } from '../utils/errors.js';
|
|
27
|
+
import { verbose } from '../utils/logger.js';
|
|
28
|
+
import { isPackageablePath } from './build-audit.js';
|
|
29
|
+
import { readBuildBaseline } from './build-baseline.js';
|
|
30
|
+
import { hasChanges, isMissingHeadError } from './git.js';
|
|
31
|
+
import { git } from './git-base.js';
|
|
32
|
+
import { getUntrackedFiles } from './git-status.js';
|
|
33
|
+
/** Cap on the number of changed paths rendered inline. */
|
|
34
|
+
const STALE_PATHS_LIMIT = 10;
|
|
35
|
+
/**
|
|
36
|
+
* Collects engine paths that changed since the baseline SHA plus any
|
|
37
|
+
* workdir modifications. Mirrors the helper inside `build-prepare.ts` but
|
|
38
|
+
* is kept separate so the test-side preflight does not need to pull in
|
|
39
|
+
* the full build-prepare dependency graph (mozconfig generation, furnace
|
|
40
|
+
* apply hooks, …).
|
|
41
|
+
*/
|
|
42
|
+
async function collectChangedEnginePaths(engineDir, baseline) {
|
|
43
|
+
const collected = new Set();
|
|
44
|
+
if (baseline.engineHeadSha) {
|
|
45
|
+
try {
|
|
46
|
+
const diff = await git(['diff', '--name-only', `${baseline.engineHeadSha}..HEAD`], engineDir);
|
|
47
|
+
for (const line of diff.split('\n')) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (trimmed)
|
|
50
|
+
collected.add(trimmed);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (!isMissingHeadError(error)) {
|
|
55
|
+
verbose(`Stale-build preflight: could not diff engine against baseline — ${toError(error).message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (await hasChanges(engineDir)) {
|
|
61
|
+
const worktreeDiff = await git(['diff', '--name-only', 'HEAD'], engineDir);
|
|
62
|
+
for (const line of worktreeDiff.split('\n')) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (trimmed)
|
|
65
|
+
collected.add(trimmed);
|
|
66
|
+
}
|
|
67
|
+
for (const untracked of await getUntrackedFiles(engineDir)) {
|
|
68
|
+
collected.add(untracked);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
verbose(`Stale-build preflight: could not enumerate workdir changes — ${toError(error).message}`);
|
|
74
|
+
}
|
|
75
|
+
return [...collected];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Probes the engine tree for packageable changes since the last successful
|
|
79
|
+
* `fireforge build`. Returns a summary the `fireforge test` handler renders
|
|
80
|
+
* as an up-front warning when `--build` was NOT passed. The probe never
|
|
81
|
+
* throws; git failures and a missing baseline both degrade to `stale: false`
|
|
82
|
+
* so a broken probe cannot block a test run.
|
|
83
|
+
*
|
|
84
|
+
* @param projectRoot Root directory of the project.
|
|
85
|
+
* @param engineDir Path to the engine directory.
|
|
86
|
+
*/
|
|
87
|
+
export async function checkStaleBuildForTest(projectRoot, engineDir) {
|
|
88
|
+
const baseline = await readBuildBaseline(projectRoot);
|
|
89
|
+
if (!baseline) {
|
|
90
|
+
return { stale: false, changedPaths: [], truncated: 0, baseline: undefined };
|
|
91
|
+
}
|
|
92
|
+
const changed = await collectChangedEnginePaths(engineDir, baseline);
|
|
93
|
+
const packageable = changed.filter((path) => isPackageablePath(path)).sort();
|
|
94
|
+
if (packageable.length === 0) {
|
|
95
|
+
return { stale: false, changedPaths: [], truncated: 0, baseline };
|
|
96
|
+
}
|
|
97
|
+
const head = packageable.slice(0, STALE_PATHS_LIMIT);
|
|
98
|
+
const truncated = Math.max(0, packageable.length - head.length);
|
|
99
|
+
return { stale: true, changedPaths: head, truncated, baseline };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Formats a human-readable warning body from a {@link StaleBuildResult}.
|
|
103
|
+
* Kept separate from the probe so test code can assert on the structured
|
|
104
|
+
* result without matching the rendered copy.
|
|
105
|
+
*/
|
|
106
|
+
export function formatStaleBuildWarning(result) {
|
|
107
|
+
const tail = result.truncated > 0 ? `, … (+${result.truncated} more)` : '';
|
|
108
|
+
const list = result.changedPaths.join(', ') + tail;
|
|
109
|
+
return (`Engine tree has changed since the last successful fireforge build (${list}).\n` +
|
|
110
|
+
'The current obj-*/dist/ bundle may not reflect those edits. If your test reads ' +
|
|
111
|
+
'packaged chrome / jar.mn resources, rerun with "fireforge test --build" (or ' +
|
|
112
|
+
'"fireforge build --ui") first. Passing --build skips this check.');
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=test-stale-check.js.map
|
|
@@ -41,6 +41,14 @@ export interface BuildOptions {
|
|
|
41
41
|
jobs?: number;
|
|
42
42
|
/** Brand to build (stable, esr, etc.) */
|
|
43
43
|
brand?: string;
|
|
44
|
+
/**
|
|
45
|
+
* When a mozinfo mismatch is detected that looks like a safe path
|
|
46
|
+
* relocation (same structure, different prefix), patch mozinfo paths
|
|
47
|
+
* in place and run `mach configure` rather than aborting with a
|
|
48
|
+
* full-rebuild instruction. Falls back to the original abort message
|
|
49
|
+
* for any mismatch the rewriter cannot prove safe.
|
|
50
|
+
*/
|
|
51
|
+
rewriteMozinfo?: boolean;
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
46
54
|
* Options for the export command.
|
|
@@ -298,6 +306,14 @@ export interface FurnaceCreateOptions {
|
|
|
298
306
|
testStyle?: 'mochikit' | 'browser-chrome' | 'xpcshell';
|
|
299
307
|
/** Stock component tag names composed internally by this component */
|
|
300
308
|
compose?: string[];
|
|
309
|
+
/**
|
|
310
|
+
* Show the planned file set and furnace.json changes without writing
|
|
311
|
+
* anything. All validation that does not require disk writes (tag name
|
|
312
|
+
* shape, name conflicts, engine pre-existence, `--compose` targets, cycle
|
|
313
|
+
* detection, prefix warning) runs before the plan is emitted, so a
|
|
314
|
+
* dry-run faithfully previews the real command's outcome.
|
|
315
|
+
*/
|
|
316
|
+
dryRun?: boolean;
|
|
301
317
|
}
|
|
302
318
|
/**
|
|
303
319
|
* Options for the wire command.
|