@hominis/fireforge 0.47.0 → 0.48.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 +65 -0
- package/dist/bin/fireforge.js +11 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/doctor-macos-sdk.d.ts +8 -0
- package/dist/src/commands/doctor-macos-sdk.js +156 -0
- package/dist/src/commands/doctor.js +2 -0
- package/dist/src/commands/download.js +64 -3
- package/dist/src/commands/import.js +5 -2
- package/dist/src/commands/lint-per-patch.js +44 -6
- package/dist/src/commands/lint-per-run-checkjs.d.ts +3 -0
- package/dist/src/commands/lint-per-run-checkjs.js +1 -0
- package/dist/src/commands/lint.js +10 -0
- package/dist/src/commands/re-export-lint.d.ts +17 -2
- package/dist/src/commands/re-export-lint.js +33 -4
- package/dist/src/commands/re-export.js +4 -2
- package/dist/src/commands/rebase/confirm.d.ts +12 -1
- package/dist/src/commands/rebase/confirm.js +6 -3
- package/dist/src/commands/rebase/index.js +77 -13
- package/dist/src/commands/run.js +43 -7
- package/dist/src/commands/status.js +5 -3
- package/dist/src/commands/test-harness-teardown.d.ts +62 -0
- package/dist/src/commands/test-harness-teardown.js +128 -0
- package/dist/src/commands/test-register.js +2 -1
- package/dist/src/commands/test-run.d.ts +2 -0
- package/dist/src/commands/test-run.js +36 -7
- package/dist/src/commands/test-verdict.d.ts +19 -4
- package/dist/src/commands/test-verdict.js +22 -4
- package/dist/src/commands/test.js +18 -10
- package/dist/src/commands/typecheck.d.ts +5 -0
- package/dist/src/commands/typecheck.js +7 -1
- package/dist/src/core/build-prepare.d.ts +2 -0
- package/dist/src/core/build-prepare.js +67 -35
- package/dist/src/core/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +2 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate.js +7 -0
- package/dist/src/core/discard-baseline.js +2 -1
- package/dist/src/core/file-lock.js +16 -9
- package/dist/src/core/forward-registrations.d.ts +33 -0
- package/dist/src/core/forward-registrations.js +16 -0
- package/dist/src/core/git-file-ops.d.ts +21 -0
- package/dist/src/core/git-file-ops.js +75 -0
- package/dist/src/core/git-status.d.ts +14 -0
- package/dist/src/core/git-status.js +41 -2
- package/dist/src/core/harness-helper-pids.d.ts +98 -0
- package/dist/src/core/harness-helper-pids.js +240 -0
- package/dist/src/core/harness-orphans.d.ts +53 -8
- package/dist/src/core/harness-orphans.js +125 -44
- package/dist/src/core/mach-mozconfig.d.ts +2 -1
- package/dist/src/core/mach-mozconfig.js +2 -0
- package/dist/src/core/mach.d.ts +16 -0
- package/dist/src/core/mach.js +6 -1
- package/dist/src/core/marionette-preflight.js +9 -3
- package/dist/src/core/parent-exit-watchdog.d.ts +62 -0
- package/dist/src/core/parent-exit-watchdog.js +69 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +12 -0
- package/dist/src/core/patch-apply-fuzz.js +27 -6
- package/dist/src/core/patch-apply.d.ts +13 -0
- package/dist/src/core/patch-apply.js +27 -2
- package/dist/src/core/patch-lint-checkjs-cache.d.ts +18 -0
- package/dist/src/core/patch-lint-checkjs-cache.js +45 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +6 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -1
- package/dist/src/core/patch-lint-cross.d.ts +7 -1
- package/dist/src/core/patch-lint-cross.js +13 -12
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +38 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +240 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +18 -5
- package/dist/src/core/patch-lint-forward-registration.js +83 -37
- package/dist/src/core/patch-lint.d.ts +11 -0
- package/dist/src/core/patch-lint.js +20 -2
- package/dist/src/core/patch-transform.d.ts +14 -13
- package/dist/src/core/patch-transform.js +29 -17
- package/dist/src/core/rebase-dry-run.d.ts +48 -0
- package/dist/src/core/rebase-dry-run.js +105 -0
- package/dist/src/core/run-profile.d.ts +50 -0
- package/dist/src/core/run-profile.js +131 -0
- package/dist/src/core/status-binary.d.ts +1 -1
- package/dist/src/core/status-binary.js +3 -1
- package/dist/src/core/status-classify.js +29 -2
- package/dist/src/core/toolchain-preflight.d.ts +6 -0
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/typecheck.d.ts +11 -1
- package/dist/src/core/typecheck.js +85 -11
- package/dist/src/errors/rebase.d.ts +9 -0
- package/dist/src/errors/rebase.js +20 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +2 -1
- package/dist/src/types/commands/options.d.ts +26 -1
- package/dist/src/types/config.d.ts +17 -0
- package/dist/src/utils/process-group.d.ts +5 -1
- package/dist/src/utils/process-group.js +7 -3
- package/dist/src/utils/process-input.d.ts +27 -0
- package/dist/src/utils/process-input.js +49 -0
- package/dist/src/utils/process.d.ts +20 -0
- package/dist/src/utils/process.js +16 -3
- package/dist/src/utils/validation.d.ts +11 -0
- package/dist/src/utils/validation.js +43 -0
- package/docs/configuration.md +88 -1
- package/docs/exit-codes.md +1 -1
- package/docs/lifecycle-invariants.md +21 -4
- package/docs/machine-output.md +22 -9
- package/docs/patch-workflow.md +35 -1
- package/docs/testing.md +68 -4
- package/package.json +7 -3
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* cost startup time but make results reproducible. The combined invocation
|
|
11
11
|
* stays available via `--no-shard`.
|
|
12
12
|
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
13
14
|
import { probeDisplaySleepState } from '../core/display-state.js';
|
|
14
15
|
import { runMachTestSuite } from '../core/mach.js';
|
|
16
|
+
import { parentExited } from '../core/parent-exit-watchdog.js';
|
|
15
17
|
import { getActiveRunLogPath } from '../core/run-log.js';
|
|
16
18
|
import { changedPrefNoiseVerdictNote } from '../core/test-changed-prefs.js';
|
|
17
19
|
import { buildHarnessCrashMessage, classifyHarnessRun, headedDisplayAsleepVerdictNote, headedNoOutputTimeoutHint, } from '../core/test-harness-crash.js';
|
|
@@ -21,6 +23,7 @@ import { TestFailureError } from '../errors/build.js';
|
|
|
21
23
|
import { info, note, warn } from '../utils/logger.js';
|
|
22
24
|
import { getPlatform } from '../utils/platform.js';
|
|
23
25
|
import { maybeInjectAppdirArg } from './test-appdir.js';
|
|
26
|
+
import { createHarnessTeardown } from './test-harness-teardown.js';
|
|
24
27
|
import { emitHarnessVerdict } from './test-verdict.js';
|
|
25
28
|
/** Default bounded retry budget for recognized harness crashes. */
|
|
26
29
|
export const DEFAULT_HARNESS_RETRIES = 2;
|
|
@@ -61,13 +64,14 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
|
61
64
|
let verdict;
|
|
62
65
|
for (;;) {
|
|
63
66
|
attempts += 1;
|
|
64
|
-
result = await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
result = await runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env);
|
|
68
|
+
// The parent that was reading this run is gone and the watchdog has
|
|
69
|
+
// already killed the harness. Classifying the corpse would call it a
|
|
70
|
+
// harness crash and retry it under nobody's supervision.
|
|
71
|
+
if (parentExited()) {
|
|
72
|
+
throw new TestFailureError('The parent process exited while the harness was running; the harness tree was ' +
|
|
73
|
+
'terminated and the run ended without a result.', `mach ${kind}`, undefined, getActiveRunLogPath());
|
|
74
|
+
}
|
|
71
75
|
result = await retryAfterXpcshellSymlinkRepair({
|
|
72
76
|
engineDir: ctx.engineDir,
|
|
73
77
|
objDir: ctx.objDir,
|
|
@@ -107,6 +111,31 @@ async function runTestsWithRetriesInner(ctx, paths, env) {
|
|
|
107
111
|
}
|
|
108
112
|
return { result, verdict, attempts, appdirInjectionAttempted, displayState };
|
|
109
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* One mach dispatch with its kill path attached: helper-pid tracking and
|
|
116
|
+
* post-close reaping, the process-group announcement, and the parent-exit
|
|
117
|
+
* watchdog for exactly as long as mach runs.
|
|
118
|
+
*/
|
|
119
|
+
async function runTrackedMachTestSuite(ctx, kind, paths, extraArgs, env) {
|
|
120
|
+
const teardown = createHarnessTeardown({
|
|
121
|
+
objDir: ctx.objDir === undefined ? undefined : join(ctx.engineDir, ctx.objDir),
|
|
122
|
+
pgidFile: ctx.pgidFile,
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
await teardown.prepare();
|
|
126
|
+
return await runMachTestSuite(kind, {
|
|
127
|
+
engineDir: ctx.engineDir,
|
|
128
|
+
testPaths: paths,
|
|
129
|
+
args: extraArgs,
|
|
130
|
+
env,
|
|
131
|
+
fullOutput: ctx.fullOutput,
|
|
132
|
+
teardown: teardown.hooks,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
teardown.dispose();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
110
139
|
/**
|
|
111
140
|
* Probes the display's power state for a headed no-output stall, and only for
|
|
112
141
|
* that shape. Every other outcome (a passing run, a real test failure, a
|
|
@@ -12,8 +12,16 @@ import type { HarnessRunVerdict } from '../core/test-harness-crash.js';
|
|
|
12
12
|
* of the others.
|
|
13
13
|
*/
|
|
14
14
|
export type FireforgeVerdictReason = 'crash' | 'no-tests' | 'test-failures' | 'preflight' | 'inconclusive' | 'lock-timeout' | 'killed';
|
|
15
|
+
/** Additive keys a run can stamp on its verdict line. */
|
|
16
|
+
export type VerdictRunAttributeKey = 'shuffle' | 'orphans-reaped';
|
|
15
17
|
/** Records an additive attribute for this run's verdict line. */
|
|
16
|
-
export declare function setVerdictRunAttribute(key:
|
|
18
|
+
export declare function setVerdictRunAttribute(key: VerdictRunAttributeKey, value: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Adds to a counting attribute. The key appears only once the total is
|
|
21
|
+
* positive, so a run that reaped nothing prints the line it always did.
|
|
22
|
+
* Sums across the preflight census, every retry attempt and every shard.
|
|
23
|
+
*/
|
|
24
|
+
export declare function addVerdictRunCount(key: 'orphans-reaped', count: number): void;
|
|
17
25
|
/**
|
|
18
26
|
* Re-arms the verdict sink for a new run. Called at `testCommand` entry so
|
|
19
27
|
* repeated programmatic invocations (and tests) each get their own
|
|
@@ -49,7 +57,14 @@ export declare function emitPassVerdict(): void;
|
|
|
49
57
|
*/
|
|
50
58
|
export declare function emitFailVerdict(reason: FireforgeVerdictReason, note?: string): void;
|
|
51
59
|
/**
|
|
52
|
-
*
|
|
60
|
+
* What ended a killed run: a signal FireForge received, or `parent-exit`,
|
|
61
|
+
* the parent-death watchdog's finding that the process above FireForge
|
|
62
|
+
* vanished (typically SIGKILLed by a supervisor) while a harness ran.
|
|
63
|
+
*/
|
|
64
|
+
export type KilledVerdictCause = NodeJS.Signals | 'parent-exit';
|
|
65
|
+
/**
|
|
66
|
+
* Emits the terminal verdict for a run killed by a signal, or ended by
|
|
67
|
+
* FireForge itself because its parent died.
|
|
53
68
|
*
|
|
54
69
|
* Without it a killed run wrote no terminal line, so a log tail could not
|
|
55
70
|
* distinguish "killed" from "still running" from "never started". Recovery
|
|
@@ -59,7 +74,7 @@ export declare function emitFailVerdict(reason: FireforgeVerdictReason, note?: s
|
|
|
59
74
|
* line asserts a test run happened, and an interrupted `status` did not
|
|
60
75
|
* have one.
|
|
61
76
|
*
|
|
62
|
-
* @param signal - The signal that terminated the run
|
|
77
|
+
* @param signal - The signal that terminated the run, or `parent-exit`
|
|
63
78
|
* @returns True when a line was written
|
|
64
79
|
*/
|
|
65
|
-
export declare function emitKilledVerdict(signal:
|
|
80
|
+
export declare function emitKilledVerdict(signal: KilledVerdictCause): boolean;
|
|
@@ -7,15 +7,32 @@ let emitted = false;
|
|
|
7
7
|
* Additive `key=value` attributes of the run in flight, appended to
|
|
8
8
|
* whichever verdict line ends it. `shuffle` is the seed a `--shuffle` run
|
|
9
9
|
* exported to the harness, so a red found by shuffling carries its own
|
|
10
|
-
* reproduction on the one line an unattended reader keeps.
|
|
10
|
+
* reproduction on the one line an unattended reader keeps. `orphans-reaped`
|
|
11
|
+
* is how many harness processes FireForge had to terminate itself, at
|
|
12
|
+
* preflight (survivors of an earlier run) or at teardown (helpers this run
|
|
13
|
+
* launched that outlived mach): a green after a reap is not the same
|
|
14
|
+
* finding as a green on a quiet machine. Cleared by
|
|
11
15
|
* {@link resetVerdictEmission}, so a later run in the same process cannot
|
|
12
|
-
* inherit
|
|
16
|
+
* inherit them.
|
|
13
17
|
*/
|
|
14
18
|
const runAttributes = new Map();
|
|
15
19
|
/** Records an additive attribute for this run's verdict line. */
|
|
16
20
|
export function setVerdictRunAttribute(key, value) {
|
|
17
21
|
runAttributes.set(key, value);
|
|
18
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Adds to a counting attribute. The key appears only once the total is
|
|
25
|
+
* positive, so a run that reaped nothing prints the line it always did.
|
|
26
|
+
* Sums across the preflight census, every retry attempt and every shard.
|
|
27
|
+
*/
|
|
28
|
+
export function addVerdictRunCount(key, count) {
|
|
29
|
+
// Written as a negated `> 0` so a NaN (a stubbed census, a bad parse) is
|
|
30
|
+
// dropped rather than stamped on the line as `orphans-reaped=NaN`.
|
|
31
|
+
if (!(count > 0))
|
|
32
|
+
return;
|
|
33
|
+
const current = Number(runAttributes.get(key) ?? '0');
|
|
34
|
+
runAttributes.set(key, String(current + count));
|
|
35
|
+
}
|
|
19
36
|
/**
|
|
20
37
|
* True once a test run has begun in this process.
|
|
21
38
|
*
|
|
@@ -96,7 +113,8 @@ export function emitFailVerdict(reason, note) {
|
|
|
96
113
|
writeVerdictLine(`FIREFORGE-VERDICT: FAIL reason=${reason}${suffix}`);
|
|
97
114
|
}
|
|
98
115
|
/**
|
|
99
|
-
* Emits the terminal verdict for a run killed by a signal
|
|
116
|
+
* Emits the terminal verdict for a run killed by a signal, or ended by
|
|
117
|
+
* FireForge itself because its parent died.
|
|
100
118
|
*
|
|
101
119
|
* Without it a killed run wrote no terminal line, so a log tail could not
|
|
102
120
|
* distinguish "killed" from "still running" from "never started". Recovery
|
|
@@ -106,7 +124,7 @@ export function emitFailVerdict(reason, note) {
|
|
|
106
124
|
* line asserts a test run happened, and an interrupted `status` did not
|
|
107
125
|
* have one.
|
|
108
126
|
*
|
|
109
|
-
* @param signal - The signal that terminated the run
|
|
127
|
+
* @param signal - The signal that terminated the run, or `parent-exit`
|
|
110
128
|
* @returns True when a line was written
|
|
111
129
|
*/
|
|
112
130
|
export function emitKilledVerdict(signal) {
|
|
@@ -22,9 +22,10 @@ import { stripEnginePrefix } from '../utils/paths.js';
|
|
|
22
22
|
import { runTestBuildPhase } from './test-build-phase.js';
|
|
23
23
|
import { diagnoseShardOutcome, finalizeSingleRunOutcome } from './test-diagnose.js';
|
|
24
24
|
import { buildPerfSampleEnv, mergeHarnessEnv, resolveShuffleSeed } from './test-harness-env.js';
|
|
25
|
+
import { removePgidFile, setActivePgidFile } from './test-harness-teardown.js';
|
|
25
26
|
import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, classifyBeforeDispatch, reportCanaryOutcome, resolveCanaryPath, } from './test-modes.js';
|
|
26
27
|
import { DEFAULT_HARNESS_RETRIES, finalizeShardedOutcome, runShardedTests, runTestsWithRetries, } from './test-run.js';
|
|
27
|
-
import { emitFailVerdict, emitPassVerdict, resetVerdictEmission, verdictEmitted, } from './test-verdict.js';
|
|
28
|
+
import { addVerdictRunCount, emitFailVerdict, emitPassVerdict, resetVerdictEmission, verdictEmitted, } from './test-verdict.js';
|
|
28
29
|
async function assertTestPathsExist(engineDir, testPaths) {
|
|
29
30
|
const missingPaths = [];
|
|
30
31
|
for (const testPath of testPaths) {
|
|
@@ -227,7 +228,7 @@ async function ensureTestMarionettePortAvailable(port, binaryName, options, skip
|
|
|
227
228
|
}
|
|
228
229
|
await assertMarionettePortAvailable(port, { binaryName });
|
|
229
230
|
}
|
|
230
|
-
async function ensureTestBrowserEnvironment(engineDir, launchablePath, xpcshellOnly,
|
|
231
|
+
async function ensureTestBrowserEnvironment(engineDir, launchablePath, xpcshellOnly, projectConfig, options, objDir) {
|
|
231
232
|
// A timed-out mochitest can leave the built app alive after its Marionette
|
|
232
233
|
// listener has disappeared. The port probe cannot see that case, but the
|
|
233
234
|
// survivor can still steal focus and wedge every later headed run.
|
|
@@ -249,14 +250,18 @@ async function ensureTestBrowserEnvironment(engineDir, launchablePath, xpcshellO
|
|
|
249
250
|
// Helpers that outlived an earlier run (httpd, pywebsocket, ssltunnel,
|
|
250
251
|
// moz-http2) slow every later run without appearing in its output. Both
|
|
251
252
|
// harnesses are affected, so this census is not gated on xpcshellOnly.
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
// Reaping is opt-in per invocation (--reap-orphans) or per repo
|
|
254
|
+
// (test.reapOrphans: "reap"); a reap is stamped on the verdict line so a
|
|
255
|
+
// green after one is not mistaken for a green on a quiet machine.
|
|
256
|
+
const census = await reportOrphanedHarnessProcesses(objDir, {
|
|
257
|
+
reap: options.reapOrphans === true || projectConfig.test?.reapOrphans === 'reap',
|
|
254
258
|
});
|
|
259
|
+
addVerdictRunCount('orphans-reaped', census.reaped);
|
|
255
260
|
const forwardedPort = options.machArg
|
|
256
261
|
? extractForwardedMarionettePort(options.machArg)
|
|
257
262
|
: undefined;
|
|
258
263
|
const effectivePort = options.marionettePort ?? forwardedPort;
|
|
259
|
-
await ensureTestMarionettePortAvailable(effectivePort, binaryName, options, {
|
|
264
|
+
await ensureTestMarionettePortAvailable(effectivePort, projectConfig.binaryName, options, {
|
|
260
265
|
xpcshellOnly,
|
|
261
266
|
doctor: options.doctor === true,
|
|
262
267
|
});
|
|
@@ -291,6 +296,7 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
291
296
|
// exactly the runs whose only output a `tail` throws away. Closed in
|
|
292
297
|
// `finally`, after the verdict line has already read the path.
|
|
293
298
|
setActiveRunLog(await openRunLog(projectRoot, 'test'));
|
|
299
|
+
setActivePgidFile(options.pgidFile);
|
|
294
300
|
try {
|
|
295
301
|
await runTestCommandBody(projectRoot, testPaths, options);
|
|
296
302
|
}
|
|
@@ -302,6 +308,9 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
302
308
|
throw error;
|
|
303
309
|
}
|
|
304
310
|
finally {
|
|
311
|
+
// A run that ends under FireForge's control owns no harness group any
|
|
312
|
+
// more; the file only outlives FireForge when FireForge was killed.
|
|
313
|
+
await removePgidFile(options.pgidFile);
|
|
305
314
|
await closeActiveRunLog();
|
|
306
315
|
}
|
|
307
316
|
}
|
|
@@ -405,7 +414,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}) {
|
|
|
405
414
|
// documented `--mach-arg --marionette-port=NNNN` route still hits the
|
|
406
415
|
// wrapper preflight refusing on 2828 before the forwarded arg reaches
|
|
407
416
|
// mach.
|
|
408
|
-
const { forwardedPort, effectivePort } = await ensureTestBrowserEnvironment(paths.engine, launchablePath, xpcshellOnly, projectConfig
|
|
417
|
+
const { forwardedPort, effectivePort } = await ensureTestBrowserEnvironment(paths.engine, launchablePath, xpcshellOnly, projectConfig, options, buildCheck.objDir);
|
|
409
418
|
if (options.doctor) {
|
|
410
419
|
const doctorOutcome = await runDoctorPreflight({
|
|
411
420
|
engineDir: paths.engine,
|
|
@@ -434,17 +443,15 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}) {
|
|
|
434
443
|
if (options.auto === true) {
|
|
435
444
|
extraArgs.push('--auto');
|
|
436
445
|
}
|
|
437
|
-
if (canaryPath !== undefined)
|
|
446
|
+
if (canaryPath !== undefined)
|
|
438
447
|
extraArgs.push(`--timeout=${canaryTimeoutSeconds(projectConfig)}`);
|
|
439
|
-
}
|
|
440
448
|
// --mach-arg is a verbatim passthrough for upstream mach/xpcshell/mochitest
|
|
441
449
|
// flags FireForge does not model directly (see the xpcshell appdir hint
|
|
442
450
|
// above for why). Appended after --headless so mach sees
|
|
443
451
|
// the FireForge-managed flags first and the escape-valve ones last, which
|
|
444
452
|
// keeps the override precedence predictable.
|
|
445
|
-
if (forwardedMachArgs.length > 0)
|
|
453
|
+
if (forwardedMachArgs.length > 0)
|
|
446
454
|
extraArgs.push(...forwardedMachArgs);
|
|
447
|
-
}
|
|
448
455
|
appendMarionetteForwardingArgs(extraArgs, options, forwardedPort, xpcshellOnly);
|
|
449
456
|
// Directory arguments mean exactly that directory: mozbuild's test
|
|
450
457
|
// resolver matches paths by string prefix, so a bare directory arg
|
|
@@ -482,6 +489,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}) {
|
|
|
482
489
|
headless: options.headless === true,
|
|
483
490
|
...(options.fullOutput === true ? { fullOutput: true } : {}),
|
|
484
491
|
...(harnessEnv ? { env: harnessEnv } : {}),
|
|
492
|
+
pgidFile: options.pgidFile,
|
|
485
493
|
};
|
|
486
494
|
const postRebuildContext = options.build
|
|
487
495
|
? createPostRebuildFailureContext('fireforge test --build', normalizedPaths)
|
|
@@ -26,6 +26,11 @@ export interface TypecheckCommandOptions {
|
|
|
26
26
|
* issues without waiting for the full set.
|
|
27
27
|
*/
|
|
28
28
|
project?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Build every project from scratch and write no incremental build info
|
|
31
|
+
* (`--no-cache`).
|
|
32
|
+
*/
|
|
33
|
+
noCache?: boolean;
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* Resolves the project list to type-check. `--project` wins over
|
|
@@ -88,7 +88,9 @@ async function runTypecheckCommandBody(projectRoot, options) {
|
|
|
88
88
|
// use so typecheck checks against the current workspace.
|
|
89
89
|
await regenerateStaleGeneratedJsconfig(projectRoot);
|
|
90
90
|
info(`Running typecheck across ${cfg.projects.length} project(s): ${cfg.projects.join(', ')}`);
|
|
91
|
-
const results = await runTypecheck(projectRoot, cfg
|
|
91
|
+
const results = await runTypecheck(projectRoot, cfg, {
|
|
92
|
+
...(options.noCache === true ? { noCache: true } : {}),
|
|
93
|
+
});
|
|
92
94
|
// Fold the EXPORT-TIME authority into this pass. Per-patch lint runs
|
|
93
95
|
// checkJs over the queue's patch-owned modules in relative isolation and
|
|
94
96
|
// resolves imported typedefs differently from the whole-project pass, so
|
|
@@ -193,11 +195,15 @@ export function registerTypecheck(program, { getProjectRoot, withErrorHandling }
|
|
|
193
195
|
.command('typecheck')
|
|
194
196
|
.description('Run TypeScript type checking against project-owned jsconfig.json files (CI-grade, whole-project)')
|
|
195
197
|
.option('--project <path>', 'Override typecheck.projects with a single jsconfig.json path (one-off run)')
|
|
198
|
+
.option('--no-cache', 'Check every project from scratch; neither read nor write the incremental build info in .fireforge/typecheck/')
|
|
196
199
|
.action(withErrorHandling(async (options) => {
|
|
197
200
|
const opts = {};
|
|
198
201
|
if (options.project !== undefined) {
|
|
199
202
|
opts.project = options.project;
|
|
200
203
|
}
|
|
204
|
+
if (options.cache === false) {
|
|
205
|
+
opts.noCache = true;
|
|
206
|
+
}
|
|
201
207
|
await typecheckCommand(getProjectRoot(), opts);
|
|
202
208
|
}));
|
|
203
209
|
}
|
|
@@ -58,6 +58,8 @@ export declare function isBackendInvalidatingFile(path: string): boolean;
|
|
|
58
58
|
* 3. Sets up branding directory if not already done
|
|
59
59
|
* 4. Applies Furnace components if furnace.json exists
|
|
60
60
|
* 5. Generates mozconfig
|
|
61
|
+
* 6. Runs `mach configure` when a backend-invalidating input, including
|
|
62
|
+
* the mozconfig just generated, changed since the previous baseline
|
|
61
63
|
*
|
|
62
64
|
* @param projectRoot - Root directory of the project
|
|
63
65
|
* @param paths - Resolved project paths
|
|
@@ -14,6 +14,7 @@ import { isBrandingSetup, setupBranding } from './branding.js';
|
|
|
14
14
|
import { isBuildInputPath, isJarManifestPath } from './build-audit.js';
|
|
15
15
|
import { evaluateJarManifestEscalation, formatJarEscalationNotice, } from './build-jar-escalation.js';
|
|
16
16
|
import { findUnexportedDriftAtRisk, formatUnexportedDriftWarning, } from './build-overwrite-guard.js';
|
|
17
|
+
import { hashEngineFile } from './coverage-extend.js';
|
|
17
18
|
import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
|
|
18
19
|
import { applyAllComponents } from './furnace-apply.js';
|
|
19
20
|
import { logApplyWarnings } from './furnace-apply-output.js';
|
|
@@ -231,6 +232,40 @@ async function applyFurnaceBeforeBuild(projectRoot) {
|
|
|
231
232
|
}
|
|
232
233
|
return furnaceApplied;
|
|
233
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Runs the auto-configure step (`mach configure`) against the freshly
|
|
237
|
+
* generated mozconfig, surfacing the underlying mozbuild error on failure.
|
|
238
|
+
* @param engineDir - Path to the engine directory
|
|
239
|
+
* @param triggers - Why the backend is being regenerated, for the notice
|
|
240
|
+
*/
|
|
241
|
+
async function runAutoConfigure(engineDir, triggers) {
|
|
242
|
+
// Warning severity: this is the "why is this run slow" explanation,
|
|
243
|
+
// and an info-level line does not survive an agent output filter that
|
|
244
|
+
// keeps only warnings and errors.
|
|
245
|
+
notice(`Backend config changed; running backend regeneration first (${triggers}). Backend command: mach configure.`);
|
|
246
|
+
const configureSpinner = spinner('Running mach configure...');
|
|
247
|
+
try {
|
|
248
|
+
const captured = await runMachCapture(['configure'], engineDir);
|
|
249
|
+
const exitCode = captured.exitCode;
|
|
250
|
+
if (exitCode !== 0) {
|
|
251
|
+
configureSpinner.error(`mach configure failed with exit code ${exitCode}`);
|
|
252
|
+
// Surface the underlying mozbuild error (e.g. UnsortedError)
|
|
253
|
+
// instead of a bare exit code, since the generic message hid the
|
|
254
|
+
// actual cause, plus any matched mach-error hints (see the helper).
|
|
255
|
+
throw buildConfigureFailureError(captured);
|
|
256
|
+
}
|
|
257
|
+
configureSpinner.stop('Backend regenerated successfully (mach configure exit code 0)');
|
|
258
|
+
info('Backend regeneration succeeded; continuing with build.');
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (error instanceof BuildError) {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
configureSpinner.error('mach configure failed');
|
|
265
|
+
verbose(`Auto-configure error: ${toError(error).message}`);
|
|
266
|
+
throw new BuildError(`Backend regeneration failed while running mach configure: ${toError(error).message}. Build stopped because continuing would hide the real configure failure.`, 'mach configure', error instanceof Error ? error : undefined);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
234
269
|
/**
|
|
235
270
|
* Runs the shared pre-flight steps for build and package commands:
|
|
236
271
|
* 1. Warns (or refuses) about unexported engine drift the writes below
|
|
@@ -239,6 +274,8 @@ async function applyFurnaceBeforeBuild(projectRoot) {
|
|
|
239
274
|
* 3. Sets up branding directory if not already done
|
|
240
275
|
* 4. Applies Furnace components if furnace.json exists
|
|
241
276
|
* 5. Generates mozconfig
|
|
277
|
+
* 6. Runs `mach configure` when a backend-invalidating input, including
|
|
278
|
+
* the mozconfig just generated, changed since the previous baseline
|
|
242
279
|
*
|
|
243
280
|
* @param projectRoot - Root directory of the project
|
|
244
281
|
* @param paths - Resolved project paths
|
|
@@ -259,11 +296,16 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
259
296
|
}
|
|
260
297
|
}
|
|
261
298
|
await reportUnexportedDriftBeforeOverwrite(projectRoot, config, options);
|
|
262
|
-
// Auto-configure: if any backend-invalidating file (moz.build,
|
|
263
|
-
// Makefile.in) changed since the last successful build,
|
|
264
|
-
// before the build step. Prevents incremental builds
|
|
265
|
-
// work against a stale recursive-make backend.
|
|
266
|
-
|
|
299
|
+
// Auto-configure census: if any backend-invalidating file (moz.build,
|
|
300
|
+
// moz.configure, Makefile.in) changed since the last successful build,
|
|
301
|
+
// `mach configure` runs before the build step. Prevents incremental builds
|
|
302
|
+
// from silently skipping work against a stale recursive-make backend. The
|
|
303
|
+
// configure itself waits until the mozconfig below has been regenerated:
|
|
304
|
+
// run earlier, it configured against the previous engine/mozconfig, and a
|
|
305
|
+
// configs/ fix for a failing configure (a pinned --with-macos-sdk) could
|
|
306
|
+
// never take effect because the failure stopped the build before the
|
|
307
|
+
// regeneration.
|
|
308
|
+
const configureTriggers = [];
|
|
267
309
|
let fullBuildRequired = false;
|
|
268
310
|
let fullBuildReason;
|
|
269
311
|
if (options.previousBaseline) {
|
|
@@ -273,35 +315,7 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
273
315
|
fullBuildRequired = jarEscalation !== undefined;
|
|
274
316
|
fullBuildReason = jarEscalation;
|
|
275
317
|
if (invalidating.length > 0) {
|
|
276
|
-
|
|
277
|
-
// and an info-level line does not survive an agent output filter that
|
|
278
|
-
// keeps only warnings and errors.
|
|
279
|
-
notice(`Backend config changed; running backend regeneration first (${invalidating.length} file${invalidating.length === 1 ? '' : 's'} touched). Backend command: mach configure.`);
|
|
280
|
-
const configureSpinner = spinner('Running mach configure...');
|
|
281
|
-
try {
|
|
282
|
-
const captured = await runMachCapture(['configure'], paths.engine);
|
|
283
|
-
const exitCode = captured.exitCode;
|
|
284
|
-
if (exitCode !== 0) {
|
|
285
|
-
configureSpinner.error(`mach configure failed with exit code ${exitCode}`);
|
|
286
|
-
// Surface the underlying mozbuild error (e.g. UnsortedError)
|
|
287
|
-
// instead of a bare exit code, since the generic message hid the
|
|
288
|
-
// actual cause, plus any matched mach-error hints (see the helper).
|
|
289
|
-
throw buildConfigureFailureError(captured);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
configureSpinner.stop('Backend regenerated successfully (mach configure exit code 0)');
|
|
293
|
-
info('Backend regeneration succeeded; continuing with build.');
|
|
294
|
-
reconfigured = true;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
if (error instanceof BuildError) {
|
|
299
|
-
throw error;
|
|
300
|
-
}
|
|
301
|
-
configureSpinner.error('mach configure failed');
|
|
302
|
-
verbose(`Auto-configure error: ${toError(error).message}`);
|
|
303
|
-
throw new BuildError(`Backend regeneration failed while running mach configure: ${toError(error).message}. Build stopped because continuing would hide the real configure failure.`, 'mach configure', error instanceof Error ? error : undefined);
|
|
304
|
-
}
|
|
318
|
+
configureTriggers.push(`${invalidating.length} file${invalidating.length === 1 ? '' : 's'} touched`);
|
|
305
319
|
}
|
|
306
320
|
}
|
|
307
321
|
// Clean stories before build to ensure they don't leak into production binary
|
|
@@ -333,14 +347,32 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
333
347
|
const furnaceApplied = await applyFurnaceBeforeBuild(projectRoot);
|
|
334
348
|
// Generate mozconfig
|
|
335
349
|
const mozconfigSpinner = spinner('Generating mozconfig...');
|
|
350
|
+
let mozconfigWritten;
|
|
336
351
|
try {
|
|
337
|
-
await generateMozconfig(paths.configs, paths.engine, config);
|
|
352
|
+
mozconfigWritten = await generateMozconfig(paths.configs, paths.engine, config);
|
|
338
353
|
mozconfigSpinner.stop('mozconfig generated');
|
|
339
354
|
}
|
|
340
355
|
catch (error) {
|
|
341
356
|
mozconfigSpinner.error('Failed to generate mozconfig');
|
|
342
357
|
throw error;
|
|
343
358
|
}
|
|
359
|
+
// A changed mozconfig is itself backend-invalidating. Compare against the
|
|
360
|
+
// hash the last successful build recorded; a baseline from before that
|
|
361
|
+
// field existed falls back to whether this run rewrote the file.
|
|
362
|
+
if (options.previousBaseline) {
|
|
363
|
+
const recorded = options.previousBaseline.mozconfigHash;
|
|
364
|
+
const mozconfigChanged = recorded !== undefined
|
|
365
|
+
? (await hashEngineFile(paths.engine, 'mozconfig')) !== recorded
|
|
366
|
+
: mozconfigWritten;
|
|
367
|
+
if (mozconfigChanged) {
|
|
368
|
+
configureTriggers.push('engine/mozconfig changed since the last build');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
let reconfigured = false;
|
|
372
|
+
if (configureTriggers.length > 0) {
|
|
373
|
+
await runAutoConfigure(paths.engine, configureTriggers.join('; '));
|
|
374
|
+
reconfigured = true;
|
|
375
|
+
}
|
|
344
376
|
return {
|
|
345
377
|
furnaceApplied,
|
|
346
378
|
reconfigured,
|
|
@@ -7,7 +7,7 @@ export declare const FIREFORGE_DIR = ".fireforge";
|
|
|
7
7
|
/** Supported top-level fireforge.json keys backed by the current schema. */
|
|
8
8
|
export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "buildAudit", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
|
|
9
9
|
/** Supported config paths that can be read or set without --force. */
|
|
10
|
-
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "firefox.allowUnverifiedDownload", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
|
|
10
|
+
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "firefox.allowUnverifiedDownload", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "test.reapOrphans", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchLint.forwardRegistration", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
|
|
11
11
|
/**
|
|
12
12
|
* Gets all project paths based on a root directory.
|
|
13
13
|
* @param root - Root directory of the project
|
|
@@ -53,6 +53,7 @@ export const SUPPORTED_CONFIG_PATHS = [
|
|
|
53
53
|
'test',
|
|
54
54
|
'test.canaryPath',
|
|
55
55
|
'test.canaryTimeoutSeconds',
|
|
56
|
+
'test.reapOrphans',
|
|
56
57
|
'externalToolchains',
|
|
57
58
|
'wire',
|
|
58
59
|
'wire.subscriptDir',
|
|
@@ -67,6 +68,7 @@ export const SUPPORTED_CONFIG_PATHS = [
|
|
|
67
68
|
'patchLint.jsdocClassMethods',
|
|
68
69
|
'patchLint.testAssertionFloor',
|
|
69
70
|
'patchLint.chromeScriptJsDoc',
|
|
71
|
+
'patchLint.forwardRegistration',
|
|
70
72
|
'patchPolicy',
|
|
71
73
|
'typecheck',
|
|
72
74
|
'typecheck.projects',
|
|
@@ -45,6 +45,13 @@ export function parseTestBlock(rec, config) {
|
|
|
45
45
|
}
|
|
46
46
|
out.canaryTimeoutSeconds = canaryTimeout;
|
|
47
47
|
}
|
|
48
|
+
const reapOrphans = testRec.raw('reapOrphans');
|
|
49
|
+
if (reapOrphans !== undefined) {
|
|
50
|
+
if (reapOrphans !== 'report' && reapOrphans !== 'reap') {
|
|
51
|
+
throw new ConfigError('Config field "test.reapOrphans" must be "report" or "reap"');
|
|
52
|
+
}
|
|
53
|
+
out.reapOrphans = reapOrphans;
|
|
54
|
+
}
|
|
48
55
|
config.test = out;
|
|
49
56
|
}
|
|
50
57
|
/** Parses optional project-specific external toolchain doctor probes. */
|
|
@@ -376,6 +376,13 @@ function parsePatchLintBlock(rec) {
|
|
|
376
376
|
if (fileSizeThresholds !== undefined) {
|
|
377
377
|
out.fileSizeThresholds = parsePatchLintFileSizeThresholds(fileSizeThresholds);
|
|
378
378
|
}
|
|
379
|
+
const forwardRegistration = rec.raw('forwardRegistration');
|
|
380
|
+
if (forwardRegistration !== undefined) {
|
|
381
|
+
if (forwardRegistration !== 'support-files' && forwardRegistration !== 'extended') {
|
|
382
|
+
throw new ConfigError('Config field "patchLint.forwardRegistration" must be one of: support-files, extended');
|
|
383
|
+
}
|
|
384
|
+
out.forwardRegistration = forwardRegistration;
|
|
385
|
+
}
|
|
379
386
|
for (const key of PATCH_LINT_SEVERITY_GATE_KEYS) {
|
|
380
387
|
const gate = parseSeverityGate(rec.raw(key), `patchLint.${key}`);
|
|
381
388
|
if (gate !== undefined)
|
|
@@ -53,11 +53,12 @@ export async function planDiscardBaselines(patchesDir, engineDir, entries) {
|
|
|
53
53
|
if (claimedEntries.length === 0) {
|
|
54
54
|
return planUpstreamDiscards(entries);
|
|
55
55
|
}
|
|
56
|
-
const { computePatched } = await createPatchedContentContext(patchesDir, engineDir);
|
|
56
|
+
const { computePatched, prefetchBase } = await createPatchedContentContext(patchesDir, engineDir);
|
|
57
57
|
const claimedFiles = [
|
|
58
58
|
...new Set(claimedEntries.flatMap((entry) => entry.originalPath !== undefined ? [entry.file, entry.originalPath] : [entry.file])),
|
|
59
59
|
];
|
|
60
60
|
const trackedInHead = await listTrackedInHead(engineDir, claimedFiles);
|
|
61
|
+
await prefetchBase(claimedFiles);
|
|
61
62
|
const computeExpected = async (path) => {
|
|
62
63
|
try {
|
|
63
64
|
return await computePatched(path);
|
|
@@ -433,15 +433,13 @@ export async function withFileLock(lockPath, operation, options = {}) {
|
|
|
433
433
|
continue;
|
|
434
434
|
}
|
|
435
435
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
throw await buildLockTimeoutError(lockPath, options.onTimeoutMessage, error, await readLockQueue(lockPath, startedAt));
|
|
444
|
-
}
|
|
436
|
+
// The probe runs before the deadline check, so a due progress line is
|
|
437
|
+
// written before the refusal that follows it, never lost to it. On a
|
|
438
|
+
// slow host (a cold Windows CI runner) the first iteration alone, with
|
|
439
|
+
// its waiter registration and stale probe, can outlast a short budget;
|
|
440
|
+
// checked the other way round, the wait timed out having reported
|
|
441
|
+
// nothing, and an advance that probe would have seen could not extend
|
|
442
|
+
// the deadline it was about to miss.
|
|
445
443
|
if (probeWanted && Date.now() - lastProbeAt >= probeIntervalMs) {
|
|
446
444
|
lastProbeAt = Date.now();
|
|
447
445
|
// Report the budget that is currently in force, not the one
|
|
@@ -473,6 +471,15 @@ export async function withFileLock(lockPath, operation, options = {}) {
|
|
|
473
471
|
}
|
|
474
472
|
}
|
|
475
473
|
}
|
|
474
|
+
if (Date.now() >= deadline) {
|
|
475
|
+
// The queue is read here, not carried over from the last progress
|
|
476
|
+
// probe: the position the wait reached is the one number that says
|
|
477
|
+
// whether a larger budget would have helped, and it must not depend
|
|
478
|
+
// on a probe having happened to land inside the budget. We are still
|
|
479
|
+
// registered at this point. `deregisterWaiter` runs in the `finally`
|
|
480
|
+
// below, which is only entered once this refusal has been built.
|
|
481
|
+
throw await buildLockTimeoutError(lockPath, options.onTimeoutMessage, error, await readLockQueue(lockPath, startedAt));
|
|
482
|
+
}
|
|
476
483
|
await sleep(currentPollMs);
|
|
477
484
|
if (options.pollMaxMs !== undefined) {
|
|
478
485
|
currentPollMs = Math.min(currentPollMs * 2, options.pollMaxMs);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public, queue-level entry point to the `forward-registration` census.
|
|
3
|
+
*
|
|
4
|
+
* A consumer that keeps its own audit (a ratchet baseline over forward
|
|
5
|
+
* edges, say) runs the rule's own predicate here instead of mirroring it.
|
|
6
|
+
* Lives apart from the rule module because it builds the queue context,
|
|
7
|
+
* which imports the rule back.
|
|
8
|
+
*/
|
|
9
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
10
|
+
import type { ForwardRegistration, ForwardRegistrationScope } from './patch-lint-forward-registration-extended.js';
|
|
11
|
+
/** Options for {@link findForwardRegistrations}. */
|
|
12
|
+
export interface FindForwardRegistrationsOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Which carriers to check. Defaults to `'extended'` (every carrier the
|
|
15
|
+
* rule can resolve), since a census wants the whole picture whatever the
|
|
16
|
+
* lint gate is configured to enforce.
|
|
17
|
+
*/
|
|
18
|
+
scope?: ForwardRegistrationScope;
|
|
19
|
+
/** Project config, for the queue's patchPolicy ordering context. Optional. */
|
|
20
|
+
config?: FireForgeConfig;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lists every undeclared forward registration in a patch queue: a line a
|
|
24
|
+
* patch adds (to a test manifest, jar.mn, moz.build or customElements.js)
|
|
25
|
+
* that names a file only a later-ordered patch creates. Each record carries
|
|
26
|
+
* the paste-and-run `patch staged-dependency` command that declares it.
|
|
27
|
+
*
|
|
28
|
+
* @param patchesDir - The project's `patches/` directory
|
|
29
|
+
* @param options - Scope and optional config
|
|
30
|
+
* @returns One record per (patch, carrier file, created file)
|
|
31
|
+
*/
|
|
32
|
+
export declare function findForwardRegistrations(patchesDir: string, options?: FindForwardRegistrationsOptions): Promise<ForwardRegistration[]>;
|
|
33
|
+
export type { ForwardRegistration, ForwardRegistrationKind, ForwardRegistrationScope, } from './patch-lint-forward-registration-extended.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { buildPatchQueueContext } from './patch-lint-cross.js';
|
|
2
|
+
import { collectForwardRegistrations } from './patch-lint-forward-registration.js';
|
|
3
|
+
/**
|
|
4
|
+
* Lists every undeclared forward registration in a patch queue: a line a
|
|
5
|
+
* patch adds (to a test manifest, jar.mn, moz.build or customElements.js)
|
|
6
|
+
* that names a file only a later-ordered patch creates. Each record carries
|
|
7
|
+
* the paste-and-run `patch staged-dependency` command that declares it.
|
|
8
|
+
*
|
|
9
|
+
* @param patchesDir - The project's `patches/` directory
|
|
10
|
+
* @param options - Scope and optional config
|
|
11
|
+
* @returns One record per (patch, carrier file, created file)
|
|
12
|
+
*/
|
|
13
|
+
export async function findForwardRegistrations(patchesDir, options = {}) {
|
|
14
|
+
const ctx = await buildPatchQueueContext(patchesDir, options.config);
|
|
15
|
+
return collectForwardRegistrations(ctx, options.scope ?? 'extended');
|
|
16
|
+
}
|