@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
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { confirm } from '@clack/prompts';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { getProjectPaths } from '../core/config.js';
|
|
5
|
+
import { assertEngineGitReady } from '../core/engine-precondition.js';
|
|
5
6
|
import { clearAppliedFurnaceState } from '../core/furnace-config.js';
|
|
6
|
-
import {
|
|
7
|
+
import { hasChanges, resetChanges } from '../core/git.js';
|
|
7
8
|
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
|
|
8
9
|
import { warnIfStaticComponentsStale } from '../core/test-stale-check.js';
|
|
9
|
-
import {
|
|
10
|
-
import { pathExists } from '../utils/fs.js';
|
|
10
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
11
11
|
import { cancel, info, intro, isCancel, outro, spinner, warn } from '../utils/logger.js';
|
|
12
12
|
import { pickDefined } from '../utils/options.js';
|
|
13
13
|
/**
|
|
@@ -18,26 +18,7 @@ import { pickDefined } from '../utils/options.js';
|
|
|
18
18
|
export async function resetCommand(projectRoot, options) {
|
|
19
19
|
intro('FireForge Reset');
|
|
20
20
|
const paths = getProjectPaths(projectRoot);
|
|
21
|
-
|
|
22
|
-
if (!(await pathExists(paths.engine))) {
|
|
23
|
-
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
24
|
-
}
|
|
25
|
-
// Check if it's a git repository
|
|
26
|
-
if (!(await isGitRepository(paths.engine))) {
|
|
27
|
-
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
28
|
-
}
|
|
29
|
-
// Unborn-HEAD guard (mirrors status): an interrupted download leaves a
|
|
30
|
-
// repo with no baseline commit, where the entire ~300k-file tree reads
|
|
31
|
-
// as untracked — a reset against that state is never what the operator
|
|
32
|
-
// wants, and the guidance names the actual fix.
|
|
33
|
-
try {
|
|
34
|
-
await getHead(paths.engine);
|
|
35
|
-
}
|
|
36
|
-
catch (headError) {
|
|
37
|
-
if (!isMissingHeadError(headError))
|
|
38
|
-
throw headError;
|
|
39
|
-
throw new GeneralError('Engine repository has no baseline commit yet — a previous "fireforge download" was interrupted before git created the initial Firefox source commit. Re-run "fireforge download --force" to recreate the baseline repository cleanly.');
|
|
40
|
-
}
|
|
21
|
+
await assertEngineGitReady(paths.engine);
|
|
41
22
|
// Check for changes
|
|
42
23
|
const hasUncommittedChanges = await hasChanges(paths.engine);
|
|
43
24
|
if (!hasUncommittedChanges) {
|
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { confirm } from '@clack/prompts';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { getProjectPaths, loadConfig, loadState, updateState } from '../core/config.js';
|
|
6
|
-
import {
|
|
6
|
+
import { assertEngineGitReady } from '../core/engine-precondition.js';
|
|
7
7
|
import { getStagedDiffForFiles } from '../core/git-diff.js';
|
|
8
8
|
import { stageFiles, unstageFiles } from '../core/git-file-ops.js';
|
|
9
9
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
@@ -30,14 +30,7 @@ export async function resolveCommand(projectRoot, options = {}) {
|
|
|
30
30
|
}
|
|
31
31
|
const { patchFilename } = state.pendingResolution;
|
|
32
32
|
info(`Resolving conflict for patch: ${patchFilename}`);
|
|
33
|
-
|
|
34
|
-
if (!(await pathExists(paths.engine))) {
|
|
35
|
-
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
36
|
-
}
|
|
37
|
-
// Check if it's a git repository
|
|
38
|
-
if (!(await isGitRepository(paths.engine))) {
|
|
39
|
-
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
40
|
-
}
|
|
33
|
+
await assertEngineGitReady(paths.engine);
|
|
41
34
|
// Non-interactive mode requires an explicit `--yes` to proceed: the
|
|
42
35
|
// operator is asserting the manual merge is complete and the
|
|
43
36
|
// refreshed diff is the one to record. Without `--yes`, an accidental
|
package/dist/src/commands/run.js
CHANGED
|
@@ -5,16 +5,17 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
7
7
|
import { warnIfFurnaceStale } from '../core/furnace-staleness.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { hasBuildArtifacts, hasRunnableBundle, run, runMachSmoke } from '../core/mach.js';
|
|
9
|
+
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
10
|
+
import { compileAllowlistFromFile, compileAllowlistFromStrings, matchAllowlist, matchesSmokeError, } from '../core/smoke-patterns.js';
|
|
10
11
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
11
|
-
import {
|
|
12
|
+
import { BuildError } from '../errors/build.js';
|
|
12
13
|
import { ExitCode } from '../errors/codes.js';
|
|
13
14
|
import { SmokeRunError } from '../errors/run.js';
|
|
14
15
|
import { toError } from '../utils/errors.js';
|
|
15
16
|
import { pathExists, removeDir, removeFile } from '../utils/fs.js';
|
|
16
17
|
import { info, intro, verbose, warn } from '../utils/logger.js';
|
|
17
|
-
import { commanderArgParser, pickDefined } from '../utils/options.js';
|
|
18
|
+
import { commanderArgParser, pickDefined, stringListOption } from '../utils/options.js';
|
|
18
19
|
/**
|
|
19
20
|
* Exit code returned by smoke-run mode when the captured console stream
|
|
20
21
|
* produced one or more error lines that did NOT match the operator's
|
|
@@ -81,20 +82,12 @@ export async function runCommand(projectRoot, options = {}) {
|
|
|
81
82
|
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
82
83
|
}
|
|
83
84
|
const buildCheck = await hasBuildArtifacts(paths.engine);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
if (!buildCheck.exists) {
|
|
92
|
-
const detail = buildCheck.objDir
|
|
93
|
-
? `Build artifacts incomplete in ${buildCheck.objDir}/`
|
|
94
|
-
: 'No build artifacts found (obj-*/ directory missing)';
|
|
95
|
-
throw new GeneralError(`Run requires a completed build. ${detail}\n\n` +
|
|
96
|
-
"Run 'fireforge build' first, then rerun 'fireforge run'.");
|
|
97
|
-
}
|
|
85
|
+
assertBuildArtifacts(paths.engine, buildCheck, {
|
|
86
|
+
label: 'Run',
|
|
87
|
+
requirement: 'Run requires a completed build.',
|
|
88
|
+
remediation: "Run 'fireforge build' first, then rerun 'fireforge run'.",
|
|
89
|
+
requireExisting: true,
|
|
90
|
+
});
|
|
98
91
|
// `hasBuildArtifacts` only checks for an `obj-*/dist/` directory; a
|
|
99
92
|
// build that configured but hasn't yet produced the launchable binary
|
|
100
93
|
// (common in a long real Firefox compile that the operator stopped
|
|
@@ -186,6 +179,7 @@ async function runSmokeExit(engineDir, options) {
|
|
|
186
179
|
const findings = [];
|
|
187
180
|
let allowlistedErrorHits = 0;
|
|
188
181
|
let allowlistedTotalHits = 0;
|
|
182
|
+
const allowlistHits = new Array(allowlist.length).fill(0);
|
|
189
183
|
const handleLine = (stream, line) => {
|
|
190
184
|
// Mirror raw output to the terminal so operators watching the smoke
|
|
191
185
|
// run still see what the browser is printing. Stream selection on the
|
|
@@ -201,9 +195,11 @@ async function runSmokeExit(engineDir, options) {
|
|
|
201
195
|
// total set of allowlisted lines (what the operator sees in the
|
|
202
196
|
// console) and the subset that were error-class (what the smoke
|
|
203
197
|
// exit contract cares about). The exit contract itself is unchanged.
|
|
204
|
-
const
|
|
198
|
+
const matchIndex = allowlist.length > 0 ? matchAllowlist(line, allowlist) : -1;
|
|
199
|
+
const isAllowlisted = matchIndex !== -1;
|
|
205
200
|
if (isAllowlisted) {
|
|
206
201
|
allowlistedTotalHits += 1;
|
|
202
|
+
allowlistHits[matchIndex] = (allowlistHits[matchIndex] ?? 0) + 1;
|
|
207
203
|
}
|
|
208
204
|
if (!matchesSmokeError(line))
|
|
209
205
|
return;
|
|
@@ -249,6 +245,8 @@ async function runSmokeExit(engineDir, options) {
|
|
|
249
245
|
timedOut: result.timedOut,
|
|
250
246
|
allowlistedErrorHits,
|
|
251
247
|
allowlistedTotalHits,
|
|
248
|
+
allowlist,
|
|
249
|
+
allowlistHits,
|
|
252
250
|
findings,
|
|
253
251
|
exitCode: result.exitCode,
|
|
254
252
|
});
|
|
@@ -314,6 +312,17 @@ function reportSmokeSummary(args) {
|
|
|
314
312
|
info(` Allowlisted error hits (suppressed): ${String(args.allowlistedErrorHits)}`);
|
|
315
313
|
info(` Allowlisted lines total: ${String(args.allowlistedTotalHits)}`);
|
|
316
314
|
info(` Child exit code: ${String(args.exitCode)}`);
|
|
315
|
+
// Per-entry attribution (FORGE G8): first-match credit per line, with
|
|
316
|
+
// zero-hit entries always visible — an allowlist entry whose suppressed
|
|
317
|
+
// shape changed upstream is only detectable as a 0× row.
|
|
318
|
+
if (args.allowlist.length > 0) {
|
|
319
|
+
info(' Allowlist attribution (first matching entry per line):');
|
|
320
|
+
args.allowlist.forEach((entry, index) => {
|
|
321
|
+
const hits = args.allowlistHits[index] ?? 0;
|
|
322
|
+
const zeroSuffix = hits === 0 ? ' (never matched — candidate for removal)' : '';
|
|
323
|
+
info(` ${String(hits)}× ${entry.origin} ${entry.source}${zeroSuffix}`);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
317
326
|
if (args.findings.length === 0)
|
|
318
327
|
return;
|
|
319
328
|
warn('');
|
|
@@ -338,10 +347,7 @@ export function registerRun(program, { getProjectRoot, withErrorHandling }) {
|
|
|
338
347
|
}
|
|
339
348
|
return parsed;
|
|
340
349
|
}))
|
|
341
|
-
.option('--console-allow <regex>', 'Allowlist regex (repeatable). Lines that match any entry do not count toward the smoke exit code.', (
|
|
342
|
-
acc.push(value);
|
|
343
|
-
return acc;
|
|
344
|
-
}, [])
|
|
350
|
+
.option('--console-allow <regex>', 'Allowlist regex (repeatable). Lines that match any entry do not count toward the smoke exit code.', ...stringListOption())
|
|
345
351
|
.option('--console-allow-file <path>', 'Newline-delimited allowlist regex file. Blank lines and # comments are ignored.')
|
|
346
352
|
.option('--capture-console <file>', 'Mirror captured console output to <file> for post-exit inspection.')
|
|
347
353
|
.option('--headless', 'Launch the browser with --headless. Recommended for --smoke-exit on a shared desktop: input into a headed smoke window contaminates the console capture.')
|
|
@@ -4,13 +4,7 @@ import { configExists, loadRawConfigDocument, validateConfig, withConfigFileLock
|
|
|
4
4
|
import { resolveArchive } from '../core/firefox-archive.js';
|
|
5
5
|
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
6
6
|
import { info, intro, outro, success } from '../utils/logger.js';
|
|
7
|
-
import { isValidFirefoxCandidate, isValidFirefoxProduct } from '../utils/validation.js';
|
|
8
|
-
const SOURCE_PRODUCTS = [
|
|
9
|
-
'firefox',
|
|
10
|
-
'firefox-esr',
|
|
11
|
-
'firefox-beta',
|
|
12
|
-
'firefox-devedition',
|
|
13
|
-
];
|
|
7
|
+
import { FIREFOX_PRODUCTS, isValidFirefoxCandidate, isValidFirefoxProduct, } from '../utils/validation.js';
|
|
14
8
|
function isRecord(value) {
|
|
15
9
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
10
|
}
|
|
@@ -25,7 +19,7 @@ function parseSourceProduct(product) {
|
|
|
25
19
|
if (isValidFirefoxProduct(product)) {
|
|
26
20
|
return product;
|
|
27
21
|
}
|
|
28
|
-
throw new InvalidArgumentError(`--product must be one of: ${
|
|
22
|
+
throw new InvalidArgumentError(`--product must be one of: ${FIREFOX_PRODUCTS.join(', ')}`, '--product');
|
|
29
23
|
}
|
|
30
24
|
function parseSourceCandidate(candidate) {
|
|
31
25
|
if (isValidFirefoxCandidate(candidate)) {
|
|
@@ -99,7 +93,7 @@ export function registerSource(program, { getProjectRoot, withErrorHandling }) {
|
|
|
99
93
|
.description('Atomically set Firefox source version, product, and optional checksum')
|
|
100
94
|
.requiredOption('--version <version>', 'Firefox version to base on')
|
|
101
95
|
.addOption(new Option('--product <product>', 'Firefox product')
|
|
102
|
-
.choices([...
|
|
96
|
+
.choices([...FIREFOX_PRODUCTS])
|
|
103
97
|
.makeOptionMandatory())
|
|
104
98
|
.option('--sha256 <hash>', 'Pinned SHA-256 for the resolved source archive')
|
|
105
99
|
.option('--clear-sha256', 'Clear any existing pinned SHA-256')
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `status --check` enforcement policy (FORGE G1). Turns the classified
|
|
3
|
+
* status view into a CI-enforceable non-zero exit: the default policy
|
|
4
|
+
* fails on any classification outside {patch-backed, branding, furnace},
|
|
5
|
+
* and `--fail-on <class,...>` replaces that set for finer control.
|
|
6
|
+
*
|
|
7
|
+
* Helper module consumed by status.ts (which sits at the max-lines
|
|
8
|
+
* budget); no top-level registrar is exported and none is wanted.
|
|
9
|
+
*/
|
|
10
|
+
import type { ClassifiedFile, FileClassification } from '../core/status-classify.js';
|
|
11
|
+
/** Resolved `--check`/`--fail-on` policy for one status invocation. */
|
|
12
|
+
export interface StatusCheckPolicy {
|
|
13
|
+
checkEnabled: boolean;
|
|
14
|
+
failOn: readonly FileClassification[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the enforcement policy from the raw flags: `--fail-on` implies
|
|
18
|
+
* `--check` and replaces the default set; both are refused alongside the
|
|
19
|
+
* modes that skip (or elide) classification.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveStatusCheckPolicy(options: {
|
|
22
|
+
check?: boolean;
|
|
23
|
+
failOn?: string;
|
|
24
|
+
raw?: boolean;
|
|
25
|
+
unmanaged?: boolean;
|
|
26
|
+
ownership?: boolean;
|
|
27
|
+
testCoverage?: boolean;
|
|
28
|
+
}): StatusCheckPolicy;
|
|
29
|
+
/** One fail-set classification with a non-empty file list. */
|
|
30
|
+
export interface StatusCheckOffender {
|
|
31
|
+
classification: FileClassification;
|
|
32
|
+
count: number;
|
|
33
|
+
files: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Collects the fail-set classifications that have offending files, in
|
|
37
|
+
* policy order with full file lists. Shared by the human `--check`
|
|
38
|
+
* verdict and the `--json --summary` payload so the two views can never
|
|
39
|
+
* disagree about what failed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function collectStatusCheckOffenders(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): StatusCheckOffender[];
|
|
42
|
+
/**
|
|
43
|
+
* Throws a GeneralError (exit 1) when the policy is enabled and any of
|
|
44
|
+
* its classifications is non-empty, naming each offending classification
|
|
45
|
+
* with its first few files. A disabled policy or a fully tool-covered
|
|
46
|
+
* tree passes silently.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runStatusCheck(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): void;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
2
|
+
const ALL_CLASSIFICATIONS = [
|
|
3
|
+
'patch-backed',
|
|
4
|
+
'patch-owned-drift',
|
|
5
|
+
'unmanaged',
|
|
6
|
+
'branding',
|
|
7
|
+
'furnace',
|
|
8
|
+
'conflict',
|
|
9
|
+
'binary-unsupported',
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Default `--check` policy: everything outside {patch-backed, branding,
|
|
13
|
+
* furnace, binary-unsupported} fails. `binary-unsupported` is deliberately
|
|
14
|
+
* excluded — an honest "cannot compare" must not keep the gate permanently
|
|
15
|
+
* red (FORGE J3); strict CI can opt in via `--fail-on binary-unsupported`.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_FAIL_CLASSIFICATIONS = [
|
|
18
|
+
'unmanaged',
|
|
19
|
+
'patch-owned-drift',
|
|
20
|
+
'conflict',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the enforcement policy from the raw flags: `--fail-on` implies
|
|
24
|
+
* `--check` and replaces the default set; both are refused alongside the
|
|
25
|
+
* modes that skip (or elide) classification.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveStatusCheckPolicy(options) {
|
|
28
|
+
const failOnList = parseFailOnClassifications(options.failOn);
|
|
29
|
+
const checkEnabled = options.check === true || failOnList !== undefined;
|
|
30
|
+
if (checkEnabled &&
|
|
31
|
+
(options.raw === true ||
|
|
32
|
+
options.unmanaged === true ||
|
|
33
|
+
options.ownership === true ||
|
|
34
|
+
options.testCoverage === true)) {
|
|
35
|
+
throw new InvalidArgumentError('--check cannot be combined with --raw, --unmanaged, --ownership, or --test-coverage. Use the default view or --json.', '--check');
|
|
36
|
+
}
|
|
37
|
+
return { checkEnabled, failOn: failOnList ?? DEFAULT_FAIL_CLASSIFICATIONS };
|
|
38
|
+
}
|
|
39
|
+
function isClassification(value) {
|
|
40
|
+
return ALL_CLASSIFICATIONS.includes(value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parses a comma-separated `--fail-on` value into a classification list.
|
|
44
|
+
* Returns undefined when the flag was not given (the caller then applies
|
|
45
|
+
* the default policy only under `--check`).
|
|
46
|
+
*/
|
|
47
|
+
function parseFailOnClassifications(raw) {
|
|
48
|
+
if (raw === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
const parts = raw
|
|
51
|
+
.split(',')
|
|
52
|
+
.map((part) => part.trim())
|
|
53
|
+
.filter((part) => part.length > 0);
|
|
54
|
+
if (parts.length === 0) {
|
|
55
|
+
throw new InvalidArgumentError(`--fail-on requires at least one classification. Valid: ${ALL_CLASSIFICATIONS.join(', ')}.`, '--fail-on');
|
|
56
|
+
}
|
|
57
|
+
const classifications = [];
|
|
58
|
+
for (const part of parts) {
|
|
59
|
+
if (!isClassification(part)) {
|
|
60
|
+
throw new InvalidArgumentError(`Unknown --fail-on classification "${part}". Valid: ${ALL_CLASSIFICATIONS.join(', ')}.`, '--fail-on');
|
|
61
|
+
}
|
|
62
|
+
if (!classifications.includes(part))
|
|
63
|
+
classifications.push(part);
|
|
64
|
+
}
|
|
65
|
+
return classifications;
|
|
66
|
+
}
|
|
67
|
+
const CHECK_FILE_PREVIEW_MAX = 3;
|
|
68
|
+
/**
|
|
69
|
+
* Collects the fail-set classifications that have offending files, in
|
|
70
|
+
* policy order with full file lists. Shared by the human `--check`
|
|
71
|
+
* verdict and the `--json --summary` payload so the two views can never
|
|
72
|
+
* disagree about what failed.
|
|
73
|
+
*/
|
|
74
|
+
export function collectStatusCheckOffenders(classified, policy) {
|
|
75
|
+
const offenders = [];
|
|
76
|
+
for (const classification of policy.failOn) {
|
|
77
|
+
const files = classified
|
|
78
|
+
.filter((entry) => entry.classification === classification)
|
|
79
|
+
.map((entry) => entry.file);
|
|
80
|
+
if (files.length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
offenders.push({ classification, count: files.length, files });
|
|
83
|
+
}
|
|
84
|
+
return offenders;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Throws a GeneralError (exit 1) when the policy is enabled and any of
|
|
88
|
+
* its classifications is non-empty, naming each offending classification
|
|
89
|
+
* with its first few files. A disabled policy or a fully tool-covered
|
|
90
|
+
* tree passes silently.
|
|
91
|
+
*/
|
|
92
|
+
export function runStatusCheck(classified, policy) {
|
|
93
|
+
if (!policy.checkEnabled)
|
|
94
|
+
return;
|
|
95
|
+
const offending = collectStatusCheckOffenders(classified, policy).map((offender) => {
|
|
96
|
+
const preview = offender.files.slice(0, CHECK_FILE_PREVIEW_MAX).join(', ');
|
|
97
|
+
const more = offender.count > CHECK_FILE_PREVIEW_MAX
|
|
98
|
+
? `, +${String(offender.count - CHECK_FILE_PREVIEW_MAX)} more`
|
|
99
|
+
: '';
|
|
100
|
+
return `${String(offender.count)} ${offender.classification} (${preview}${more})`;
|
|
101
|
+
});
|
|
102
|
+
if (offending.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
throw new GeneralError(`status --check failed: ${offending.join(', ')}. Export or adopt unmanaged files, ` +
|
|
105
|
+
`re-export drifted patches, and run "fireforge verify" for conflict details.`);
|
|
106
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `status --json` payload rendering (schemaVersion 1). Helper module
|
|
3
|
+
* consumed by status.ts (which sits at the max-lines budget); no
|
|
4
|
+
* top-level registrar is exported and none is wanted.
|
|
5
|
+
*
|
|
6
|
+
* Two shapes share one counting pass:
|
|
7
|
+
* - the full payload (`renderJsonStatus`) with the per-file `files[]` list;
|
|
8
|
+
* - the `--summary` gate payload (`renderJsonSummaryStatus`, FORGE K8),
|
|
9
|
+
* which omits `files[]` entirely — engine-clean gates need the verdict,
|
|
10
|
+
* per-class counts, and offender names, not a payload that grows with
|
|
11
|
+
* the queue (175 KB+ observed on a 290-patch consumer).
|
|
12
|
+
*/
|
|
13
|
+
import type { ClassifiedFile } from '../core/status-classify.js';
|
|
14
|
+
import { type StatusCheckPolicy } from './status-check.js';
|
|
15
|
+
import type { OwnershipJsonBlock } from './status-ownership.js';
|
|
16
|
+
/**
|
|
17
|
+
* Full `--json` payload: summary plus the per-file `files[]` list, and —
|
|
18
|
+
* under `--include-ownership` (FORGE L3) — the additive `ownership` block.
|
|
19
|
+
* Additive within schemaVersion 1: a consumer that does not ask for the
|
|
20
|
+
* block never sees the key.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderJsonStatus(classified: readonly ClassifiedFile[], ownership?: OwnershipJsonBlock): void;
|
|
23
|
+
/**
|
|
24
|
+
* Summary-only `--json --summary` payload: counts always; when the
|
|
25
|
+
* `--check`/`--fail-on` policy is active, a `check` block with the active
|
|
26
|
+
* fail set, the verdict, and the offending files per fail-set
|
|
27
|
+
* classification (sourced from the same collector as the human `--check`
|
|
28
|
+
* message, so the two views can never disagree). The absent `files[]` is
|
|
29
|
+
* the mode signal within schemaVersion 1 — consumers opt in via the flag.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderJsonSummaryStatus(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownership?: OwnershipJsonBlock): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { collectStatusCheckOffenders, } from './status-check.js';
|
|
2
|
+
function countByClassification(classified) {
|
|
3
|
+
const byClassification = {
|
|
4
|
+
unmanaged: 0,
|
|
5
|
+
'patch-owned-drift': 0,
|
|
6
|
+
'patch-backed': 0,
|
|
7
|
+
branding: 0,
|
|
8
|
+
furnace: 0,
|
|
9
|
+
conflict: 0,
|
|
10
|
+
'binary-unsupported': 0,
|
|
11
|
+
};
|
|
12
|
+
for (const file of classified) {
|
|
13
|
+
byClassification[file.classification]++;
|
|
14
|
+
}
|
|
15
|
+
return byClassification;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Full `--json` payload: summary plus the per-file `files[]` list, and —
|
|
19
|
+
* under `--include-ownership` (FORGE L3) — the additive `ownership` block.
|
|
20
|
+
* Additive within schemaVersion 1: a consumer that does not ask for the
|
|
21
|
+
* block never sees the key.
|
|
22
|
+
*/
|
|
23
|
+
export function renderJsonStatus(classified, ownership) {
|
|
24
|
+
const outputFiles = classified.map((f) => {
|
|
25
|
+
const entry = {
|
|
26
|
+
file: f.file,
|
|
27
|
+
status: f.status.trim(),
|
|
28
|
+
classification: f.classification,
|
|
29
|
+
patch: f.owner ?? null,
|
|
30
|
+
};
|
|
31
|
+
if (f.classification === 'conflict' && f.claimedBy && f.claimedBy.length > 0) {
|
|
32
|
+
entry.claimedBy = [...f.claimedBy];
|
|
33
|
+
}
|
|
34
|
+
return entry;
|
|
35
|
+
});
|
|
36
|
+
const output = {
|
|
37
|
+
schemaVersion: 1,
|
|
38
|
+
summary: {
|
|
39
|
+
total: outputFiles.length,
|
|
40
|
+
byClassification: countByClassification(classified),
|
|
41
|
+
},
|
|
42
|
+
files: outputFiles,
|
|
43
|
+
...(ownership !== undefined ? { ownership } : {}),
|
|
44
|
+
};
|
|
45
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Summary-only `--json --summary` payload: counts always; when the
|
|
49
|
+
* `--check`/`--fail-on` policy is active, a `check` block with the active
|
|
50
|
+
* fail set, the verdict, and the offending files per fail-set
|
|
51
|
+
* classification (sourced from the same collector as the human `--check`
|
|
52
|
+
* message, so the two views can never disagree). The absent `files[]` is
|
|
53
|
+
* the mode signal within schemaVersion 1 — consumers opt in via the flag.
|
|
54
|
+
*/
|
|
55
|
+
export function renderJsonSummaryStatus(classified, policy, ownership) {
|
|
56
|
+
const output = {
|
|
57
|
+
schemaVersion: 1,
|
|
58
|
+
summary: {
|
|
59
|
+
total: classified.length,
|
|
60
|
+
byClassification: countByClassification(classified),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
if (policy.checkEnabled) {
|
|
64
|
+
const offenders = collectStatusCheckOffenders(classified, policy);
|
|
65
|
+
output.check = {
|
|
66
|
+
enabled: true,
|
|
67
|
+
failOn: policy.failOn,
|
|
68
|
+
failed: offenders.length > 0,
|
|
69
|
+
offenders,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (ownership !== undefined) {
|
|
73
|
+
output.ownership = ownership;
|
|
74
|
+
}
|
|
75
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
|
|
76
|
+
}
|
|
@@ -7,6 +7,7 @@ export interface ClassifiedBuckets {
|
|
|
7
7
|
patchOwnedDrift: ClassifiedFile[];
|
|
8
8
|
branding: ClassifiedFile[];
|
|
9
9
|
furnace: ClassifiedFile[];
|
|
10
|
+
binaryUnsupported: ClassifiedFile[];
|
|
10
11
|
}
|
|
11
12
|
/** Renders the unmanaged-only status view and registration hints. */
|
|
12
13
|
export declare function renderUnmanagedOnly(unmanagedFiles: ClassifiedFile[], totalModified: number, projectRoot: string, binaryName: string): Promise<void>;
|
|
@@ -107,7 +107,7 @@ function renderConflictSection(conflict) {
|
|
|
107
107
|
}
|
|
108
108
|
/** Renders the default classified status buckets. */
|
|
109
109
|
export async function renderDefaultStatus(totalModified, buckets, projectRoot, binaryName) {
|
|
110
|
-
const { conflict, unmanaged, patchBacked, patchOwnedDrift, branding, furnace } = buckets;
|
|
110
|
+
const { conflict, unmanaged, patchBacked, patchOwnedDrift, branding, furnace, binaryUnsupported, } = buckets;
|
|
111
111
|
info(`${totalModified} modified file${totalModified === 1 ? '' : 's'}:\n`);
|
|
112
112
|
// Sections render in this fixed order, separated by a blank line
|
|
113
113
|
// whenever an earlier section already printed (the pre-refactor code
|
|
@@ -163,6 +163,15 @@ export async function renderDefaultStatus(totalModified, buckets, projectRoot, b
|
|
|
163
163
|
printStatusGroups(furnace);
|
|
164
164
|
},
|
|
165
165
|
},
|
|
166
|
+
{
|
|
167
|
+
files: binaryUnsupported,
|
|
168
|
+
label: 'binary — comparison unsupported',
|
|
169
|
+
render: () => {
|
|
170
|
+
warn('Binary — comparison unsupported:');
|
|
171
|
+
printStatusGroups(binaryUnsupported);
|
|
172
|
+
info('These patch-owned binary files carry no comparable blob hash in their patch body, so durability cannot be verified. Re-export the owning patch with a git binary body to make them comparable.');
|
|
173
|
+
},
|
|
174
|
+
},
|
|
166
175
|
];
|
|
167
176
|
let printedAny = false;
|
|
168
177
|
for (const section of sections) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ownership-table assembly for `status`, split out of `status.ts` (FORGE
|
|
3
|
+
* L3) so the human `--ownership` mode and the additive `ownership` block on
|
|
4
|
+
* the `--json` payload build the SAME rows from the SAME scan.
|
|
5
|
+
*
|
|
6
|
+
* Before L3 the `--ownership` branch re-implemented the shared scan and
|
|
7
|
+
* inlined a second copy of the classification call, then discarded
|
|
8
|
+
* everything but each file's classification — three back-to-back `status`
|
|
9
|
+
* invocations in a gate therefore paid three full worktree scans. Both
|
|
10
|
+
* callers now hand this module the already-classified files.
|
|
11
|
+
*
|
|
12
|
+
* No renderer lives here: `renderOwnershipTable` (human) and the JSON
|
|
13
|
+
* serializer stay with their respective output layers.
|
|
14
|
+
*/
|
|
15
|
+
import type { OwnershipRow } from '../core/ownership-table.js';
|
|
16
|
+
import type { ClassifiedFile, StatusFile } from '../core/status-classify.js';
|
|
17
|
+
import type { PatchMetadata } from '../types/commands/index.js';
|
|
18
|
+
/** Counts the human outro prints and the JSON block carries. */
|
|
19
|
+
export interface OwnershipSummary {
|
|
20
|
+
managed: number;
|
|
21
|
+
unmanaged: number;
|
|
22
|
+
conflicts: number;
|
|
23
|
+
}
|
|
24
|
+
/** The additive `ownership` block on the schemaVersion-1 JSON payload. */
|
|
25
|
+
export interface OwnershipJsonBlock {
|
|
26
|
+
rows: OwnershipRow[];
|
|
27
|
+
summary: OwnershipSummary;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds the flat path→owning-patch rows. Sources are the manifest's
|
|
31
|
+
* `filesAffected`, worktree drift, and the cross-patch
|
|
32
|
+
* duplicate-new-file-creation map produced by walking each patch body —
|
|
33
|
+
* the last being the alignment fix between `status --ownership` and
|
|
34
|
+
* `fireforge verify` (see `buildOwnershipTable`'s header).
|
|
35
|
+
* @param patchesDir - Path to the patches directory
|
|
36
|
+
* @param manifestPatches - Patch rows from patches.json (empty when absent)
|
|
37
|
+
* @param files - Scanned worktree entries, temp-filtered
|
|
38
|
+
* @param classified - The same entries, already classified
|
|
39
|
+
*/
|
|
40
|
+
export declare function collectOwnershipRows(patchesDir: string, manifestPatches: readonly PatchMetadata[], files: StatusFile[], classified: readonly ClassifiedFile[]): Promise<OwnershipRow[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Counts managed / unmanaged / conflicted rows. Conflicts are rows flagged
|
|
43
|
+
* `conflict`, not a separate collection.
|
|
44
|
+
* @param rows - Rows from {@link collectOwnershipRows}
|
|
45
|
+
*/
|
|
46
|
+
export declare function summarizeOwnership(rows: readonly OwnershipRow[]): OwnershipSummary;
|
|
47
|
+
/**
|
|
48
|
+
* Assembles the JSON block. Rows are carried verbatim so the machine shape
|
|
49
|
+
* and the rendered table can never disagree.
|
|
50
|
+
* @param rows - Rows from {@link collectOwnershipRows}
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildOwnershipJsonBlock(rows: OwnershipRow[]): OwnershipJsonBlock;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Ownership-table assembly for `status`, split out of `status.ts` (FORGE
|
|
4
|
+
* L3) so the human `--ownership` mode and the additive `ownership` block on
|
|
5
|
+
* the `--json` payload build the SAME rows from the SAME scan.
|
|
6
|
+
*
|
|
7
|
+
* Before L3 the `--ownership` branch re-implemented the shared scan and
|
|
8
|
+
* inlined a second copy of the classification call, then discarded
|
|
9
|
+
* everything but each file's classification — three back-to-back `status`
|
|
10
|
+
* invocations in a gate therefore paid three full worktree scans. Both
|
|
11
|
+
* callers now hand this module the already-classified files.
|
|
12
|
+
*
|
|
13
|
+
* No renderer lives here: `renderOwnershipTable` (human) and the JSON
|
|
14
|
+
* serializer stay with their respective output layers.
|
|
15
|
+
*/
|
|
16
|
+
import { buildOwnershipTable } from '../core/ownership-table.js';
|
|
17
|
+
import { buildPatchQueueContext, collectNewFileCreatorsByPath } from '../core/patch-lint.js';
|
|
18
|
+
import { pathExists } from '../utils/fs.js';
|
|
19
|
+
/**
|
|
20
|
+
* Builds the flat path→owning-patch rows. Sources are the manifest's
|
|
21
|
+
* `filesAffected`, worktree drift, and the cross-patch
|
|
22
|
+
* duplicate-new-file-creation map produced by walking each patch body —
|
|
23
|
+
* the last being the alignment fix between `status --ownership` and
|
|
24
|
+
* `fireforge verify` (see `buildOwnershipTable`'s header).
|
|
25
|
+
* @param patchesDir - Path to the patches directory
|
|
26
|
+
* @param manifestPatches - Patch rows from patches.json (empty when absent)
|
|
27
|
+
* @param files - Scanned worktree entries, temp-filtered
|
|
28
|
+
* @param classified - The same entries, already classified
|
|
29
|
+
*/
|
|
30
|
+
export async function collectOwnershipRows(patchesDir, manifestPatches, files, classified) {
|
|
31
|
+
// Only walk patch bodies when the directory actually exists: a fresh
|
|
32
|
+
// project with no queue degrades to filesAffected-only behavior.
|
|
33
|
+
const newFileCreatorsByPath = (await pathExists(patchesDir))
|
|
34
|
+
? collectNewFileCreatorsByPath(await buildPatchQueueContext(patchesDir))
|
|
35
|
+
: new Map();
|
|
36
|
+
return buildOwnershipTable([...manifestPatches], files, newFileCreatorsByPath, new Map(classified.map((entry) => [entry.file, entry.classification])));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Counts managed / unmanaged / conflicted rows. Conflicts are rows flagged
|
|
40
|
+
* `conflict`, not a separate collection.
|
|
41
|
+
* @param rows - Rows from {@link collectOwnershipRows}
|
|
42
|
+
*/
|
|
43
|
+
export function summarizeOwnership(rows) {
|
|
44
|
+
return {
|
|
45
|
+
managed: rows.filter((r) => !r.unmanaged).length,
|
|
46
|
+
unmanaged: rows.filter((r) => r.unmanaged).length,
|
|
47
|
+
conflicts: rows.filter((r) => r.conflict).length,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Assembles the JSON block. Rows are carried verbatim so the machine shape
|
|
52
|
+
* and the rendered table can never disagree.
|
|
53
|
+
* @param rows - Rows from {@link collectOwnershipRows}
|
|
54
|
+
*/
|
|
55
|
+
export function buildOwnershipJsonBlock(rows) {
|
|
56
|
+
return { rows, summary: summarizeOwnership(rows) };
|
|
57
|
+
}
|