@hominis/fireforge 0.35.0 → 0.37.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 +42 -0
- package/README.md +34 -9
- package/dist/bin/fireforge.js +21 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +5 -4
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +18 -3
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-orphaned-harness.d.ts +52 -0
- package/dist/src/commands/doctor-orphaned-harness.js +132 -0
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +6 -1
- package/dist/src/commands/download.js +28 -13
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +22 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.d.ts +1 -1
- package/dist/src/commands/patch/staged-dependency.js +114 -52
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +5 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +14 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +4 -4
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +38 -3
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +147 -96
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.d.ts +24 -0
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.d.ts +5 -2
- package/dist/src/core/build-baseline.js +5 -2
- package/dist/src/core/build-prepare.js +2 -2
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +60 -4
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.js +75 -16
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +10 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-stale-export.d.ts +59 -0
- package/dist/src/core/furnace-stale-export.js +123 -0
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +9 -3
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +14 -4
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +63 -6
- package/dist/src/core/license-headers.js +0 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +0 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +60 -0
- package/dist/src/core/mach-known-noise-filter.js +193 -0
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.d.ts +8 -0
- package/dist/src/core/mach.js +36 -4
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +101 -18
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +80 -3
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +47 -34
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +19 -2
- package/dist/src/core/patch-manifest-io.js +57 -21
- package/dist/src/core/patch-manifest-query.js +18 -10
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +24 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +80 -7
- package/dist/src/core/test-harness-crash.js +251 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +21 -13
- package/dist/src/core/test-path-scope.js +43 -28
- package/dist/src/core/test-stale-check.d.ts +29 -1
- package/dist/src/core/test-stale-check.js +59 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +31 -10
- package/dist/src/core/toolchain-preflight.js +97 -36
- package/dist/src/core/typecheck-shim.js +13 -1
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +0 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +40 -9
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +37 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +23 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process-group.d.ts +33 -0
- package/dist/src/utils/process-group.js +161 -0
- package/dist/src/utils/process.d.ts +48 -4
- package/dist/src/utils/process.js +258 -59
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +9 -8
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { Command } from 'commander';
|
|
1
3
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
2
4
|
import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
|
|
3
5
|
import { getHead, getStatusWithCodes, isGitRepository, isMissingHeadError } from '../core/git.js';
|
|
4
|
-
import { getUntrackedFilesInDir } from '../core/git-status.js';
|
|
6
|
+
import { getUntrackedFilesInDir, resolveMaxUntrackedFilesPerDir } from '../core/git-status.js';
|
|
5
7
|
import { buildOwnershipTable, renderOwnershipTable } from '../core/ownership-table.js';
|
|
6
8
|
import { buildPatchQueueContext, collectNewFileCreatorsByPath } from '../core/patch-lint.js';
|
|
7
9
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
@@ -9,7 +11,7 @@ import { classifyFiles, } from '../core/status-classify.js';
|
|
|
9
11
|
import { CommandError, GeneralError } from '../errors/base.js';
|
|
10
12
|
import { ExitCode } from '../errors/codes.js';
|
|
11
13
|
import { FIREFORGE_TMP_PATH_PATTERN, pathExists } from '../utils/fs.js';
|
|
12
|
-
import { info, intro, outro, warn } from '../utils/logger.js';
|
|
14
|
+
import { info, intro, isMachineOutputMode, outro, setMachineOutputMode, warn, } from '../utils/logger.js';
|
|
13
15
|
import { renderDefaultStatus, renderUnmanagedOnly, } from './status-output.js';
|
|
14
16
|
/**
|
|
15
17
|
* Renders raw worktree status as machine-parseable porcelain-style output.
|
|
@@ -20,19 +22,15 @@ function renderRawStatus(files) {
|
|
|
20
22
|
process.stdout.write(`${status.trim()}\t${file}\n`);
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return DEFAULT_MAX_UNTRACKED_FILES_PER_DIR;
|
|
32
|
-
}
|
|
33
|
-
return parsed;
|
|
25
|
+
// Resolved lazily at first use (shared with core/git-status): this module
|
|
26
|
+
// is imported by the command manifest for EVERY command, so a
|
|
27
|
+
// module-load-time parse printed the status-specific env warning during
|
|
28
|
+
// `fireforge build` etc.
|
|
29
|
+
let maxUntrackedFilesPerDir;
|
|
30
|
+
function getMaxUntrackedFilesPerDir() {
|
|
31
|
+
maxUntrackedFilesPerDir ??= resolveMaxUntrackedFilesPerDir();
|
|
32
|
+
return maxUntrackedFilesPerDir;
|
|
34
33
|
}
|
|
35
|
-
const MAX_UNTRACKED_FILES_PER_DIR = resolveMaxUntrackedFilesPerDir();
|
|
36
34
|
function renderTruncationBanner(truncations) {
|
|
37
35
|
if (truncations.length === 0)
|
|
38
36
|
return;
|
|
@@ -48,15 +46,17 @@ async function expandDirectoryEntries(files, engineDir) {
|
|
|
48
46
|
for (const entry of files) {
|
|
49
47
|
if (entry.file.endsWith('/') && entry.status.includes('?')) {
|
|
50
48
|
const individualFiles = await getUntrackedFilesInDir(engineDir, entry.file);
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const cap = getMaxUntrackedFilesPerDir();
|
|
50
|
+
if (individualFiles.length > cap) {
|
|
51
|
+
// Recorded once here, reported once by renderTruncationBanner —
|
|
52
|
+
// the previous per-directory warn duplicated the banner's content.
|
|
53
53
|
truncations.push({
|
|
54
54
|
dir: entry.file,
|
|
55
55
|
total: individualFiles.length,
|
|
56
|
-
shown:
|
|
56
|
+
shown: cap,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
const limited = individualFiles.slice(0,
|
|
59
|
+
const limited = individualFiles.slice(0, cap);
|
|
60
60
|
for (const f of limited) {
|
|
61
61
|
expanded.push({ status: '??', file: f });
|
|
62
62
|
}
|
|
@@ -161,130 +161,145 @@ async function assertEngineHasBaselineCommit(engineDir, options) {
|
|
|
161
161
|
* @param options - Status display options
|
|
162
162
|
*/
|
|
163
163
|
export async function statusCommand(projectRoot, options = {}) {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
const previousMachineOutputMode = isMachineOutputMode();
|
|
165
|
+
const shouldUseMachineOutput = options.json === true || options.raw === true;
|
|
166
|
+
// Machine modes own stdout exclusively: every diagnostic (clack warnings,
|
|
167
|
+
// withErrorHandling's styled errors, spinner steps) routes to stderr so
|
|
168
|
+
// `status --json | jq .` and `--raw` pipes always parse.
|
|
169
|
+
if (shouldUseMachineOutput) {
|
|
170
|
+
setMachineOutputMode(true);
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
try {
|
|
173
|
+
const modeCount = [options.raw, options.unmanaged, options.ownership, options.json].filter((v) => v === true).length;
|
|
174
|
+
if (modeCount > 1) {
|
|
175
|
+
throw new GeneralError('Cannot use --raw, --unmanaged, --ownership, and --json together. Pick at most one.');
|
|
176
|
+
}
|
|
177
|
+
if (!options.raw && !options.json) {
|
|
178
|
+
intro('FireForge Status');
|
|
179
|
+
}
|
|
180
|
+
const paths = getProjectPaths(projectRoot);
|
|
181
|
+
const config = await loadConfig(projectRoot);
|
|
182
|
+
const emitJsonError = (code, message) => {
|
|
183
|
+
process.stdout.write(JSON.stringify({ schemaVersion: 1, error: message, code }) + '\n');
|
|
184
|
+
throw new CommandError(ExitCode.GENERAL_ERROR);
|
|
185
|
+
};
|
|
186
|
+
// Ownership mode is a flat file→patch table; sources are the manifest's
|
|
187
|
+
// filesAffected, any worktree drift, and the cross-patch
|
|
188
|
+
// duplicate-new-file-creation map produced by walking each patch
|
|
189
|
+
// body. The latter is the alignment fix between `status --ownership`
|
|
190
|
+
// and `fireforge verify` — see buildOwnershipTable's header comment.
|
|
191
|
+
// Runs before the default classify path so we can short-circuit
|
|
192
|
+
// without computing patch-backed state.
|
|
193
|
+
if (options.ownership) {
|
|
194
|
+
if (!(await pathExists(paths.engine))) {
|
|
195
|
+
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
196
|
+
}
|
|
197
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
198
|
+
const ownershipExpansion = (await isGitRepository(paths.engine))
|
|
199
|
+
? await expandDirectoryEntries(await getStatusWithCodes(paths.engine), paths.engine)
|
|
200
|
+
: { entries: [], truncations: [] };
|
|
201
|
+
// Filter atomic-write temp files (Finding #18) so a mid-flight
|
|
202
|
+
// `.fireforge-tmp-<pid>-<uuid>` artefact never shows up in any
|
|
203
|
+
// status mode. The pattern is tight enough to let legitimately
|
|
204
|
+
// similar names through.
|
|
205
|
+
const rawFilesOwnership = filterFireForgeTempFiles(ownershipExpansion.entries);
|
|
206
|
+
renderTruncationBanner(ownershipExpansion.truncations);
|
|
207
|
+
// Only walk the patch bodies when the directory actually exists.
|
|
208
|
+
// Fresh projects with no patch queue yet pass through with an empty
|
|
209
|
+
// creators map, which degrades to the old filesAffected-only
|
|
210
|
+
// behavior for the empty case.
|
|
211
|
+
const newFileCreatorsByPath = (await pathExists(paths.patches))
|
|
212
|
+
? collectNewFileCreatorsByPath(await buildPatchQueueContext(paths.patches))
|
|
213
|
+
: new Map();
|
|
214
|
+
const rows = buildOwnershipTable(manifest?.patches ?? [], rawFilesOwnership, newFileCreatorsByPath, new Map((await classifyFiles(rawFilesOwnership, paths.engine, paths.patches, config.binaryName, await collectFurnaceManagedPrefixes(projectRoot))).map((entry) => [entry.file, entry.classification])));
|
|
215
|
+
renderOwnershipTable(rows);
|
|
216
|
+
const conflictCount = rows.filter((r) => r.conflict).length;
|
|
217
|
+
const unmanagedCount = rows.filter((r) => r.unmanaged).length;
|
|
218
|
+
const managedCount = rows.filter((r) => !r.unmanaged).length;
|
|
219
|
+
const parts = [`${managedCount} managed`];
|
|
220
|
+
if (conflictCount > 0)
|
|
221
|
+
parts.push(`${conflictCount} conflict${conflictCount === 1 ? '' : 's'}`);
|
|
222
|
+
if (unmanagedCount > 0)
|
|
223
|
+
parts.push(`${unmanagedCount} unmanaged`);
|
|
224
|
+
outro(parts.join(', '));
|
|
225
|
+
if (conflictCount > 0) {
|
|
226
|
+
throw new GeneralError(`${conflictCount} path(s) are claimed by more than one patch. ` +
|
|
227
|
+
'Run "fireforge verify" for full details, then use "re-export --files" or ' +
|
|
228
|
+
'"patch delete" to resolve.');
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Check if engine exists
|
|
185
233
|
if (!(await pathExists(paths.engine))) {
|
|
234
|
+
if (options.json) {
|
|
235
|
+
emitJsonError('engine-missing', 'Firefox source not found. Run "fireforge download" first.');
|
|
236
|
+
}
|
|
186
237
|
throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
|
|
187
238
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// status mode. The pattern is tight enough to let legitimately
|
|
195
|
-
// similar names through.
|
|
196
|
-
const rawFilesOwnership = filterFireForgeTempFiles(ownershipExpansion.entries);
|
|
197
|
-
renderTruncationBanner(ownershipExpansion.truncations);
|
|
198
|
-
// Only walk the patch bodies when the directory actually exists.
|
|
199
|
-
// Fresh projects with no patch queue yet pass through with an empty
|
|
200
|
-
// creators map, which degrades to the old filesAffected-only
|
|
201
|
-
// behavior for the empty case.
|
|
202
|
-
const newFileCreatorsByPath = (await pathExists(paths.patches))
|
|
203
|
-
? collectNewFileCreatorsByPath(await buildPatchQueueContext(paths.patches))
|
|
204
|
-
: new Map();
|
|
205
|
-
const rows = buildOwnershipTable(manifest?.patches ?? [], rawFilesOwnership, newFileCreatorsByPath, new Map((await classifyFiles(rawFilesOwnership, paths.engine, paths.patches, config.binaryName, await collectFurnaceManagedPrefixes(projectRoot))).map((entry) => [entry.file, entry.classification])));
|
|
206
|
-
renderOwnershipTable(rows);
|
|
207
|
-
const conflictCount = rows.filter((r) => r.conflict).length;
|
|
208
|
-
const unmanagedCount = rows.filter((r) => r.unmanaged).length;
|
|
209
|
-
const managedCount = rows.filter((r) => !r.unmanaged).length;
|
|
210
|
-
const parts = [`${managedCount} managed`];
|
|
211
|
-
if (conflictCount > 0)
|
|
212
|
-
parts.push(`${conflictCount} conflict${conflictCount === 1 ? '' : 's'}`);
|
|
213
|
-
if (unmanagedCount > 0)
|
|
214
|
-
parts.push(`${unmanagedCount} unmanaged`);
|
|
215
|
-
outro(parts.join(', '));
|
|
216
|
-
if (conflictCount > 0) {
|
|
217
|
-
throw new GeneralError(`${conflictCount} path(s) are claimed by more than one patch. ` +
|
|
218
|
-
'Run "fireforge verify" for full details, then use "re-export --files" or ' +
|
|
219
|
-
'"patch delete" to resolve.');
|
|
239
|
+
// Check if it's a git repository
|
|
240
|
+
if (!(await isGitRepository(paths.engine))) {
|
|
241
|
+
if (options.json) {
|
|
242
|
+
emitJsonError('engine-not-git', 'Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
243
|
+
}
|
|
244
|
+
throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
|
|
220
245
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
246
|
+
await assertEngineHasBaselineCommit(paths.engine, options);
|
|
247
|
+
const rawFiles = await getStatusWithCodes(paths.engine);
|
|
248
|
+
const { entries: expanded, truncations } = await expandDirectoryEntries(rawFiles, paths.engine);
|
|
249
|
+
// Strip atomic-write temp files (Finding #18) before every mode
|
|
250
|
+
// branch so raw / unmanaged / default / json all agree.
|
|
251
|
+
const files = filterFireForgeTempFiles(expanded);
|
|
252
|
+
renderTruncationBanner(truncations);
|
|
253
|
+
// `--json` callers expect machine-parseable output on every invocation,
|
|
254
|
+
// including the clean-tree case. Before this ordering fix a clean tree
|
|
255
|
+
// printed "No modified files" / "Working tree clean" via the human
|
|
256
|
+
// branch below and `--json` was silently ignored, so scripts that piped
|
|
257
|
+
// the output through a JSON parser broke precisely when there was
|
|
258
|
+
// nothing to report. Emit `[]` here and return before the human fallback.
|
|
225
259
|
if (options.json) {
|
|
226
|
-
|
|
260
|
+
await renderJsonStatus(files, paths, projectRoot, config.binaryName);
|
|
261
|
+
return;
|
|
227
262
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (options.
|
|
233
|
-
|
|
263
|
+
// `--raw` consumers parse the native `git status --porcelain` output
|
|
264
|
+
// directly. On a clean tree the raw mode should produce nothing on
|
|
265
|
+
// stdout — the human "Working tree clean" banner would contaminate the
|
|
266
|
+
// pipe. Short-circuit before the human clean-tree branch below.
|
|
267
|
+
if (options.raw && files.length === 0) {
|
|
268
|
+
return;
|
|
234
269
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
info('No modified files');
|
|
263
|
-
outro('Working tree clean');
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
// Raw mode: existing behavior
|
|
267
|
-
if (options.raw) {
|
|
268
|
-
renderRawStatus(files);
|
|
269
|
-
return;
|
|
270
|
+
if (files.length === 0) {
|
|
271
|
+
info('No modified files');
|
|
272
|
+
outro('Working tree clean');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Raw mode: existing behavior
|
|
276
|
+
if (options.raw) {
|
|
277
|
+
renderRawStatus(files);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Patch-aware classification
|
|
281
|
+
const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
|
|
282
|
+
const classified = await classifyFiles(files, paths.engine, paths.patches, config.binaryName, furnacePrefixes);
|
|
283
|
+
const buckets = {
|
|
284
|
+
conflict: classified.filter((f) => f.classification === 'conflict'),
|
|
285
|
+
unmanaged: classified.filter((f) => f.classification === 'unmanaged'),
|
|
286
|
+
patchOwnedDrift: classified.filter((f) => f.classification === 'patch-owned-drift'),
|
|
287
|
+
patchBacked: classified.filter((f) => f.classification === 'patch-backed'),
|
|
288
|
+
branding: classified.filter((f) => f.classification === 'branding'),
|
|
289
|
+
furnace: classified.filter((f) => f.classification === 'furnace'),
|
|
290
|
+
};
|
|
291
|
+
// --unmanaged mode: only show unmanaged
|
|
292
|
+
if (options.unmanaged) {
|
|
293
|
+
await renderUnmanagedOnly(buckets.unmanaged, files.length, projectRoot, config.binaryName);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
await renderDefaultStatus(files.length, buckets, projectRoot, config.binaryName);
|
|
270
297
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
conflict: classified.filter((f) => f.classification === 'conflict'),
|
|
276
|
-
unmanaged: classified.filter((f) => f.classification === 'unmanaged'),
|
|
277
|
-
patchOwnedDrift: classified.filter((f) => f.classification === 'patch-owned-drift'),
|
|
278
|
-
patchBacked: classified.filter((f) => f.classification === 'patch-backed'),
|
|
279
|
-
branding: classified.filter((f) => f.classification === 'branding'),
|
|
280
|
-
furnace: classified.filter((f) => f.classification === 'furnace'),
|
|
281
|
-
};
|
|
282
|
-
// --unmanaged mode: only show unmanaged
|
|
283
|
-
if (options.unmanaged) {
|
|
284
|
-
await renderUnmanagedOnly(buckets.unmanaged, files.length, projectRoot, config.binaryName);
|
|
285
|
-
return;
|
|
298
|
+
finally {
|
|
299
|
+
if (isMachineOutputMode() !== previousMachineOutputMode) {
|
|
300
|
+
setMachineOutputMode(previousMachineOutputMode);
|
|
301
|
+
}
|
|
286
302
|
}
|
|
287
|
-
await renderDefaultStatus(files.length, buckets, projectRoot, config.binaryName);
|
|
288
303
|
}
|
|
289
304
|
/** Registers the status command on the CLI program. */
|
|
290
305
|
export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
|
|
@@ -299,4 +314,3 @@ export function registerStatus(program, { getProjectRoot, withErrorHandling }) {
|
|
|
299
314
|
await statusCommand(getProjectRoot(), options);
|
|
300
315
|
}));
|
|
301
316
|
}
|
|
302
|
-
//# sourceMappingURL=status.js.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `test-harness-crash.ts` for single and sharded invocations. Split out of
|
|
8
8
|
* `test.ts` to keep both files within the per-file line budget.
|
|
9
9
|
*/
|
|
10
|
-
import { buildHarnessCrashMessage, buildNoTestsRanMessage } from '../core/test-harness-crash.js';
|
|
10
|
+
import { buildGreenSummaryRejectedMessage, buildHarnessCrashMessage, buildNoTestsRanMessage, } from '../core/test-harness-crash.js';
|
|
11
11
|
import { buildHarnessEarlyExitMessage, classifyHarnessEarlyExit, completePostRebuildFailureContext, prependPostRebuildFailureContext, } from '../core/test-harness-output.js';
|
|
12
12
|
import { GeneralError } from '../errors/base.js';
|
|
13
13
|
import { BuildError } from '../errors/build.js';
|
|
@@ -200,6 +200,24 @@ export function finalizeSingleRunOutcome(outcome, normalizedPaths, binaryName, p
|
|
|
200
200
|
// zero TEST-START lines must fail, not pass.
|
|
201
201
|
throw new GeneralError(buildNoTestsRanMessage(0, normalizedPaths));
|
|
202
202
|
}
|
|
203
|
+
// A green-LOOKING summary rejected on crash/truncation evidence gets its
|
|
204
|
+
// own explanation instead of the generic exit-code message — the operator
|
|
205
|
+
// must see WHY the green counts were not trusted (0.35.0 green-wash
|
|
206
|
+
// field report: SIGSEGV at file 2 of 8, `Passed: 2 / Failed: 0`).
|
|
207
|
+
if (outcome.verdict.greenSummaryRejected) {
|
|
208
|
+
throw new GeneralError(buildGreenSummaryRejectedMessage(outcome.verdict.greenSummaryRejected, outcome.result.exitCode));
|
|
209
|
+
}
|
|
210
|
+
if (outcome.verdict.realFailureLine !== undefined) {
|
|
211
|
+
info(`First real test failure: ${outcome.verdict.realFailureLine}`);
|
|
212
|
+
const blocks = formatFailureBlocks(outcome.verdict.realFailureBlocks);
|
|
213
|
+
if (blocks !== undefined) {
|
|
214
|
+
info(blocks);
|
|
215
|
+
}
|
|
216
|
+
if (outcome.verdict.secondaryHarnessSignature !== undefined) {
|
|
217
|
+
info(`Secondary harness noise also present: ${outcome.verdict.secondaryHarnessSignature.reason}; ` +
|
|
218
|
+
outcome.verdict.secondaryHarnessSignature.line);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
203
221
|
handleNonZeroTestExit(outcome.result, normalizedPaths, outcome.appdirInjectionAttempted, binaryName, postRebuildContext);
|
|
204
222
|
}
|
|
205
223
|
/**
|
|
@@ -211,12 +229,29 @@ export function diagnoseShardOutcome(outcome, path, binaryName, postRebuildConte
|
|
|
211
229
|
if (outcome.verdict.kind === 'no-tests' && outcome.result.exitCode === 0) {
|
|
212
230
|
return buildNoTestsRanMessage(0, [path]);
|
|
213
231
|
}
|
|
232
|
+
if (outcome.verdict.greenSummaryRejected) {
|
|
233
|
+
return buildGreenSummaryRejectedMessage(outcome.verdict.greenSummaryRejected, outcome.result.exitCode);
|
|
234
|
+
}
|
|
214
235
|
try {
|
|
215
236
|
handleNonZeroTestExit(outcome.result, [path], outcome.appdirInjectionAttempted, binaryName, postRebuildContext);
|
|
216
237
|
return undefined;
|
|
217
238
|
}
|
|
218
239
|
catch (error) {
|
|
219
|
-
|
|
240
|
+
const diagnosis = error instanceof Error ? error.message : String(error);
|
|
241
|
+
const blocks = formatFailureBlocks(outcome.verdict.realFailureBlocks);
|
|
242
|
+
return blocks !== undefined ? `${blocks}\n${diagnosis}` : diagnosis;
|
|
220
243
|
}
|
|
221
244
|
}
|
|
222
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Renders the verdict's TEST-UNEXPECTED blocks (verbatim failing lines +
|
|
247
|
+
* their assertion/diff context) for the failure summary. Returns undefined
|
|
248
|
+
* when the verdict carries none, so callers can skip the section entirely.
|
|
249
|
+
*/
|
|
250
|
+
function formatFailureBlocks(blocks) {
|
|
251
|
+
if (blocks === undefined || blocks.length === 0)
|
|
252
|
+
return undefined;
|
|
253
|
+
// The collector appends a `…(+N more …)` note when it truncated; that
|
|
254
|
+
// note is not a failure block, so it does not count toward the header.
|
|
255
|
+
const shown = blocks.filter((block) => !block.startsWith('…(')).length;
|
|
256
|
+
return `Unexpected test failures (first ${shown}):\n${blocks.join('\n\n')}`;
|
|
257
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadConfig } from '../core/config.js';
|
|
2
|
+
import type { TestOptions } from '../types/commands/index.js';
|
|
3
|
+
import type { TestRunOutcome } from './test-run.js';
|
|
4
|
+
type ProjectConfig = Awaited<ReturnType<typeof loadConfig>>;
|
|
5
|
+
/** Rejects pathless `fireforge test` unless the operator selected a pathless mode. */
|
|
6
|
+
export declare function assertPathlessTestMode(testPaths: readonly string[], options: TestOptions): void;
|
|
7
|
+
/** Rejects invalid combinations for mode-like `fireforge test` options. */
|
|
8
|
+
export declare function assertTestModeCombinations(testPaths: readonly string[], options: TestOptions, canaryPath: string | undefined): void;
|
|
9
|
+
/** Resolves the canary path from CLI or config. */
|
|
10
|
+
export declare function resolveCanaryPath(options: TestOptions, projectConfig: ProjectConfig): string | undefined;
|
|
11
|
+
/** Returns the configured canary no-output budget, defaulting to 60 seconds. */
|
|
12
|
+
export declare function canaryTimeoutSeconds(projectConfig: ProjectConfig): number;
|
|
13
|
+
/** Emits or throws the one-word canary verdict. */
|
|
14
|
+
export declare function reportCanaryOutcome(outcome: TestRunOutcome): void;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { loadConfig } from '../core/config.js';
|
|
3
|
+
import { GeneralError } from '../errors/base.js';
|
|
4
|
+
import { success } from '../utils/logger.js';
|
|
5
|
+
/** Rejects pathless `fireforge test` unless the operator selected a pathless mode. */
|
|
6
|
+
export function assertPathlessTestMode(testPaths, options) {
|
|
7
|
+
if (testPaths.length === 0 &&
|
|
8
|
+
options.auto !== true &&
|
|
9
|
+
options.doctor !== true &&
|
|
10
|
+
options.canary === undefined) {
|
|
11
|
+
throw new GeneralError(buildPathlessTestMessage());
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Rejects invalid combinations for mode-like `fireforge test` options. */
|
|
15
|
+
export function assertTestModeCombinations(testPaths, options, canaryPath) {
|
|
16
|
+
if (options.auto === true && testPaths.length > 0) {
|
|
17
|
+
throw new GeneralError('`fireforge test --auto` is valid only when no explicit paths are provided.');
|
|
18
|
+
}
|
|
19
|
+
if (options.canary !== undefined && testPaths.length > 0) {
|
|
20
|
+
throw new GeneralError('`fireforge test --canary` runs one canary path; do not pass positional test paths.');
|
|
21
|
+
}
|
|
22
|
+
if (options.canary !== undefined && canaryPath === undefined) {
|
|
23
|
+
throw new GeneralError('No test canary path is configured.\n\n' +
|
|
24
|
+
'Pass `fireforge test --canary <path>` or set `test.canaryPath` in fireforge.json.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Resolves the canary path from CLI or config. */
|
|
28
|
+
export function resolveCanaryPath(options, projectConfig) {
|
|
29
|
+
if (options.canary === undefined || options.canary === false)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (typeof options.canary === 'string')
|
|
32
|
+
return options.canary;
|
|
33
|
+
return projectConfig.test?.canaryPath;
|
|
34
|
+
}
|
|
35
|
+
/** Returns the configured canary no-output budget, defaulting to 60 seconds. */
|
|
36
|
+
export function canaryTimeoutSeconds(projectConfig) {
|
|
37
|
+
return projectConfig.test?.canaryTimeoutSeconds ?? 60;
|
|
38
|
+
}
|
|
39
|
+
/** Emits or throws the one-word canary verdict. */
|
|
40
|
+
export function reportCanaryOutcome(outcome) {
|
|
41
|
+
if (outcome.verdict.kind === 'tests-ran-ok') {
|
|
42
|
+
success('Canary: green');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (outcome.verdict.kind === 'harness-crash' &&
|
|
46
|
+
outcome.verdict.signature?.reason.includes('no-output timeout')) {
|
|
47
|
+
throw new GeneralError(`Canary: hang\n\n${outcome.verdict.signature.line}`);
|
|
48
|
+
}
|
|
49
|
+
if (outcome.verdict.kind === 'harness-crash') {
|
|
50
|
+
throw new GeneralError(`Canary: crash\n\n${outcome.verdict.signature ? outcome.verdict.signature.line : 'Harness crashed before the canary completed.'}`);
|
|
51
|
+
}
|
|
52
|
+
throw new GeneralError('Canary: crash\n\nThe canary did not complete green; see mach output above.');
|
|
53
|
+
}
|
|
54
|
+
function buildPathlessTestMessage() {
|
|
55
|
+
return ('`fireforge test` now requires an explicit test path unless you choose a pathless mode.\n\n' +
|
|
56
|
+
'Use one of:\n' +
|
|
57
|
+
' - `fireforge test <engine-relative test path>` for focused runs\n' +
|
|
58
|
+
" - `fireforge test --auto` to forward mach's own auto-selection mode\n" +
|
|
59
|
+
' - `fireforge test --doctor` to run the Marionette preflight only\n' +
|
|
60
|
+
' - `fireforge test --canary` to run the configured short harness canary');
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { withEngineSessionLock } from '../core/engine-session-lock.js';
|
|
4
|
+
import { GeneralError } from '../errors/base.js';
|
|
5
|
+
import { commanderArgParser, pickDefined } from '../utils/options.js';
|
|
6
|
+
import { testCommand } from './test.js';
|
|
7
|
+
import { DEFAULT_HARNESS_RETRIES } from './test-run.js';
|
|
8
|
+
/** Registers the test command on the CLI program. */
|
|
9
|
+
export function registerTest(program, { getProjectRoot, withErrorHandling }) {
|
|
10
|
+
program
|
|
11
|
+
.command('test [paths...]')
|
|
12
|
+
.description('Run tests via mach test')
|
|
13
|
+
.option('--headless', 'Run tests in headless mode')
|
|
14
|
+
.option('--build', 'Run incremental UI build before testing')
|
|
15
|
+
.option('--auto', 'Forward mach test --auto. Valid only when no explicit paths are provided.')
|
|
16
|
+
.option('--allow-stale-build', 'Allow tests to run even when packageable engine files changed since the last successful FireForge build')
|
|
17
|
+
.option('--kill-stale-marionette', 'Terminate a recognized stale browser process holding the Marionette port before running tests')
|
|
18
|
+
.option('--canary [path]', 'Run one short browser-chrome harness canary. Uses test.canaryPath from fireforge.json when no path is supplied.')
|
|
19
|
+
.option('--doctor', 'Run a marionette handshake preflight before tests (exit 1 on FAIL). With no paths, runs the preflight only.')
|
|
20
|
+
.option('--mach-arg <arg>', 'Forward this argument verbatim to `mach test` (repeatable). Escape valve for upstream xpcshell/mochitest flags FireForge does not model.', (value, acc) => {
|
|
21
|
+
acc.push(value);
|
|
22
|
+
return acc;
|
|
23
|
+
}, [])
|
|
24
|
+
.option('--harness-retries <n>', `Retry budget for recognized harness crashes (resource-monitor tracebacks, pre-test hangs, post-green shutdown re-entry). 0 disables retries. Default: ${String(DEFAULT_HARNESS_RETRIES)}.`, commanderArgParser((raw) => {
|
|
25
|
+
const n = Number.parseInt(raw, 10);
|
|
26
|
+
if (!Number.isFinite(n) || n < 0 || n > 10) {
|
|
27
|
+
throw new GeneralError(`--harness-retries must be an integer in 0..10 (got "${raw}")`);
|
|
28
|
+
}
|
|
29
|
+
return n;
|
|
30
|
+
}))
|
|
31
|
+
.option('--generic-mach-test', 'Force dispatch through generic `mach test` instead of the suite-specific `mach xpcshell-test` / `mach mochitest` a single-suite run auto-selects (the suite-specific commands skip the mozlog resource monitor that crashes `mach test` on some hosts).')
|
|
32
|
+
.option('--no-shard', 'Run multiple test paths in one combined mach invocation instead of sequential per-file shards (isolated instances do not exercise cross-file state, so use this to reproduce cross-file pollution bugs)')
|
|
33
|
+
.option('--perf-samples <path>', 'Publish a perf-sample artifact path to the harness via <BINARYNAME>_PERF_SAMPLE_JSON (resolved against the project root)')
|
|
34
|
+
.option('--marionette-port <port>', 'Override the Marionette control port (default 2828) for the stale-browser probe, the --doctor preflight, and (unless --mach-arg includes --flavor=xpcshell) auto-forwarded mach args: --setpref=marionette.port=<n> (browser listener) and --marionette=127.0.0.1:<n> (mochitest client). Omits the client flag when --mach-arg already sets --marionette. Use when 2828 is busy or CI assigns another port.', commanderArgParser((raw) => {
|
|
35
|
+
const n = Number.parseInt(raw, 10);
|
|
36
|
+
if (!Number.isFinite(n) || n < 1 || n > 65535) {
|
|
37
|
+
throw new GeneralError(`--marionette-port must be an integer in 1..65535 (got "${raw}")`);
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
}))
|
|
41
|
+
.addHelpText('after', [
|
|
42
|
+
'',
|
|
43
|
+
'[paths...] semantics: a directory argument selects EXACTLY that',
|
|
44
|
+
'directory. FireForge enumerates the test files of exactly that',
|
|
45
|
+
'directory and passes the explicit file list to mach, because mach',
|
|
46
|
+
'resolves test paths by string prefix and a bare directory name',
|
|
47
|
+
'silently sweeps in sibling directories sharing the prefix (e.g.',
|
|
48
|
+
'foo also running foo-extras) — an explicit file list cannot',
|
|
49
|
+
'prefix-match anything. The directory still runs as ONE mach',
|
|
50
|
+
'invocation (one browser instance), so cross-file state carries',
|
|
51
|
+
'within it. When prefix-named siblings exist, the excluded',
|
|
52
|
+
'directories are listed with their test-file counts; pass them as',
|
|
53
|
+
'separate paths to include them.',
|
|
54
|
+
'',
|
|
55
|
+
'Multiple path arguments shard into sequential isolated harness',
|
|
56
|
+
'runs (one browser instance per argument) by default, which does',
|
|
57
|
+
'not exercise cross-argument state; --no-shard restores the',
|
|
58
|
+
'combined single-instance invocation.',
|
|
59
|
+
].join('\n'))
|
|
60
|
+
.action(withErrorHandling(async (paths, options) => {
|
|
61
|
+
const projectRoot = getProjectRoot();
|
|
62
|
+
await withEngineSessionLock(projectRoot, 'test', () => testCommand(projectRoot, paths, pickDefined(options)));
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
@@ -53,16 +53,32 @@ export interface TestRunOutcome {
|
|
|
53
53
|
* stale-xpcshell-symlink repair path the single-run flow already used.
|
|
54
54
|
*/
|
|
55
55
|
export declare function runTestsWithRetries(ctx: TestRunContext, paths: string[]): Promise<TestRunOutcome>;
|
|
56
|
+
/**
|
|
57
|
+
* One sequential harness invocation of a sharded run: a requested path
|
|
58
|
+
* argument and the mach paths dispatched for it. A file argument is a
|
|
59
|
+
* group of one; a directory argument groups its enumerated test files so
|
|
60
|
+
* the whole directory still runs in ONE browser instance (cross-file
|
|
61
|
+
* state carries within a directory run exactly like the pre-enumeration
|
|
62
|
+
* behavior).
|
|
63
|
+
*/
|
|
64
|
+
export interface ShardGroup {
|
|
65
|
+
/** The path argument as the operator passed it (display label). */
|
|
66
|
+
label: string;
|
|
67
|
+
/** The mach dispatch paths for this argument. */
|
|
68
|
+
paths: string[];
|
|
69
|
+
}
|
|
56
70
|
/** Per-shard summary entry. */
|
|
57
71
|
export interface ShardOutcome {
|
|
58
|
-
|
|
72
|
+
label: string;
|
|
59
73
|
outcome: TestRunOutcome;
|
|
60
74
|
}
|
|
61
75
|
/**
|
|
62
|
-
* Runs each requested path as its own sequential harness
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
76
|
+
* Runs each requested path argument as its own sequential harness
|
|
77
|
+
* invocation (a directory argument's enumerated files stay together in
|
|
78
|
+
* one invocation — see {@link ShardGroup}) and prints an aggregate
|
|
79
|
+
* report. Per-shard failures are diagnosed via `diagnoseShardFailure`
|
|
80
|
+
* (which receives the throwing diagnosis chain from the command layer)
|
|
81
|
+
* but downgraded to warnings so every shard runs; a single aggregate
|
|
82
|
+
* error is thrown at the end when any shard did not pass.
|
|
67
83
|
*/
|
|
68
|
-
export declare function runShardedTests(ctx: TestRunContext,
|
|
84
|
+
export declare function runShardedTests(ctx: TestRunContext, groups: ShardGroup[], diagnoseShardFailure: (outcome: TestRunOutcome, label: string) => string | undefined): Promise<void>;
|