@hominis/fireforge 0.39.0 → 0.41.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 +100 -0
- package/README.md +35 -5
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +27 -3
- package/dist/src/commands/bootstrap.js +5 -1
- package/dist/src/commands/discard.js +2 -22
- package/dist/src/commands/doctor/post-rebase-audit.js +27 -4
- package/dist/src/commands/doctor-check-core.d.ts +20 -0
- package/dist/src/commands/doctor-check-core.js +28 -0
- package/dist/src/commands/doctor-external-toolchains.js +2 -0
- package/dist/src/commands/doctor-furnace-manifest-sync.js +144 -46
- package/dist/src/commands/doctor-furnace.js +13 -11
- package/dist/src/commands/doctor-orphaned-harness.js +2 -1
- package/dist/src/commands/doctor.js +2 -2
- package/dist/src/commands/export-all.js +9 -1
- package/dist/src/commands/export-flow.js +27 -9
- package/dist/src/commands/export-placement-conflicts.d.ts +17 -0
- package/dist/src/commands/export-placement-conflicts.js +74 -0
- package/dist/src/commands/export-placement-gate.js +1 -1
- package/dist/src/commands/export-shared.d.ts +8 -1
- package/dist/src/commands/export-shared.js +30 -10
- package/dist/src/commands/export.js +2 -2
- package/dist/src/commands/furnace/apply.js +4 -3
- package/dist/src/commands/furnace/chrome-doc-remove.js +13 -2
- package/dist/src/commands/furnace/chrome-doc.js +13 -2
- package/dist/src/commands/furnace/create-browser-test.d.ts +5 -0
- package/dist/src/commands/furnace/create-browser-test.js +5 -0
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/diff.js +2 -1
- package/dist/src/commands/furnace/init.js +4 -6
- package/dist/src/commands/furnace/override.js +2 -2
- package/dist/src/commands/furnace/remove-mutations.d.ts +40 -0
- package/dist/src/commands/furnace/remove-mutations.js +171 -0
- package/dist/src/commands/furnace/remove.js +21 -128
- package/dist/src/commands/furnace/rename-browser-test.js +54 -5
- package/dist/src/commands/furnace/scan.js +27 -36
- package/dist/src/commands/furnace/status.js +30 -28
- package/dist/src/commands/furnace/validate.js +25 -18
- package/dist/src/commands/import.js +6 -20
- package/dist/src/commands/lint-per-patch.js +73 -51
- package/dist/src/commands/lint-per-run-checkjs.d.ts +64 -0
- package/dist/src/commands/lint-per-run-checkjs.js +102 -0
- package/dist/src/commands/lint-report.d.ts +21 -0
- package/dist/src/commands/lint-report.js +62 -0
- package/dist/src/commands/lint.js +26 -22
- package/dist/src/commands/manifest.js +3 -1
- package/dist/src/commands/package.js +9 -16
- package/dist/src/commands/patch/compact.js +5 -5
- package/dist/src/commands/patch/delete.js +5 -5
- package/dist/src/commands/patch/lint-ignore.js +3 -2
- package/dist/src/commands/patch/move-files-create.js +22 -4
- package/dist/src/commands/patch/move-files-into.js +14 -10
- package/dist/src/commands/patch/move-files.js +6 -5
- package/dist/src/commands/patch/rename.d.ts +2 -2
- package/dist/src/commands/patch/rename.js +152 -51
- package/dist/src/commands/patch/reorder.d.ts +9 -0
- package/dist/src/commands/patch/reorder.js +14 -10
- package/dist/src/commands/patch/split-plan.d.ts +13 -6
- package/dist/src/commands/patch/split-plan.js +22 -12
- package/dist/src/commands/patch/split.d.ts +1 -1
- package/dist/src/commands/patch/split.js +23 -9
- package/dist/src/commands/patch/staged-dependency-validate.d.ts +15 -0
- package/dist/src/commands/patch/staged-dependency-validate.js +49 -0
- package/dist/src/commands/patch/staged-dependency.js +112 -14
- package/dist/src/commands/re-export-adjacent.d.ts +34 -0
- package/dist/src/commands/re-export-adjacent.js +105 -0
- package/dist/src/commands/re-export-bulk-scan.d.ts +13 -0
- package/dist/src/commands/re-export-bulk-scan.js +142 -2
- package/dist/src/commands/re-export-drift.d.ts +74 -0
- package/dist/src/commands/re-export-drift.js +234 -0
- package/dist/src/commands/re-export-files.js +5 -3
- package/dist/src/commands/re-export-lint.d.ts +97 -0
- package/dist/src/commands/re-export-lint.js +210 -0
- package/dist/src/commands/re-export-options.js +11 -0
- package/dist/src/commands/re-export-register.d.ts +18 -0
- package/dist/src/commands/re-export-register.js +61 -0
- package/dist/src/commands/re-export-scan.js +13 -7
- package/dist/src/commands/re-export-single.d.ts +20 -0
- package/dist/src/commands/re-export-single.js +261 -0
- package/dist/src/commands/re-export.d.ts +0 -4
- package/dist/src/commands/re-export.js +123 -289
- package/dist/src/commands/rebase/abort.js +13 -4
- package/dist/src/commands/rebase/continue.js +11 -4
- package/dist/src/commands/rebase/index.js +19 -29
- package/dist/src/commands/rebase/patch-loop.js +12 -0
- package/dist/src/commands/reset.js +4 -23
- package/dist/src/commands/resolve.js +2 -9
- package/dist/src/commands/run.js +29 -23
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +48 -0
- package/dist/src/commands/status-check.js +106 -0
- package/dist/src/commands/status-json.d.ts +31 -0
- package/dist/src/commands/status-json.js +76 -0
- package/dist/src/commands/status-output.d.ts +1 -0
- package/dist/src/commands/status-output.js +10 -1
- package/dist/src/commands/status-ownership.d.ts +52 -0
- package/dist/src/commands/status-ownership.js +57 -0
- package/dist/src/commands/status.js +190 -170
- package/dist/src/commands/test-build-phase.d.ts +19 -0
- package/dist/src/commands/test-build-phase.js +168 -0
- package/dist/src/commands/test-diagnose.js +30 -2
- package/dist/src/commands/test-modes.d.ts +9 -1
- package/dist/src/commands/test-modes.js +46 -13
- package/dist/src/commands/test-register.js +59 -27
- package/dist/src/commands/test-run.d.ts +29 -3
- package/dist/src/commands/test-run.js +59 -7
- package/dist/src/commands/test-verdict.d.ts +32 -0
- package/dist/src/commands/test-verdict.js +48 -0
- package/dist/src/commands/test.d.ts +9 -0
- package/dist/src/commands/test.js +100 -123
- package/dist/src/commands/token-coverage.js +4 -9
- package/dist/src/commands/token.js +4 -1
- package/dist/src/commands/tree.d.ts +19 -0
- package/dist/src/commands/tree.js +320 -0
- package/dist/src/commands/verify.js +2 -2
- package/dist/src/commands/watch.js +22 -21
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.d.ts +11 -0
- package/dist/src/core/ast-utils.js +11 -0
- package/dist/src/core/build-audit-platform.js +6 -1
- package/dist/src/core/build-audit-registration.js +2 -0
- package/dist/src/core/build-audit-resolve.d.ts +13 -0
- package/dist/src/core/build-audit-resolve.js +34 -13
- package/dist/src/core/build-audit.js +4 -20
- package/dist/src/core/build-baseline-types.d.ts +29 -8
- package/dist/src/core/build-baseline-types.js +2 -1
- package/dist/src/core/build-baseline.d.ts +10 -2
- package/dist/src/core/build-baseline.js +39 -8
- package/dist/src/core/build-prepare.d.ts +4 -0
- package/dist/src/core/build-prepare.js +7 -1
- 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 +3 -2
- package/dist/src/core/config-validate.js +45 -30
- package/dist/src/core/config.js +16 -3
- package/dist/src/core/coverage-extend.d.ts +89 -0
- package/dist/src/core/coverage-extend.js +188 -0
- package/dist/src/core/destructive.js +3 -1
- package/dist/src/core/discard-baseline.js +18 -4
- package/dist/src/core/engine-precondition.d.ts +35 -0
- package/dist/src/core/engine-precondition.js +50 -0
- package/dist/src/core/engine-session-lock.d.ts +38 -1
- package/dist/src/core/engine-session-lock.js +78 -4
- package/dist/src/core/file-lock.d.ts +7 -0
- package/dist/src/core/file-lock.js +20 -30
- package/dist/src/core/firefox.js +5 -2
- package/dist/src/core/furnace-apply-helpers.d.ts +12 -1
- package/dist/src/core/furnace-apply-helpers.js +12 -3
- package/dist/src/core/furnace-apply-output.js +9 -0
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +63 -0
- package/dist/src/core/furnace-apply-overwrite-warn.js +123 -0
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +11 -0
- package/dist/src/core/furnace-apply-undeploy-actions.js +49 -0
- package/dist/src/core/furnace-apply.d.ts +16 -4
- package/dist/src/core/furnace-apply.js +39 -43
- package/dist/src/core/furnace-config.js +2 -0
- package/dist/src/core/furnace-css-fragments.js +2 -0
- package/dist/src/core/furnace-jsconfig.js +14 -7
- package/dist/src/core/furnace-registration-ast.js +8 -8
- package/dist/src/core/furnace-registration-remove.js +6 -3
- package/dist/src/core/furnace-registration.js +9 -12
- package/dist/src/core/furnace-rollback.js +1 -1
- package/dist/src/core/furnace-scanner.js +2 -1
- package/dist/src/core/furnace-stale-export.js +2 -1
- package/dist/src/core/furnace-state-persist.d.ts +4 -6
- package/dist/src/core/furnace-validate-compatibility.js +4 -7
- package/dist/src/core/furnace-validate-helpers.js +33 -1
- package/dist/src/core/furnace-validate-registration.js +16 -0
- package/dist/src/core/furnace-validate.js +4 -2
- package/dist/src/core/git-diff.js +37 -2
- package/dist/src/core/lint-cache.d.ts +15 -5
- package/dist/src/core/lint-cache.js +49 -14
- package/dist/src/core/mach-build-artifacts.d.ts +63 -1
- package/dist/src/core/mach-build-artifacts.js +108 -2
- package/dist/src/core/mach-resource-shim.js +1 -0
- package/dist/src/core/marionette-port.d.ts +16 -0
- package/dist/src/core/marionette-port.js +93 -8
- package/dist/src/core/marionette-preflight.js +6 -5
- package/dist/src/core/patch-apply.d.ts +20 -13
- package/dist/src/core/patch-apply.js +32 -33
- package/dist/src/core/patch-export.d.ts +2 -2
- package/dist/src/core/patch-export.js +4 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +42 -2
- package/dist/src/core/patch-lint-checkjs.js +132 -8
- package/dist/src/core/patch-lint-chrome-jsdoc.js +9 -0
- package/dist/src/core/patch-lint-creators.d.ts +30 -0
- package/dist/src/core/patch-lint-creators.js +45 -0
- package/dist/src/core/patch-lint-cross.d.ts +1 -17
- package/dist/src/core/patch-lint-cross.js +16 -32
- package/dist/src/core/patch-lint-css.js +64 -16
- package/dist/src/core/patch-lint-diff.d.ts +8 -0
- package/dist/src/core/patch-lint-diff.js +31 -0
- package/dist/src/core/patch-lint-jsdoc.d.ts +3 -1
- package/dist/src/core/patch-lint-jsdoc.js +15 -5
- package/dist/src/core/patch-lint-module-registration.d.ts +15 -0
- package/dist/src/core/patch-lint-module-registration.js +88 -0
- package/dist/src/core/patch-lint-mozbuild.js +99 -11
- package/dist/src/core/patch-lint-ownership.d.ts +21 -0
- package/dist/src/core/patch-lint-ownership.js +30 -0
- package/dist/src/core/patch-lint-reexports.d.ts +2 -1
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +31 -0
- package/dist/src/core/patch-lint.js +114 -8
- package/dist/src/core/patch-lock.d.ts +12 -1
- package/dist/src/core/patch-lock.js +40 -4
- package/dist/src/core/patch-manifest-consistency.js +15 -3
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-parse.d.ts +8 -0
- package/dist/src/core/patch-parse.js +10 -0
- package/dist/src/core/patch-policy.d.ts +7 -0
- package/dist/src/core/patch-policy.js +4 -1
- package/dist/src/core/patch-transform.d.ts +10 -0
- package/dist/src/core/patch-transform.js +13 -2
- package/dist/src/core/rebase-session.d.ts +44 -2
- package/dist/src/core/rebase-session.js +114 -17
- package/dist/src/core/register-browser-content.js +2 -1
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/smoke-patterns.d.ts +24 -16
- package/dist/src/core/smoke-patterns.js +26 -14
- package/dist/src/core/status-binary.d.ts +55 -0
- package/dist/src/core/status-binary.js +124 -0
- package/dist/src/core/status-classify.d.ts +22 -1
- package/dist/src/core/status-classify.js +112 -70
- package/dist/src/core/test-harness-crash.d.ts +32 -1
- package/dist/src/core/test-harness-crash.js +59 -4
- package/dist/src/core/test-path-scope.js +2 -0
- package/dist/src/core/test-stale-check.js +1 -20
- package/dist/src/core/token-category.d.ts +40 -0
- package/dist/src/core/token-category.js +264 -0
- package/dist/src/core/token-manager.d.ts +7 -0
- package/dist/src/core/token-manager.js +48 -196
- package/dist/src/core/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-cow.d.ts +18 -0
- package/dist/src/core/tree-cow.js +71 -0
- package/dist/src/core/tree-guard.d.ts +35 -0
- package/dist/src/core/tree-guard.js +179 -0
- package/dist/src/core/tree-store.d.ts +129 -0
- package/dist/src/core/tree-store.js +452 -0
- package/dist/src/core/typecheck-shim.d.ts +14 -0
- package/dist/src/core/typecheck-shim.js +41 -2
- package/dist/src/core/typecheck.js +2 -1
- package/dist/src/core/wire-destroy.js +14 -3
- package/dist/src/core/wire-init.js +37 -40
- package/dist/src/core/wire-subscript.js +11 -3
- package/dist/src/core/wire-utils.js +4 -4
- package/dist/src/core/xpcshell-appdir.js +3 -0
- package/dist/src/core/xpcshell-profile-dir.d.ts +12 -0
- package/dist/src/core/xpcshell-profile-dir.js +45 -0
- package/dist/src/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/git.d.ts +8 -0
- package/dist/src/errors/git.js +17 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/types/commands/options.d.ts +119 -3
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/config.d.ts +19 -0
- package/dist/src/types/furnace.d.ts +7 -0
- package/dist/src/utils/build-info.d.ts +31 -0
- package/dist/src/utils/build-info.js +98 -0
- package/dist/src/utils/concurrency.d.ts +8 -0
- package/dist/src/utils/concurrency.js +21 -0
- package/dist/src/utils/errors.d.ts +39 -0
- package/dist/src/utils/errors.js +55 -0
- package/dist/src/utils/fs.js +5 -12
- package/dist/src/utils/logger.d.ts +14 -2
- package/dist/src/utils/logger.js +43 -13
- package/dist/src/utils/options.d.ts +21 -4
- package/dist/src/utils/options.js +26 -6
- package/dist/src/utils/package-root.js +7 -4
- package/dist/src/utils/process-group.js +13 -2
- package/dist/src/utils/stdio-drain.d.ts +28 -0
- package/dist/src/utils/stdio-drain.js +54 -0
- package/dist/src/utils/validation.d.ts +33 -2
- package/dist/src/utils/validation.js +57 -2
- package/package.json +13 -13
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* into it would make the dependency graph cyclic.
|
|
7
7
|
*/
|
|
8
8
|
/** Shape of the on-disk baseline marker. */
|
|
9
|
+
export declare const DELETED_FILE_FINGERPRINT = "<deleted>";
|
|
9
10
|
export interface BuildBaseline {
|
|
10
11
|
/** SHA of engine HEAD at the time the build succeeded. */
|
|
11
12
|
engineHeadSha: string;
|
|
@@ -32,7 +33,8 @@ export interface BuildBaseline {
|
|
|
32
33
|
*
|
|
33
34
|
* Keys are engine-relative POSIX paths. Values are hex-encoded
|
|
34
35
|
* SHA-256 digests of the file contents at the moment the baseline
|
|
35
|
-
* was recorded
|
|
36
|
+
* was recorded, or {@link DELETED_FILE_FINGERPRINT} when the successful
|
|
37
|
+
* build observed a tracked deletion.
|
|
36
38
|
*/
|
|
37
39
|
packageableFingerprints?: Record<string, string>;
|
|
38
40
|
/**
|
|
@@ -56,14 +58,32 @@ export interface BuildBaseline {
|
|
|
56
58
|
* checkouts are refused up-front (`AmbiguousBuildArtifactsError`), so at
|
|
57
59
|
* most one obj dir exists per project.
|
|
58
60
|
*
|
|
59
|
-
* Union/"shared coverage" across successive scoped builds
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* Union/"shared coverage" across successive scoped builds is unsound in
|
|
62
|
+
* general — every baseline write refreshes `packageableFingerprints` for
|
|
63
|
+
* ALL dirty packageable paths, so a blind union would whitewash an
|
|
64
|
+
* earlier scope's edited fixtures while `obj-*`/`_tests/` still holds its
|
|
65
|
+
* stale staging; coverage therefore REPLACES by default.
|
|
66
|
+
*
|
|
67
|
+
* The one exception is `test --build --extend-coverage` (FORGE L1), which
|
|
68
|
+
* unions only after proving the previous record's anchor still holds:
|
|
69
|
+
* same engine HEAD, same {@link BuildBaseline.mozconfigHash}, and every
|
|
70
|
+
* previously fingerprinted path byte-identical — i.e. the wholesale
|
|
71
|
+
* fingerprint refresh is a no-op for everything the earlier scope's
|
|
72
|
+
* staging depended on. Any divergence refuses fail-closed. See
|
|
73
|
+
* `src/core/coverage-extend.ts`, which also documents the one boundary
|
|
74
|
+
* that guard does not cover (dirty non-packageable fixtures).
|
|
65
75
|
*/
|
|
66
76
|
testPackagingCoverage?: TestPackagingCoverage;
|
|
77
|
+
/**
|
|
78
|
+
* Hex-encoded SHA-256 of `engine/mozconfig` as it stood for this build.
|
|
79
|
+
* Recorded because the mozconfig is REGENERATED from project-side
|
|
80
|
+
* `configs/*.mozconfig` templates plus `fireforge.json` on every build,
|
|
81
|
+
* so `engineHeadSha` does not cover it: two builds at the same engine SHA
|
|
82
|
+
* can configure differently. Consumed by the `--extend-coverage` anchor.
|
|
83
|
+
* Missing on pre-0.41.0 baselines, which `--extend-coverage` refuses
|
|
84
|
+
* (one plain build re-records it).
|
|
85
|
+
*/
|
|
86
|
+
mozconfigHash?: string;
|
|
67
87
|
/**
|
|
68
88
|
* Anchor for the compiled StaticComponents table: the engine HEAD SHA of
|
|
69
89
|
* the last FULL-coverage build plus content fingerprints of the
|
|
@@ -94,7 +114,8 @@ export interface StaticComponentsBaseline {
|
|
|
94
114
|
engineHeadSha: string;
|
|
95
115
|
/**
|
|
96
116
|
* Hex-encoded SHA-256 per `components.conf` path that was dirty
|
|
97
|
-
* (modified-against-HEAD or untracked) when the full build ran.
|
|
117
|
+
* (modified-against-HEAD or untracked) when the full build ran. A tracked
|
|
118
|
+
* deletion is represented by {@link DELETED_FILE_FINGERPRINT}. Keys are
|
|
98
119
|
* engine-relative POSIX paths.
|
|
99
120
|
*/
|
|
100
121
|
fingerprints: Record<string, string>;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* on successful build completion; a failed build does not update it, so a
|
|
16
16
|
* subsequent run still audits against the last known-good tree.
|
|
17
17
|
*/
|
|
18
|
-
import type
|
|
18
|
+
import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
19
19
|
/** Name of the last-build marker file under `.fireforge/`. */
|
|
20
20
|
export declare const BUILD_BASELINE_FILENAME = "last-build.json";
|
|
21
21
|
/**
|
|
@@ -47,5 +47,13 @@ export declare function readBuildBaseline(projectRoot: string): Promise<BuildBas
|
|
|
47
47
|
* `staticComponentsBaseline` forward verbatim — `mach build faster`
|
|
48
48
|
* does not rebake `components.conf` registrations, so the last FULL
|
|
49
49
|
* build stays the honest anchor for the compiled StaticComponents table.
|
|
50
|
+
* @param recordedBy - Invocation shape that produced this baseline.
|
|
51
|
+
* @param staticComponentsHandling - `'auto'` (default) refreshes the
|
|
52
|
+
* static-components anchor whenever the coverage claim is `'full'` or
|
|
53
|
+
* absent. `'refresh'` records it even for a scoped coverage claim whose
|
|
54
|
+
* implementation escalated to a full build. `'carry-forward'` always keeps the previous anchor: needed by
|
|
55
|
+
* `--extend-coverage` (FORGE L1), whose union can EVALUATE to `'full'`
|
|
56
|
+
* while the build that produced it was still a scoped `mach build
|
|
57
|
+
* faster` that did not rebake the compiled table.
|
|
50
58
|
*/
|
|
51
|
-
export declare function writeBuildBaseline(projectRoot: string, engineDir: string, binaryName: string, testPackagingCoverage?: TestPackagingCoverage, previousBaseline?: BuildBaseline, recordedBy?: string): Promise<void>;
|
|
59
|
+
export declare function writeBuildBaseline(projectRoot: string, engineDir: string, binaryName: string, testPackagingCoverage?: TestPackagingCoverage, previousBaseline?: BuildBaseline, recordedBy?: string, staticComponentsHandling?: 'auto' | 'refresh' | 'carry-forward'): Promise<void>;
|
|
@@ -19,14 +19,22 @@
|
|
|
19
19
|
import { createHash } from 'node:crypto';
|
|
20
20
|
import { readFile } from 'node:fs/promises';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
|
-
import {
|
|
22
|
+
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
23
|
+
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
23
24
|
import { pathExists, readJson, writeJson } from '../utils/fs.js';
|
|
24
25
|
import { verbose } from '../utils/logger.js';
|
|
25
26
|
import { isPackageablePath, isXpcomManifestPath } from './build-audit.js';
|
|
27
|
+
import { DELETED_FILE_FINGERPRINT, } from './build-baseline-types.js';
|
|
26
28
|
import { FIREFORGE_DIR } from './config-paths.js';
|
|
29
|
+
import { hashEngineFile } from './coverage-extend.js';
|
|
27
30
|
import { getHead, hasChanges, isMissingHeadError } from './git.js';
|
|
28
31
|
import { git } from './git-base.js';
|
|
29
32
|
import { getUntrackedFiles } from './git-status.js';
|
|
33
|
+
/*
|
|
34
|
+
* Keep the IO fan-out bounded: a patched Firefox checkout can carry hundreds
|
|
35
|
+
* of dirty packageable paths and serial reads add directly to every build.
|
|
36
|
+
*/
|
|
37
|
+
const FINGERPRINT_IO_CONCURRENCY = 16;
|
|
30
38
|
/** Name of the last-build marker file under `.fireforge/`. */
|
|
31
39
|
export const BUILD_BASELINE_FILENAME = 'last-build.json';
|
|
32
40
|
/**
|
|
@@ -73,8 +81,16 @@ export async function readBuildBaseline(projectRoot) {
|
|
|
73
81
|
* `staticComponentsBaseline` forward verbatim — `mach build faster`
|
|
74
82
|
* does not rebake `components.conf` registrations, so the last FULL
|
|
75
83
|
* build stays the honest anchor for the compiled StaticComponents table.
|
|
84
|
+
* @param recordedBy - Invocation shape that produced this baseline.
|
|
85
|
+
* @param staticComponentsHandling - `'auto'` (default) refreshes the
|
|
86
|
+
* static-components anchor whenever the coverage claim is `'full'` or
|
|
87
|
+
* absent. `'refresh'` records it even for a scoped coverage claim whose
|
|
88
|
+
* implementation escalated to a full build. `'carry-forward'` always keeps the previous anchor: needed by
|
|
89
|
+
* `--extend-coverage` (FORGE L1), whose union can EVALUATE to `'full'`
|
|
90
|
+
* while the build that produced it was still a scoped `mach build
|
|
91
|
+
* faster` that did not rebake the compiled table.
|
|
76
92
|
*/
|
|
77
|
-
export async function writeBuildBaseline(projectRoot, engineDir, binaryName, testPackagingCoverage, previousBaseline, recordedBy) {
|
|
93
|
+
export async function writeBuildBaseline(projectRoot, engineDir, binaryName, testPackagingCoverage, previousBaseline, recordedBy, staticComponentsHandling = 'auto') {
|
|
78
94
|
let engineHeadSha = '';
|
|
79
95
|
try {
|
|
80
96
|
engineHeadSha = await getHead(engineDir);
|
|
@@ -93,15 +109,19 @@ export async function writeBuildBaseline(projectRoot, engineDir, binaryName, tes
|
|
|
93
109
|
// just recompiled the StaticComponents table, so it anchors the table to
|
|
94
110
|
// the current engine state. A scoped write did not — carry the previous
|
|
95
111
|
// anchor forward verbatim.
|
|
96
|
-
const staticComponentsBaseline =
|
|
112
|
+
const staticComponentsBaseline = staticComponentsHandling === 'refresh' ||
|
|
113
|
+
(staticComponentsHandling === 'auto' &&
|
|
114
|
+
(testPackagingCoverage === undefined || testPackagingCoverage === 'full'))
|
|
97
115
|
? await collectStaticComponentsBaseline(engineDir, engineHeadSha)
|
|
98
116
|
: previousBaseline?.staticComponentsBaseline;
|
|
117
|
+
const mozconfigHash = await hashEngineFile(engineDir, 'mozconfig');
|
|
99
118
|
const baseline = {
|
|
100
119
|
engineHeadSha,
|
|
101
120
|
builtAt: new Date().toISOString(),
|
|
102
121
|
binaryName,
|
|
103
122
|
...(packageableFingerprints !== undefined ? { packageableFingerprints } : {}),
|
|
104
123
|
...(testPackagingCoverage !== undefined ? { testPackagingCoverage } : {}),
|
|
124
|
+
...(mozconfigHash !== undefined ? { mozconfigHash } : {}),
|
|
105
125
|
...(staticComponentsBaseline !== undefined ? { staticComponentsBaseline } : {}),
|
|
106
126
|
...(recordedBy !== undefined ? { recordedBy } : {}),
|
|
107
127
|
};
|
|
@@ -165,18 +185,29 @@ async function collectDirtyFingerprints(engineDir, includePath, contextLabel) {
|
|
|
165
185
|
if (included.length === 0) {
|
|
166
186
|
return {};
|
|
167
187
|
}
|
|
168
|
-
const
|
|
169
|
-
for (const relPath of included) {
|
|
188
|
+
const entries = await mapWithConcurrency(included, FINGERPRINT_IO_CONCURRENCY, async (relPath) => {
|
|
170
189
|
try {
|
|
171
190
|
const buffer = await readFile(join(engineDir, relPath));
|
|
172
|
-
|
|
191
|
+
return [relPath, createHash('sha256').update(buffer).digest('hex')];
|
|
173
192
|
}
|
|
174
193
|
catch (fileError) {
|
|
194
|
+
if (getNodeErrorCode(fileError) === 'ENOENT') {
|
|
195
|
+
// A path reported by git but absent on disk is normally a tracked
|
|
196
|
+
// deletion, not a failed fingerprint. Recording the tombstone lets
|
|
197
|
+
// the next stale check prove that deletion was already built.
|
|
198
|
+
return [relPath, DELETED_FILE_FINGERPRINT];
|
|
199
|
+
}
|
|
175
200
|
// A file that disappeared between status probe and hash is
|
|
176
|
-
// expected in concurrent scenarios;
|
|
177
|
-
// whole baseline write.
|
|
201
|
+
// expected in concurrent scenarios; non-ENOENT failures remain
|
|
202
|
+
// untrusted and are skipped without failing the whole baseline write.
|
|
178
203
|
verbose(`Build baseline: skipping ${contextLabel} for ${relPath} — ${toError(fileError).message}`);
|
|
204
|
+
return undefined;
|
|
179
205
|
}
|
|
206
|
+
});
|
|
207
|
+
const fingerprints = {};
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (entry !== undefined)
|
|
210
|
+
fingerprints[entry[0]] = entry[1];
|
|
180
211
|
}
|
|
181
212
|
return fingerprints;
|
|
182
213
|
}
|
|
@@ -12,6 +12,8 @@ export interface BuildPreparation {
|
|
|
12
12
|
furnaceApplied: number;
|
|
13
13
|
/** True when `mach configure` was auto-run to refresh a stale backend. */
|
|
14
14
|
reconfigured: boolean;
|
|
15
|
+
/** A changed packaging manifest cannot be safely handled by `mach build faster`. */
|
|
16
|
+
fullBuildRequired?: boolean;
|
|
15
17
|
}
|
|
16
18
|
/** Options for {@link prepareBuildEnvironment}. */
|
|
17
19
|
export interface PrepareBuildOptions {
|
|
@@ -24,6 +26,8 @@ export interface PrepareBuildOptions {
|
|
|
24
26
|
*/
|
|
25
27
|
previousBaseline?: BuildBaseline | undefined;
|
|
26
28
|
}
|
|
29
|
+
/** Packaging manifests whose graph/destination directories need a full build. */
|
|
30
|
+
export declare function requiresFullBuildForIncrementalTest(path: string): boolean;
|
|
27
31
|
/**
|
|
28
32
|
* Describes an exit code in the shell's 128+N signal convention. Truncated
|
|
29
33
|
* configure/build logs with a signal-shaped exit (e.g. 144 = 128+16, SIGURG
|
|
@@ -20,6 +20,10 @@ import { generateMozconfig, runMachCapture } from './mach.js';
|
|
|
20
20
|
import { explainMachError } from './mach-error-hints.js';
|
|
21
21
|
/** Path fragments of files whose edits invalidate the recursive-make backend. */
|
|
22
22
|
const BACKEND_INVALIDATING_SUFFIXES = ['moz.build', 'moz.configure', 'Makefile.in'];
|
|
23
|
+
/** Packaging manifests whose graph/destination directories need a full build. */
|
|
24
|
+
export function requiresFullBuildForIncrementalTest(path) {
|
|
25
|
+
return path === 'jar.mn' || path.endsWith('/jar.mn');
|
|
26
|
+
}
|
|
23
27
|
/**
|
|
24
28
|
* Extracts the tail of captured `mach configure` output so the underlying
|
|
25
29
|
* mozbuild failure (e.g. `mozbuild.util.UnsortedError: ... is not in sorted
|
|
@@ -112,9 +116,11 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
112
116
|
// before the build step. Prevents incremental builds from silently skipping
|
|
113
117
|
// work against a stale recursive-make backend.
|
|
114
118
|
let reconfigured = false;
|
|
119
|
+
let fullBuildRequired = false;
|
|
115
120
|
if (options.previousBaseline) {
|
|
116
121
|
const changed = await collectChangedEnginePaths(paths.engine, options.previousBaseline, 'Auto-configure');
|
|
117
122
|
const invalidating = changed.filter(isBackendInvalidatingFile);
|
|
123
|
+
fullBuildRequired = changed.some(requiresFullBuildForIncrementalTest);
|
|
118
124
|
if (invalidating.length > 0) {
|
|
119
125
|
info(`Backend config changed; running backend regeneration first (${invalidating.length} file${invalidating.length === 1 ? '' : 's'} touched).`);
|
|
120
126
|
info(`Backend command: mach configure`);
|
|
@@ -233,5 +239,5 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
233
239
|
mozconfigSpinner.error('Failed to generate mozconfig');
|
|
234
240
|
throw error;
|
|
235
241
|
}
|
|
236
|
-
return { furnaceApplied, reconfigured };
|
|
242
|
+
return { furnaceApplied, reconfigured, fullBuildRequired };
|
|
237
243
|
}
|
|
@@ -11,7 +11,7 @@ export declare const STATE_FILENAME = "state.json";
|
|
|
11
11
|
/** Supported top-level fireforge.json keys backed by the current schema. */
|
|
12
12
|
export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
|
|
13
13
|
/** Supported config paths that can be read or set without --force. */
|
|
14
|
-
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
|
|
14
|
+
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "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"];
|
|
15
15
|
/**
|
|
16
16
|
* Gets all project paths based on a root directory.
|
|
17
17
|
* @param root - Root directory of the project
|
|
@@ -59,6 +59,8 @@ export const SUPPORTED_CONFIG_PATHS = [
|
|
|
59
59
|
'patchLint.checkJsStrict',
|
|
60
60
|
'patchLint.checkJsCompilerOptions',
|
|
61
61
|
'patchLint.checkJsExtraShim',
|
|
62
|
+
'patchLint.checkJsTestFiles',
|
|
63
|
+
'patchLint.checkJsTestShim',
|
|
62
64
|
'patchLint.rawColorAllowlist',
|
|
63
65
|
'patchLint.jsdocClassMethods',
|
|
64
66
|
'patchLint.testAssertionFloor',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ConfigError } from '../errors/config.js';
|
|
3
3
|
import { parseObject } from '../utils/parse.js';
|
|
4
4
|
import { isContainedRelativePath } from '../utils/paths.js';
|
|
5
|
+
import { isObject } from '../utils/validation.js';
|
|
5
6
|
function optionalConfigString(rec, key, label) {
|
|
6
7
|
const value = rec.raw(key);
|
|
7
8
|
if (value === undefined)
|
|
@@ -56,7 +57,7 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
56
57
|
throw new ConfigError('Config field "externalToolchains" must be an array');
|
|
57
58
|
}
|
|
58
59
|
config.externalToolchains = raw.map((entry, index) => {
|
|
59
|
-
if (
|
|
60
|
+
if (!isObject(entry)) {
|
|
60
61
|
throw new ConfigError(`Config field "externalToolchains[${String(index)}]" must be an object`);
|
|
61
62
|
}
|
|
62
63
|
const item = entry;
|
|
@@ -76,7 +77,7 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
76
77
|
}
|
|
77
78
|
function parseExternalToolRequirement(tool, index, toolIndex) {
|
|
78
79
|
const label = `externalToolchains[${String(index)}].tools[${String(toolIndex)}]`;
|
|
79
|
-
if (
|
|
80
|
+
if (!isObject(tool)) {
|
|
80
81
|
throw new ConfigError(`Config field "${label}" must be an object`);
|
|
81
82
|
}
|
|
82
83
|
const toolRec = tool;
|
|
@@ -6,7 +6,7 @@ import { ConfigError } from '../errors/config.js';
|
|
|
6
6
|
import { verbose } from '../utils/logger.js';
|
|
7
7
|
import { parseObject } from '../utils/parse.js';
|
|
8
8
|
import { isContainedRelativePath, isExplicitAbsolutePath } from '../utils/paths.js';
|
|
9
|
-
import { isValidAppId, isValidFirefoxCandidate, isValidFirefoxVersion, isValidProjectLicense, PROJECT_LICENSES, validateFirefoxProductVersionCompatibility, } from '../utils/validation.js';
|
|
9
|
+
import { isObject, isValidAppId, isValidFirefoxCandidate, isValidFirefoxVersion, isValidProjectLicense, PROJECT_LICENSES, validateFirefoxProductVersionCompatibility, } from '../utils/validation.js';
|
|
10
10
|
import { SUPPORTED_CONFIG_ROOT_KEYS } from './config-paths.js';
|
|
11
11
|
import { parsePatchPolicyBlock } from './config-validate-patch-policy.js';
|
|
12
12
|
import { parseExternalToolchainsBlock, parseTestBlock } from './config-validate-test-toolchains.js';
|
|
@@ -257,7 +257,7 @@ const PATCH_LINT_CHECKJS_COMPILER_OPTION_KEYS = [
|
|
|
257
257
|
* be typed from their real sources without an ambient stub shim.
|
|
258
258
|
*/
|
|
259
259
|
function parseCheckJsPathsMapping(raw) {
|
|
260
|
-
if (
|
|
260
|
+
if (!isObject(raw)) {
|
|
261
261
|
throw new ConfigError('Config field "patchLint.checkJsCompilerOptions.paths" must be a plain object');
|
|
262
262
|
}
|
|
263
263
|
const rec = raw;
|
|
@@ -274,7 +274,7 @@ function parseCheckJsPathsMapping(raw) {
|
|
|
274
274
|
return out;
|
|
275
275
|
}
|
|
276
276
|
function parsePatchLintCheckJsCompilerOptions(raw) {
|
|
277
|
-
if (
|
|
277
|
+
if (!isObject(raw)) {
|
|
278
278
|
throw new ConfigError('Config field "patchLint.checkJsCompilerOptions" must be a plain object');
|
|
279
279
|
}
|
|
280
280
|
const rec = raw;
|
|
@@ -304,22 +304,35 @@ function parseSeverityGate(raw, label) {
|
|
|
304
304
|
}
|
|
305
305
|
return raw;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Reads an optional boolean `patchLint` field, rejecting a non-boolean with
|
|
309
|
+
* the field-named message. Three identical inline copies of this shape were
|
|
310
|
+
* part of what held `parsePatchLintBlock` at complexity 25/30.
|
|
311
|
+
*/
|
|
312
|
+
function optionalPatchLintBoolean(rec, key) {
|
|
313
|
+
const value = rec.raw(key);
|
|
314
|
+
if (value === undefined)
|
|
315
|
+
return undefined;
|
|
316
|
+
if (typeof value !== 'boolean') {
|
|
317
|
+
throw new ConfigError(`Config field "patchLint.${key}" must be a boolean`);
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
/** Severity-gate fields, each parsed and assigned identically. */
|
|
322
|
+
const PATCH_LINT_SEVERITY_GATE_KEYS = [
|
|
323
|
+
'jsdocClassMethods',
|
|
324
|
+
'testAssertionFloor',
|
|
325
|
+
'chromeScriptJsDoc',
|
|
326
|
+
'undefinedIdentifiers',
|
|
327
|
+
];
|
|
307
328
|
function parsePatchLintBlock(rec) {
|
|
308
329
|
const out = {};
|
|
309
|
-
const checkJs = rec
|
|
310
|
-
if (checkJs !== undefined)
|
|
311
|
-
if (typeof checkJs !== 'boolean') {
|
|
312
|
-
throw new ConfigError('Config field "patchLint.checkJs" must be a boolean');
|
|
313
|
-
}
|
|
330
|
+
const checkJs = optionalPatchLintBoolean(rec, 'checkJs');
|
|
331
|
+
if (checkJs !== undefined)
|
|
314
332
|
out.checkJs = checkJs;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (checkJsStrict !== undefined) {
|
|
318
|
-
if (typeof checkJsStrict !== 'boolean') {
|
|
319
|
-
throw new ConfigError('Config field "patchLint.checkJsStrict" must be a boolean');
|
|
320
|
-
}
|
|
333
|
+
const checkJsStrict = optionalPatchLintBoolean(rec, 'checkJsStrict');
|
|
334
|
+
if (checkJsStrict !== undefined)
|
|
321
335
|
out.checkJsStrict = checkJsStrict;
|
|
322
|
-
}
|
|
323
336
|
const checkJsCompilerOptionsRaw = rec.raw('checkJsCompilerOptions');
|
|
324
337
|
if (checkJsCompilerOptionsRaw !== undefined) {
|
|
325
338
|
out.checkJsCompilerOptions = parsePatchLintCheckJsCompilerOptions(checkJsCompilerOptionsRaw);
|
|
@@ -328,6 +341,13 @@ function parsePatchLintBlock(rec) {
|
|
|
328
341
|
if (checkJsExtraShim !== undefined) {
|
|
329
342
|
out.checkJsExtraShim = parseShimPath(checkJsExtraShim, 'patchLint.checkJsExtraShim');
|
|
330
343
|
}
|
|
344
|
+
const checkJsTestFiles = optionalPatchLintBoolean(rec, 'checkJsTestFiles');
|
|
345
|
+
if (checkJsTestFiles !== undefined)
|
|
346
|
+
out.checkJsTestFiles = checkJsTestFiles;
|
|
347
|
+
const checkJsTestShim = rec.raw('checkJsTestShim');
|
|
348
|
+
if (checkJsTestShim !== undefined) {
|
|
349
|
+
out.checkJsTestShim = parseShimPath(checkJsTestShim, 'patchLint.checkJsTestShim');
|
|
350
|
+
}
|
|
331
351
|
const rawColorAllowlist = rec.raw('rawColorAllowlist');
|
|
332
352
|
if (rawColorAllowlist !== undefined) {
|
|
333
353
|
if (!Array.isArray(rawColorAllowlist) ||
|
|
@@ -336,21 +356,10 @@ function parsePatchLintBlock(rec) {
|
|
|
336
356
|
}
|
|
337
357
|
out.rawColorAllowlist = rawColorAllowlist;
|
|
338
358
|
}
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const testAssertionFloor = parseSeverityGate(rec.raw('testAssertionFloor'), 'patchLint.testAssertionFloor');
|
|
344
|
-
if (testAssertionFloor !== undefined) {
|
|
345
|
-
out.testAssertionFloor = testAssertionFloor;
|
|
346
|
-
}
|
|
347
|
-
const chromeScriptJsDoc = parseSeverityGate(rec.raw('chromeScriptJsDoc'), 'patchLint.chromeScriptJsDoc');
|
|
348
|
-
if (chromeScriptJsDoc !== undefined) {
|
|
349
|
-
out.chromeScriptJsDoc = chromeScriptJsDoc;
|
|
350
|
-
}
|
|
351
|
-
const undefinedIdentifiers = parseSeverityGate(rec.raw('undefinedIdentifiers'), 'patchLint.undefinedIdentifiers');
|
|
352
|
-
if (undefinedIdentifiers !== undefined) {
|
|
353
|
-
out.undefinedIdentifiers = undefinedIdentifiers;
|
|
359
|
+
for (const key of PATCH_LINT_SEVERITY_GATE_KEYS) {
|
|
360
|
+
const gate = parseSeverityGate(rec.raw(key), `patchLint.${key}`);
|
|
361
|
+
if (gate !== undefined)
|
|
362
|
+
out[key] = gate;
|
|
354
363
|
}
|
|
355
364
|
if (out.checkJsStrict === true && out.checkJs !== true) {
|
|
356
365
|
throw new ConfigError('Config field "patchLint.checkJsStrict" requires "patchLint.checkJs": true');
|
|
@@ -358,6 +367,12 @@ function parsePatchLintBlock(rec) {
|
|
|
358
367
|
if (out.checkJsCompilerOptions !== undefined && out.checkJsStrict !== true) {
|
|
359
368
|
throw new ConfigError('Config field "patchLint.checkJsCompilerOptions" requires "patchLint.checkJsStrict": true');
|
|
360
369
|
}
|
|
370
|
+
if (out.checkJsTestFiles === true && out.checkJs !== true) {
|
|
371
|
+
throw new ConfigError('Config field "patchLint.checkJsTestFiles" requires "patchLint.checkJs": true');
|
|
372
|
+
}
|
|
373
|
+
if (out.checkJsTestShim !== undefined && out.checkJsTestFiles !== true) {
|
|
374
|
+
throw new ConfigError('Config field "patchLint.checkJsTestShim" requires "patchLint.checkJsTestFiles": true');
|
|
375
|
+
}
|
|
361
376
|
return out;
|
|
362
377
|
}
|
|
363
378
|
/**
|
package/dist/src/core/config.js
CHANGED
|
@@ -12,6 +12,7 @@ import { basename } from 'node:path';
|
|
|
12
12
|
import { ConfigError, ConfigNotFoundError } from '../errors/config.js';
|
|
13
13
|
import { toError } from '../utils/errors.js';
|
|
14
14
|
import * as fsUtils from '../utils/fs.js';
|
|
15
|
+
import { isObject } from '../utils/validation.js';
|
|
15
16
|
import { getProjectPaths } from './config-paths.js';
|
|
16
17
|
import { validateConfig } from './config-validate.js';
|
|
17
18
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
@@ -21,9 +22,21 @@ export { CONFIG_FILENAME, FIREFORGE_DIR, getProjectPaths, STATE_FILENAME, SUPPOR
|
|
|
21
22
|
export { loadState, saveState, updateState } from './config-state.js';
|
|
22
23
|
export { validateConfig } from './config-validate.js';
|
|
23
24
|
// ---- config I/O (stays here because it bridges paths + validation) ----
|
|
25
|
+
/**
|
|
26
|
+
* Config-file existence probe.
|
|
27
|
+
*
|
|
28
|
+
* Uses {@link pathExistsStrict} deliberately: a permission error probing
|
|
29
|
+
* `fireforge.json` must propagate rather than read as "no config here", which
|
|
30
|
+
* is what plain `pathExists` would report.
|
|
31
|
+
*
|
|
32
|
+
* The `fsUtils as typeof fsUtils & { pathExistsStrict?: … }` cast this
|
|
33
|
+
* replaced re-declared a real, non-optional export as optional so a partial
|
|
34
|
+
* `vi.mock` of `utils/fs.js` would fall back to `pathExists` — production code
|
|
35
|
+
* shaped around a test double, and shaped around the *wrong* function, since
|
|
36
|
+
* the two differ precisely on whether EACCES propagates.
|
|
37
|
+
*/
|
|
24
38
|
async function configPathExists(path) {
|
|
25
|
-
|
|
26
|
-
return (fs.pathExistsStrict ?? fsUtils.pathExists)(path);
|
|
39
|
+
return fsUtils.pathExistsStrict(path);
|
|
27
40
|
}
|
|
28
41
|
/**
|
|
29
42
|
* Checks if a fireforge.json exists in the given directory.
|
|
@@ -79,7 +92,7 @@ export async function loadRawConfigDocument(root) {
|
|
|
79
92
|
}
|
|
80
93
|
try {
|
|
81
94
|
const data = await fsUtils.readJson(paths.config);
|
|
82
|
-
if (
|
|
95
|
+
if (!isObject(data)) {
|
|
83
96
|
throw new ConfigError(`Invalid fireforge.json at ${paths.config}: expected an object`);
|
|
84
97
|
}
|
|
85
98
|
return data;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `test --build/--build-only --extend-coverage` (FORGE L1): unions the
|
|
3
|
+
* requested paths into the recorded `testPackagingCoverage` instead of
|
|
4
|
+
* replacing it, under an anchor guard.
|
|
5
|
+
*
|
|
6
|
+
* Coverage REPLACES by default for a documented reason
|
|
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 — every committed edit is excluded;
|
|
16
|
+
* 2. every path the previous baseline fingerprinted still hashes to the
|
|
17
|
+
* recorded digest — the earlier scope's staging inputs are byte-identical.
|
|
18
|
+
* Files that became dirty SINCE that build are fine: the current
|
|
19
|
+
* whole-tree `mach build faster` repackages `dist/` and vouches for them;
|
|
20
|
+
* 3. the generated `engine/mozconfig` is unchanged. Engine HEAD does not
|
|
21
|
+
* cover this — the mozconfig is regenerated from project-side
|
|
22
|
+
* `configs/*.mozconfig` templates plus `fireforge.json` on every build,
|
|
23
|
+
* so two builds at the same engine SHA can configure differently.
|
|
24
|
+
*
|
|
25
|
+
* Every failure refuses fail-closed; the operator's remedy is always a plain
|
|
26
|
+
* scoped build, which resets the claim to the paths it actually vouches for.
|
|
27
|
+
*
|
|
28
|
+
* Known boundary, documented rather than closed: dirty NON-packageable
|
|
29
|
+
* fixtures under previously covered paths (plain test files, manifests
|
|
30
|
+
* `isPackageablePath` rejects) are invisible to check 2. Editing one
|
|
31
|
+
* uncommitted between two builds leaves the earlier scope's `_tests/`
|
|
32
|
+
* staging stale while extend still vouches for it. Plain REPLACE has no
|
|
33
|
+
* such window, because the earlier scope simply stops being covered.
|
|
34
|
+
*/
|
|
35
|
+
import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
36
|
+
/** Why `--extend-coverage` could not union onto the recorded claim. */
|
|
37
|
+
export type ExtendAnchorReason = 'no-baseline' | 'no-fingerprints' | 'no-mozconfig-hash' | 'head-moved' | 'fingerprint-diverged' | 'mozconfig-changed';
|
|
38
|
+
/** Outcome of an anchor check: either extend is sound, or it is refused. */
|
|
39
|
+
export type ExtendAnchorResult = {
|
|
40
|
+
ok: true;
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
reason: ExtendAnchorReason;
|
|
44
|
+
detail: string[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Verifies the parts of the anchor knowable before the build runs: a usable
|
|
48
|
+
* previous baseline, an unchanged engine HEAD, and byte-identical content
|
|
49
|
+
* for every path that baseline fingerprinted.
|
|
50
|
+
* @param engineDir - Path to the engine directory
|
|
51
|
+
* @param previousBaseline - Baseline recorded by the previous build
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkExtendCoverageAnchor(engineDir: string, previousBaseline: BuildBaseline | undefined): Promise<ExtendAnchorResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Verifies the mozconfig half of the anchor. Split from
|
|
56
|
+
* {@link checkExtendCoverageAnchor} because it can only run AFTER
|
|
57
|
+
* `prepareBuildEnvironment` regenerates `engine/mozconfig` — that file is
|
|
58
|
+
* the one this build will configure with — and must still run BEFORE mach,
|
|
59
|
+
* so a refusal costs no build time.
|
|
60
|
+
* @param engineDir - Path to the engine directory
|
|
61
|
+
* @param previousBaseline - Baseline recorded by the previous build
|
|
62
|
+
*/
|
|
63
|
+
export declare function checkExtendMozconfigAnchor(engineDir: string, previousBaseline: BuildBaseline | undefined): Promise<ExtendAnchorResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Unions the newly built paths onto the recorded claim. A `'full'` claim
|
|
66
|
+
* (or a pre-0.37.0 absent one, which means full historically) stays
|
|
67
|
+
* `'full'` — it already covers everything.
|
|
68
|
+
* @param previous - Coverage recorded by the previous build
|
|
69
|
+
* @param requested - Normalized request paths this build packaged
|
|
70
|
+
*/
|
|
71
|
+
export declare function unionTestPackagingCoverage(previous: TestPackagingCoverage | undefined, requested: readonly string[]): TestPackagingCoverage;
|
|
72
|
+
/**
|
|
73
|
+
* Renders the fail-closed refusal for an anchor check that did not pass.
|
|
74
|
+
* Kept separate from the probes so tests can assert the structured result
|
|
75
|
+
* without matching copy.
|
|
76
|
+
* @param result - Failing result from an anchor check
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatExtendCoverageRefusal(result: {
|
|
79
|
+
reason: ExtendAnchorReason;
|
|
80
|
+
detail: string[];
|
|
81
|
+
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* Hex-encoded SHA-256 of an engine-relative file, matching the digest the
|
|
84
|
+
* baseline writer produces. `undefined` on any IO error, which every caller
|
|
85
|
+
* treats as "cannot prove unchanged" — i.e. a refusal.
|
|
86
|
+
* @param engineDir - Path to the engine directory
|
|
87
|
+
* @param relPath - Engine-relative POSIX path
|
|
88
|
+
*/
|
|
89
|
+
export declare function hashEngineFile(engineDir: string, relPath: string): Promise<string | undefined>;
|