@hominis/fireforge 0.40.0 → 0.42.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 +85 -0
- package/README.md +16 -10
- package/dist/bin/fireforge.js +25 -4
- package/dist/build-info.json +9 -0
- package/dist/src/cli.js +20 -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 +13 -3
- 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 +16 -7
- 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 +12 -59
- 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.js +4 -4
- package/dist/src/commands/manifest.js +1 -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 +151 -50
- 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 +10 -0
- package/dist/src/commands/re-export-adjacent.js +5 -6
- 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 +8 -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 +113 -268
- 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 +11 -21
- package/dist/src/commands/source.js +3 -9
- package/dist/src/commands/status-check.d.ts +13 -0
- package/dist/src/commands/status-check.js +31 -13
- 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 +184 -175
- 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 +41 -4
- 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/tree.d.ts +3 -0
- package/dist/src/commands/tree.js +171 -25
- 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/branding.d.ts +16 -0
- package/dist/src/core/branding.js +18 -2
- 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 +34 -36
- 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 +1 -1
- package/dist/src/core/lint-cache.js +33 -10
- 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-mozconfig.js +9 -1
- 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.js +3 -5
- package/dist/src/core/patch-lint-checkjs.d.ts +25 -3
- package/dist/src/core/patch-lint-checkjs.js +78 -7
- 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-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-reexports.d.ts +1 -0
- package/dist/src/core/patch-lint-reexports.js +1 -0
- package/dist/src/core/patch-lint.d.ts +8 -0
- package/dist/src/core/patch-lint.js +84 -6
- 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-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 +0 -8
- package/dist/src/core/smoke-patterns.js +3 -2
- 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 +6 -1
- package/dist/src/core/status-classify.js +101 -73
- 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/toolchain-preflight.js +24 -26
- package/dist/src/core/tree-guard.d.ts +1 -1
- package/dist/src/core/tree-guard.js +67 -12
- package/dist/src/core/tree-store.d.ts +86 -10
- package/dist/src/core/tree-store.js +310 -72
- 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/errors/base.d.ts +13 -0
- package/dist/src/errors/base.js +13 -0
- package/dist/src/errors/rebase.d.ts +13 -0
- package/dist/src/errors/rebase.js +17 -0
- package/dist/src/types/commands/options.d.ts +86 -1
- package/dist/src/types/commands/patches.d.ts +8 -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 +23 -2
- package/dist/src/utils/validation.js +29 -3
- package/package.json +9 -9
|
@@ -5,16 +5,17 @@ import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
|
5
5
|
import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
|
|
6
6
|
import { getHead, getStatusWithCodes, isGitRepository, isMissingHeadError } from '../core/git.js';
|
|
7
7
|
import { getUntrackedFilesInDir, resolveMaxUntrackedFilesPerDir } from '../core/git-status.js';
|
|
8
|
-
import {
|
|
9
|
-
import { buildPatchQueueContext, collectNewFileCreatorsByPath } from '../core/patch-lint.js';
|
|
8
|
+
import { renderOwnershipTable } from '../core/ownership-table.js';
|
|
10
9
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
11
|
-
import { classifyFiles
|
|
12
|
-
import { CommandError, GeneralError } from '../errors/base.js';
|
|
10
|
+
import { classifyFiles } from '../core/status-classify.js';
|
|
11
|
+
import { CommandError, GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
13
12
|
import { ExitCode } from '../errors/codes.js';
|
|
14
13
|
import { FIREFORGE_TMP_PATH_PATTERN, pathExists } from '../utils/fs.js';
|
|
15
14
|
import { info, intro, isMachineOutputMode, outro, setMachineOutputMode, warn, } from '../utils/logger.js';
|
|
16
15
|
import { resolveStatusCheckPolicy, runStatusCheck } from './status-check.js';
|
|
16
|
+
import { renderJsonStatus, renderJsonSummaryStatus } from './status-json.js';
|
|
17
17
|
import { renderDefaultStatus, renderTestCoverageStatus, renderUnmanagedOnly, } from './status-output.js';
|
|
18
|
+
import { buildOwnershipJsonBlock, collectOwnershipRows, summarizeOwnership, } from './status-ownership.js';
|
|
18
19
|
/**
|
|
19
20
|
* Renders raw worktree status as machine-parseable porcelain-style output.
|
|
20
21
|
* Each line is: STATUS<tab>FILE
|
|
@@ -91,39 +92,48 @@ async function classifyStatusFiles(files, paths, projectRoot, binaryName) {
|
|
|
91
92
|
const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
|
|
92
93
|
return classifyFiles(files, paths.engine, paths.patches, binaryName, furnacePrefixes);
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const byClassification = {
|
|
108
|
-
unmanaged: 0,
|
|
109
|
-
'patch-owned-drift': 0,
|
|
110
|
-
'patch-backed': 0,
|
|
111
|
-
branding: 0,
|
|
112
|
-
furnace: 0,
|
|
113
|
-
conflict: 0,
|
|
114
|
-
};
|
|
115
|
-
for (const file of outputFiles) {
|
|
116
|
-
byClassification[file.classification]++;
|
|
95
|
+
/**
|
|
96
|
+
* The one worktree scan every classifying mode shares: porcelain status,
|
|
97
|
+
* collapsed-directory expansion, atomic-temp-file filtering, and the
|
|
98
|
+
* truncation banner. `--ownership` used to carry its own copy of this
|
|
99
|
+
* block (FORGE L3); a non-git engine degrades to an empty list there,
|
|
100
|
+
* which is why the git probe is a parameter rather than a hard guard.
|
|
101
|
+
* @param engineDir - Path to the engine directory
|
|
102
|
+
* @param requireGitRepository - When false, a non-git engine yields `[]`
|
|
103
|
+
* instead of being probed (the historical `--ownership` behavior)
|
|
104
|
+
*/
|
|
105
|
+
async function scanEngineStatusFiles(engineDir, requireGitRepository) {
|
|
106
|
+
if (!requireGitRepository && !(await isGitRepository(engineDir))) {
|
|
107
|
+
return [];
|
|
117
108
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
const { entries, truncations } = await expandDirectoryEntries(await getStatusWithCodes(engineDir), engineDir);
|
|
110
|
+
renderTruncationBanner(truncations);
|
|
111
|
+
return filterFireForgeTempFiles(entries);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Emits the `--json` payload (full or `--summary`) and applies the
|
|
115
|
+
* `--check` policy, which stays the SOLE exit driver on this path.
|
|
116
|
+
*
|
|
117
|
+
* `--include-ownership` (FORGE L3) is a MODIFIER, not a mode: it appends an
|
|
118
|
+
* ownership block without touching exit semantics, so an ownership conflict
|
|
119
|
+
* still fails only the human `--ownership` mode.
|
|
120
|
+
* @param classified - Classified worktree entries
|
|
121
|
+
* @param files - The same entries pre-classification, for ownership rows
|
|
122
|
+
* @param paths - Resolved project paths
|
|
123
|
+
* @param options - Status display options
|
|
124
|
+
* @param checkPolicy - Resolved `--check`/`--fail-on` policy
|
|
125
|
+
*/
|
|
126
|
+
async function renderJsonMode(classified, files, paths, options, checkPolicy) {
|
|
127
|
+
const ownership = options.includeOwnership === true
|
|
128
|
+
? buildOwnershipJsonBlock(await collectOwnershipRows(paths.patches, (await loadPatchesManifest(paths.patches))?.patches ?? [], files, classified))
|
|
129
|
+
: undefined;
|
|
130
|
+
if (options.summary === true) {
|
|
131
|
+
renderJsonSummaryStatus(classified, checkPolicy, ownership);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
renderJsonStatus(classified, ownership);
|
|
135
|
+
}
|
|
136
|
+
runStatusCheck(classified, checkPolicy);
|
|
127
137
|
}
|
|
128
138
|
/**
|
|
129
139
|
* Detects the "unborn HEAD" aftermath of an interrupted `fireforge download`
|
|
@@ -174,154 +184,151 @@ export async function statusCommand(projectRoot, options = {}) {
|
|
|
174
184
|
if (shouldUseMachineOutput) {
|
|
175
185
|
setMachineOutputMode(true);
|
|
176
186
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
renderOwnershipTable(rows);
|
|
236
|
-
const conflictCount = rows.filter((r) => r.conflict).length;
|
|
237
|
-
const unmanagedCount = rows.filter((r) => r.unmanaged).length;
|
|
238
|
-
const managedCount = rows.filter((r) => !r.unmanaged).length;
|
|
239
|
-
const parts = [`${managedCount} managed`];
|
|
240
|
-
if (conflictCount > 0)
|
|
241
|
-
parts.push(`${conflictCount} conflict${conflictCount === 1 ? '' : 's'}`);
|
|
242
|
-
if (unmanagedCount > 0)
|
|
243
|
-
parts.push(`${unmanagedCount} unmanaged`);
|
|
244
|
-
outro(parts.join(', '));
|
|
245
|
-
if (conflictCount > 0) {
|
|
246
|
-
throw new GeneralError(`${conflictCount} path(s) are claimed by more than one patch. ` +
|
|
247
|
-
'Run "fireforge verify" for full details, then use "re-export --files" or ' +
|
|
248
|
-
'"patch delete" to resolve.');
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
// Check if engine exists
|
|
187
|
+
// On a throw, machine mode deliberately stays ENGAGED: withErrorHandling
|
|
188
|
+
// routes the styled error to stderr while the mode is on and resets it
|
|
189
|
+
// centrally (a mid-throw restore here put the refusal on stdout after the
|
|
190
|
+
// JSON payload, breaking the 0.40.0 stderr promise).
|
|
191
|
+
await runStatusCommandBody(projectRoot, options);
|
|
192
|
+
if (isMachineOutputMode() !== previousMachineOutputMode) {
|
|
193
|
+
setMachineOutputMode(previousMachineOutputMode);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** The status body proper; the machine-mode lifecycle lives in {@link statusCommand}. */
|
|
197
|
+
async function runStatusCommandBody(projectRoot, options) {
|
|
198
|
+
const modeCount = [
|
|
199
|
+
options.raw,
|
|
200
|
+
options.unmanaged,
|
|
201
|
+
options.ownership,
|
|
202
|
+
options.testCoverage,
|
|
203
|
+
options.json,
|
|
204
|
+
].filter((v) => v === true).length;
|
|
205
|
+
if (modeCount > 1) {
|
|
206
|
+
throw new GeneralError('Cannot use --raw, --unmanaged, --ownership, --test-coverage, and --json together. Pick at most one.');
|
|
207
|
+
}
|
|
208
|
+
// --summary elides the files[] payload for gate consumers (FORGE K8);
|
|
209
|
+
// it only makes sense on the JSON shape.
|
|
210
|
+
if (options.summary === true && options.json !== true) {
|
|
211
|
+
throw new InvalidArgumentError('--summary requires --json.', '--summary');
|
|
212
|
+
}
|
|
213
|
+
// --include-ownership adds a block to the JSON payload; it is meaningless
|
|
214
|
+
// on the human views, where --ownership is the mode that renders the
|
|
215
|
+
// table (FORGE L3).
|
|
216
|
+
if (options.includeOwnership === true && options.json !== true) {
|
|
217
|
+
throw new InvalidArgumentError('--include-ownership requires --json. Use --ownership for the human table.', '--include-ownership');
|
|
218
|
+
}
|
|
219
|
+
// --check / --fail-on enforcement policy (FORGE G1). Applies only
|
|
220
|
+
// where classification runs (the default view and --json).
|
|
221
|
+
const checkPolicy = resolveStatusCheckPolicy(options);
|
|
222
|
+
if (!options.raw && !options.json) {
|
|
223
|
+
intro('FireForge Status');
|
|
224
|
+
}
|
|
225
|
+
// Test-coverage mode needs only the project root (the baseline lives
|
|
226
|
+
// in .fireforge/), so it short-circuits before any engine/git guards.
|
|
227
|
+
if (options.testCoverage) {
|
|
228
|
+
renderTestCoverageStatus(await readBuildBaseline(projectRoot));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const paths = getProjectPaths(projectRoot);
|
|
232
|
+
const config = await loadConfig(projectRoot);
|
|
233
|
+
const emitJsonError = (code, message) => {
|
|
234
|
+
process.stdout.write(JSON.stringify({ schemaVersion: 1, error: message, code }) + '\n');
|
|
235
|
+
throw new CommandError(ExitCode.GENERAL_ERROR);
|
|
236
|
+
};
|
|
237
|
+
// Ownership mode is a flat file→patch table; sources are the manifest's
|
|
238
|
+
// filesAffected, any worktree drift, and the cross-patch
|
|
239
|
+
// duplicate-new-file-creation map produced by walking each patch
|
|
240
|
+
// body. The latter is the alignment fix between `status --ownership`
|
|
241
|
+
// and `fireforge verify` — see buildOwnershipTable's header comment.
|
|
242
|
+
// Runs before the default classify path so we can short-circuit
|
|
243
|
+
// without computing patch-backed state.
|
|
244
|
+
if (options.ownership) {
|
|
253
245
|
if (!(await pathExists(paths.engine))) {
|
|
254
|
-
if (options.json) {
|
|
255
|
-
emitJsonError('engine-missing', 'Firefox source not found. Run "fireforge download" first.');
|
|
256
|
-
}
|
|
257
246
|
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
258
247
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
248
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
249
|
+
// Same scan and the SAME classification pass as every other mode
|
|
250
|
+
// (FORGE L3). This branch keeps its historical guard shape on purpose:
|
|
251
|
+
// no baseline-commit assertion, and a non-git engine degrades to an
|
|
252
|
+
// empty table rather than the recovery banner.
|
|
253
|
+
const rawFilesOwnership = await scanEngineStatusFiles(paths.engine, false);
|
|
254
|
+
const rows = await collectOwnershipRows(paths.patches, manifest?.patches ?? [], rawFilesOwnership, await classifyStatusFiles(rawFilesOwnership, paths, projectRoot, config.binaryName));
|
|
255
|
+
renderOwnershipTable(rows);
|
|
256
|
+
const { managed, unmanaged, conflicts } = summarizeOwnership(rows);
|
|
257
|
+
const parts = [`${managed} managed`];
|
|
258
|
+
if (conflicts > 0)
|
|
259
|
+
parts.push(`${conflicts} conflict${conflicts === 1 ? '' : 's'}`);
|
|
260
|
+
if (unmanaged > 0)
|
|
261
|
+
parts.push(`${unmanaged} unmanaged`);
|
|
262
|
+
outro(parts.join(', '));
|
|
263
|
+
if (conflicts > 0) {
|
|
264
|
+
throw new GeneralError(`${conflicts} path(s) are claimed by more than one patch. ` +
|
|
265
|
+
'Run "fireforge verify" for full details, then use "re-export --files" or ' +
|
|
266
|
+
'"patch delete" to resolve.');
|
|
265
267
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// branch so raw / unmanaged / default / json all agree.
|
|
271
|
-
const files = filterFireForgeTempFiles(expanded);
|
|
272
|
-
renderTruncationBanner(truncations);
|
|
273
|
-
// `--json` callers expect machine-parseable output on every invocation,
|
|
274
|
-
// including the clean-tree case. Before this ordering fix a clean tree
|
|
275
|
-
// printed "No modified files" / "Working tree clean" via the human
|
|
276
|
-
// branch below and `--json` was silently ignored, so scripts that piped
|
|
277
|
-
// the output through a JSON parser broke precisely when there was
|
|
278
|
-
// nothing to report. Emit `[]` here and return before the human fallback.
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Check if engine exists
|
|
271
|
+
if (!(await pathExists(paths.engine))) {
|
|
279
272
|
if (options.json) {
|
|
280
|
-
|
|
281
|
-
renderJsonStatus(classified);
|
|
282
|
-
runStatusCheck(classified, checkPolicy);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
// `--raw` consumers parse the native `git status --porcelain` output
|
|
286
|
-
// directly. On a clean tree the raw mode should produce nothing on
|
|
287
|
-
// stdout — the human "Working tree clean" banner would contaminate the
|
|
288
|
-
// pipe. Short-circuit before the human clean-tree branch below.
|
|
289
|
-
if (options.raw && files.length === 0) {
|
|
290
|
-
return;
|
|
273
|
+
emitJsonError('engine-missing', 'Firefox source not found. Run "fireforge download" first.');
|
|
291
274
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (options.raw) {
|
|
299
|
-
renderRawStatus(files);
|
|
300
|
-
return;
|
|
275
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
276
|
+
}
|
|
277
|
+
// Check if it's a git repository
|
|
278
|
+
if (!(await isGitRepository(paths.engine))) {
|
|
279
|
+
if (options.json) {
|
|
280
|
+
emitJsonError('engine-not-git', 'Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
301
281
|
}
|
|
302
|
-
|
|
282
|
+
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
283
|
+
}
|
|
284
|
+
await assertEngineHasBaselineCommit(paths.engine, options);
|
|
285
|
+
const files = await scanEngineStatusFiles(paths.engine, true);
|
|
286
|
+
// `--json` callers expect machine-parseable output on every invocation,
|
|
287
|
+
// including the clean-tree case. Before this ordering fix a clean tree
|
|
288
|
+
// printed "No modified files" / "Working tree clean" via the human
|
|
289
|
+
// branch below and `--json` was silently ignored, so scripts that piped
|
|
290
|
+
// the output through a JSON parser broke precisely when there was
|
|
291
|
+
// nothing to report. Emit `[]` here and return before the human fallback.
|
|
292
|
+
if (options.json) {
|
|
303
293
|
const classified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
unmanaged: classified.filter((f) => f.classification === 'unmanaged'),
|
|
307
|
-
patchOwnedDrift: classified.filter((f) => f.classification === 'patch-owned-drift'),
|
|
308
|
-
patchBacked: classified.filter((f) => f.classification === 'patch-backed'),
|
|
309
|
-
branding: classified.filter((f) => f.classification === 'branding'),
|
|
310
|
-
furnace: classified.filter((f) => f.classification === 'furnace'),
|
|
311
|
-
};
|
|
312
|
-
// --unmanaged mode: only show unmanaged
|
|
313
|
-
if (options.unmanaged) {
|
|
314
|
-
await renderUnmanagedOnly(buckets.unmanaged, files.length, projectRoot, config.binaryName);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
await renderDefaultStatus(files.length, buckets, projectRoot, config.binaryName);
|
|
318
|
-
runStatusCheck(classified, checkPolicy);
|
|
294
|
+
await renderJsonMode(classified, files, paths, options, checkPolicy);
|
|
295
|
+
return;
|
|
319
296
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
297
|
+
// `--raw` consumers parse the native `git status --porcelain` output
|
|
298
|
+
// directly. On a clean tree the raw mode should produce nothing on
|
|
299
|
+
// stdout — the human "Working tree clean" banner would contaminate the
|
|
300
|
+
// pipe. Short-circuit before the human clean-tree branch below.
|
|
301
|
+
if (options.raw && files.length === 0) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (files.length === 0) {
|
|
305
|
+
info('No modified files');
|
|
306
|
+
outro('Working tree clean');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
// Raw mode: existing behavior
|
|
310
|
+
if (options.raw) {
|
|
311
|
+
renderRawStatus(files);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Patch-aware classification
|
|
315
|
+
const classified = await classifyStatusFiles(files, paths, projectRoot, config.binaryName);
|
|
316
|
+
const buckets = {
|
|
317
|
+
conflict: classified.filter((f) => f.classification === 'conflict'),
|
|
318
|
+
unmanaged: classified.filter((f) => f.classification === 'unmanaged'),
|
|
319
|
+
patchOwnedDrift: classified.filter((f) => f.classification === 'patch-owned-drift'),
|
|
320
|
+
patchBacked: classified.filter((f) => f.classification === 'patch-backed'),
|
|
321
|
+
branding: classified.filter((f) => f.classification === 'branding'),
|
|
322
|
+
furnace: classified.filter((f) => f.classification === 'furnace'),
|
|
323
|
+
binaryUnsupported: classified.filter((f) => f.classification === 'binary-unsupported'),
|
|
324
|
+
};
|
|
325
|
+
// --unmanaged mode: only show unmanaged
|
|
326
|
+
if (options.unmanaged) {
|
|
327
|
+
await renderUnmanagedOnly(buckets.unmanaged, files.length, projectRoot, config.binaryName);
|
|
328
|
+
return;
|
|
324
329
|
}
|
|
330
|
+
await renderDefaultStatus(files.length, buckets, projectRoot, config.binaryName);
|
|
331
|
+
runStatusCheck(classified, checkPolicy);
|
|
325
332
|
}
|
|
326
333
|
/** Registers the status command on the CLI program. */
|
|
327
334
|
export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
|
|
@@ -333,8 +340,10 @@ export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
|
|
|
333
340
|
.option('--ownership', 'Show a flat path → owning patch table (flags files claimed by multiple patches)')
|
|
334
341
|
.option('--test-coverage', 'Show what the last recorded build covers for test packaging (full or scoped paths)')
|
|
335
342
|
.option('--json', 'Output classified file status as JSON')
|
|
343
|
+
.option('--summary', 'With --json: emit only summary counts (plus offending files when --check/--fail-on is active), omitting the per-file files[] payload')
|
|
344
|
+
.option('--include-ownership', 'With --json (composes with --summary): append an ownership block (path->owning-patch rows plus managed/unmanaged/conflict counts) to the payload, so one scan serves the classification, ownership, and check views. Does not change exit semantics.')
|
|
336
345
|
.option('--check', 'Exit non-zero when any unmanaged, patch-owned-drift, or conflict file exists (composes with --json; combine with --fail-on for finer policy)')
|
|
337
|
-
.option('--fail-on <classifications>', 'Comma-separated classification list that fails --check, replacing the default set (implies --check). Valid: patch-backed, patch-owned-drift, unmanaged, branding, furnace, conflict')
|
|
346
|
+
.option('--fail-on <classifications>', 'Comma-separated classification list that fails --check, replacing the default set (implies --check). Valid: patch-backed, patch-owned-drift, unmanaged, branding, furnace, conflict, binary-unsupported')
|
|
338
347
|
.action(withErrorHandling(async (options) => {
|
|
339
348
|
await statusCommand(getProjectRoot(), options);
|
|
340
349
|
}));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-test build phase for `fireforge test`, split out of `test.ts` (at
|
|
3
|
+
* the per-file line budget): the incremental protected mach build with
|
|
4
|
+
* its baseline write, and the FORGE J9 `--build-only` union-build shape.
|
|
5
|
+
*/
|
|
6
|
+
import type { getProjectPaths, loadConfig } from '../core/config.js';
|
|
7
|
+
import type { TestOptions } from '../types/commands/index.js';
|
|
8
|
+
import type { HarnessClassification } from './test-modes.js';
|
|
9
|
+
/**
|
|
10
|
+
* Runs the pre-test build (or the stale-build gate when no build was
|
|
11
|
+
* requested). Returns `true` when the run is COMPLETE — the `--build-only`
|
|
12
|
+
* union-build shape (FORGE J9), which packages every requested path
|
|
13
|
+
* (mixed harnesses legal, nothing dispatches), prints the per-harness
|
|
14
|
+
* next steps, and emits the run's single `PASS` verdict.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runTestBuildPhase(projectRoot: string, paths: ReturnType<typeof getProjectPaths>, projectConfig: Awaited<ReturnType<typeof loadConfig>>, harnessRetries: number, options: TestOptions, request: {
|
|
17
|
+
classification: HarnessClassification;
|
|
18
|
+
normalizedPaths: string[];
|
|
19
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Pre-test build phase for `fireforge test`, split out of `test.ts` (at
|
|
4
|
+
* the per-file line budget): the incremental protected mach build with
|
|
5
|
+
* its baseline write, and the FORGE J9 `--build-only` union-build shape.
|
|
6
|
+
*/
|
|
7
|
+
import { readBuildBaseline, writeBuildBaseline } from '../core/build-baseline.js';
|
|
8
|
+
import { prepareBuildEnvironment } from '../core/build-prepare.js';
|
|
9
|
+
import { checkExtendCoverageAnchor, checkExtendMozconfigAnchor, formatExtendCoverageRefusal, unionTestPackagingCoverage, } from '../core/coverage-extend.js';
|
|
10
|
+
import { runProtectedMachBuild, withBuildLock } from '../core/mach.js';
|
|
11
|
+
import { buildHarnessCrashMessage } from '../core/test-harness-crash.js';
|
|
12
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
13
|
+
import { BuildError } from '../errors/build.js';
|
|
14
|
+
import { toError } from '../utils/errors.js';
|
|
15
|
+
import { info, spinner, verbose } from '../utils/logger.js';
|
|
16
|
+
import { reportBuildOnlyCompletion } from './test-modes.js';
|
|
17
|
+
import { enforceStaleBuildGate, enforceStaticComponentsGate } from './test-stale-gate.js';
|
|
18
|
+
import { emitPassVerdict } from './test-verdict.js';
|
|
19
|
+
async function runPreTestBuild(projectRoot, paths, projectConfig, harnessRetries, testPackagingCoverage, extend) {
|
|
20
|
+
await withBuildLock(projectRoot, async () => {
|
|
21
|
+
// Pass the previous baseline exactly like `fireforge build` does, so
|
|
22
|
+
// auto-configure runs under the same conditions on both paths. The
|
|
23
|
+
// pre-test build must never invalidate more of the objdir than a plain
|
|
24
|
+
// `mach build faster` would (field incident: a failed pre-test build
|
|
25
|
+
// was followed by a ~64-minute full rebuild where an incremental one
|
|
26
|
+
// should have sufficed).
|
|
27
|
+
const previousBaseline = await readBuildBaseline(projectRoot);
|
|
28
|
+
const preparation = await prepareBuildEnvironment(projectRoot, paths, projectConfig, {
|
|
29
|
+
previousBaseline,
|
|
30
|
+
});
|
|
31
|
+
// The mozconfig half of the --extend-coverage anchor (FORGE L1) can
|
|
32
|
+
// only be checked once prepareBuildEnvironment has regenerated
|
|
33
|
+
// engine/mozconfig — that is the file this build will configure with —
|
|
34
|
+
// and must still be checked before mach, so a refusal costs no build.
|
|
35
|
+
if (extend !== undefined) {
|
|
36
|
+
const mozconfigAnchor = await checkExtendMozconfigAnchor(paths.engine, previousBaseline);
|
|
37
|
+
if (!mozconfigAnchor.ok) {
|
|
38
|
+
throw new GeneralError(formatExtendCoverageRefusal(mozconfigAnchor));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const buildKind = preparation.fullBuildRequired ? 'full' : 'faster';
|
|
42
|
+
if (preparation.fullBuildRequired) {
|
|
43
|
+
info('A jar.mn registration changed since the last successful build; escalating this pre-test build to a full mach build so new install-manifest destinations are created.');
|
|
44
|
+
}
|
|
45
|
+
const s = spinner(preparation.fullBuildRequired
|
|
46
|
+
? 'Running required full build...'
|
|
47
|
+
: 'Running incremental build...');
|
|
48
|
+
// The pre-test build routes through the same protected mach dispatch as
|
|
49
|
+
// `fireforge build` / `build --ui`: in-venv resource-monitor guard plus
|
|
50
|
+
// the uniform recognized-crash retry budget, with a fresh mach process
|
|
51
|
+
// (and fresh guard install) per attempt. prepareBuildEnvironment runs
|
|
52
|
+
// ONCE, outside the retry loop — retries never re-run mach configure.
|
|
53
|
+
const result = await runProtectedMachBuild(buildKind, paths.engine, {
|
|
54
|
+
retries: harnessRetries,
|
|
55
|
+
onRetry: (signature, nextAttempt, maxAttempts) => {
|
|
56
|
+
s.message(`Pre-test build hit a harness crash (${signature.reason}); ` +
|
|
57
|
+
`retrying (attempt ${nextAttempt} of ${maxAttempts})...`);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (result.exitCode === 0) {
|
|
61
|
+
s.stop('Build complete');
|
|
62
|
+
// Same record, same failure tolerance as `fireforge build` /
|
|
63
|
+
// `build --ui` (build.ts): a green pre-test build refreshes the
|
|
64
|
+
// stale-build baseline so a later plain `fireforge test` over the
|
|
65
|
+
// same files — in any invocation shape — is not refused. A failed
|
|
66
|
+
// write never fails the run. The coverage claim is scoped to the
|
|
67
|
+
// requested test paths, since a file-scoped `test --build` only
|
|
68
|
+
// guarantees packaging for those manifests. The previous baseline is
|
|
69
|
+
// passed through so a scoped write carries the static-components
|
|
70
|
+
// anchor forward (a `mach build faster` does not rebake
|
|
71
|
+
// components.conf into the compiled table).
|
|
72
|
+
try {
|
|
73
|
+
// Under --extend-coverage the claim is the UNION of the previous
|
|
74
|
+
// record and this build's paths, and the static-components anchor
|
|
75
|
+
// is always carried forward: that union can evaluate to 'full'
|
|
76
|
+
// while the build behind it was a scoped `mach build faster` that
|
|
77
|
+
// did not rebake the compiled table.
|
|
78
|
+
const recordedCoverage = extend !== undefined
|
|
79
|
+
? unionTestPackagingCoverage(previousBaseline?.testPackagingCoverage, extend.requestedPaths)
|
|
80
|
+
: testPackagingCoverage;
|
|
81
|
+
await writeBuildBaseline(projectRoot, paths.engine, projectConfig.binaryName, recordedCoverage, previousBaseline, describeBuildInvocation(extend !== undefined, testPackagingCoverage), preparation.fullBuildRequired
|
|
82
|
+
? 'refresh'
|
|
83
|
+
: extend !== undefined
|
|
84
|
+
? 'carry-forward'
|
|
85
|
+
: 'auto');
|
|
86
|
+
}
|
|
87
|
+
catch (baselineError) {
|
|
88
|
+
verbose(`Could not persist build baseline: ${toError(baselineError).message}`);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
s.error('Pre-test build failed');
|
|
93
|
+
throw new BuildError(result.crashSignature
|
|
94
|
+
? buildHarnessCrashMessage(result.crashSignature, result.attempts, 'mach build faster')
|
|
95
|
+
: 'Pre-test build failed', preparation.fullBuildRequired ? 'mach build' : 'mach build faster');
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `--extend-coverage` only means something for a SCOPED build: it unions
|
|
100
|
+
* this build's paths onto the recorded claim. Without `--build`/
|
|
101
|
+
* `--build-only` there is no build to extend from, and a path-less build
|
|
102
|
+
* records `'full'`, which already covers everything — refusing both is more
|
|
103
|
+
* honest than silently ignoring the flag.
|
|
104
|
+
*/
|
|
105
|
+
function assertExtendCoverageUsage(options, normalizedPaths) {
|
|
106
|
+
if (options.build !== true && options.buildOnly !== true) {
|
|
107
|
+
throw new InvalidArgumentError('--extend-coverage requires --build or --build-only: it unions the paths a build packages onto the recorded coverage claim.', '--extend-coverage');
|
|
108
|
+
}
|
|
109
|
+
if (normalizedPaths.length === 0) {
|
|
110
|
+
throw new InvalidArgumentError('--extend-coverage requires explicit test paths: a path-less build records full coverage, which already covers every path.', '--extend-coverage');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Renders the `recordedBy` invocation string surfaced by `status --test-coverage`. */
|
|
114
|
+
function describeBuildInvocation(extending, coverage) {
|
|
115
|
+
const extendFlag = extending ? ' --extend-coverage' : '';
|
|
116
|
+
return coverage === 'full'
|
|
117
|
+
? `fireforge test --build${extendFlag}`
|
|
118
|
+
: `fireforge test --build${extendFlag} ${coverage.join(' ')}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Runs the pre-test build (or the stale-build gate when no build was
|
|
122
|
+
* requested). Returns `true` when the run is COMPLETE — the `--build-only`
|
|
123
|
+
* union-build shape (FORGE J9), which packages every requested path
|
|
124
|
+
* (mixed harnesses legal, nothing dispatches), prints the per-harness
|
|
125
|
+
* next steps, and emits the run's single `PASS` verdict.
|
|
126
|
+
*/
|
|
127
|
+
export async function runTestBuildPhase(projectRoot, paths, projectConfig, harnessRetries, options, request) {
|
|
128
|
+
const { classification, normalizedPaths } = request;
|
|
129
|
+
const extending = options.extendCoverage === true;
|
|
130
|
+
if (extending) {
|
|
131
|
+
assertExtendCoverageUsage(options, normalizedPaths);
|
|
132
|
+
}
|
|
133
|
+
if (options.build || options.buildOnly) {
|
|
134
|
+
// A path-less `test --build` runs (and packages for) the full suite;
|
|
135
|
+
// a scoped invocation only vouches for the requested paths. A SCOPED
|
|
136
|
+
// rebuild also cannot regenerate the compiled StaticComponents table,
|
|
137
|
+
// so it runs the components.conf gate up-front (the path-less shape
|
|
138
|
+
// refreshes the anchor itself and skips it).
|
|
139
|
+
const coverage = normalizedPaths.length === 0 ? 'full' : normalizedPaths;
|
|
140
|
+
if (normalizedPaths.length > 0) {
|
|
141
|
+
const previousBaseline = await readBuildBaseline(projectRoot);
|
|
142
|
+
await enforceStaticComponentsGate(paths.engine, previousBaseline, options);
|
|
143
|
+
if (extending) {
|
|
144
|
+
// Head/fingerprint half of the anchor, before the build lock: a
|
|
145
|
+
// refusal here costs neither build time nor the lock.
|
|
146
|
+
const anchor = await checkExtendCoverageAnchor(paths.engine, previousBaseline);
|
|
147
|
+
if (!anchor.ok) {
|
|
148
|
+
throw new GeneralError(formatExtendCoverageRefusal(anchor));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
await runPreTestBuild(projectRoot, paths, projectConfig, harnessRetries, coverage, extending ? { requestedPaths: normalizedPaths } : undefined);
|
|
153
|
+
info('');
|
|
154
|
+
if (options.buildOnly) {
|
|
155
|
+
// Union build for mixed harnesses (FORGE J9): the coverage claim
|
|
156
|
+
// above lists every requested path, so each harness half can run
|
|
157
|
+
// build-less against this packaging. Nothing dispatches here; the
|
|
158
|
+
// run still ends with exactly one verdict line.
|
|
159
|
+
reportBuildOnlyCompletion(classification, normalizedPaths);
|
|
160
|
+
emitPassVerdict();
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await enforceStaleBuildGate(projectRoot, paths.engine, options, normalizedPaths);
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|