@hominis/fireforge 0.47.1 → 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 +83 -0
- package/dist/bin/fireforge.js +5 -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.d.ts +8 -0
- package/dist/src/commands/doctor-macos-sdk.js +158 -0
- package/dist/src/commands/doctor-source-pin.js +9 -2
- package/dist/src/commands/doctor.js +4 -0
- package/dist/src/commands/download.js +64 -3
- package/dist/src/commands/furnace/apply.js +10 -4
- package/dist/src/commands/furnace/create-templates.js +0 -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 +14 -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-lint.d.ts +17 -2
- package/dist/src/commands/re-export-lint.js +33 -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 +8 -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-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/commands/typecheck.d.ts +5 -0
- package/dist/src/commands/typecheck.js +7 -1
- 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/build-prepare.d.ts +2 -0
- package/dist/src/core/build-prepare.js +67 -35
- 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/config-paths.d.ts +1 -1
- package/dist/src/core/config-paths.js +1 -0
- package/dist/src/core/config-validate.js +7 -0
- package/dist/src/core/coverage-extend.d.ts +5 -34
- package/dist/src/core/coverage-extend.js +26 -37
- package/dist/src/core/discard-baseline.js +2 -1
- 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/forward-registrations.d.ts +33 -0
- package/dist/src/core/forward-registrations.js +16 -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/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/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-mozconfig.d.ts +2 -1
- package/dist/src/core/mach-mozconfig.js +2 -0
- 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-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 +10 -2
- 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/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/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/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/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 +86 -12
- 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 +29 -3
- package/dist/src/types/config.d.ts +10 -0
- 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.d.ts +27 -0
- package/dist/src/utils/process-input.js +46 -0
- package/dist/src/utils/process.d.ts +5 -2
- package/dist/src/utils/process.js +21 -8
- package/dist/src/utils/validation.d.ts +11 -0
- package/dist/src/utils/validation.js +43 -0
- package/docs/configuration.md +82 -0
- package/docs/exit-codes.md +1 -1
- package/docs/lifecycle-invariants.md +13 -8
- package/docs/machine-output.md +36 -19
- package/docs/patch-workflow.md +50 -1
- package/docs/testing.md +77 -12
- package/package.json +10 -5
|
@@ -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
|
}
|
|
@@ -35,6 +35,12 @@ export interface BuildBaseline {
|
|
|
35
35
|
* a tracked deletion.
|
|
36
36
|
*/
|
|
37
37
|
packageableFingerprints?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* All dirty staging inputs, including plain test scripts and support fixtures.
|
|
40
|
+
* Omitted when any input could not be fingerprinted; a partial map cannot
|
|
41
|
+
* anchor retained coverage.
|
|
42
|
+
*/
|
|
43
|
+
testInputFingerprints?: Record<string, string>;
|
|
38
44
|
/**
|
|
39
45
|
* Content hash per build-input manifest (`jar.mn`, `moz.build`,
|
|
40
46
|
* `moz.configure`, `Makefile.in`, `mozbuild.in`) that was dirty
|
|
@@ -83,20 +89,12 @@ export interface BuildBaseline {
|
|
|
83
89
|
* checkouts are refused up-front (`AmbiguousBuildArtifactsError`), so at
|
|
84
90
|
* most one obj dir exists per project.
|
|
85
91
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* The one exception is `test --build --extend-coverage`, which unions only
|
|
93
|
-
* after proving the previous record's anchor still holds: same engine
|
|
94
|
-
* HEAD, same {@link BuildBaseline.mozconfigHash}, and every previously
|
|
95
|
-
* fingerprinted path byte-identical, i.e. the wholesale fingerprint
|
|
96
|
-
* refresh is a no-op for everything the earlier scope's staging depended
|
|
97
|
-
* on. Any divergence refuses fail-closed. See `src/core/coverage-extend.ts`
|
|
98
|
-
* for the one boundary that guard does not cover (dirty non-packageable
|
|
99
|
-
* fixtures).
|
|
92
|
+
* Successive scoped builds retain the previous claim only when HEAD,
|
|
93
|
+
* mozconfig and complete testInputFingerprints outside the rebuilt scope
|
|
94
|
+
* remain anchored. Explicitly rebuilt paths may change. A failed anchor
|
|
95
|
+
* replaces coverage with a dropped-path notice, or refuses when the caller
|
|
96
|
+
* required --extend-coverage. Legacy packageable-only anchors cannot retain
|
|
97
|
+
* coverage because they cannot prove test scripts and fixtures unchanged.
|
|
100
98
|
*/
|
|
101
99
|
testPackagingCoverage?: TestPackagingCoverage;
|
|
102
100
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { isObject } from '../utils/validation.js';
|
|
3
|
+
import { DELETED_FILE_FINGERPRINT } from './build-baseline-types.js';
|
|
4
|
+
/** Baseline paths are engine-relative POSIX identifiers, never filesystem escapes. */
|
|
5
|
+
function isBaselinePath(value) {
|
|
6
|
+
return (typeof value === 'string' &&
|
|
7
|
+
value.length > 0 &&
|
|
8
|
+
!value.includes('\0') &&
|
|
9
|
+
!value.includes('\\') &&
|
|
10
|
+
!value.includes(':') &&
|
|
11
|
+
value.split('/').every((part) => part.length > 0 && part !== '.' && part !== '..'));
|
|
12
|
+
}
|
|
13
|
+
function isFingerprint(value) {
|
|
14
|
+
return (typeof value === 'string' &&
|
|
15
|
+
(/^[a-f0-9]{64}$/i.test(value) || value === DELETED_FILE_FINGERPRINT));
|
|
16
|
+
}
|
|
17
|
+
function isFingerprintMap(value) {
|
|
18
|
+
return (isObject(value) &&
|
|
19
|
+
Object.entries(value).every(([path, fingerprint]) => isBaselinePath(path) && isFingerprint(fingerprint)));
|
|
20
|
+
}
|
|
21
|
+
/** Validates persisted fields without converting malformed coverage into a full claim. */
|
|
22
|
+
export function isBuildBaseline(value) {
|
|
23
|
+
if (!isObject(value))
|
|
24
|
+
return false;
|
|
25
|
+
if (typeof value['engineHeadSha'] !== 'string' ||
|
|
26
|
+
typeof value['builtAt'] !== 'string' ||
|
|
27
|
+
!Number.isFinite(Date.parse(value['builtAt'])) ||
|
|
28
|
+
typeof value['binaryName'] !== 'string' ||
|
|
29
|
+
!/^[a-z][a-z0-9-]*$/.test(value['binaryName']))
|
|
30
|
+
return false;
|
|
31
|
+
for (const field of [
|
|
32
|
+
'packageableFingerprints',
|
|
33
|
+
'testInputFingerprints',
|
|
34
|
+
'buildInputFingerprints',
|
|
35
|
+
]) {
|
|
36
|
+
if (value[field] !== undefined && !isFingerprintMap(value[field]))
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const coverage = value['testPackagingCoverage'];
|
|
40
|
+
if (coverage !== undefined &&
|
|
41
|
+
coverage !== 'full' &&
|
|
42
|
+
!(Array.isArray(coverage) && coverage.every(isBaselinePath)))
|
|
43
|
+
return false;
|
|
44
|
+
if (value['mozconfigHash'] !== undefined && !isFingerprint(value['mozconfigHash']))
|
|
45
|
+
return false;
|
|
46
|
+
if (value['recordedBy'] !== undefined && typeof value['recordedBy'] !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
const components = value['staticComponentsBaseline'];
|
|
49
|
+
return (components === undefined ||
|
|
50
|
+
(isObject(components) &&
|
|
51
|
+
typeof components['engineHeadSha'] === 'string' &&
|
|
52
|
+
isFingerprintMap(components['fingerprints'])));
|
|
53
|
+
}
|
|
@@ -33,11 +33,12 @@ export type BaselineBuildKind = 'full' | 'faster';
|
|
|
33
33
|
export declare function getBuildBaselinePath(projectRoot: string): string;
|
|
34
34
|
/**
|
|
35
35
|
* Reads the last-build baseline if present. Returns undefined when no
|
|
36
|
-
* previous successful build has been recorded.
|
|
37
|
-
*
|
|
36
|
+
* previous successful build has been recorded. Builds can replace invalid
|
|
37
|
+
* markers; dispatch preflights pass `refuse` so invalid coverage never
|
|
38
|
+
* becomes the legacy implicit full claim.
|
|
38
39
|
* @param projectRoot - Root directory of the project
|
|
39
40
|
*/
|
|
40
|
-
export declare function readBuildBaseline(projectRoot: string): Promise<BuildBaseline | undefined>;
|
|
41
|
+
export declare function readBuildBaseline(projectRoot: string, invalid?: 'ignore' | 'refuse'): Promise<BuildBaseline | undefined>;
|
|
41
42
|
export interface WriteBuildBaselineOptions {
|
|
42
43
|
/** Root directory of the project. */
|
|
43
44
|
projectRoot: string;
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { readFile } from 'node:fs/promises';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
|
+
import { PreflightRefusalError } from '../errors/base.js';
|
|
21
22
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
22
23
|
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
23
|
-
import {
|
|
24
|
+
import { readJson, writeJson } from '../utils/fs.js';
|
|
24
25
|
import { sha256Hex } from '../utils/hash.js';
|
|
25
26
|
import { verbose } from '../utils/logger.js';
|
|
26
27
|
import { isBuildInputPath, isJarManifestPath, isPackageablePath, isXpcomManifestPath, } from './build-audit.js';
|
|
27
28
|
import { DELETED_FILE_FINGERPRINT, } from './build-baseline-types.js';
|
|
29
|
+
import { isBuildBaseline } from './build-baseline-validate.js';
|
|
28
30
|
import { FIREFORGE_DIR } from './config-paths.js';
|
|
29
31
|
import { hashEngineFile } from './coverage-extend.js';
|
|
30
32
|
import { getHead, hasChanges, isMissingHeadError } from './git.js';
|
|
@@ -47,21 +49,26 @@ export function getBuildBaselinePath(projectRoot) {
|
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
51
|
* Reads the last-build baseline if present. Returns undefined when no
|
|
50
|
-
* previous successful build has been recorded.
|
|
51
|
-
*
|
|
52
|
+
* previous successful build has been recorded. Builds can replace invalid
|
|
53
|
+
* markers; dispatch preflights pass `refuse` so invalid coverage never
|
|
54
|
+
* becomes the legacy implicit full claim.
|
|
52
55
|
* @param projectRoot - Root directory of the project
|
|
53
56
|
*/
|
|
54
|
-
export async function readBuildBaseline(projectRoot) {
|
|
57
|
+
export async function readBuildBaseline(projectRoot, invalid = 'ignore') {
|
|
55
58
|
const path = getBuildBaselinePath(projectRoot);
|
|
56
|
-
if (!(await pathExists(path))) {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
59
|
try {
|
|
60
|
-
|
|
60
|
+
const value = await readJson(path);
|
|
61
|
+
if (!isBuildBaseline(value))
|
|
62
|
+
throw new Error('invalid baseline fields');
|
|
63
|
+
return value;
|
|
61
64
|
}
|
|
62
|
-
catch {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (getNodeErrorCode(error) === 'ENOENT')
|
|
67
|
+
return undefined;
|
|
68
|
+
if (invalid === 'refuse') {
|
|
69
|
+
throw new PreflightRefusalError(`Build baseline ${path} is invalid: ${toError(error).message}. Run "fireforge build" to record a fresh baseline before dispatching tests.`, 'invalid-build-baseline');
|
|
70
|
+
}
|
|
71
|
+
verbose(`Ignoring invalid build baseline ${path}: ${toError(error).message}`);
|
|
65
72
|
return undefined;
|
|
66
73
|
}
|
|
67
74
|
}
|
|
@@ -89,6 +96,7 @@ export async function writeBuildBaseline(options) {
|
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
const packageableFingerprints = await collectPackageableFingerprints(engineDir);
|
|
99
|
+
const testInputFingerprints = await collectDirtyFingerprints(engineDir, () => true, 'test staging fingerprint');
|
|
92
100
|
const buildInputFingerprints = await collectBuildInputFingerprints(engineDir, previousBaseline, buildKind);
|
|
93
101
|
// A full-coverage write (and the legacy no-claim `fireforge build` shape)
|
|
94
102
|
// just recompiled the StaticComponents table, so it anchors the table to
|
|
@@ -105,6 +113,7 @@ export async function writeBuildBaseline(options) {
|
|
|
105
113
|
builtAt: new Date().toISOString(),
|
|
106
114
|
binaryName,
|
|
107
115
|
...(packageableFingerprints !== undefined ? { packageableFingerprints } : {}),
|
|
116
|
+
...(testInputFingerprints !== undefined ? { testInputFingerprints } : {}),
|
|
108
117
|
...(buildInputFingerprints !== undefined ? { buildInputFingerprints } : {}),
|
|
109
118
|
...(testPackagingCoverage !== undefined ? { testPackagingCoverage } : {}),
|
|
110
119
|
...(mozconfigHash !== undefined ? { mozconfigHash } : {}),
|
|
@@ -218,6 +227,9 @@ async function collectDirtyFingerprints(engineDir, includePath, contextLabel) {
|
|
|
218
227
|
}
|
|
219
228
|
});
|
|
220
229
|
const fingerprints = {};
|
|
230
|
+
// Incomplete maps cannot prove retained test inputs unchanged.
|
|
231
|
+
if (entries.some((entry) => entry === undefined))
|
|
232
|
+
return undefined;
|
|
221
233
|
for (const entry of entries) {
|
|
222
234
|
if (entry !== undefined)
|
|
223
235
|
fingerprints[entry[0]] = entry[1];
|
|
@@ -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,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FurnaceConfig } from '../types/furnace.js';
|
|
2
|
+
import type { FurnacePaths } from './furnace-config.js';
|
|
3
|
+
/** Records authored bytes without following directory aliases or writing to source directories. */
|
|
4
|
+
export declare function snapshotComponentSources(roots: readonly string[]): Promise<Map<string, string>>;
|
|
5
|
+
/** Detects reverted, removed or added source files before deployment can be committed. */
|
|
6
|
+
export declare function changedComponentSources(roots: readonly string[], before: ReadonlyMap<string, string>): Promise<string[]>;
|
|
7
|
+
interface ComponentSourceSnapshot {
|
|
8
|
+
roots: string[];
|
|
9
|
+
files: Map<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/** Captures only configured inputs for a real deploy, plus shared CSS fragments. */
|
|
12
|
+
export declare function captureComponentSources(paths: FurnacePaths, config: FurnaceConfig, dryRun: boolean, options?: {
|
|
13
|
+
componentName?: string;
|
|
14
|
+
}): Promise<ComponentSourceSnapshot | undefined>;
|
|
15
|
+
/** Produces a blocking apply error before the journal or ownership state is committed. */
|
|
16
|
+
export declare function componentSourceErrors(snapshot: ComponentSourceSnapshot | undefined): Promise<Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
error: string;
|
|
19
|
+
}>>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getNodeErrorCode } from '../utils/errors.js';
|
|
5
|
+
import { sha256Hex } from '../utils/hash.js';
|
|
6
|
+
/** Records authored bytes without following directory aliases or writing to source directories. */
|
|
7
|
+
export async function snapshotComponentSources(roots) {
|
|
8
|
+
const files = new Map();
|
|
9
|
+
async function visit(directory) {
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (getNodeErrorCode(error) === 'ENOENT')
|
|
16
|
+
return;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const path = join(directory, entry.name);
|
|
21
|
+
if (entry.isDirectory())
|
|
22
|
+
await visit(path);
|
|
23
|
+
else if (entry.isFile())
|
|
24
|
+
files.set(path, sha256Hex(await readFile(path)));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const root of roots)
|
|
28
|
+
await visit(root);
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
/** Detects reverted, removed or added source files before deployment can be committed. */
|
|
32
|
+
export async function changedComponentSources(roots, before) {
|
|
33
|
+
const after = await snapshotComponentSources(roots);
|
|
34
|
+
return [...new Set([...before.keys(), ...after.keys()])]
|
|
35
|
+
.filter((path) => before.get(path) !== after.get(path))
|
|
36
|
+
.sort();
|
|
37
|
+
}
|
|
38
|
+
/** Captures only configured inputs for a real deploy, plus shared CSS fragments. */
|
|
39
|
+
export async function captureComponentSources(paths, config, dryRun, options) {
|
|
40
|
+
if (dryRun)
|
|
41
|
+
return undefined;
|
|
42
|
+
const componentName = options?.componentName;
|
|
43
|
+
const selected = (name) => componentName === undefined || name === componentName;
|
|
44
|
+
const roots = [
|
|
45
|
+
...Object.keys(config.custom)
|
|
46
|
+
.filter(selected)
|
|
47
|
+
.map((name) => join(paths.customDir, name)),
|
|
48
|
+
...Object.keys(config.overrides)
|
|
49
|
+
.filter(selected)
|
|
50
|
+
.map((name) => join(paths.overridesDir, name)),
|
|
51
|
+
paths.sharedDir,
|
|
52
|
+
];
|
|
53
|
+
return { roots, files: await snapshotComponentSources(roots) };
|
|
54
|
+
}
|
|
55
|
+
/** Produces a blocking apply error before the journal or ownership state is committed. */
|
|
56
|
+
export async function componentSourceErrors(snapshot) {
|
|
57
|
+
if (!snapshot)
|
|
58
|
+
return [];
|
|
59
|
+
const changed = await changedComponentSources(snapshot.roots, snapshot.files);
|
|
60
|
+
return changed.length === 0
|
|
61
|
+
? []
|
|
62
|
+
: [
|
|
63
|
+
{
|
|
64
|
+
name: 'component-sources',
|
|
65
|
+
error: `Component sources changed during deployment: ${changed.join(', ')}. Refusing this build/proof; inspect the source edits before retrying.`,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|
|
@@ -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", "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", "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
|
|
@@ -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)
|
|
@@ -1,37 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Coverage replaces by default for a reason documented in
|
|
7
|
-
* `build-baseline-types.ts`: every baseline write refreshes
|
|
8
|
-
* `packageableFingerprints` for all dirty packageable paths, so a blind
|
|
9
|
-
* union would whitewash an earlier scope's edited fixtures while
|
|
10
|
-
* `obj-*`/`_tests/` still holds that scope's stale staging. Extending is
|
|
11
|
-
* honest only while everything the previous record vouched for is still
|
|
12
|
-
* true, which is what {@link checkExtendCoverageAnchor} and
|
|
13
|
-
* {@link checkExtendMozconfigAnchor} verify:
|
|
14
|
-
*
|
|
15
|
-
* 1. engine HEAD is unchanged, so every committed edit is excluded.
|
|
16
|
-
* 2. every path the previous baseline fingerprinted still hashes to the
|
|
17
|
-
* recorded digest, so the earlier scope's staging inputs are
|
|
18
|
-
* byte-identical. Files that became dirty since that build are fine:
|
|
19
|
-
* the current whole-tree `mach build faster` repackages `dist/` and
|
|
20
|
-
* vouches for them.
|
|
21
|
-
* 3. the generated `engine/mozconfig` is unchanged. Engine HEAD does not
|
|
22
|
-
* cover this: the mozconfig is regenerated from project-side
|
|
23
|
-
* `configs/*.mozconfig` templates plus `fireforge.json` on every build,
|
|
24
|
-
* so two builds at the same engine SHA can configure differently.
|
|
25
|
-
*
|
|
26
|
-
* Every failure refuses fail-closed. The operator's remedy is always a plain
|
|
27
|
-
* scoped build, which resets the claim to the paths it actually vouches for.
|
|
28
|
-
*
|
|
29
|
-
* Known boundary, documented rather than closed: dirty non-packageable
|
|
30
|
-
* fixtures under previously covered paths (plain test files, manifests
|
|
31
|
-
* `isPackageablePath` rejects) are invisible to check 2. Editing one
|
|
32
|
-
* uncommitted between two builds leaves the earlier scope's `_tests/`
|
|
33
|
-
* staging stale while extend still vouches for it. Plain replacement has no such
|
|
34
|
-
* window, because the earlier scope simply stops being covered.
|
|
2
|
+
* Retains prior test staging only when HEAD, mozconfig and every recorded
|
|
3
|
+
* dirty input outside the newly rebuilt scope are unchanged. Complete
|
|
4
|
+
* input fingerprints are required: packageable-only legacy records cannot
|
|
5
|
+
* prove that a test fixture or manifest still matches its staged copy.
|
|
35
6
|
*/
|
|
36
7
|
import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
37
8
|
/** Why `--extend-coverage` could not union onto the recorded claim. */
|
|
@@ -51,7 +22,7 @@ export type ExtendAnchorResult = {
|
|
|
51
22
|
* @param engineDir - Path to the engine directory
|
|
52
23
|
* @param previousBaseline - Baseline recorded by the previous build
|
|
53
24
|
*/
|
|
54
|
-
export declare function checkExtendCoverageAnchor(engineDir: string, previousBaseline: BuildBaseline | undefined): Promise<ExtendAnchorResult>;
|
|
25
|
+
export declare function checkExtendCoverageAnchor(engineDir: string, previousBaseline: BuildBaseline | undefined, rebuiltPaths?: readonly string[]): Promise<ExtendAnchorResult>;
|
|
55
26
|
/**
|
|
56
27
|
* Verifies the mozconfig half of the anchor. Split from
|
|
57
28
|
* {@link checkExtendCoverageAnchor} because it can only run after
|