@hominis/fireforge 0.49.2 → 0.51.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 +87 -1
- 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 +41 -0
- package/dist/src/commands/check-report.js +91 -0
- package/dist/src/commands/check-run.d.ts +33 -0
- package/dist/src/commands/check-run.js +112 -0
- package/dist/src/commands/check-steps.d.ts +75 -0
- package/dist/src/commands/check-steps.js +200 -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/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- 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-placement-gate.js +8 -0
- 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.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 +98 -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 +151 -113
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +36 -61
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +20 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +4 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -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 +24 -104
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +10 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +78 -31
- package/dist/src/commands/patch/move-files-create.js +34 -18
- package/dist/src/commands/patch/move-files-into.js +15 -7
- package/dist/src/commands/patch/move-files.js +6 -2
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- 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 +20 -1
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +133 -0
- package/dist/src/commands/patch/split-plan.d.ts +17 -8
- package/dist/src/commands/patch/split-plan.js +60 -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 +62 -17
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- 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-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +54 -139
- 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 +50 -2
- package/dist/src/commands/re-export-adjacent.js +160 -40
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- 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-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- 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 +74 -6
- 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-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +56 -17
- package/dist/src/commands/re-export-removed.d.ts +13 -2
- package/dist/src/commands/re-export-removed.js +57 -12
- 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 +19 -9
- package/dist/src/commands/re-export-scan.js +60 -24
- package/dist/src/commands/re-export-single.js +122 -46
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- 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 +71 -57
- 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 +22 -0
- package/dist/src/commands/test-build-furnace.js +54 -0
- package/dist/src/commands/test-build-phase.js +64 -41
- package/dist/src/commands/test-checkjs.js +10 -2
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +211 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -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 +52 -0
- package/dist/src/commands/test-orchestrate.js +240 -0
- package/dist/src/commands/test-perf-scope.d.ts +50 -0
- package/dist/src/commands/test-perf-scope.js +138 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +273 -0
- package/dist/src/commands/test-register.js +69 -10
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +74 -99
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +60 -28
- package/dist/src/commands/test-verdict.d.ts +11 -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 +120 -124
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +151 -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 +67 -36
- 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 +13 -0
- package/dist/src/core/build-prepare.js +42 -10
- 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 +10 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +219 -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 +21 -0
- package/dist/src/core/config-validate-token-scales.d.ts +9 -0
- package/dist/src/core/config-validate-token-scales.js +92 -0
- package/dist/src/core/config-validate.js +20 -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 +108 -0
- package/dist/src/core/coverage-records.js +384 -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 +16 -1
- package/dist/src/core/engine-session-lock.js +38 -10
- 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-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +13 -25
- package/dist/src/core/file-lock.js +163 -140
- 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 +22 -12
- 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 +16 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
- package/dist/src/core/furnace-config-validate.js +14 -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-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- 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 +8 -4
- package/dist/src/core/furnace-validate-accessibility.js +71 -28
- package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
- package/dist/src/core/furnace-validate-exceptions.js +80 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +78 -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 +164 -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/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +32 -10
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -0
- package/dist/src/core/negative-control-diff.d.ts +51 -0
- package/dist/src/core/negative-control-diff.js +179 -0
- package/dist/src/core/negative-control.d.ts +150 -0
- package/dist/src/core/negative-control.js +399 -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/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +6 -0
- package/dist/src/core/patch-export-update.js +13 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +22 -16
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +20 -13
- 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.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +17 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +24 -14
- 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 +23 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +196 -0
- package/dist/src/core/patch-lint-ignore.js +429 -0
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -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 +331 -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 +57 -0
- package/dist/src/core/patch-lint-token-scale.js +286 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +66 -288
- 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 +27 -9
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +61 -3
- package/dist/src/core/patch-order-legal.d.ts +26 -0
- package/dist/src/core/patch-order-legal.js +88 -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 +22 -5
- package/dist/src/core/patch-policy.js +26 -12
- package/dist/src/core/patch-retired.d.ts +57 -0
- package/dist/src/core/patch-retired.js +92 -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 +26 -0
- package/dist/src/core/run-log.js +106 -4
- 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 +19 -1
- package/dist/src/core/test-harness-crash.js +8 -9
- package/dist/src/core/test-harness-signature.d.ts +33 -0
- package/dist/src/core/test-harness-signature.js +23 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +90 -0
- package/dist/src/core/test-run-completeness.js +184 -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/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +53 -0
- package/dist/src/core/token-lint.js +131 -0
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +5 -0
- package/dist/src/core/typecheck-shim.d.ts +6 -2
- package/dist/src/core/typecheck-shim.js +205 -4
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +14 -3
- package/dist/src/index.js +6 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +212 -24
- package/dist/src/types/commands/patches.d.ts +118 -1
- package/dist/src/types/config.d.ts +264 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +63 -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 +36 -7
- package/dist/src/utils/options.js +94 -8
- 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 +369 -11
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +139 -3
- package/docs/lifecycle-invariants.md +65 -30
- package/docs/machine-output.md +120 -12
- package/docs/patch-workflow.md +458 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +346 -21
- package/package.json +3 -3
|
@@ -0,0 +1,151 @@
|
|
|
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 { buildPatchQueueContext } from '../core/patch-lint.js';
|
|
12
|
+
import { buildJarTargetIndex } from '../core/patch-lint-forward-registration-extended.js';
|
|
13
|
+
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
14
|
+
import { collectDeclaredTokenNames, lintTokenLockstep, lintUndeclaredTokenReferences, } from '../core/token-lint.js';
|
|
15
|
+
import { getTokensCssPath } from '../core/token-manager.js';
|
|
16
|
+
import { buildSiblingSheetMap } from '../core/token-sibling-sheets.js';
|
|
17
|
+
import { GeneralError } from '../errors/base.js';
|
|
18
|
+
import { toError } from '../utils/errors.js';
|
|
19
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
20
|
+
import { info, intro, outro, setMachineOutputMode, setStdoutSealed, success, warn, } from '../utils/logger.js';
|
|
21
|
+
import { emitMachineError, MACHINE_OUTPUT_SCHEMA_VERSION } from '../utils/machine-output.js';
|
|
22
|
+
/** Token prefix, exempt names and sibling sheets from furnace.json, with the init default. */
|
|
23
|
+
async function loadTokenSettings(projectRoot, binaryName) {
|
|
24
|
+
const fallback = { tokenPrefix: `--${binaryName}-`, exempt: new Set() };
|
|
25
|
+
if (!(await furnaceConfigExists(projectRoot)))
|
|
26
|
+
return fallback;
|
|
27
|
+
try {
|
|
28
|
+
const furnace = await loadFurnaceConfig(projectRoot);
|
|
29
|
+
return {
|
|
30
|
+
tokenPrefix: furnace.tokenPrefix ?? fallback.tokenPrefix,
|
|
31
|
+
exempt: new Set([...(furnace.tokenAllowlist ?? []), ...(furnace.runtimeVariables ?? [])]),
|
|
32
|
+
siblingSheets: furnace.tokenSiblingSheets,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
warn(`Could not load furnace.json; using the default token prefix (${toError(error).message})`);
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function collectTokenLint(projectRoot) {
|
|
41
|
+
const paths = getProjectPaths(projectRoot);
|
|
42
|
+
const config = await loadConfig(projectRoot);
|
|
43
|
+
const tokensCssPath = getTokensCssPath(config.binaryName);
|
|
44
|
+
const tokensFile = join(paths.engine, tokensCssPath);
|
|
45
|
+
if (!(await pathExists(tokensFile))) {
|
|
46
|
+
throw new GeneralError(`Token CSS file not found: ${tokensCssPath}. Run "fireforge furnace init" to seed it.`);
|
|
47
|
+
}
|
|
48
|
+
const { tokenPrefix, exempt, siblingSheets } = await loadTokenSettings(projectRoot, config.binaryName);
|
|
49
|
+
const tokensCss = await readText(tokensFile);
|
|
50
|
+
const lines = tokensCss.split('\n');
|
|
51
|
+
const declared = collectDeclaredTokenNames(lines);
|
|
52
|
+
const findings = [
|
|
53
|
+
...lintTokenLockstep(lines, tokensCssPath, tokenPrefix),
|
|
54
|
+
...lintUndeclaredTokenReferences(tokensCssPath, tokensCss, tokenPrefix, declared, exempt),
|
|
55
|
+
];
|
|
56
|
+
// Every CSS file the patch queue touches: the fork's own stylesheets and
|
|
57
|
+
// the upstream ones it edits.
|
|
58
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
59
|
+
const touched = [...new Set((manifest?.patches ?? []).flatMap((patch) => patch.filesAffected))];
|
|
60
|
+
const cssFiles = touched.filter((file) => file.endsWith('.css') && file !== tokensCssPath).sort();
|
|
61
|
+
const contents = await readEngineFiles(paths.engine, touched.filter((file) => LINKING_FILE.test(file)));
|
|
62
|
+
const siblings = buildSiblingSheetMap({
|
|
63
|
+
documents: contents,
|
|
64
|
+
jarTargets: buildJarTargetIndex((await buildPatchQueueContext(paths.patches)).entries),
|
|
65
|
+
sheets: cssFiles,
|
|
66
|
+
explicit: siblingSheets,
|
|
67
|
+
});
|
|
68
|
+
let filesScanned = 1;
|
|
69
|
+
for (const file of cssFiles) {
|
|
70
|
+
const css = contents.get(file);
|
|
71
|
+
if (css === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
filesScanned++;
|
|
74
|
+
findings.push(...lintUndeclaredTokenReferences(file, css, tokenPrefix, await withSiblingDeclarations(paths.engine, declared, siblings.get(file), contents), exempt));
|
|
75
|
+
}
|
|
76
|
+
return { tokensCssPath, tokenPrefix, filesScanned, findings };
|
|
77
|
+
}
|
|
78
|
+
/** Files whose content can link a stylesheet: markup, scripts, and sheets (`@import`). */
|
|
79
|
+
const LINKING_FILE = /\.(?:css|xhtml|html|xml|mjs|js)$/;
|
|
80
|
+
/** Reads the engine files that exist, keyed by engine-relative path. */
|
|
81
|
+
async function readEngineFiles(engineDir, files) {
|
|
82
|
+
const contents = new Map();
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
const absolute = join(engineDir, file);
|
|
85
|
+
if (await pathExists(absolute))
|
|
86
|
+
contents.set(file, await readText(absolute));
|
|
87
|
+
}
|
|
88
|
+
return contents;
|
|
89
|
+
}
|
|
90
|
+
/** The tokens CSS declarations plus every declaration of a sheet's siblings. */
|
|
91
|
+
async function withSiblingDeclarations(engineDir, declared, siblings, contents) {
|
|
92
|
+
if (siblings === undefined || siblings.size === 0)
|
|
93
|
+
return declared;
|
|
94
|
+
const merged = new Set(declared);
|
|
95
|
+
for (const sibling of siblings) {
|
|
96
|
+
if (!contents.has(sibling)) {
|
|
97
|
+
const absolute = join(engineDir, sibling);
|
|
98
|
+
if (!(await pathExists(absolute)))
|
|
99
|
+
continue;
|
|
100
|
+
contents.set(sibling, await readText(absolute));
|
|
101
|
+
}
|
|
102
|
+
const css = contents.get(sibling) ?? '';
|
|
103
|
+
for (const name of collectDeclaredTokenNames(css.split('\n')))
|
|
104
|
+
merged.add(name);
|
|
105
|
+
}
|
|
106
|
+
return merged;
|
|
107
|
+
}
|
|
108
|
+
function failureMessage(count) {
|
|
109
|
+
return `token lint found ${String(count)} finding(s).`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Runs `token lint`. Exits non-zero (GeneralError) when any finding is
|
|
113
|
+
* reported; with `--json` the report is written first, as `status --check
|
|
114
|
+
* --json` does.
|
|
115
|
+
*
|
|
116
|
+
* @param projectRoot - Root directory of the project
|
|
117
|
+
* @param options - Output-mode options
|
|
118
|
+
*/
|
|
119
|
+
export async function tokenLintCommand(projectRoot, options = {}) {
|
|
120
|
+
if (options.json === true) {
|
|
121
|
+
setMachineOutputMode(true);
|
|
122
|
+
let findings = 0;
|
|
123
|
+
try {
|
|
124
|
+
const report = await collectTokenLint(projectRoot);
|
|
125
|
+
findings = report.findings.length;
|
|
126
|
+
process.stdout.write(`${JSON.stringify({ schemaVersion: MACHINE_OUTPUT_SCHEMA_VERSION, ...report }, null, 2)}\n`);
|
|
127
|
+
setStdoutSealed(true);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
emitMachineError('token-lint-failed', toError(error).message);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
setMachineOutputMode(false);
|
|
134
|
+
}
|
|
135
|
+
if (findings > 0)
|
|
136
|
+
throw new GeneralError(failureMessage(findings));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
intro('Token Lint');
|
|
140
|
+
const report = await collectTokenLint(projectRoot);
|
|
141
|
+
for (const finding of report.findings) {
|
|
142
|
+
warn(`${finding.file}:${String(finding.line)} [${finding.check}] ${finding.message}`);
|
|
143
|
+
}
|
|
144
|
+
info(`${String(report.filesScanned)} CSS file(s) scanned for ${report.tokenPrefix}* tokens`);
|
|
145
|
+
if (report.findings.length > 0) {
|
|
146
|
+
outro('Token lint failed');
|
|
147
|
+
throw new GeneralError(failureMessage(report.findings.length));
|
|
148
|
+
}
|
|
149
|
+
success('Themed blocks are in lockstep with the base block; every token reference resolves');
|
|
150
|
+
outro('Token lint passed');
|
|
151
|
+
}
|
|
@@ -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
|
*
|
|
@@ -21,16 +21,18 @@ import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
|
|
|
21
21
|
import { isGitRepository } from '../core/git.js';
|
|
22
22
|
import { withPrivateGitIndex } from '../core/git-readonly-index.js';
|
|
23
23
|
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from '../core/git-status.js';
|
|
24
|
-
import { findUnresolvedSystemModuleImports, formatUnresolvedSystemModuleImports, } from '../core/module-resolution-preflight.js';
|
|
24
|
+
import { collectWorktreeModuleFiles, findUnresolvedSystemModuleImports, formatUnresolvedSystemModuleImports, } from '../core/module-resolution-preflight.js';
|
|
25
25
|
import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
|
|
26
26
|
import { loadPatchesManifest, validatePatchesManifestConsistency } from '../core/patch-manifest.js';
|
|
27
27
|
import { evaluatePatchPolicy } from '../core/patch-policy.js';
|
|
28
|
+
import { evaluatePatchPolicyContentForQueue } from '../core/patch-policy-content.js';
|
|
28
29
|
import { collectPatchRegistrationReferences } from '../core/patch-registration-refs.js';
|
|
29
30
|
import { buildPatchClaims } from '../core/status-classify.js';
|
|
30
31
|
import { classifyFiles } from '../core/status-classify.js';
|
|
31
32
|
import { GeneralError } from '../errors/base.js';
|
|
32
33
|
import { pathExists, readText } from '../utils/fs.js';
|
|
33
34
|
import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
35
|
+
import { anyPatchInScope, isFilenameInScope, resolveVerifyScope, scopeLintIssues, scopePatches, } from './verify-scope.js';
|
|
34
36
|
/**
|
|
35
37
|
* Walks each patch body in the manifest, extracts the set of
|
|
36
38
|
* component-shaped registration references it adds (widget paths
|
|
@@ -41,7 +43,7 @@ import { info, intro, outro, success, warn } from '../utils/logger.js';
|
|
|
41
43
|
* registers a file that nothing in the world supplies, which fails at
|
|
42
44
|
* install time.
|
|
43
45
|
*/
|
|
44
|
-
async function detectDanglingRegistrations(patchesDir, engineDir, patches) {
|
|
46
|
+
async function detectDanglingRegistrations(patchesDir, engineDir, patches, scope) {
|
|
45
47
|
// Aggregate the set of all paths any patch in the queue is responsible for
|
|
46
48
|
// (per `filesAffected`). Individual patch bodies are deliberately not
|
|
47
49
|
// parsed for new-file creations here: `filesAffected` is already the
|
|
@@ -54,8 +56,9 @@ async function detectDanglingRegistrations(patchesDir, engineDir, patches) {
|
|
|
54
56
|
coveredByPatches.add(file);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
59
|
+
// Every patch supplies targets; only the scoped ones are judged.
|
|
57
60
|
const issues = [];
|
|
58
|
-
for (const patch of patches) {
|
|
61
|
+
for (const patch of scopePatches(patches, scope)) {
|
|
59
62
|
const patchPath = join(patchesDir, patch.filename);
|
|
60
63
|
if (!(await pathExists(patchPath)))
|
|
61
64
|
continue;
|
|
@@ -100,7 +103,7 @@ function detectCrossPatchFileClaims(manifestPatches) {
|
|
|
100
103
|
}
|
|
101
104
|
return results;
|
|
102
105
|
}
|
|
103
|
-
async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir, binaryName) {
|
|
106
|
+
async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir, binaryName, scope) {
|
|
104
107
|
if (!(await pathExists(engineDir)) || !(await isGitRepository(engineDir))) {
|
|
105
108
|
return { unowned: [], patchOwnedDrift: [] };
|
|
106
109
|
}
|
|
@@ -108,24 +111,51 @@ async function detectWorktreeOwnershipDrift(projectRoot, engineDir, patchesDir,
|
|
|
108
111
|
const furnacePrefixes = await collectFurnaceManagedPrefixes(projectRoot);
|
|
109
112
|
const classified = await classifyFiles(entries, engineDir, patchesDir, binaryName, furnacePrefixes);
|
|
110
113
|
return {
|
|
114
|
+
// An unowned change belongs to no patch, so no --patches scope covers it.
|
|
111
115
|
unowned: [
|
|
112
116
|
...new Set(classified
|
|
113
|
-
.filter((entry) => entry.classification === 'unmanaged')
|
|
117
|
+
.filter((entry) => scope === undefined && entry.classification === 'unmanaged')
|
|
114
118
|
.map((entry) => entry.file)),
|
|
115
119
|
].sort(),
|
|
116
120
|
patchOwnedDrift: [
|
|
117
121
|
...new Set(classified
|
|
118
|
-
.filter((entry) => entry.classification === 'patch-owned-drift'
|
|
122
|
+
.filter((entry) => entry.classification === 'patch-owned-drift' &&
|
|
123
|
+
anyPatchInScope(scope, entry.owner === undefined ? [] : [entry.owner]))
|
|
119
124
|
.map((entry) => entry.file)),
|
|
120
125
|
].sort(),
|
|
121
126
|
};
|
|
122
127
|
}
|
|
128
|
+
/** The warning groups for unowned changes and patch-owned drift. */
|
|
129
|
+
function buildWorktreeDriftGroups(drift) {
|
|
130
|
+
const groups = [];
|
|
131
|
+
if (drift.unowned.length > 0) {
|
|
132
|
+
groups.push({
|
|
133
|
+
title: `Unowned worktree changes (${drift.unowned.length})`,
|
|
134
|
+
issues: drift.unowned.map((file) => `${file} is changed in engine/ but is not listed in any patch filesAffected entry`),
|
|
135
|
+
errorCount: 0,
|
|
136
|
+
warningCount: drift.unowned.length,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (drift.patchOwnedDrift.length > 0) {
|
|
140
|
+
groups.push({
|
|
141
|
+
title: `Patch-owned worktree drift (${drift.patchOwnedDrift.length})`,
|
|
142
|
+
issues: drift.patchOwnedDrift.map((file) => `${file} is claimed by exactly one patch, but engine/ no longer matches that patch output`),
|
|
143
|
+
errorCount: 0,
|
|
144
|
+
warningCount: drift.patchOwnedDrift.length,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return groups;
|
|
148
|
+
}
|
|
123
149
|
/**
|
|
124
150
|
* Collects the same queue-health findings reported by `fireforge verify`
|
|
125
151
|
* without printing. Used by doctor recovery paths that need a read-only
|
|
126
152
|
* "is this queue healthy?" decision before clearing stale state.
|
|
153
|
+
*
|
|
154
|
+
* @param projectRoot - Project root directory
|
|
155
|
+
* @param scope - `verify --patches`: keep only the findings that name these
|
|
156
|
+
* patches (see verify-scope.ts). Omitted, every finding counts.
|
|
127
157
|
*/
|
|
128
|
-
export async function collectPatchQueueHealth(projectRoot) {
|
|
158
|
+
export async function collectPatchQueueHealth(projectRoot, scope) {
|
|
129
159
|
const paths = getProjectPaths(projectRoot);
|
|
130
160
|
const config = await loadConfig(projectRoot);
|
|
131
161
|
if (!(await pathExists(paths.patches))) {
|
|
@@ -139,7 +169,7 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
139
169
|
const groups = [];
|
|
140
170
|
let errorCount = 0;
|
|
141
171
|
let warningCount = 0;
|
|
142
|
-
const consistencyIssues = await validatePatchesManifestConsistency(paths.patches);
|
|
172
|
+
const consistencyIssues = (await validatePatchesManifestConsistency(paths.patches)).filter((issue) => isFilenameInScope(scope, issue.filename));
|
|
143
173
|
if (consistencyIssues.length > 0) {
|
|
144
174
|
const issues = consistencyIssues.map((issue) => `[${issue.code}] ${issue.message}`);
|
|
145
175
|
groups.push({
|
|
@@ -152,7 +182,10 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
152
182
|
}
|
|
153
183
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
154
184
|
if (manifest) {
|
|
155
|
-
const policyIssues =
|
|
185
|
+
const policyIssues = [
|
|
186
|
+
...evaluatePatchPolicy(config, manifest),
|
|
187
|
+
...(await evaluatePatchPolicyContentForQueue(config, projectRoot, paths.patches, manifest)),
|
|
188
|
+
].filter((issue) => isFilenameInScope(scope, issue.filename));
|
|
156
189
|
if (policyIssues.length > 0) {
|
|
157
190
|
const policyErrors = policyIssues.filter((issue) => issue.severity === 'error').length;
|
|
158
191
|
const policyWarnings = policyIssues.length - policyErrors;
|
|
@@ -173,8 +206,15 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
173
206
|
// CREATES. This asks the same question of the engine, so an import ADDED
|
|
174
207
|
// to an existing module is named as text instead of surfacing later as a
|
|
175
208
|
// bare `xpcshell return code: -11` with zero output.
|
|
209
|
+
// Unscoped, the engine worktree joins in: an unexported chrome script
|
|
210
|
+
// importing an unexported, unregistered module crashes every objdir
|
|
211
|
+
// start before either file is in the queue. A scoped run never judges
|
|
212
|
+
// unmanaged files.
|
|
176
213
|
if (await pathExists(paths.engine)) {
|
|
177
|
-
const
|
|
214
|
+
const worktree = scope === undefined && (await isGitRepository(paths.engine))
|
|
215
|
+
? await collectWorktreeModuleFiles(paths.engine)
|
|
216
|
+
: undefined;
|
|
217
|
+
const unresolved = await findUnresolvedSystemModuleImports(paths.engine, scopePatches(manifest.patches, scope).flatMap((patch) => patch.filesAffected), worktree);
|
|
178
218
|
if (unresolved.length > 0) {
|
|
179
219
|
groups.push({
|
|
180
220
|
title: `Unresolvable system-module imports (${unresolved.length})`,
|
|
@@ -185,7 +225,7 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
185
225
|
errorCount += unresolved.length;
|
|
186
226
|
}
|
|
187
227
|
}
|
|
188
|
-
const crossClaims = detectCrossPatchFileClaims(manifest.patches);
|
|
228
|
+
const crossClaims = detectCrossPatchFileClaims(manifest.patches).filter((claim) => anyPatchInScope(scope, claim.filenames));
|
|
189
229
|
if (crossClaims.length > 0) {
|
|
190
230
|
groups.push({
|
|
191
231
|
title: `Cross-patch filesAffected conflicts (${crossClaims.length})`,
|
|
@@ -197,7 +237,7 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
197
237
|
}
|
|
198
238
|
}
|
|
199
239
|
const ctx = await buildPatchQueueContext(paths.patches);
|
|
200
|
-
const lintIssues = lintPatchQueue(ctx);
|
|
240
|
+
const lintIssues = scopeLintIssues(lintPatchQueue(ctx), scope);
|
|
201
241
|
if (lintIssues.length > 0) {
|
|
202
242
|
const lintErrors = lintIssues.filter((issue) => issue.severity === 'error').length;
|
|
203
243
|
const lintWarnings = lintIssues.filter((issue) => issue.severity === 'warning').length;
|
|
@@ -214,26 +254,11 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
214
254
|
warningCount += lintWarnings;
|
|
215
255
|
}
|
|
216
256
|
if (manifest) {
|
|
217
|
-
const worktreeDrift = await detectWorktreeOwnershipDrift(projectRoot, paths.engine, paths.patches, config.binaryName);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
errorCount: 0,
|
|
223
|
-
warningCount: worktreeDrift.unowned.length,
|
|
224
|
-
});
|
|
225
|
-
warningCount += worktreeDrift.unowned.length;
|
|
226
|
-
}
|
|
227
|
-
if (worktreeDrift.patchOwnedDrift.length > 0) {
|
|
228
|
-
groups.push({
|
|
229
|
-
title: `Patch-owned worktree drift (${worktreeDrift.patchOwnedDrift.length})`,
|
|
230
|
-
issues: worktreeDrift.patchOwnedDrift.map((file) => `${file} is claimed by exactly one patch, but engine/ no longer matches that patch output`),
|
|
231
|
-
errorCount: 0,
|
|
232
|
-
warningCount: worktreeDrift.patchOwnedDrift.length,
|
|
233
|
-
});
|
|
234
|
-
warningCount += worktreeDrift.patchOwnedDrift.length;
|
|
235
|
-
}
|
|
236
|
-
const registrationIssues = await detectDanglingRegistrations(paths.patches, paths.engine, manifest.patches);
|
|
257
|
+
const worktreeDrift = await detectWorktreeOwnershipDrift(projectRoot, paths.engine, paths.patches, config.binaryName, scope);
|
|
258
|
+
const driftGroups = buildWorktreeDriftGroups(worktreeDrift);
|
|
259
|
+
groups.push(...driftGroups);
|
|
260
|
+
warningCount += driftGroups.reduce((sum, group) => sum + group.warningCount, 0);
|
|
261
|
+
const registrationIssues = await detectDanglingRegistrations(paths.patches, paths.engine, manifest.patches, scope);
|
|
237
262
|
if (registrationIssues.length > 0) {
|
|
238
263
|
groups.push({
|
|
239
264
|
title: `Dangling registration references (${registrationIssues.length})`,
|
|
@@ -256,15 +281,20 @@ export async function collectPatchQueueHealth(projectRoot) {
|
|
|
256
281
|
* Read-only. Exits non-zero on any error-severity finding.
|
|
257
282
|
*
|
|
258
283
|
* @param projectRoot - Project root directory
|
|
284
|
+
* @param options - `patches`: the raw `--patches` values
|
|
259
285
|
*/
|
|
260
|
-
export async function verifyCommand(projectRoot) {
|
|
286
|
+
export async function verifyCommand(projectRoot, options = {}) {
|
|
261
287
|
intro('FireForge Verify');
|
|
288
|
+
const scope = await resolveVerifyScope(getProjectPaths(projectRoot).patches, options.patches);
|
|
289
|
+
if (scope !== undefined) {
|
|
290
|
+
info(`Judging only: ${[...scope.patches].join(', ')}`);
|
|
291
|
+
}
|
|
262
292
|
// `verify` is read-only to the OPERATOR but not to git: the working-tree
|
|
263
293
|
// status probe refreshes (and rewrites) `engine/.git/index`, making this
|
|
264
294
|
// command a second writer of the primary checkout and invalidating a
|
|
265
295
|
// concurrent `fireforge test`'s engine fingerprint. A private index keeps
|
|
266
296
|
// every refresh off the primary checkout.
|
|
267
|
-
const health = await withPrivateGitIndex(getProjectPaths(projectRoot).engine, () => collectPatchQueueHealth(projectRoot));
|
|
297
|
+
const health = await withPrivateGitIndex(getProjectPaths(projectRoot).engine, () => collectPatchQueueHealth(projectRoot, scope));
|
|
268
298
|
if (!health.hasPatchesDirectory) {
|
|
269
299
|
info('No patches directory. Nothing to verify.');
|
|
270
300
|
outro('Verify clean');
|
|
@@ -298,7 +328,8 @@ export function registerVerify(program, { getProjectRoot, withErrorHandling }) {
|
|
|
298
328
|
program
|
|
299
329
|
.command('verify')
|
|
300
330
|
.description('Read-only fsck for the patch queue (manifest + cross-patch lint)')
|
|
301
|
-
.
|
|
302
|
-
|
|
331
|
+
.option('--patches <names...>', 'Judge only findings that name these patches (filename, stem, order number, manifest name, or slug; repeat or comma-separate). Queue-wide findings stay; unowned worktree changes drop out')
|
|
332
|
+
.action(withErrorHandling(async (options) => {
|
|
333
|
+
await verifyCommand(getProjectRoot(), options);
|
|
303
334
|
}));
|
|
304
335
|
}
|
|
@@ -95,8 +95,28 @@ export interface BuildBaseline {
|
|
|
95
95
|
* replaces coverage with a dropped-path notice, or refuses when the caller
|
|
96
96
|
* required --extend-coverage. Legacy packageable-only anchors cannot retain
|
|
97
97
|
* coverage because they cannot prove test scripts and fixtures unchanged.
|
|
98
|
+
*
|
|
99
|
+
* Since 0.50.0 retention is decided per staged path: see
|
|
100
|
+
* {@link BuildBaseline.testCoverageRecords} and
|
|
101
|
+
* {@link BuildBaseline.fullCoverageAnchor}.
|
|
98
102
|
*/
|
|
99
103
|
testPackagingCoverage?: TestPackagingCoverage;
|
|
104
|
+
/**
|
|
105
|
+
* Per-path anchors of a scoped claim, keyed by the request path in
|
|
106
|
+
* {@link BuildBaseline.testPackagingCoverage}. Each record holds that
|
|
107
|
+
* path's own staging inputs and their content at the build that staged
|
|
108
|
+
* it, so a later scoped build keeps the path while those inputs are
|
|
109
|
+
* unchanged, whatever moved elsewhere in the engine. A listed path without
|
|
110
|
+
* a record falls back to the baseline-wide anchor. Absent on full claims.
|
|
111
|
+
*/
|
|
112
|
+
testCoverageRecords?: Record<string, CoverageRecord>;
|
|
113
|
+
/**
|
|
114
|
+
* The anchor of the last full-coverage build, carried forward by scoped
|
|
115
|
+
* builds once something outside their scope moved. A request path outside
|
|
116
|
+
* the scoped list stays covered while its own staging inputs are unchanged
|
|
117
|
+
* against this anchor. The check runs at dispatch, per requested path.
|
|
118
|
+
*/
|
|
119
|
+
fullCoverageAnchor?: CoverageAnchor;
|
|
100
120
|
/**
|
|
101
121
|
* Hex-encoded SHA-256 of `engine/mozconfig` as it stood for this build.
|
|
102
122
|
* Recorded because the mozconfig is regenerated from project-side
|
|
@@ -141,5 +161,31 @@ export interface StaticComponentsBaseline {
|
|
|
141
161
|
*/
|
|
142
162
|
fingerprints: Record<string, string>;
|
|
143
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Content anchor of one packaging claim. A path's staged content is
|
|
166
|
+
* `fingerprints[path]` when recorded, otherwise its content at
|
|
167
|
+
* `engineHeadSha`.
|
|
168
|
+
*/
|
|
169
|
+
export interface CoverageAnchor {
|
|
170
|
+
/** Engine HEAD the staged content is relative to. */
|
|
171
|
+
engineHeadSha: string;
|
|
172
|
+
/** Hex SHA-256 of `engine/mozconfig` the claim was configured with. */
|
|
173
|
+
mozconfigHash: string;
|
|
174
|
+
/**
|
|
175
|
+
* Hex SHA-256 (or {@link DELETED_FILE_FINGERPRINT}) per engine-relative
|
|
176
|
+
* path whose staged content differs from `engineHeadSha`.
|
|
177
|
+
*/
|
|
178
|
+
fingerprints: Record<string, string>;
|
|
179
|
+
}
|
|
180
|
+
/** Anchor of one staged request path. See {@link BuildBaseline.testCoverageRecords}. */
|
|
181
|
+
export interface CoverageRecord extends CoverageAnchor {
|
|
182
|
+
/**
|
|
183
|
+
* Engine-relative paths (files or directories) whose content decides what
|
|
184
|
+
* the harness stages for the request path: its manifest directory plus
|
|
185
|
+
* whatever a manifest there names outside it. `fingerprints` is
|
|
186
|
+
* restricted to these.
|
|
187
|
+
*/
|
|
188
|
+
inputs: string[];
|
|
189
|
+
}
|
|
144
190
|
/** Coverage claim of the packaged test runtime. See {@link BuildBaseline.testPackagingCoverage}. */
|
|
145
191
|
export type TestPackagingCoverage = 'full' | string[];
|
|
@@ -18,6 +18,20 @@ function isFingerprintMap(value) {
|
|
|
18
18
|
return (isObject(value) &&
|
|
19
19
|
Object.entries(value).every(([path, fingerprint]) => isBaselinePath(path) && isFingerprint(fingerprint)));
|
|
20
20
|
}
|
|
21
|
+
function isCoverageAnchor(value) {
|
|
22
|
+
return (isObject(value) &&
|
|
23
|
+
typeof value['engineHeadSha'] === 'string' &&
|
|
24
|
+
isFingerprint(value['mozconfigHash']) &&
|
|
25
|
+
isFingerprintMap(value['fingerprints']));
|
|
26
|
+
}
|
|
27
|
+
function isCoverageRecordMap(value) {
|
|
28
|
+
return (isObject(value) &&
|
|
29
|
+
Object.entries(value).every(([path, record]) => isBaselinePath(path) &&
|
|
30
|
+
isCoverageAnchor(record) &&
|
|
31
|
+
isObject(record) &&
|
|
32
|
+
Array.isArray(record['inputs']) &&
|
|
33
|
+
record['inputs'].every(isBaselinePath)));
|
|
34
|
+
}
|
|
21
35
|
/** Validates persisted fields without converting malformed coverage into a full claim. */
|
|
22
36
|
export function isBuildBaseline(value) {
|
|
23
37
|
if (!isObject(value))
|
|
@@ -45,6 +59,11 @@ export function isBuildBaseline(value) {
|
|
|
45
59
|
return false;
|
|
46
60
|
if (value['recordedBy'] !== undefined && typeof value['recordedBy'] !== 'string')
|
|
47
61
|
return false;
|
|
62
|
+
if (value['testCoverageRecords'] !== undefined &&
|
|
63
|
+
!isCoverageRecordMap(value['testCoverageRecords']))
|
|
64
|
+
return false;
|
|
65
|
+
if (value['fullCoverageAnchor'] !== undefined && !isCoverageAnchor(value['fullCoverageAnchor']))
|
|
66
|
+
return false;
|
|
48
67
|
const components = value['staticComponentsBaseline'];
|
|
49
68
|
return (components === undefined ||
|
|
50
69
|
(isObject(components) &&
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* on successful build completion. A failed build does not update it, so a
|
|
16
16
|
* subsequent run still audits against the last known-good tree.
|
|
17
17
|
*/
|
|
18
|
-
import { type BuildBaseline, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
18
|
+
import { type BuildBaseline, type CoverageAnchor, type CoverageRecord, type TestPackagingCoverage } from './build-baseline-types.js';
|
|
19
19
|
/**
|
|
20
20
|
* Which mach build produced the baseline being written. Decides whether
|
|
21
21
|
* the `jar.mn` half of {@link BuildBaseline.buildInputFingerprints} is
|
|
@@ -52,6 +52,10 @@ export interface WriteBuildBaselineOptions {
|
|
|
52
52
|
* Omitted → field left off the marker.
|
|
53
53
|
*/
|
|
54
54
|
testPackagingCoverage?: TestPackagingCoverage | undefined;
|
|
55
|
+
/** Per-path anchors of a scoped claim. Omitted → field left off the marker. */
|
|
56
|
+
testCoverageRecords?: Record<string, CoverageRecord> | undefined;
|
|
57
|
+
/** Carried full-build anchor of a narrowed claim. Omitted → field left off. */
|
|
58
|
+
fullCoverageAnchor?: CoverageAnchor | undefined;
|
|
55
59
|
/**
|
|
56
60
|
* The baseline this write replaces, when the caller has it. A scoped
|
|
57
61
|
* (non-`'full'`) write carries its `staticComponentsBaseline` forward
|