@hominis/fireforge 0.48.0 → 0.49.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 +29 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +16 -28
- package/dist/src/commands/build.js +30 -4
- package/dist/src/commands/doctor-engine-eslint.d.ts +3 -0
- package/dist/src/commands/doctor-engine-eslint.js +12 -0
- package/dist/src/commands/doctor-macos-sdk.js +7 -5
- package/dist/src/commands/doctor-source-pin.js +9 -2
- package/dist/src/commands/doctor.js +2 -0
- package/dist/src/commands/furnace/apply.js +10 -4
- package/dist/src/commands/furnace/create-templates.js +0 -3
- package/dist/src/commands/lint.js +4 -0
- package/dist/src/commands/manifest.d.ts +1 -2
- package/dist/src/commands/patch/delete.js +18 -4
- package/dist/src/commands/re-export-options.js +7 -0
- package/dist/src/commands/re-export-register.js +1 -0
- package/dist/src/commands/re-export-removed.d.ts +4 -0
- package/dist/src/commands/re-export-removed.js +49 -0
- package/dist/src/commands/re-export-single.js +4 -1
- package/dist/src/commands/re-export.js +4 -0
- package/dist/src/commands/test-browser-preflight.d.ts +28 -0
- package/dist/src/commands/test-browser-preflight.js +181 -0
- package/dist/src/commands/test-build-phase.js +30 -7
- package/dist/src/commands/test-checkjs.d.ts +3 -0
- package/dist/src/commands/test-checkjs.js +27 -0
- package/dist/src/commands/test-diagnose.js +3 -0
- package/dist/src/commands/test-harness-env.d.ts +5 -6
- package/dist/src/commands/test-harness-env.js +27 -7
- package/dist/src/commands/test-modes.d.ts +2 -4
- package/dist/src/commands/test-register.js +11 -5
- package/dist/src/commands/test-run.d.ts +2 -0
- package/dist/src/commands/test-run.js +21 -1
- package/dist/src/commands/test-verdict.d.ts +2 -2
- package/dist/src/commands/test.js +83 -211
- package/dist/src/core/build-baseline-types.d.ts +12 -14
- package/dist/src/core/build-baseline-validate.d.ts +3 -0
- package/dist/src/core/build-baseline-validate.js +53 -0
- package/dist/src/core/build-baseline.d.ts +4 -3
- package/dist/src/core/build-baseline.js +23 -11
- package/dist/src/core/component-source-guard.d.ts +20 -0
- package/dist/src/core/component-source-guard.js +68 -0
- package/dist/src/core/coverage-extend.d.ts +5 -34
- package/dist/src/core/coverage-extend.js +26 -37
- package/dist/src/core/engine-write-boundary.d.ts +5 -0
- package/dist/src/core/engine-write-boundary.js +41 -0
- package/dist/src/core/furnace-apply-helpers.js +8 -2
- package/dist/src/core/furnace-apply.js +7 -0
- package/dist/src/core/furnace-validate-accessibility.js +9 -12
- package/dist/src/core/furnace-validate.js +0 -43
- package/dist/src/core/gecko-dom-types.d.ts +2 -0
- package/dist/src/core/gecko-dom-types.js +17 -0
- package/dist/src/core/lit-template-regions.d.ts +4 -0
- package/dist/src/core/lit-template-regions.js +50 -0
- package/dist/src/core/mach-build-artifacts.d.ts +6 -6
- package/dist/src/core/mach-build-artifacts.js +13 -7
- package/dist/src/core/mach-resource-shim.d.ts +1 -1
- package/dist/src/core/mach-resource-shim.js +2 -0
- package/dist/src/core/marionette-port.d.ts +2 -0
- package/dist/src/core/marionette-port.js +38 -53
- package/dist/src/core/mochitest-server-port.d.ts +1 -1
- package/dist/src/core/mochitest-server-port.js +20 -17
- package/dist/src/core/objdir-maintenance.d.ts +10 -0
- package/dist/src/core/objdir-maintenance.js +151 -0
- package/dist/src/core/patch-lint-checkjs.js +2 -1
- package/dist/src/core/preflight-wait.d.ts +2 -0
- package/dist/src/core/preflight-wait.js +24 -0
- package/dist/src/core/process-owner.d.ts +9 -0
- package/dist/src/core/process-owner.js +43 -0
- package/dist/src/core/test-harness-crash.d.ts +1 -1
- package/dist/src/core/test-harness-crash.js +5 -5
- package/dist/src/core/test-host-state.d.ts +14 -0
- package/dist/src/core/test-host-state.js +80 -0
- package/dist/src/core/test-profile-files.d.ts +17 -0
- package/dist/src/core/test-profile-files.js +75 -0
- package/dist/src/core/test-stale-check.d.ts +6 -7
- package/dist/src/core/test-stale-check.js +9 -32
- package/dist/src/core/token-category.d.ts +7 -26
- package/dist/src/core/token-category.js +68 -121
- package/dist/src/core/token-inventory.js +12 -7
- package/dist/src/core/typecheck.js +6 -6
- package/dist/src/types/commands/options.d.ts +14 -3
- package/dist/src/utils/assert.js +2 -7
- package/dist/src/utils/child-env.d.ts +0 -3
- package/dist/src/utils/child-env.js +0 -3
- package/dist/src/utils/concurrency.d.ts +2 -2
- package/dist/src/utils/concurrency.js +15 -4
- package/dist/src/utils/fs.js +11 -1
- package/dist/src/utils/options.d.ts +16 -1
- package/dist/src/utils/options.js +5 -3
- package/dist/src/utils/paths.js +3 -2
- package/dist/src/utils/process-group.d.ts +1 -2
- package/dist/src/utils/process-group.js +1 -2
- package/dist/src/utils/process-input.js +0 -3
- package/dist/src/utils/process.d.ts +3 -2
- package/dist/src/utils/process.js +19 -7
- package/docs/configuration.md +12 -11
- package/docs/lifecycle-invariants.md +9 -5
- package/docs/machine-output.md +36 -19
- package/docs/patch-workflow.md +15 -0
- package/docs/testing.md +77 -12
- package/package.json +4 -3
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* categories a project actually has, so the only way to name one correctly
|
|
9
9
|
* was to hand-parse a neighbouring `= Category =` banner out of the file.
|
|
10
10
|
* This module is that report. It reuses the banner and
|
|
11
|
-
*
|
|
11
|
+
* category-region model from `token-category.ts` rather than parsing
|
|
12
12
|
* tokens CSS a second way: two readers that disagree on what a banner is
|
|
13
13
|
* would put `token list` and `token add` in different sections of the same
|
|
14
14
|
* file.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { findBaseRootBounds, maskCommentLines, parseTokenCategorySections, } from './token-category.js';
|
|
17
17
|
/** Matches a custom-property declaration and captures its name and value. */
|
|
18
18
|
const DECLARATION_PATTERN = /^\s*(--[A-Za-z0-9_-]+)\s*:\s*([^;]*);?/;
|
|
19
19
|
/**
|
|
@@ -32,14 +32,18 @@ export function collectTokenInventory(lines) {
|
|
|
32
32
|
if (bounds === undefined || bounds.close === -1)
|
|
33
33
|
return [];
|
|
34
34
|
const masked = maskCommentLines(lines);
|
|
35
|
+
const sections = parseTokenCategorySections(lines);
|
|
36
|
+
const headers = new Map(sections.map((section) => [section.categoryLine, section]));
|
|
35
37
|
const groups = [];
|
|
36
38
|
let current;
|
|
39
|
+
let sectionEnd = bounds.close;
|
|
37
40
|
for (let i = bounds.open + 1; i < bounds.close; i++) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
current = { category:
|
|
41
|
+
if (i >= sectionEnd)
|
|
42
|
+
current = undefined;
|
|
43
|
+
const section = headers.get(i);
|
|
44
|
+
if (section !== undefined) {
|
|
45
|
+
current = { category: section.name, tokens: [] };
|
|
46
|
+
sectionEnd = section.sectionEnd;
|
|
43
47
|
groups.push(current);
|
|
44
48
|
continue;
|
|
45
49
|
}
|
|
@@ -48,6 +52,7 @@ export function collectTokenInventory(lines) {
|
|
|
48
52
|
continue;
|
|
49
53
|
if (current === undefined) {
|
|
50
54
|
current = { category: null, tokens: [] };
|
|
55
|
+
sectionEnd = bounds.close;
|
|
51
56
|
groups.push(current);
|
|
52
57
|
}
|
|
53
58
|
current.tokens.push({ name: match[1], line: i + 1, value: (match[2] ?? '').trim() });
|
|
@@ -26,6 +26,7 @@ import { sha256Hex } from '../utils/hash.js';
|
|
|
26
26
|
import { verbose } from '../utils/logger.js';
|
|
27
27
|
import { getPackageVersion } from '../utils/package-root.js';
|
|
28
28
|
import { normalizePathSlashes } from '../utils/paths.js';
|
|
29
|
+
import { withGeckoDomTypes } from './gecko-dom-types.js';
|
|
29
30
|
import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES, UNDEFINED_IDENTIFIER_CODES, UNDEFINED_IDENTIFIER_HINT, } from './typecheck-shim.js';
|
|
30
31
|
/**
|
|
31
32
|
* Sentinel string surfaced as a `TypecheckIssue.message` when the
|
|
@@ -252,7 +253,7 @@ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource,
|
|
|
252
253
|
// filtered out below, so the pass reports failures that do not exist.
|
|
253
254
|
const shimPath = normalizePathSlashes(resolve(projectDir, `.fireforge-${SHIM_FILENAME}`));
|
|
254
255
|
const rootFiles = [...parsed.fileNames, shimPath];
|
|
255
|
-
const defaultHost = ts.createCompilerHost(options);
|
|
256
|
+
const defaultHost = withGeckoDomTypes(ts, ts.createCompilerHost(options));
|
|
256
257
|
const host = {
|
|
257
258
|
...defaultHost,
|
|
258
259
|
getSourceFile(fileName, languageVersion, onError, shouldCreate) {
|
|
@@ -293,11 +294,10 @@ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource,
|
|
|
293
294
|
program = ts.createProgram(rootFiles, options, host);
|
|
294
295
|
}
|
|
295
296
|
else {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
});
|
|
297
|
+
// Always check the entire project in root order. Restoring TypeScript's
|
|
298
|
+
// per-file semantic cache makes checkJs inference depend on the first
|
|
299
|
+
// invalidated importer and can both invent and hide diagnostics.
|
|
300
|
+
const builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootFiles, { ...options, incremental: true, tsBuildInfoFile: buildInfoFile }, withSourceFileVersions(host));
|
|
301
301
|
program = builder;
|
|
302
302
|
// Under noEmit, emit() writes the build info and nothing else.
|
|
303
303
|
writeBuildInfo = () => {
|
|
@@ -109,6 +109,8 @@ export interface SourceSetOptions {
|
|
|
109
109
|
* Options for the build command.
|
|
110
110
|
*/
|
|
111
111
|
export interface BuildOptions extends EngineLockWaitable {
|
|
112
|
+
/** Wait for the objdir browser before deploying/building. */
|
|
113
|
+
waitBrowser?: number | boolean;
|
|
112
114
|
/** Fast UI-only rebuild */
|
|
113
115
|
ui?: boolean;
|
|
114
116
|
/** Number of parallel jobs */
|
|
@@ -368,6 +370,8 @@ export interface ReExportOptions extends EngineLockWaitable {
|
|
|
368
370
|
* rather than hidden.
|
|
369
371
|
*/
|
|
370
372
|
expectUnmanaged?: string[];
|
|
373
|
+
/** Explicitly retire named absent patch-created files in a scan-less export. */
|
|
374
|
+
expectRemoved?: string[];
|
|
371
375
|
}
|
|
372
376
|
/**
|
|
373
377
|
* Options for the `fireforge patch tier` subcommand. Sets or clears the
|
|
@@ -551,6 +555,12 @@ export interface RunOptions {
|
|
|
551
555
|
* Options for the test command.
|
|
552
556
|
*/
|
|
553
557
|
export interface TestOptions extends EngineLockWaitable {
|
|
558
|
+
/** Queue behind a browser using this objdir; bare flag means 60 seconds. */
|
|
559
|
+
waitBrowser?: number | boolean;
|
|
560
|
+
/** Queue behind the mochitest/Marionette port holder. */
|
|
561
|
+
waitPort?: number | boolean;
|
|
562
|
+
/** Copy a file into the profile at a relative destination (repeatable source=destination). */
|
|
563
|
+
profileFile?: string[];
|
|
554
564
|
/** Run tests in headless mode */
|
|
555
565
|
headless?: boolean;
|
|
556
566
|
/**
|
|
@@ -675,9 +685,10 @@ export interface TestOptions extends EngineLockWaitable {
|
|
|
675
685
|
*/
|
|
676
686
|
perfSamples?: string;
|
|
677
687
|
/**
|
|
678
|
-
* Seeded
|
|
679
|
-
* a number replays
|
|
680
|
-
*
|
|
688
|
+
* Seeded isolated path-argument order for mochitest runs. `true` draws a
|
|
689
|
+
* fresh seed; a number replays the shard permutation. Native mach
|
|
690
|
+
* `--shuffle` within a shard remains unseeded. Also exported as
|
|
691
|
+
* `FIREFORGE_SHUFFLE_SEED` for custom task code and recorded on the verdict.
|
|
681
692
|
*/
|
|
682
693
|
shuffle?: number | boolean;
|
|
683
694
|
}
|
package/dist/src/utils/assert.js
CHANGED
|
@@ -11,13 +11,8 @@
|
|
|
11
11
|
* says the opposite: nothing the operator could have done would have
|
|
12
12
|
* prevented it.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* permanently-uncovered branch to every function it appears in, and the
|
|
17
|
-
* per-module coverage floors in `scripts/check-coverage-thresholds.mjs`
|
|
18
|
-
* would pay for it in ~40 places. Routed through a call, the branch lives
|
|
19
|
-
* here once and is covered here once, and the `complexity` ceiling in
|
|
20
|
-
* `eslint.config.js` does not see it at all.
|
|
14
|
+
* Shared helpers provide consistent classification and TypeScript narrowing,
|
|
15
|
+
* and keep expensive failure descriptions lazy on successful paths.
|
|
21
16
|
*
|
|
22
17
|
* This module is a leaf: it imports the error class and nothing else, per
|
|
23
18
|
* the dependency-direction note in `src/utils/errors.ts`.
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Environment construction for spawned children.
|
|
3
|
-
*
|
|
4
|
-
* Its own module rather than a helper inside `process.ts` only because that
|
|
5
|
-
* file sits on a 500-line budget. The logic belongs to the exec layer.
|
|
6
3
|
*/
|
|
7
4
|
/** The `env` / `envUnset` slice of exec options this module needs. */
|
|
8
5
|
interface ChildEnvOptions {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Environment construction for spawned children.
|
|
4
|
-
*
|
|
5
|
-
* Its own module rather than a helper inside `process.ts` only because that
|
|
6
|
-
* file sits on a 500-line budget. The logic belongs to the exec layer.
|
|
7
4
|
*/
|
|
8
5
|
/**
|
|
9
6
|
* The child's environment: `process.env`, with `env` merged over it, then
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bounded-concurrency helpers shared by commands that fan out per-file IO
|
|
3
3
|
* (status classification, import's unmanaged-dirty guard, re-export scans,
|
|
4
|
-
* the dry-run purity guard).
|
|
5
|
-
*
|
|
4
|
+
* the dry-run purity guard). On failure, stop scheduling new items and await
|
|
5
|
+
* every started operation before rejecting, so callers can safely clean up.
|
|
6
6
|
*/
|
|
7
7
|
/** Maps items with at most `limit` in-flight promises. Preserves order. */
|
|
8
8
|
export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bounded-concurrency helpers shared by commands that fan out per-file IO
|
|
3
3
|
* (status classification, import's unmanaged-dirty guard, re-export scans,
|
|
4
|
-
* the dry-run purity guard).
|
|
5
|
-
*
|
|
4
|
+
* the dry-run purity guard). On failure, stop scheduling new items and await
|
|
5
|
+
* every started operation before rejecting, so callers can safely clean up.
|
|
6
6
|
*/
|
|
7
7
|
/** Maps items with at most `limit` in-flight promises. Preserves order. */
|
|
8
8
|
export async function mapWithConcurrency(items, limit, fn) {
|
|
9
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
10
|
+
throw new RangeError('Concurrency limit must be a positive integer');
|
|
11
|
+
}
|
|
9
12
|
const results = new Array(items.length);
|
|
10
13
|
let next = 0;
|
|
14
|
+
const state = {};
|
|
11
15
|
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
12
|
-
|
|
16
|
+
while (!state.failure) {
|
|
13
17
|
const index = next++;
|
|
14
18
|
if (index >= items.length)
|
|
15
19
|
return;
|
|
16
|
-
|
|
20
|
+
try {
|
|
21
|
+
results[index] = await fn(items[index], index);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
state.failure ??= { error };
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
});
|
|
19
28
|
await Promise.all(workers);
|
|
29
|
+
if (state.failure)
|
|
30
|
+
throw state.failure.error;
|
|
20
31
|
return results;
|
|
21
32
|
}
|
package/dist/src/utils/fs.js
CHANGED
|
@@ -99,7 +99,17 @@ export async function isSymlink(path) {
|
|
|
99
99
|
*/
|
|
100
100
|
export async function copyFile(src, dest) {
|
|
101
101
|
await ensureParentDir(dest);
|
|
102
|
-
|
|
102
|
+
// Never follow a destination symlink or truncate a shared hardlink:
|
|
103
|
+
// build/deploy copies must not write through into components/ or a peer.
|
|
104
|
+
const tempPath = createAtomicTempPath(dest);
|
|
105
|
+
try {
|
|
106
|
+
await fsCopyFile(src, tempPath);
|
|
107
|
+
await renameWithRetries(tempPath, dest);
|
|
108
|
+
await syncParentDir(dest);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await rm(tempPath, { force: true });
|
|
112
|
+
}
|
|
103
113
|
}
|
|
104
114
|
/**
|
|
105
115
|
* Reads a JSON file and parses it.
|
|
@@ -89,7 +89,22 @@ export declare function resolveWaitLockSeconds(value: string | number | boolean
|
|
|
89
89
|
*/
|
|
90
90
|
export declare function sessionWaitLockMs(): number | undefined;
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* Registers `--wait-lock [seconds]` on a command that takes no lock, so a
|
|
93
|
+
* scripted sequence can blanket-append the flag without hitting a usage
|
|
94
|
+
* error. A command that rejects it with "unknown option" kills the whole
|
|
95
|
+
* sequence with a usage error instead of a lock message.
|
|
96
|
+
*
|
|
97
|
+
* The flag is accepted and ignored here, never silently repurposed, and
|
|
98
|
+
* the help text says so, so nobody reads its presence as evidence that this
|
|
99
|
+
* command waits for anything.
|
|
100
|
+
*
|
|
101
|
+
* The value is parsed and validated exactly as the honoring variant does:
|
|
102
|
+
* `--wait-lock nonsense` must still be a usage error everywhere, or the
|
|
103
|
+
* uniformity would be a lie.
|
|
104
|
+
*/
|
|
105
|
+
export declare function addAcceptedWaitLockOption(command: Command): Command;
|
|
106
|
+
/**
|
|
107
|
+
* Recursively gives every leaf command in the tree a `--wait-lock` flag: the
|
|
93
108
|
* honoring registration where one already exists, the accept-and-ignore
|
|
94
109
|
* one everywhere else.
|
|
95
110
|
*/
|
|
@@ -182,7 +182,7 @@ export function sessionWaitLockMs() {
|
|
|
182
182
|
* `--wait-lock nonsense` must still be a usage error everywhere, or the
|
|
183
183
|
* uniformity would be a lie.
|
|
184
184
|
*/
|
|
185
|
-
function addAcceptedWaitLockOption(command) {
|
|
185
|
+
export function addAcceptedWaitLockOption(command) {
|
|
186
186
|
return command.option('--wait-lock [seconds]', 'Accepted for scripting uniformity and ignored: this command takes no FireForge lock', commanderArgParser((raw) => resolveWaitLockSeconds(raw)));
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
@@ -194,13 +194,15 @@ function hasWaitLockOption(command) {
|
|
|
194
194
|
return command.options.some((option) => option.long === '--wait-lock');
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
|
-
* Recursively gives every command in the tree a `--wait-lock` flag: the
|
|
197
|
+
* Recursively gives every leaf command in the tree a `--wait-lock` flag: the
|
|
198
198
|
* honoring registration where one already exists, the accept-and-ignore
|
|
199
199
|
* one everywhere else.
|
|
200
200
|
*/
|
|
201
201
|
export function ensureWaitLockOptionEverywhere(command) {
|
|
202
202
|
for (const sub of command.commands) {
|
|
203
|
-
|
|
203
|
+
// Commander groups otherwise capture a trailing leaf option before the
|
|
204
|
+
// action can see it. Register the uniform flag only on actionable leaves.
|
|
205
|
+
if (sub.commands.length === 0 && !hasWaitLockOption(sub))
|
|
204
206
|
addAcceptedWaitLockOption(sub);
|
|
205
207
|
ensureWaitLockOptionEverywhere(sub);
|
|
206
208
|
}
|
package/dist/src/utils/paths.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
-
import { isAbsolute, relative, resolve } from 'node:path';
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
3
3
|
const WINDOWS_ABSOLUTE_PATH = /^[a-zA-Z]:[\\/]/;
|
|
4
4
|
const RELATIVE_PATH_ROOT = resolve('/__fireforge_path_root__');
|
|
5
5
|
/**
|
|
@@ -55,7 +55,8 @@ export function isPathInsideRoot(root, candidate) {
|
|
|
55
55
|
: resolve(resolvedRoot, candidate);
|
|
56
56
|
const relativePath = relative(resolvedRoot, resolvedCandidate);
|
|
57
57
|
return (relativePath === '' ||
|
|
58
|
-
(
|
|
58
|
+
(relativePath !== '..' &&
|
|
59
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
59
60
|
!isAbsolute(relativePath) &&
|
|
60
61
|
!WINDOWS_ABSOLUTE_PATH.test(relativePath)));
|
|
61
62
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Process-group kill and post-run sweep helpers for the exec layer.
|
|
3
|
-
* out of `process.ts` to keep that file within the per-file line budget.
|
|
2
|
+
* Process-group kill and post-run sweep helpers for the exec layer.
|
|
4
3
|
* Spawn-based rather than `exec`-based so the two modules do not import
|
|
5
4
|
* each other cyclically.
|
|
6
5
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Process-group kill and post-run sweep helpers for the exec layer.
|
|
4
|
-
* out of `process.ts` to keep that file within the per-file line budget.
|
|
3
|
+
* Process-group kill and post-run sweep helpers for the exec layer.
|
|
5
4
|
* Spawn-based rather than `exec`-based so the two modules do not import
|
|
6
5
|
* each other cyclically.
|
|
7
6
|
*/
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
3
|
* Stdin-fed command execution with byte-exact stdout.
|
|
4
|
-
*
|
|
5
|
-
* Its own module rather than a function in `process.ts` only because that
|
|
6
|
-
* file sits on a 500-line budget. The logic belongs to the exec layer.
|
|
7
4
|
*/
|
|
8
5
|
import { spawn } from 'node:child_process';
|
|
9
6
|
import { buildChildEnv } from './child-env.js';
|
|
@@ -150,8 +150,9 @@ export declare function execStream(command: string, args: string[], options?: St
|
|
|
150
150
|
* Registers a child whose shutdown {@link waitForActiveChildShutdown} must
|
|
151
151
|
* wait for, and returns the handle that deregisters it.
|
|
152
152
|
*
|
|
153
|
-
* @returns `settle()`. Call
|
|
154
|
-
*
|
|
153
|
+
* @returns `settle()`. Call after child closure and required teardown finish.
|
|
154
|
+
* An abort's `error` event does not prove closure; do not settle there.
|
|
155
|
+
* A failed start may settle when no live child or teardown remains.
|
|
155
156
|
*/
|
|
156
157
|
export declare function trackChildClosure(): {
|
|
157
158
|
settle: () => void;
|
|
@@ -4,6 +4,7 @@ import { constants as osConstants } from 'node:os';
|
|
|
4
4
|
import { StringDecoder } from 'node:string_decoder';
|
|
5
5
|
import { ExecTimeoutError } from '../errors/base.js';
|
|
6
6
|
import { buildChildEnv } from './child-env.js';
|
|
7
|
+
import { toError } from './errors.js';
|
|
7
8
|
import { dispatchCompleteLines } from './line-dispatch.js';
|
|
8
9
|
import { killProcessTree, sweepProcessGroup } from './process-group.js';
|
|
9
10
|
// 50 MB cap per stream to prevent OOM. Large toolchain builds (e.g. Firefox, Chromium)
|
|
@@ -169,6 +170,7 @@ async function spawnTracked(spec) {
|
|
|
169
170
|
? installGracefulShutdownForwarder(child, spec.graceMs, killTarget)
|
|
170
171
|
: undefined;
|
|
171
172
|
const closure = trackChildClosure();
|
|
173
|
+
let rejection;
|
|
172
174
|
child.on('error', (error) => {
|
|
173
175
|
// Abort/startup failure: make sure a partially-started tree does not
|
|
174
176
|
// outlive the dispatch (a mach dying at startup used to strand
|
|
@@ -176,15 +178,24 @@ async function spawnTracked(spec) {
|
|
|
176
178
|
if (options.processGroup === true) {
|
|
177
179
|
killProcessTree(child, 'SIGKILL', usesProcessGroup);
|
|
178
180
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
// An abort emits error before close. Keep the dispatch tracked until
|
|
182
|
+
// the child closes and both teardown sweeps finish, including on timeout.
|
|
183
|
+
rejection = toExecRejection(error, command, args, options.timeout, spec.rejectionOutput?.());
|
|
182
184
|
});
|
|
183
185
|
child.on('close', (code, signal) => {
|
|
184
186
|
forwarder?.dispose();
|
|
185
187
|
const finish = () => {
|
|
186
188
|
closure.settle();
|
|
187
|
-
|
|
189
|
+
if (rejection)
|
|
190
|
+
reject(rejection);
|
|
191
|
+
else {
|
|
192
|
+
try {
|
|
193
|
+
resolve(spec.result(code, signal));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
reject(toError(error));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
188
199
|
};
|
|
189
200
|
// Group sweep first (it takes the helpers that stayed in the group),
|
|
190
201
|
// then the caller's own sweep (the ones that did not), then settle.
|
|
@@ -194,7 +205,7 @@ async function spawnTracked(spec) {
|
|
|
194
205
|
finish();
|
|
195
206
|
return;
|
|
196
207
|
}
|
|
197
|
-
|
|
208
|
+
Promise.resolve().then(sweep).then(finish, finish);
|
|
198
209
|
};
|
|
199
210
|
if (groupPid !== undefined) {
|
|
200
211
|
sweepProcessGroup(groupPid).then(callerSweep, callerSweep);
|
|
@@ -265,8 +276,9 @@ const activeChildClosures = new Set();
|
|
|
265
276
|
* Registers a child whose shutdown {@link waitForActiveChildShutdown} must
|
|
266
277
|
* wait for, and returns the handle that deregisters it.
|
|
267
278
|
*
|
|
268
|
-
* @returns `settle()`. Call
|
|
269
|
-
*
|
|
279
|
+
* @returns `settle()`. Call after child closure and required teardown finish.
|
|
280
|
+
* An abort's `error` event does not prove closure; do not settle there.
|
|
281
|
+
* A failed start may settle when no live child or teardown remains.
|
|
270
282
|
*/
|
|
271
283
|
export function trackChildClosure() {
|
|
272
284
|
let resolveClosed;
|
package/docs/configuration.md
CHANGED
|
@@ -149,17 +149,18 @@ When `patchLint.checkJs` is on, the same command also runs the per-patch
|
|
|
149
149
|
checkJs pass that `export` enforces, so a green `typecheck` means
|
|
150
150
|
export-clean types.
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
program
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`
|
|
152
|
+
Project build info is written in `.fireforge/typecheck/`, outside `engine/`,
|
|
153
|
+
with a key covering the shim, TypeScript version, compiler options and
|
|
154
|
+
FireForge version. Diagnostics always use a fresh project
|
|
155
|
+
program: prior incremental diagnostic state is never restored. Cached and
|
|
156
|
+
`--no-cache` runs therefore check the same complete file set. `--no-cache`
|
|
157
|
+
also disables build-info writes. A jsconfig's own `incremental` and
|
|
158
|
+
`tsBuildInfoFile` remain ignored.
|
|
159
|
+
|
|
160
|
+
Both project and per-patch checkJs provide Gecko DOM constructor
|
|
161
|
+
`isInstance(value)` predicates with instance narrowing, plus
|
|
162
|
+
`MouseEvent.MOZ_SOURCE_PEN`. These declarations retain normal DOM member
|
|
163
|
+
checking; they do not turn constructors or instances into `any`.
|
|
163
164
|
|
|
164
165
|
## `patchPolicy`
|
|
165
166
|
|
|
@@ -113,11 +113,15 @@ command, read the decision table at the bottom first.
|
|
|
113
113
|
carry primary shebangs, and mach rebuilds them in-tree), and runs
|
|
114
114
|
`mach configure` inside the tree so the configure-generated root files are
|
|
115
115
|
regenerated against the tree's paths rather than keeping the primary's. It
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
Before configure, relocation rewrites old-source symlinks, `.pp`/`.d`,
|
|
117
|
+
FasterMake and Cargo fingerprint metadata, and removes copied
|
|
118
|
+
`config.statusd/*/config.track` inventories. Those inventories contain
|
|
119
|
+
absolute WRITE destinations and must never be consumed from a clone.
|
|
120
|
+
Aliased objdirs or partial-config directories are refused. Afterwards
|
|
121
|
+
`findObjdirRelocationViolation` verifies config.status, backend.mk,
|
|
122
|
+
Makefile, config/autoconf.mk and every remaining objdir symlink for the
|
|
123
|
+
old source prefix. External toolchain links are allowed; source links
|
|
124
|
+
must no longer consult the old checkout. Finally it
|
|
121
125
|
copies `last-build.json` so the in-tree stale-build gates anchor
|
|
122
126
|
correctly.
|
|
123
127
|
|
package/docs/machine-output.md
CHANGED
|
@@ -86,15 +86,16 @@ moved.
|
|
|
86
86
|
`FIREFORGE-VERDICT: FAIL reason=<reason>` uses a closed set
|
|
87
87
|
(`FireforgeVerdictReason`, `src/commands/test-verdict.ts`):
|
|
88
88
|
|
|
89
|
-
| Reason
|
|
90
|
-
|
|
|
91
|
-
| `crash`
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
89
|
+
| Reason | Meaning |
|
|
90
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `crash` | The harness or the browser died, so there is no trustworthy suite result. |
|
|
92
|
+
| `harness-arguments` | Profile or other harness argument setup failed before any tests could run; a rebuild is not the repair. |
|
|
93
|
+
| `no-tests` | The run dispatched but nothing ran. |
|
|
94
|
+
| `test-failures` | The suite ran and reported unexpected results. |
|
|
95
|
+
| `preflight` | The run was refused before the harness was reached. |
|
|
96
|
+
| `inconclusive` | A result exists, but `engine/` moved under it or a perf run changed power source, so the result was discarded. |
|
|
97
|
+
| `lock-timeout` | The run never started: the engine session lock stayed contended. |
|
|
98
|
+
| `killed` | A signal terminated the run, or FireForge ended it because its parent process vanished. Written from the signal handler (or the parent-exit watchdog) so that a log tail always describes itself. A killed run must never be silent, or "killed", "still running" and "never started" all look alike. `signal=` names the cause: a signal name, or `parent-exit`. |
|
|
98
99
|
|
|
99
100
|
### The additive `note=` key
|
|
100
101
|
|
|
@@ -109,20 +110,19 @@ unchanged and still comes from the closed set above, so a consumer that
|
|
|
109
110
|
tokenises `key=value` pairs is unaffected, and a gate that names no class
|
|
110
111
|
emits no `note=` at all. The classes are not a closed set and may grow, so
|
|
111
112
|
consumers should treat an unrecognised one as opaque. Current values:
|
|
112
|
-
`stale-browser`, `stale-browser-kill-failed`, `marionette-port-busy`,
|
|
113
|
+
`browser-busy`, `stale-browser`, `stale-browser-kill-failed`, `marionette-port-busy`, `mochitest-port-busy`,
|
|
113
114
|
`coverage-replaced`, `stale-build`, `stale-components`.
|
|
114
115
|
|
|
115
116
|
### The additive `shuffle=` key
|
|
116
117
|
|
|
117
|
-
A `fireforge test --shuffle [seed]` run
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
shuffle.
|
|
118
|
+
A `fireforge test --shuffle [seed]` run records the seed used to permute
|
|
119
|
+
FireForge's isolated path-argument shards. Repeating the seed and selection
|
|
120
|
+
repeats that shard order. It also forwards native mach `--shuffle`, which
|
|
121
|
+
randomizes files within an invocation without using this seed. `--no-shard`
|
|
122
|
+
disables FireForge's seeded argument permutation. The seed is exported as
|
|
123
|
+
`FIREFORGE_SHUFFLE_SEED` for custom in-file task code and appended as
|
|
124
|
+
`shuffle=<seed>` to every verdict form. This field does not claim replayable
|
|
125
|
+
native harness file order. Absent when shuffle was not requested.
|
|
126
126
|
|
|
127
127
|
### The additive `orphans-reaped=` key
|
|
128
128
|
|
|
@@ -141,3 +141,20 @@ A preflight refusal's own text is written to stdout before the verdict line,
|
|
|
141
141
|
and into the run log, so the verdict stays the last stdout write while the
|
|
142
142
|
reason survives both a redirect and a `tail`. See
|
|
143
143
|
[`run-logs.md`](run-logs.md).
|
|
144
|
+
|
|
145
|
+
### Additive host and perf keys
|
|
146
|
+
|
|
147
|
+
`host-load=<number>` records the one-minute load at suite startup. Each suite
|
|
148
|
+
also samples the highest CPU process before and after dispatch. A contention
|
|
149
|
+
warning appends `host-cpu-warning=true`; the diagnostic names the process
|
|
150
|
+
and measured CPU. These diagnostics annotate a test failure without changing
|
|
151
|
+
its classifier reason.
|
|
152
|
+
|
|
153
|
+
Perf runs append `power-source=ac|battery|unknown`. On macOS the source is
|
|
154
|
+
read from `pmset -g ps`, so charging on AC is correctly `ac`. A known source
|
|
155
|
+
transition appends `power-changed=true` and refuses the run as
|
|
156
|
+
`reason=inconclusive`. The producer's sample JSON retains its fields and
|
|
157
|
+
gains `fireforgeHost: { start, end, powerSourceChanged }`, where each host
|
|
158
|
+
state includes `load`, `power` and an optional `topProcess` with CPU and
|
|
159
|
+
command. Consumers should tolerate this additive object. Unknown power
|
|
160
|
+
state is visible but never treated as a proven transition.
|
package/docs/patch-workflow.md
CHANGED
|
@@ -56,6 +56,21 @@ requested path produces no hunk. That is checked against the full requested
|
|
|
56
56
|
list rather than the subset left after filtering, so nothing dropped upstream
|
|
57
57
|
can pass as a silent success.
|
|
58
58
|
|
|
59
|
+
An absent file originally created by a selected patch can be retired with
|
|
60
|
+
repeatable `--expect-removed <path>` on scan-less re-export:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
fireforge re-export 202 --refuse-adjacent-unmanaged --refuse-foreign-drift --expect browser/themes/shared/hominis/widgets.css --expect-removed browser/themes/shared/hominis/stop.svg
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The removal must have exactly one selected owner, be absent from disk and
|
|
67
|
+
engine HEAD, and have a `new file mode` section in that owner's patch.
|
|
68
|
+
Every removal is validated before writes, in dry-run too. Other paths keep
|
|
69
|
+
both refusal checks; retirement grants no unmanaged or foreign-drift
|
|
70
|
+
exception. Removing every file would empty the patch and requires `patch
|
|
71
|
+
drop` instead. `--expect-removed` cannot combine with `--scan`, `--files` or
|
|
72
|
+
`--all`.
|
|
73
|
+
|
|
59
74
|
## Queue maintenance
|
|
60
75
|
|
|
61
76
|
Under `fireforge patch`:
|