@hominis/fireforge 0.49.1 → 0.50.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 +41 -0
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create-templates.d.ts +8 -3
- package/dist/src/commands/furnace/create-templates.js +8 -23
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +93 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +51 -15
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency.js +14 -41
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +37 -24
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +59 -35
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +97 -0
- package/dist/src/core/coverage-records.js +350 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +12 -2
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +157 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +323 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +20 -0
- package/dist/src/core/run-log.js +89 -3
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +34 -6
- package/dist/src/utils/options.js +86 -5
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +2 -2
|
@@ -3,16 +3,16 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
4
4
|
import { assertEngineExists } from '../core/engine-precondition.js';
|
|
5
5
|
import { assertEngineGenerationUnchanged, snapshotEngineGeneration, } from '../core/engine-session-lock.js';
|
|
6
|
-
import { hasBuildArtifacts
|
|
6
|
+
import { hasBuildArtifacts } from '../core/mach.js';
|
|
7
7
|
import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
|
|
8
8
|
import { ensureLaunchableBrowserNotRunning } from '../core/marionette-port.js';
|
|
9
9
|
import { waitForPreflight } from '../core/preflight-wait.js';
|
|
10
|
-
import { closeActiveRunLog, openRunLog, setActiveRunLog, writeToActiveRunLog, } from '../core/run-log.js';
|
|
10
|
+
import { closeActiveRunLog, keepRunLogCopy, openRunLog, setActiveRunLog, writeToActiveRunLog, } from '../core/run-log.js';
|
|
11
11
|
import { createPostRebuildFailureContext } from '../core/test-harness-output.js';
|
|
12
12
|
import { analyzeTestPathScopes, formatScopeNotice, } from '../core/test-path-scope.js';
|
|
13
13
|
import { cleanupProfileFiles, stageProfileFiles, } from '../core/test-profile-files.js';
|
|
14
14
|
import { assertObjdirMatchesTreeMarker } from '../core/tree-store.js';
|
|
15
|
-
import { FireForgeError, GeneralError, PreflightRefusalError } from '../errors/base.js';
|
|
15
|
+
import { FireForgeError, GeneralError, LockContentionError, PreflightRefusalError, } from '../errors/base.js';
|
|
16
16
|
import { BuildError } from '../errors/build.js';
|
|
17
17
|
import { toError } from '../utils/errors.js';
|
|
18
18
|
import { pathExists } from '../utils/fs.js';
|
|
@@ -24,7 +24,10 @@ import { runTestBuildPhase } from './test-build-phase.js';
|
|
|
24
24
|
import { diagnoseShardOutcome, finalizeSingleRunOutcome } from './test-diagnose.js';
|
|
25
25
|
import { buildPerfSampleEnv, mergeHarnessEnv, resolveShuffleSeed, shuffleTestGroups, } from './test-harness-env.js';
|
|
26
26
|
import { removePgidFile, setActivePgidFile } from './test-harness-teardown.js';
|
|
27
|
-
import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, classifyBeforeDispatch, reportCanaryOutcome, resolveCanaryPath, } from './test-modes.js';
|
|
27
|
+
import { assertPathlessTestMode, assertTestModeCombinations, canaryTimeoutSeconds, classifyBeforeDispatch, reportCanaryOutcome, resolveCanaryPath, restartBetweenTestsArgs, } from './test-modes.js';
|
|
28
|
+
import { orchestrateTestRuns } from './test-orchestrate.js';
|
|
29
|
+
import { perfGenerationTolerance } from './test-perf-scope.js';
|
|
30
|
+
import { resolveLaunchablePathForTests, runTestPreLockPreflight, TEST_BUILD_PREFLIGHT, } from './test-prelock.js';
|
|
28
31
|
import { DEFAULT_HARNESS_RETRIES, finalizeShardedOutcome, runShardedTests, runTestsWithRetries, } from './test-run.js';
|
|
29
32
|
import { emitFailVerdict, resetVerdictEmission, verdictEmitted } from './test-verdict.js';
|
|
30
33
|
async function assertTestPathsExist(engineDir, testPaths) {
|
|
@@ -77,20 +80,6 @@ function filterRedundantXpcshellFlavorArgs(machArgs, classification) {
|
|
|
77
80
|
}
|
|
78
81
|
return filtered;
|
|
79
82
|
}
|
|
80
|
-
async function resolveLaunchablePathForTests(engineDir, binaryName, objDir) {
|
|
81
|
-
if (!objDir)
|
|
82
|
-
return undefined;
|
|
83
|
-
const bundleCheck = await hasRunnableBundle(engineDir, binaryName, objDir);
|
|
84
|
-
if (!bundleCheck.runnable) {
|
|
85
|
-
const expectedSuffix = bundleCheck.expectedPath
|
|
86
|
-
? ` (expected at engine/${bundleCheck.expectedPath})`
|
|
87
|
-
: '';
|
|
88
|
-
throw new GeneralError(`Tests require a complete launchable build${expectedSuffix}. ` +
|
|
89
|
-
'The obj-*/dist/ tree exists but the launchable binary is missing — typically the result of an interrupted or partially failed `fireforge build`.\n\n' +
|
|
90
|
-
'Run "fireforge build" again and let it finish before retrying "fireforge test".');
|
|
91
|
-
}
|
|
92
|
-
return bundleCheck.expectedPath;
|
|
93
|
-
}
|
|
94
83
|
function logTestSelection(scopes) {
|
|
95
84
|
if (scopes.length > 0) {
|
|
96
85
|
const labels = scopes.map((scope) => scope.isDirectory && scope.testFileCount > 0
|
|
@@ -103,13 +92,6 @@ function logTestSelection(scopes) {
|
|
|
103
92
|
}
|
|
104
93
|
info('');
|
|
105
94
|
}
|
|
106
|
-
/** Build-artifact preflight wording for `fireforge test`. */
|
|
107
|
-
const TEST_BUILD_PREFLIGHT = {
|
|
108
|
-
label: 'Tests',
|
|
109
|
-
requirement: 'Tests require a completed build.',
|
|
110
|
-
remediation: "Run 'fireforge build' first, then run 'fireforge test'.",
|
|
111
|
-
requireExisting: true,
|
|
112
|
-
};
|
|
113
95
|
/**
|
|
114
96
|
* Runs the test command to execute mach tests.
|
|
115
97
|
*
|
|
@@ -121,17 +103,22 @@ const TEST_BUILD_PREFLIGHT = {
|
|
|
121
103
|
* since no harness classification exists for such a run. Writers closer to
|
|
122
104
|
* the harness (doctor, canary, single, sharded, the engine-generation
|
|
123
105
|
* guard) emit first and win.
|
|
106
|
+
* The lock-free checks (launchable binary, named-file checkJs, the
|
|
107
|
+
* `--wait-browser`/`--wait-port` budgets) run first, outside `underLock`,
|
|
108
|
+
* so a run that would be refused or would only wait never holds a place in
|
|
109
|
+
* the engine lock queue. See `test-prelock.ts`.
|
|
124
110
|
* @param projectRoot - Root directory of the project
|
|
125
111
|
* @param testPaths - Test file or directory paths
|
|
126
112
|
* @param options - Test options
|
|
113
|
+
* @param underLock - Runs the rest of the command under the engine session lock
|
|
127
114
|
*/
|
|
128
|
-
export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
115
|
+
export async function testCommand(projectRoot, testPaths, options = {}, underLock = (body) => body()) {
|
|
129
116
|
intro('FireForge Test');
|
|
130
117
|
resetVerdictEmission();
|
|
131
118
|
// Opened before any work so a preflight refusal is logged too. Those are
|
|
132
119
|
// exactly the runs whose only output a `tail` throws away. Closed in
|
|
133
120
|
// `finally`, after the verdict line has already read the path.
|
|
134
|
-
setActiveRunLog(await openRunLog(projectRoot, 'test'));
|
|
121
|
+
setActiveRunLog(await keepRunLogCopy(await openRunLog(projectRoot, 'test'), options.keepLog, 'test'));
|
|
135
122
|
setActivePgidFile(options.pgidFile);
|
|
136
123
|
let staged;
|
|
137
124
|
try {
|
|
@@ -139,12 +126,22 @@ export async function testCommand(projectRoot, testPaths, options = {}) {
|
|
|
139
126
|
staged = await stageProfileFiles(projectRoot, options.profileFile);
|
|
140
127
|
options = { ...options, machArg: [...(options.machArg ?? []), ...staged.args] };
|
|
141
128
|
}
|
|
142
|
-
await
|
|
129
|
+
const lockedOptions = await runTestPreLockPreflight(projectRoot, testPaths, options);
|
|
130
|
+
const profileEnv = staged?.env;
|
|
131
|
+
await underLock(() => orchestrateTestRuns(projectRoot, testPaths, lockedOptions, (paths, runOptions, hooks) => runTestCommandBody(projectRoot, paths, runOptions, profileEnv, hooks), (error) => {
|
|
132
|
+
renderPreflightRefusal(error);
|
|
133
|
+
emitFailVerdict('preflight', refusalNote(error));
|
|
134
|
+
}));
|
|
143
135
|
}
|
|
144
136
|
catch (error) {
|
|
145
137
|
if (!verdictEmitted()) {
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
if (error instanceof LockContentionError) {
|
|
139
|
+
emitFailVerdict('lock-timeout');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
renderPreflightRefusal(error);
|
|
143
|
+
emitFailVerdict('preflight', refusalNote(error));
|
|
144
|
+
}
|
|
148
145
|
}
|
|
149
146
|
throw error;
|
|
150
147
|
}
|
|
@@ -208,9 +205,11 @@ function renderPreflightRefusal(error) {
|
|
|
208
205
|
* misdescribe a run whose harness already executed). It is emitted here so the
|
|
209
206
|
* guard's throw can never leave a stale or missing verdict line behind.
|
|
210
207
|
*/
|
|
211
|
-
async function verifyEngineGenerationOrEmitInconclusive(engineDir, before, powerChanged = false) {
|
|
208
|
+
async function verifyEngineGenerationOrEmitInconclusive(engineDir, before, powerChanged = false, perfSamples) {
|
|
212
209
|
try {
|
|
213
|
-
await
|
|
210
|
+
await (perfSamples === undefined
|
|
211
|
+
? assertEngineGenerationUnchanged(engineDir, before)
|
|
212
|
+
: assertEngineGenerationUnchanged(engineDir, before, perfGenerationTolerance(engineDir)));
|
|
214
213
|
if (powerChanged)
|
|
215
214
|
throw new GeneralError('Perf power source changed during the run; repeat this sitting on a stable power source.');
|
|
216
215
|
}
|
|
@@ -255,9 +254,35 @@ function prepareDispatchArguments(classification, options, canaryPath, projectCo
|
|
|
255
254
|
? filterRedundantXpcshellFlavorArgs(options.machArg, classification)
|
|
256
255
|
: [];
|
|
257
256
|
const extraArgs = prepareExtraMachArgs(options, shuffleSeed, canaryPath, projectConfig, forwardedMachArgs);
|
|
257
|
+
extraArgs.push(...restartBetweenTestsArgs(options, suite, forwardedMachArgs));
|
|
258
258
|
return { suite, shuffleSeed, extraArgs };
|
|
259
259
|
}
|
|
260
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Directory arguments mean exactly that directory: mozbuild's test resolver
|
|
262
|
+
* matches paths by string prefix, so a bare directory arg silently sweeps in
|
|
263
|
+
* prefix-named siblings, with `…/test/hominis` also running
|
|
264
|
+
* `…/test/hominis-tiles`, with no indication the scope widened. Each
|
|
265
|
+
* directory argument therefore dispatches as its enumerated explicit
|
|
266
|
+
* test-file list, which cannot prefix-match a sibling. A trailing-`/`
|
|
267
|
+
* normalization does not work, as mach still sweeps the sibling in. Any
|
|
268
|
+
* prefix-siblings are echoed so the narrowed scope is visible.
|
|
269
|
+
*
|
|
270
|
+
* xpcshell appdir auto-injection happens per harness invocation inside
|
|
271
|
+
* `runTestsWithRetries` (src/commands/test-run.ts) so sharded runs probe the
|
|
272
|
+
* manifest for each file individually. See src/core/xpcshell-appdir.ts for
|
|
273
|
+
* the full motivation.
|
|
274
|
+
*/
|
|
275
|
+
async function resolveDispatchGroups(engineDir, normalizedPaths) {
|
|
276
|
+
const scopes = await analyzeTestPathScopes(engineDir, normalizedPaths);
|
|
277
|
+
for (const scope of scopes) {
|
|
278
|
+
const notice = formatScopeNotice(scope);
|
|
279
|
+
if (notice)
|
|
280
|
+
info(notice);
|
|
281
|
+
}
|
|
282
|
+
logTestSelection(scopes);
|
|
283
|
+
return scopes.map((scope) => ({ label: scope.requestedPath, paths: scope.dispatchPaths }));
|
|
284
|
+
}
|
|
285
|
+
async function runTestCommandBody(projectRoot, testPaths, options = {}, profileEnv, hooks = {}) {
|
|
261
286
|
const paths = getProjectPaths(projectRoot);
|
|
262
287
|
// Check if engine exists
|
|
263
288
|
await assertEngineExists(paths.engine);
|
|
@@ -281,7 +306,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
281
306
|
// against an incomplete build surface the missing-bundle path instead of a
|
|
282
307
|
// cryptic `Browser process exited during spawn (exit code 1, signal none).
|
|
283
308
|
// stderr tail: (empty)`.
|
|
284
|
-
const launchablePath = await resolveLaunchablePathForTests(paths.engine, projectConfig.binaryName, buildCheck.objDir);
|
|
309
|
+
const launchablePath = await resolveLaunchablePathForTests(projectRoot, paths.engine, projectConfig.binaryName, buildCheck.objDir);
|
|
285
310
|
const harnessRetries = options.harnessRetries ?? DEFAULT_HARNESS_RETRIES;
|
|
286
311
|
// Normalized engine-relative request paths, hoisted above the build/stale
|
|
287
312
|
// gate: the pre-test build records them as the packaging-coverage claim,
|
|
@@ -304,7 +329,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
304
329
|
// mach.
|
|
305
330
|
if (await runTestBuildPhase(projectRoot, paths, projectConfig, harnessRetries, options, {
|
|
306
331
|
classification,
|
|
307
|
-
normalizedPaths,
|
|
332
|
+
normalizedPaths: hooks.coveragePaths ?? normalizedPaths,
|
|
308
333
|
})) {
|
|
309
334
|
return;
|
|
310
335
|
}
|
|
@@ -324,31 +349,9 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
324
349
|
await assertTestPathsExist(paths.engine, normalizedPaths);
|
|
325
350
|
const { suite, shuffleSeed, extraArgs } = prepareDispatchArguments(classification, options, canaryPath, projectConfig);
|
|
326
351
|
appendMarionetteForwardingArgs(extraArgs, options, forwardedPort, xpcshellOnly);
|
|
327
|
-
// Directory arguments mean exactly that directory: mozbuild's test
|
|
328
|
-
// resolver matches paths by string prefix, so a bare directory arg
|
|
329
|
-
// silently sweeps in prefix-named siblings, with `…/test/hominis` also
|
|
330
|
-
// running `…/test/hominis-tiles`, with no indication the scope widened. Each
|
|
331
|
-
// directory argument therefore dispatches as its enumerated explicit
|
|
332
|
-
// test-file list, which cannot prefix-match a sibling. A trailing-`/`
|
|
333
|
-
// normalization does not work, as mach still sweeps the sibling in. Any
|
|
334
|
-
// prefix-siblings are echoed so the narrowed scope is visible.
|
|
335
352
|
// Classification above intentionally used the raw argument forms. Only the
|
|
336
353
|
// mach dispatch needs the exact-match shape.
|
|
337
|
-
const
|
|
338
|
-
const dispatchGroups = scopes.map((scope) => ({
|
|
339
|
-
label: scope.requestedPath,
|
|
340
|
-
paths: scope.dispatchPaths,
|
|
341
|
-
}));
|
|
342
|
-
for (const scope of scopes) {
|
|
343
|
-
const notice = formatScopeNotice(scope);
|
|
344
|
-
if (notice)
|
|
345
|
-
info(notice);
|
|
346
|
-
}
|
|
347
|
-
// xpcshell appdir auto-injection happens per harness invocation inside
|
|
348
|
-
// `runTestsWithRetries` (src/commands/test-run.ts) so sharded runs probe
|
|
349
|
-
// the manifest for each file individually. See src/core/xpcshell-appdir.ts
|
|
350
|
-
// for the full motivation.
|
|
351
|
-
logTestSelection(scopes);
|
|
354
|
+
const dispatchGroups = await resolveDispatchGroups(paths.engine, normalizedPaths);
|
|
352
355
|
const harnessEnv = mergeHarnessEnv(buildPerfSampleEnv(projectRoot, projectConfig.binaryName, options.perfSamples), profileEnv, shuffleSeed === undefined ? undefined : { FIREFORGE_SHUFFLE_SEED: String(shuffleSeed) });
|
|
353
356
|
const runCtx = {
|
|
354
357
|
engineDir: paths.engine,
|
|
@@ -365,6 +368,9 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
365
368
|
const postRebuildContext = options.build
|
|
366
369
|
? createPostRebuildFailureContext('fireforge test --build', normalizedPaths)
|
|
367
370
|
: undefined;
|
|
371
|
+
// A negative control goes in here: after every gate and the build, so
|
|
372
|
+
// packaging cannot overwrite it and stale-build cannot refuse it.
|
|
373
|
+
await hooks.beforeDispatch?.();
|
|
368
374
|
// Multi-argument requests shard into sequential harness runs by default:
|
|
369
375
|
// one shared mochitest profile across files bleeds pref/media-query state
|
|
370
376
|
// into later files. Sharding is per path argument: a directory argument
|
|
@@ -390,7 +396,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
390
396
|
// `FAIL reason=inconclusive` and throws, so an invalidated run can
|
|
391
397
|
// never print `PASS shards=N/N` first. (A throw here masks an
|
|
392
398
|
// in-flight shard error, as the plain assert always did.)
|
|
393
|
-
await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged);
|
|
399
|
+
await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged, options.perfSamples);
|
|
394
400
|
}
|
|
395
401
|
finalizeShardedOutcome(summary);
|
|
396
402
|
return;
|
|
@@ -405,7 +411,7 @@ async function runTestCommandBody(projectRoot, testPaths, options = {}, profileE
|
|
|
405
411
|
throw new BuildError('Test process failed to start', 'mach test', error instanceof Error ? error : undefined);
|
|
406
412
|
}
|
|
407
413
|
finally {
|
|
408
|
-
await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged);
|
|
414
|
+
await verifyEngineGenerationOrEmitInconclusive(paths.engine, generationBefore, runCtx.powerChanged, options.perfSamples);
|
|
409
415
|
}
|
|
410
416
|
if (canaryPath !== undefined) {
|
|
411
417
|
reportCanaryOutcome(outcome);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Options accepted by {@link tokenLintCommand}. */
|
|
2
|
+
export interface TokenLintOptions {
|
|
3
|
+
/** Emit the machine-readable envelope instead of the human report. */
|
|
4
|
+
json?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Runs `token lint`. Exits non-zero (GeneralError) when any finding is
|
|
8
|
+
* reported; with `--json` the report is written first, as `status --check
|
|
9
|
+
* --json` does.
|
|
10
|
+
*
|
|
11
|
+
* @param projectRoot - Root directory of the project
|
|
12
|
+
* @param options - Output-mode options
|
|
13
|
+
*/
|
|
14
|
+
export declare function tokenLintCommand(projectRoot: string, options?: TokenLintOptions): Promise<void>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge token lint`: themed-block lockstep and undeclared token
|
|
4
|
+
* references (see `core/token-lint.ts` for the rules).
|
|
5
|
+
*
|
|
6
|
+
* Helper module: no registrar, called from `token.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { getProjectPaths, loadConfig } from '../core/config.js';
|
|
10
|
+
import { furnaceConfigExists, loadFurnaceConfig } from '../core/furnace-config.js';
|
|
11
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
12
|
+
import { collectDeclaredTokenNames, lintTokenLockstep, lintUndeclaredTokenReferences, } from '../core/token-lint.js';
|
|
13
|
+
import { getTokensCssPath } from '../core/token-manager.js';
|
|
14
|
+
import { GeneralError } from '../errors/base.js';
|
|
15
|
+
import { toError } from '../utils/errors.js';
|
|
16
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
17
|
+
import { info, intro, outro, setMachineOutputMode, setStdoutSealed, success, warn, } from '../utils/logger.js';
|
|
18
|
+
import { emitMachineError, MACHINE_OUTPUT_SCHEMA_VERSION } from '../utils/machine-output.js';
|
|
19
|
+
/** Token prefix and exempt names from furnace.json, with the init default. */
|
|
20
|
+
async function loadTokenSettings(projectRoot, binaryName) {
|
|
21
|
+
const fallback = { tokenPrefix: `--${binaryName}-`, exempt: new Set() };
|
|
22
|
+
if (!(await furnaceConfigExists(projectRoot)))
|
|
23
|
+
return fallback;
|
|
24
|
+
try {
|
|
25
|
+
const furnace = await loadFurnaceConfig(projectRoot);
|
|
26
|
+
return {
|
|
27
|
+
tokenPrefix: furnace.tokenPrefix ?? fallback.tokenPrefix,
|
|
28
|
+
exempt: new Set([...(furnace.tokenAllowlist ?? []), ...(furnace.runtimeVariables ?? [])]),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
warn(`Could not load furnace.json; using the default token prefix (${toError(error).message})`);
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function collectTokenLint(projectRoot) {
|
|
37
|
+
const paths = getProjectPaths(projectRoot);
|
|
38
|
+
const config = await loadConfig(projectRoot);
|
|
39
|
+
const tokensCssPath = getTokensCssPath(config.binaryName);
|
|
40
|
+
const tokensFile = join(paths.engine, tokensCssPath);
|
|
41
|
+
if (!(await pathExists(tokensFile))) {
|
|
42
|
+
throw new GeneralError(`Token CSS file not found: ${tokensCssPath}. Run "fireforge furnace init" to seed it.`);
|
|
43
|
+
}
|
|
44
|
+
const { tokenPrefix, exempt } = await loadTokenSettings(projectRoot, config.binaryName);
|
|
45
|
+
const tokensCss = await readText(tokensFile);
|
|
46
|
+
const lines = tokensCss.split('\n');
|
|
47
|
+
const declared = collectDeclaredTokenNames(lines);
|
|
48
|
+
const findings = [
|
|
49
|
+
...lintTokenLockstep(lines, tokensCssPath, tokenPrefix),
|
|
50
|
+
...lintUndeclaredTokenReferences(tokensCssPath, tokensCss, tokenPrefix, declared, exempt),
|
|
51
|
+
];
|
|
52
|
+
// Every CSS file the patch queue touches: the fork's own stylesheets and
|
|
53
|
+
// the upstream ones it edits.
|
|
54
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
55
|
+
const cssFiles = [
|
|
56
|
+
...new Set((manifest?.patches ?? []).flatMap((patch) => patch.filesAffected.filter((file) => file.endsWith('.css') && file !== tokensCssPath))),
|
|
57
|
+
].sort();
|
|
58
|
+
let filesScanned = 1;
|
|
59
|
+
for (const file of cssFiles) {
|
|
60
|
+
const absolute = join(paths.engine, file);
|
|
61
|
+
if (!(await pathExists(absolute)))
|
|
62
|
+
continue;
|
|
63
|
+
filesScanned++;
|
|
64
|
+
findings.push(...lintUndeclaredTokenReferences(file, await readText(absolute), tokenPrefix, declared, exempt));
|
|
65
|
+
}
|
|
66
|
+
return { tokensCssPath, tokenPrefix, filesScanned, findings };
|
|
67
|
+
}
|
|
68
|
+
function failureMessage(count) {
|
|
69
|
+
return `token lint found ${String(count)} finding(s).`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Runs `token lint`. Exits non-zero (GeneralError) when any finding is
|
|
73
|
+
* reported; with `--json` the report is written first, as `status --check
|
|
74
|
+
* --json` does.
|
|
75
|
+
*
|
|
76
|
+
* @param projectRoot - Root directory of the project
|
|
77
|
+
* @param options - Output-mode options
|
|
78
|
+
*/
|
|
79
|
+
export async function tokenLintCommand(projectRoot, options = {}) {
|
|
80
|
+
if (options.json === true) {
|
|
81
|
+
setMachineOutputMode(true);
|
|
82
|
+
let findings = 0;
|
|
83
|
+
try {
|
|
84
|
+
const report = await collectTokenLint(projectRoot);
|
|
85
|
+
findings = report.findings.length;
|
|
86
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: MACHINE_OUTPUT_SCHEMA_VERSION, ...report }, null, 2)}\n`);
|
|
87
|
+
setStdoutSealed(true);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
emitMachineError('token-lint-failed', toError(error).message);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
setMachineOutputMode(false);
|
|
94
|
+
}
|
|
95
|
+
if (findings > 0)
|
|
96
|
+
throw new GeneralError(failureMessage(findings));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
intro('Token Lint');
|
|
100
|
+
const report = await collectTokenLint(projectRoot);
|
|
101
|
+
for (const finding of report.findings) {
|
|
102
|
+
warn(`${finding.file}:${String(finding.line)} [${finding.check}] ${finding.message}`);
|
|
103
|
+
}
|
|
104
|
+
info(`${String(report.filesScanned)} CSS file(s) scanned for ${report.tokenPrefix}* tokens`);
|
|
105
|
+
if (report.findings.length > 0) {
|
|
106
|
+
outro('Token lint failed');
|
|
107
|
+
throw new GeneralError(failureMessage(report.findings.length));
|
|
108
|
+
}
|
|
109
|
+
success('Themed blocks are in lockstep with the base block; every token reference resolves');
|
|
110
|
+
outro('Token lint passed');
|
|
111
|
+
}
|
|
@@ -10,6 +10,7 @@ import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
|
10
10
|
import { pickDefined } from '../utils/options.js';
|
|
11
11
|
import { normalizeTokenName } from '../utils/validation.js';
|
|
12
12
|
import { tokenCoverageCommand } from './token-coverage.js';
|
|
13
|
+
import { tokenLintCommand } from './token-lint.js';
|
|
13
14
|
import { tokenListCommand, tokenShowCommand } from './token-list.js';
|
|
14
15
|
async function normalizeTokenNameForProject(projectRoot, rawTokenName) {
|
|
15
16
|
if (rawTokenName.startsWith('--')) {
|
|
@@ -217,6 +218,15 @@ export function registerToken(program, { getProjectRoot, withErrorHandling }) {
|
|
|
217
218
|
.action(withErrorHandling(async (tokenName, options) => {
|
|
218
219
|
await tokenShowCommand(getProjectRoot(), tokenName, pickDefined({ json: options.json }));
|
|
219
220
|
}));
|
|
221
|
+
token
|
|
222
|
+
.command('lint')
|
|
223
|
+
.description('Check the token system: themed override blocks declare only tokens the base :root ' +
|
|
224
|
+
'declares, and every var(--<prefix>…) in the tokens CSS or a patch-touched CSS file ' +
|
|
225
|
+
'names a declared token. Exits non-zero on any finding')
|
|
226
|
+
.option('--json', 'Emit the machine-readable envelope on stdout (see docs/machine-output.md)')
|
|
227
|
+
.action(withErrorHandling(async (options) => {
|
|
228
|
+
await tokenLintCommand(getProjectRoot(), pickDefined({ json: options.json }));
|
|
229
|
+
}));
|
|
220
230
|
token
|
|
221
231
|
.command('coverage')
|
|
222
232
|
.description('Measure design token usage across modified CSS files')
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
/** The resolved scope: manifest filenames, and every path they claim. */
|
|
3
|
+
export interface VerifyScope {
|
|
4
|
+
patches: ReadonlySet<string>;
|
|
5
|
+
files: ReadonlySet<string>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves raw `--patches` values against the manifest. Returns undefined
|
|
9
|
+
* when the flag was not given and refuses a name that matches no patch.
|
|
10
|
+
*
|
|
11
|
+
* @param patchesDir - Absolute path to the patches directory
|
|
12
|
+
* @param requested - Raw `--patches` values (each may be a comma list)
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveVerifyScope(patchesDir: string, requested: readonly string[] | undefined): Promise<VerifyScope | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* True when a filename-keyed finding (manifest consistency, policy) is in
|
|
17
|
+
* scope: it names a scoped patch, or it names no patch at all.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isFilenameInScope(scope: VerifyScope | undefined, filename: string): boolean;
|
|
20
|
+
/** True when any of the implicated patches is in scope. */
|
|
21
|
+
export declare function anyPatchInScope(scope: VerifyScope | undefined, filenames: readonly string[]): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Keeps the lint issues that implicate a scoped patch. An issue without a
|
|
24
|
+
* `patches` list is attributed through its file instead.
|
|
25
|
+
*/
|
|
26
|
+
export declare function scopeLintIssues(issues: PatchLintIssue[], scope: VerifyScope | undefined): PatchLintIssue[];
|
|
27
|
+
/** Keeps the manifest rows in scope. No scope keeps all. */
|
|
28
|
+
export declare function scopePatches<T extends Pick<PatchMetadata, 'filename'>>(patches: readonly T[], scope: VerifyScope | undefined): T[];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `verify --patches <list>`: judges only the named patches' findings. Split
|
|
4
|
+
* out of verify.ts so the per-group attribution rules sit in one place.
|
|
5
|
+
*
|
|
6
|
+
* A finding is in scope when it names a scoped patch: a manifest or policy
|
|
7
|
+
* issue by its filename, a cross-patch claim or lint issue by any patch it
|
|
8
|
+
* implicates, a drifted file by its owning patch, a dangling registration
|
|
9
|
+
* by the patch that registers it. Findings about the queue as a whole (a
|
|
10
|
+
* manifest that does not parse) are kept, because no patch can be judged
|
|
11
|
+
* past them. Unowned worktree changes belong to no patch and drop out.
|
|
12
|
+
*/
|
|
13
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
14
|
+
import { selectPatchSubset } from '../core/patch-subset.js';
|
|
15
|
+
import { GeneralError } from '../errors/base.js';
|
|
16
|
+
/**
|
|
17
|
+
* Resolves raw `--patches` values against the manifest. Returns undefined
|
|
18
|
+
* when the flag was not given and refuses a name that matches no patch.
|
|
19
|
+
*
|
|
20
|
+
* @param patchesDir - Absolute path to the patches directory
|
|
21
|
+
* @param requested - Raw `--patches` values (each may be a comma list)
|
|
22
|
+
*/
|
|
23
|
+
export async function resolveVerifyScope(patchesDir, requested) {
|
|
24
|
+
if (requested === undefined || requested.length === 0)
|
|
25
|
+
return undefined;
|
|
26
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
27
|
+
if (!manifest || manifest.patches.length === 0) {
|
|
28
|
+
throw new GeneralError('--patches needs a patch queue, but patches.json lists no patches.');
|
|
29
|
+
}
|
|
30
|
+
const subset = selectPatchSubset(manifest, requested, '--patches selects patches (filename, stem, order number, manifest name, or slug).');
|
|
31
|
+
return {
|
|
32
|
+
patches: new Set(subset.map((patch) => patch.filename)),
|
|
33
|
+
files: new Set(subset.flatMap((patch) => patch.filesAffected)),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when a filename-keyed finding (manifest consistency, policy) is in
|
|
38
|
+
* scope: it names a scoped patch, or it names no patch at all.
|
|
39
|
+
*/
|
|
40
|
+
export function isFilenameInScope(scope, filename) {
|
|
41
|
+
if (scope === undefined)
|
|
42
|
+
return true;
|
|
43
|
+
return scope.patches.has(filename) || !filename.endsWith('.patch');
|
|
44
|
+
}
|
|
45
|
+
/** True when any of the implicated patches is in scope. */
|
|
46
|
+
export function anyPatchInScope(scope, filenames) {
|
|
47
|
+
if (scope === undefined)
|
|
48
|
+
return true;
|
|
49
|
+
return filenames.some((filename) => scope.patches.has(filename));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Keeps the lint issues that implicate a scoped patch. An issue without a
|
|
53
|
+
* `patches` list is attributed through its file instead.
|
|
54
|
+
*/
|
|
55
|
+
export function scopeLintIssues(issues, scope) {
|
|
56
|
+
if (scope === undefined)
|
|
57
|
+
return issues;
|
|
58
|
+
return issues.filter((issue) => issue.patches !== undefined && issue.patches.length > 0
|
|
59
|
+
? anyPatchInScope(scope, issue.patches)
|
|
60
|
+
: scope.files.has(issue.file));
|
|
61
|
+
}
|
|
62
|
+
/** Keeps the manifest rows in scope. No scope keeps all. */
|
|
63
|
+
export function scopePatches(patches, scope) {
|
|
64
|
+
if (scope === undefined)
|
|
65
|
+
return [...patches];
|
|
66
|
+
return patches.filter((patch) => scope.patches.has(patch.filename));
|
|
67
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Command } from 'commander';
|
|
17
17
|
import type { CommandContext } from '../types/cli.js';
|
|
18
|
+
import { type VerifyScope } from './verify-scope.js';
|
|
18
19
|
interface VerifyIssueGroup {
|
|
19
20
|
title: string;
|
|
20
21
|
issues: string[];
|
|
@@ -31,15 +32,22 @@ export interface PatchQueueHealth {
|
|
|
31
32
|
* Collects the same queue-health findings reported by `fireforge verify`
|
|
32
33
|
* without printing. Used by doctor recovery paths that need a read-only
|
|
33
34
|
* "is this queue healthy?" decision before clearing stale state.
|
|
35
|
+
*
|
|
36
|
+
* @param projectRoot - Project root directory
|
|
37
|
+
* @param scope - `verify --patches`: keep only the findings that name these
|
|
38
|
+
* patches (see verify-scope.ts). Omitted, every finding counts.
|
|
34
39
|
*/
|
|
35
|
-
export declare function collectPatchQueueHealth(projectRoot: string): Promise<PatchQueueHealth>;
|
|
40
|
+
export declare function collectPatchQueueHealth(projectRoot: string, scope?: VerifyScope): Promise<PatchQueueHealth>;
|
|
36
41
|
/**
|
|
37
42
|
* Runs the `verify` command: manifest consistency + cross-patch lint.
|
|
38
43
|
* Read-only. Exits non-zero on any error-severity finding.
|
|
39
44
|
*
|
|
40
45
|
* @param projectRoot - Project root directory
|
|
46
|
+
* @param options - `patches`: the raw `--patches` values
|
|
41
47
|
*/
|
|
42
|
-
export declare function verifyCommand(projectRoot: string
|
|
48
|
+
export declare function verifyCommand(projectRoot: string, options?: {
|
|
49
|
+
patches?: string[];
|
|
50
|
+
}): Promise<void>;
|
|
43
51
|
/**
|
|
44
52
|
* Registers the `verify` command on the CLI program.
|
|
45
53
|
*
|