@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
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { pathExists, readText } from '../utils/fs.js';
|
|
4
5
|
import { createIssue } from './furnace-validate-helpers.js';
|
|
6
|
+
import { countUnlabelledIconButtons } from './furnace-validate-icon-button.js';
|
|
5
7
|
import { collectLitTemplateRegions, parseTemplateAttributes } from './lit-template-regions.js';
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* Reads the composed component: `<tagName>.mjs` followed by its companion
|
|
10
|
+
* modules (`<tagName>-*.mjs`, tests and stories left out), so a template's
|
|
11
|
+
* findings do not depend on which of the component's modules it lives in.
|
|
12
|
+
* Returns undefined when the host module is missing.
|
|
13
|
+
*/
|
|
14
|
+
async function readComposedComponentSource(componentDir, tagName) {
|
|
15
|
+
const hostPath = join(componentDir, `${tagName}.mjs`);
|
|
16
|
+
if (!(await pathExists(hostPath)))
|
|
17
|
+
return undefined;
|
|
18
|
+
const entries = await readdir(componentDir).catch(() => []);
|
|
19
|
+
const companions = entries
|
|
20
|
+
.filter((name) => name.startsWith(`${tagName}-`) &&
|
|
21
|
+
name.endsWith('.mjs') &&
|
|
22
|
+
!/\.(test|spec|stories)\./.test(name))
|
|
23
|
+
.sort();
|
|
24
|
+
const sources = [await readText(hostPath)];
|
|
25
|
+
for (const name of companions)
|
|
26
|
+
sources.push(await readText(join(componentDir, name)));
|
|
27
|
+
return sources.join('\n');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates accessibility patterns in a component's composed source: its
|
|
31
|
+
* host module and every companion module (see
|
|
32
|
+
* {@link readComposedComponentSource}). Checks for ARIA roles, keyboard
|
|
33
|
+
* handlers, l10n, and focus delegation.
|
|
9
34
|
*
|
|
10
35
|
* @param componentDir - Directory holding the component's sources
|
|
11
36
|
* @param tagName - Custom element tag name, used to locate `<tagName>.mjs`
|
|
@@ -15,12 +40,13 @@ import { collectLitTemplateRegions, parseTemplateAttributes } from './lit-templa
|
|
|
15
40
|
* (`keyboardCovered: true`) or via `composes` naming a native-interactive
|
|
16
41
|
* inner element. Optional so stock/override callers and test fixtures
|
|
17
42
|
* without config in scope can continue to call without changes.
|
|
43
|
+
* @param optInChecks - `furnace.json#optInChecks`. `icon-button-label`
|
|
44
|
+
* adds the icon-only `<moz-button>` naming check.
|
|
18
45
|
*/
|
|
19
|
-
export async function validateAccessibility(componentDir, tagName, customConfig) {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
46
|
+
export async function validateAccessibility(componentDir, tagName, customConfig, optInChecks = []) {
|
|
47
|
+
const content = await readComposedComponentSource(componentDir, tagName);
|
|
48
|
+
if (content === undefined)
|
|
22
49
|
return [];
|
|
23
|
-
const content = await readText(mjsPath);
|
|
24
50
|
const issues = [];
|
|
25
51
|
if (!hasAriaRole(content) && hasGenericInteractiveElement(content)) {
|
|
26
52
|
issues.push(createIssue(tagName, 'warning', 'no-aria-role', 'Generic interactive markup has no native semantics. Prefer native elements, or add role= when native markup cannot provide the semantics.'));
|
|
@@ -38,10 +64,13 @@ export async function validateAccessibility(componentDir, tagName, customConfig)
|
|
|
38
64
|
// activation flows through the inner element and a duplicate handler on
|
|
39
65
|
// the wrapper would either no-op or fire twice alongside the child's
|
|
40
66
|
// built-in keyboard path.
|
|
41
|
-
const
|
|
67
|
+
const syntheticTags = syntheticClickTags(content);
|
|
42
68
|
const keyboardCovered = isKeyboardCoveredByComposition(customConfig);
|
|
43
|
-
if (
|
|
44
|
-
issues.push(
|
|
69
|
+
if (syntheticTags.length > 0 && !hasKeyboardHandler && !keyboardCovered) {
|
|
70
|
+
issues.push({
|
|
71
|
+
...createIssue(tagName, 'warning', 'no-keyboard-handler', `Interactive element has @click but no keyboard event handler (@keydown/@keypress/@keyup), on ${syntheticTags.map((tag) => `<${tag}>`).join(', ')}.`),
|
|
72
|
+
tags: syntheticTags,
|
|
73
|
+
});
|
|
45
74
|
}
|
|
46
75
|
if (containsHardcodedTemplateText(content)) {
|
|
47
76
|
issues.push(createIssue(tagName, 'warning', 'hardcoded-text', 'Possible hardcoded string found. Use data-l10n-id for localization.'));
|
|
@@ -56,6 +85,12 @@ export async function validateAccessibility(componentDir, tagName, customConfig)
|
|
|
56
85
|
if (hasUnlabelledFormInput(content)) {
|
|
57
86
|
issues.push(createIssue(tagName, 'warning', 'unlabelled-form-input', 'Form input without an accessible label. Add aria-label, aria-labelledby, or an associated <label> element.'));
|
|
58
87
|
}
|
|
88
|
+
if (optInChecks.includes('icon-button-label')) {
|
|
89
|
+
const unlabelled = countUnlabelledIconButtons(content);
|
|
90
|
+
if (unlabelled > 0) {
|
|
91
|
+
issues.push(createIssue(tagName, 'warning', 'icon-button-label', `${unlabelled} icon-only <moz-button> without an accessible name. Add data-l10n-id (with the label in its Fluent message) or aria-label.`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
59
94
|
return issues;
|
|
60
95
|
}
|
|
61
96
|
/** Detects whether template or script content assigns an ARIA role. */
|
|
@@ -155,6 +190,9 @@ const NATIVE_CLICK_INTERACTIVE_TAGS = new Set([
|
|
|
155
190
|
'moz-radio',
|
|
156
191
|
'moz-radio-group',
|
|
157
192
|
'moz-menulist',
|
|
193
|
+
// Toolkit's panel-list row renders a button that takes Enter and Space
|
|
194
|
+
// and dispatches click.
|
|
195
|
+
'panel-item',
|
|
158
196
|
]);
|
|
159
197
|
/**
|
|
160
198
|
* Returns true when `customConfig` declares that the component's keyboard
|
|
@@ -176,28 +214,33 @@ function isKeyboardCoveredByComposition(customConfig) {
|
|
|
176
214
|
return composes.some((tag) => NATIVE_CLICK_INTERACTIVE_TAGS.has(tag));
|
|
177
215
|
}
|
|
178
216
|
/**
|
|
179
|
-
* Returns
|
|
180
|
-
*
|
|
181
|
-
* keyboard activation and therefore needs an explicit key
|
|
182
|
-
* Enter/Space.
|
|
183
|
-
* interactive element. Those already fire `click` on keyboard
|
|
217
|
+
* Returns the tags (in first-seen order) of every *synthetic* interactive
|
|
218
|
+
* element carrying an `@click=${...}` handler (e.g. `<div @click>`), which
|
|
219
|
+
* lacks native keyboard activation and therefore needs an explicit key
|
|
220
|
+
* handler for Enter/Space. Empty when every `@click` handler sits on a
|
|
221
|
+
* native interactive element. Those already fire `click` on keyboard
|
|
222
|
+
* activation.
|
|
184
223
|
*/
|
|
185
|
-
function
|
|
224
|
+
function syntheticClickTags(content) {
|
|
186
225
|
const pattern = /@click\s*=\s*\$\{/g;
|
|
226
|
+
const tags = [];
|
|
187
227
|
let match;
|
|
188
228
|
while ((match = pattern.exec(content)) !== null) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
const tag = syntheticClickTag(content, match.index);
|
|
230
|
+
if (tag !== undefined && !tags.includes(tag))
|
|
231
|
+
tags.push(tag);
|
|
192
232
|
}
|
|
193
|
-
return
|
|
233
|
+
return tags;
|
|
194
234
|
}
|
|
235
|
+
/** The tag a synthetic-element `@click` sits on, `?` when it cannot be told. */
|
|
236
|
+
const UNKNOWN_TAG = '?';
|
|
195
237
|
/**
|
|
196
238
|
* Given the offset of an `@click=${...}` occurrence, walks backwards to find
|
|
197
|
-
* the opening `<tag` that owns it and
|
|
198
|
-
* interactive element (
|
|
239
|
+
* the opening `<tag` that owns it and returns that tag when it is a
|
|
240
|
+
* synthetic interactive element (warning needed), or undefined when it is a
|
|
241
|
+
* native one (no warning needed).
|
|
199
242
|
*/
|
|
200
|
-
function
|
|
243
|
+
function syntheticClickTag(content, clickIndex) {
|
|
201
244
|
// Find the nearest preceding `<` that starts a tag (skip `</` closers).
|
|
202
245
|
let i = clickIndex - 1;
|
|
203
246
|
let tagOpenIndex = -1;
|
|
@@ -210,15 +253,15 @@ function isClickOnSyntheticInteractive(content, clickIndex) {
|
|
|
210
253
|
// which shouldn't happen for a well-formed template but we defensively
|
|
211
254
|
// treat it as synthetic to preserve the prior-behaviour warning.
|
|
212
255
|
if (content[i] === '>') {
|
|
213
|
-
return
|
|
256
|
+
return UNKNOWN_TAG;
|
|
214
257
|
}
|
|
215
258
|
i--;
|
|
216
259
|
}
|
|
217
260
|
if (tagOpenIndex < 0)
|
|
218
|
-
return
|
|
261
|
+
return UNKNOWN_TAG;
|
|
219
262
|
const tagMatch = /^<([a-zA-Z][a-zA-Z0-9-]*)/.exec(content.slice(tagOpenIndex));
|
|
220
263
|
if (!tagMatch?.[1])
|
|
221
|
-
return
|
|
264
|
+
return UNKNOWN_TAG;
|
|
222
265
|
const tagName = tagMatch[1].toLowerCase();
|
|
223
266
|
if (tagName === 'a') {
|
|
224
267
|
// Bare <a> (no href) is non-interactive, so require a keyboard handler.
|
|
@@ -226,11 +269,11 @@ function isClickOnSyntheticInteractive(content, clickIndex) {
|
|
|
226
269
|
// attribute text in between for an href attribute.
|
|
227
270
|
const tagEnd = content.indexOf('>', tagOpenIndex);
|
|
228
271
|
if (tagEnd < 0)
|
|
229
|
-
return
|
|
272
|
+
return 'a';
|
|
230
273
|
const attrs = content.slice(tagOpenIndex, tagEnd);
|
|
231
|
-
return
|
|
274
|
+
return /\shref\s*=/.test(attrs) ? undefined : 'a';
|
|
232
275
|
}
|
|
233
|
-
return
|
|
276
|
+
return NATIVE_CLICK_INTERACTIVE_TAGS.has(tagName) ? undefined : tagName;
|
|
234
277
|
}
|
|
235
278
|
/** ASCII punctuation that reads as decoration rather than localizable text. */
|
|
236
279
|
const SYMBOL_ONLY_ASCII = '+-*=<>|/\\^~@#&!?%';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies `furnace.json#validationExceptions[]` to a `furnace validate`
|
|
3
|
+
* result. An exception suppresses every finding of its check on its
|
|
4
|
+
* component, or with `tag`, a finding whose element tags it (and its
|
|
5
|
+
* sibling tag entries) cover. One that suppresses nothing is stale: the
|
|
6
|
+
* finding was fixed (or renamed) and the review record outlived it, so it
|
|
7
|
+
* is reported as a warning naming the entry to delete, which keeps the
|
|
8
|
+
* list honest under `--max-warnings 0`.
|
|
9
|
+
*
|
|
10
|
+
* An exception never suppresses an error: an error blocks apply, and a
|
|
11
|
+
* review record cannot make a broken component deployable. The error stays
|
|
12
|
+
* counted and the matching entry is reported as
|
|
13
|
+
* `validation-exception-on-error`.
|
|
14
|
+
*/
|
|
15
|
+
import type { FurnaceValidationException, ValidationIssue } from '../types/furnace.js';
|
|
16
|
+
/** The check name of the warning a stale exception produces. */
|
|
17
|
+
export declare const STALE_VALIDATION_EXCEPTION_CHECK = "stale-validation-exception";
|
|
18
|
+
/** The check name of the error an exception matching an error produces. */
|
|
19
|
+
export declare const EXCEPTION_ON_ERROR_CHECK = "validation-exception-on-error";
|
|
20
|
+
/** A finding an exception suppressed, with the exception's reason. */
|
|
21
|
+
export interface ExceptedValidationIssue extends ValidationIssue {
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
/** {@link applyValidationExceptions}'s result. */
|
|
25
|
+
export interface ValidationExceptionOutcome {
|
|
26
|
+
/** Findings that still count, stale-exception warnings included. */
|
|
27
|
+
issues: ValidationIssue[];
|
|
28
|
+
/** Findings an exception suppressed. */
|
|
29
|
+
excepted: ExceptedValidationIssue[];
|
|
30
|
+
/** Exceptions that matched no finding of a component this run judged. */
|
|
31
|
+
stale: FurnaceValidationException[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Splits findings into counted and excepted, and reports stale exceptions.
|
|
35
|
+
*
|
|
36
|
+
* @param issues - Every finding of the run
|
|
37
|
+
* @param exceptions - `furnace.json#validationExceptions`
|
|
38
|
+
* @param judged - Whether the run judged an exception's component. A run
|
|
39
|
+
* scoped to one component cannot call another component's exception
|
|
40
|
+
* stale, while the full run judges them all (including components
|
|
41
|
+
* furnace.json no longer declares)
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyValidationExceptions(issues: readonly ValidationIssue[], exceptions: readonly FurnaceValidationException[] | undefined, judged: (component: string) => boolean): ValidationExceptionOutcome;
|
|
44
|
+
/** Counts error- and warning-severity findings. */
|
|
45
|
+
export declare function countValidationSeverities(issues: readonly ValidationIssue[]): {
|
|
46
|
+
errors: number;
|
|
47
|
+
warnings: number;
|
|
48
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createIssue } from './furnace-validate-helpers.js';
|
|
2
|
+
/** The check name of the warning a stale exception produces. */
|
|
3
|
+
export const STALE_VALIDATION_EXCEPTION_CHECK = 'stale-validation-exception';
|
|
4
|
+
/** The check name of the error an exception matching an error produces. */
|
|
5
|
+
export const EXCEPTION_ON_ERROR_CHECK = 'validation-exception-on-error';
|
|
6
|
+
function matches(exception, issue) {
|
|
7
|
+
return exception.component === issue.component && exception.check === issue.check;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The exceptions that suppress a warning: an untagged one alone, or tagged
|
|
11
|
+
* ones that together cover every tag the finding names.
|
|
12
|
+
*/
|
|
13
|
+
function coveringExceptions(issue, candidates) {
|
|
14
|
+
const untagged = candidates.find((entry) => entry.tag === undefined);
|
|
15
|
+
if (untagged !== undefined)
|
|
16
|
+
return [untagged];
|
|
17
|
+
const tags = issue.tags ?? [];
|
|
18
|
+
if (tags.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const covering = tags.map((tag) => candidates.find((entry) => entry.tag === tag));
|
|
21
|
+
return covering.every((entry) => entry !== undefined) ? [...new Set(covering)] : [];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Splits findings into counted and excepted, and reports stale exceptions.
|
|
25
|
+
*
|
|
26
|
+
* @param issues - Every finding of the run
|
|
27
|
+
* @param exceptions - `furnace.json#validationExceptions`
|
|
28
|
+
* @param judged - Whether the run judged an exception's component. A run
|
|
29
|
+
* scoped to one component cannot call another component's exception
|
|
30
|
+
* stale, while the full run judges them all (including components
|
|
31
|
+
* furnace.json no longer declares)
|
|
32
|
+
*/
|
|
33
|
+
export function applyValidationExceptions(issues, exceptions, judged) {
|
|
34
|
+
const outcome = { issues: [], excepted: [], stale: [] };
|
|
35
|
+
const list = exceptions ?? [];
|
|
36
|
+
const used = new Set();
|
|
37
|
+
const onError = new Set();
|
|
38
|
+
for (const issue of issues) {
|
|
39
|
+
const candidates = list.filter((entry) => matches(entry, issue));
|
|
40
|
+
if (issue.severity === 'error') {
|
|
41
|
+
for (const entry of candidates) {
|
|
42
|
+
used.add(entry);
|
|
43
|
+
onError.add(entry);
|
|
44
|
+
}
|
|
45
|
+
outcome.issues.push(issue);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const covering = coveringExceptions(issue, candidates);
|
|
49
|
+
if (covering.length === 0) {
|
|
50
|
+
// A tagged exception that covers some of the tags is not stale: its
|
|
51
|
+
// element still carries the finding, beside one it does not cover.
|
|
52
|
+
for (const entry of candidates) {
|
|
53
|
+
if (entry.tag !== undefined && issue.tags?.includes(entry.tag) === true)
|
|
54
|
+
used.add(entry);
|
|
55
|
+
}
|
|
56
|
+
outcome.issues.push(issue);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const entry of covering)
|
|
60
|
+
used.add(entry);
|
|
61
|
+
outcome.excepted.push({ ...issue, reason: covering.map((entry) => entry.reason).join('; ') });
|
|
62
|
+
}
|
|
63
|
+
for (const exception of onError) {
|
|
64
|
+
outcome.issues.push(createIssue(exception.component, 'error', EXCEPTION_ON_ERROR_CHECK, `furnace.json validationExceptions entry for [${exception.check}] matches an error, ` +
|
|
65
|
+
'which an exception cannot suppress. Fix the error, then delete the entry.'));
|
|
66
|
+
}
|
|
67
|
+
for (const exception of list) {
|
|
68
|
+
if (used.has(exception) || !judged(exception.component))
|
|
69
|
+
continue;
|
|
70
|
+
outcome.stale.push(exception);
|
|
71
|
+
outcome.issues.push(createIssue(exception.component, 'warning', STALE_VALIDATION_EXCEPTION_CHECK, `furnace.json validationExceptions entry for [${exception.check}] matches no finding. ` +
|
|
72
|
+
'Delete the entry: the finding it records is gone.'));
|
|
73
|
+
}
|
|
74
|
+
return outcome;
|
|
75
|
+
}
|
|
76
|
+
/** Counts error- and warning-severity findings. */
|
|
77
|
+
export function countValidationSeverities(issues) {
|
|
78
|
+
const errors = issues.filter((issue) => issue.severity === 'error').length;
|
|
79
|
+
return { errors, warnings: issues.length - errors };
|
|
80
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counts icon-only `<moz-button>` elements in the module's Lit templates
|
|
3
|
+
* that carry neither `data-l10n-id` nor `aria-label`.
|
|
4
|
+
*
|
|
5
|
+
* @param content - The component module's source
|
|
6
|
+
*/
|
|
7
|
+
export declare function countUnlabelledIconButtons(content: string): number;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The opt-in `icon-button-label` check (`furnace.json#optInChecks`): an
|
|
4
|
+
* icon-only `<moz-button>` has no visible text, so its accessible name has
|
|
5
|
+
* to come from `data-l10n-id` (whose FTL message carries the label or
|
|
6
|
+
* `.aria-label`) or from `aria-label`. Without one, screen readers announce
|
|
7
|
+
* just "button".
|
|
8
|
+
*
|
|
9
|
+
* Scans the Lit `html` templates of the component module only. A button
|
|
10
|
+
* built with `document.createElement` is not seen.
|
|
11
|
+
*/
|
|
12
|
+
import { collectLitTemplateRegions, parseTemplateAttributes } from './lit-template-regions.js';
|
|
13
|
+
/**
|
|
14
|
+
* Attributes (and their Lit property-binding spellings) that give the
|
|
15
|
+
* button an accessible name. `parseTemplateAttributes` lowercases names,
|
|
16
|
+
* so `.ariaLabel=${…}` arrives as `.arialabel`.
|
|
17
|
+
*/
|
|
18
|
+
const NAMING_ATTRIBUTES = ['data-l10n-id', 'aria-label', '.arialabel'];
|
|
19
|
+
/** Attributes that give the button a visible label, so it is not icon-only. */
|
|
20
|
+
const LABEL_ATTRIBUTES = ['label', '.label'];
|
|
21
|
+
/**
|
|
22
|
+
* Whether the button's content has a non-blank character outside tags.
|
|
23
|
+
* Scans instead of stripping tags, so nested or unbalanced markup cannot
|
|
24
|
+
* leave a fragment behind that reads as text, and a `>` inside a quoted
|
|
25
|
+
* attribute value does not end its tag.
|
|
26
|
+
*/
|
|
27
|
+
function hasVisibleText(markup) {
|
|
28
|
+
let inTag = false;
|
|
29
|
+
let quote;
|
|
30
|
+
for (const char of markup) {
|
|
31
|
+
if (quote !== undefined) {
|
|
32
|
+
if (char === quote)
|
|
33
|
+
quote = undefined;
|
|
34
|
+
}
|
|
35
|
+
else if (inTag) {
|
|
36
|
+
if (char === '"' || char === "'")
|
|
37
|
+
quote = char;
|
|
38
|
+
else if (char === '>')
|
|
39
|
+
inTag = false;
|
|
40
|
+
}
|
|
41
|
+
else if (char === '<') {
|
|
42
|
+
inTag = true;
|
|
43
|
+
}
|
|
44
|
+
else if (char.trim() !== '') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function isIconOnly(attrs, innerText) {
|
|
51
|
+
if (LABEL_ATTRIBUTES.some((name) => attrs.has(name)))
|
|
52
|
+
return false;
|
|
53
|
+
if (hasVisibleText(innerText))
|
|
54
|
+
return false;
|
|
55
|
+
const type = attrs.get('type') ?? '';
|
|
56
|
+
return attrs.has('iconsrc') || attrs.has('.iconsrc') || /(^|\s)icon(\s|$)/.test(type);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Counts icon-only `<moz-button>` elements in the module's Lit templates
|
|
60
|
+
* that carry neither `data-l10n-id` nor `aria-label`.
|
|
61
|
+
*
|
|
62
|
+
* @param content - The component module's source
|
|
63
|
+
*/
|
|
64
|
+
export function countUnlabelledIconButtons(content) {
|
|
65
|
+
let count = 0;
|
|
66
|
+
for (const region of collectLitTemplateRegions(content)) {
|
|
67
|
+
const pattern = /<moz-button\b((?:"[^"]*"|'[^']*'|[^'">])*)>([\s\S]*?)<\/moz-button>/gi;
|
|
68
|
+
for (const match of region.matchAll(pattern)) {
|
|
69
|
+
const attrs = parseTemplateAttributes(match[1] ?? '');
|
|
70
|
+
if (!isIconOnly(attrs, match[2] ?? ''))
|
|
71
|
+
continue;
|
|
72
|
+
if (NAMING_ATTRIBUTES.some((name) => attrs.has(name)))
|
|
73
|
+
continue;
|
|
74
|
+
count++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return count;
|
|
78
|
+
}
|
|
@@ -49,7 +49,7 @@ export async function validateComponent(componentDir, tagName, type, config, roo
|
|
|
49
49
|
// parameter, so this is a no-op for stock and override components.
|
|
50
50
|
const customConfigForTag = type === 'custom' ? config?.custom[tagName] : undefined;
|
|
51
51
|
issues.push(...(await validateStructure(componentDir, tagName, type, customConfigForTag)));
|
|
52
|
-
issues.push(...(await validateAccessibility(componentDir, tagName, customConfigForTag)));
|
|
52
|
+
issues.push(...(await validateAccessibility(componentDir, tagName, customConfigForTag, config?.optInChecks)));
|
|
53
53
|
issues.push(...(await validateCompatibility(componentDir, tagName, type, config, root)));
|
|
54
54
|
if (root && config && type === 'override') {
|
|
55
55
|
const forgeConfig = await loadConfig(root);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge test --auto-ports` (or `test.mochitestPorts: "auto"`): let two
|
|
3
|
+
* checkouts run browser suites at the same time.
|
|
4
|
+
*
|
|
5
|
+
* Desktop `mach mochitest` has no `--http-port`/`--ssl-port` options; those
|
|
6
|
+
* exist only in the Android and iOS argument sets. What recent engines do
|
|
7
|
+
* have is a fallback inside `testing/mochitest/runtests.py`: before starting
|
|
8
|
+
* its servers the harness bind-probes the HTTP server, ssltunnel and
|
|
9
|
+
* WebSocket ports, and before launching the browser the Marionette port,
|
|
10
|
+
* and moves any that is busy to a free port, routing `mochi.test:8888`
|
|
11
|
+
* through its proxy to wherever the server actually landed. FireForge's
|
|
12
|
+
* own preflight refused before that fallback could run. In auto mode it
|
|
13
|
+
* steps aside for a holder that is not this checkout's own (a sibling
|
|
14
|
+
* checkout's live suite, or an unrelated listener), and keeps refusing for
|
|
15
|
+
* this checkout's own leftovers, exactly as in the default mode.
|
|
16
|
+
*
|
|
17
|
+
* Auto mode is opt-in, and it only takes effect when the engine's harness
|
|
18
|
+
* is seen to carry the fallback: on an engine without it, a busy port would
|
|
19
|
+
* still collide, so the default refusal stays in force there.
|
|
20
|
+
*/
|
|
21
|
+
import type { TestOptions } from '../types/commands/index.js';
|
|
22
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
23
|
+
import { type MarionettePortHolder } from './marionette-port.js';
|
|
24
|
+
/** Which busy-port fallbacks the engine's mochitest harness carries. */
|
|
25
|
+
export interface HarnessPortFallback {
|
|
26
|
+
/** HTTP server, ssltunnel and WebSocket ports (`startServers`). */
|
|
27
|
+
serverPorts: boolean;
|
|
28
|
+
/** The Marionette port (before the browser launch). */
|
|
29
|
+
marionettePort: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Reads the engine's `runtests.py` for the busy-port fallbacks. An
|
|
33
|
+
* unreadable file reports neither, which keeps the default refusal.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectHarnessPortFallback(engineDir: string): Promise<HarnessPortFallback>;
|
|
36
|
+
/** True when the operator asked for auto ports, by flag or by config. */
|
|
37
|
+
export declare function autoPortsRequested(options: TestOptions, config: FireForgeConfig): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* True when the holder's command line points into THIS checkout's engine
|
|
40
|
+
* directory: our own leftover, which auto mode never steps aside for.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isThisCheckoutsHolder(holder: MarionettePortHolder, engineDir: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the effective auto-ports posture for a browser run: what the
|
|
45
|
+
* engine's harness can fall back from, or `undefined` when auto mode is
|
|
46
|
+
* off or the engine cannot honour it (in which case the default refusal
|
|
47
|
+
* stays, and an explicit request says why).
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveAutoPorts(engineDir: string, options: TestOptions, config: FireForgeConfig): Promise<HarnessPortFallback | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Steps aside for a busy port the harness will fall back from. Returns true
|
|
52
|
+
* (after a notice naming the holder) when the port is held by anything that
|
|
53
|
+
* is not this checkout's own process, false when the port is free or the
|
|
54
|
+
* holder is ours, so the caller's normal preflight runs.
|
|
55
|
+
*
|
|
56
|
+
* @param port - The default port the harness would use
|
|
57
|
+
* @param label - Human name of the port for the notice
|
|
58
|
+
* @param engineDir - This checkout's engine directory
|
|
59
|
+
*/
|
|
60
|
+
export declare function deferBusyPortToHarness(port: number, label: string, engineDir: string): Promise<boolean>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge test --auto-ports` (or `test.mochitestPorts: "auto"`): let two
|
|
4
|
+
* checkouts run browser suites at the same time.
|
|
5
|
+
*
|
|
6
|
+
* Desktop `mach mochitest` has no `--http-port`/`--ssl-port` options; those
|
|
7
|
+
* exist only in the Android and iOS argument sets. What recent engines do
|
|
8
|
+
* have is a fallback inside `testing/mochitest/runtests.py`: before starting
|
|
9
|
+
* its servers the harness bind-probes the HTTP server, ssltunnel and
|
|
10
|
+
* WebSocket ports, and before launching the browser the Marionette port,
|
|
11
|
+
* and moves any that is busy to a free port, routing `mochi.test:8888`
|
|
12
|
+
* through its proxy to wherever the server actually landed. FireForge's
|
|
13
|
+
* own preflight refused before that fallback could run. In auto mode it
|
|
14
|
+
* steps aside for a holder that is not this checkout's own (a sibling
|
|
15
|
+
* checkout's live suite, or an unrelated listener), and keeps refusing for
|
|
16
|
+
* this checkout's own leftovers, exactly as in the default mode.
|
|
17
|
+
*
|
|
18
|
+
* Auto mode is opt-in, and it only takes effect when the engine's harness
|
|
19
|
+
* is seen to carry the fallback: on an engine without it, a busy port would
|
|
20
|
+
* still collide, so the default refusal stays in force there.
|
|
21
|
+
*/
|
|
22
|
+
import { readFile } from 'node:fs/promises';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { notice } from '../utils/logger.js';
|
|
25
|
+
import { isExplicitAbsolutePath, isPathInsideRoot } from '../utils/paths.js';
|
|
26
|
+
import { probeMarionettePort } from './marionette-port.js';
|
|
27
|
+
const RUNTESTS_PATH = 'testing/mochitest/runtests.py';
|
|
28
|
+
/**
|
|
29
|
+
* Reads the engine's `runtests.py` for the busy-port fallbacks. An
|
|
30
|
+
* unreadable file reports neither, which keeps the default refusal.
|
|
31
|
+
*/
|
|
32
|
+
export async function detectHarnessPortFallback(engineDir) {
|
|
33
|
+
let source;
|
|
34
|
+
try {
|
|
35
|
+
source = await readFile(join(engineDir, RUNTESTS_PATH), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { serverPorts: false, marionettePort: false };
|
|
39
|
+
}
|
|
40
|
+
const fallsBack = /falling back to port/.test(source);
|
|
41
|
+
return {
|
|
42
|
+
serverPorts: fallsBack && /\("httpPort",/.test(source) && /_port_in_use\(port\)/.test(source),
|
|
43
|
+
marionettePort: fallsBack && /_port_in_use\(marionette_port\)/.test(source),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** True when the operator asked for auto ports, by flag or by config. */
|
|
47
|
+
export function autoPortsRequested(options, config) {
|
|
48
|
+
return options.autoPorts === true || config.test?.mochitestPorts === 'auto';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True when the holder's command line points into THIS checkout's engine
|
|
52
|
+
* directory: our own leftover, which auto mode never steps aside for.
|
|
53
|
+
*/
|
|
54
|
+
export function isThisCheckoutsHolder(holder, engineDir) {
|
|
55
|
+
return holder.commandLine
|
|
56
|
+
.split(/\s+/)
|
|
57
|
+
.filter(isExplicitAbsolutePath)
|
|
58
|
+
.some((token) => isPathInsideRoot(engineDir, token));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolves the effective auto-ports posture for a browser run: what the
|
|
62
|
+
* engine's harness can fall back from, or `undefined` when auto mode is
|
|
63
|
+
* off or the engine cannot honour it (in which case the default refusal
|
|
64
|
+
* stays, and an explicit request says why).
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveAutoPorts(engineDir, options, config) {
|
|
67
|
+
if (!autoPortsRequested(options, config))
|
|
68
|
+
return undefined;
|
|
69
|
+
const fallback = await detectHarnessPortFallback(engineDir);
|
|
70
|
+
if (!fallback.serverPorts && !fallback.marionettePort) {
|
|
71
|
+
notice(`Auto ports requested, but engine/${RUNTESTS_PATH} has no busy-port fallback, so a busy ` +
|
|
72
|
+
'mochitest or Marionette port is still refused as in the default mode.');
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Steps aside for a busy port the harness will fall back from. Returns true
|
|
79
|
+
* (after a notice naming the holder) when the port is held by anything that
|
|
80
|
+
* is not this checkout's own process, false when the port is free or the
|
|
81
|
+
* holder is ours, so the caller's normal preflight runs.
|
|
82
|
+
*
|
|
83
|
+
* @param port - The default port the harness would use
|
|
84
|
+
* @param label - Human name of the port for the notice
|
|
85
|
+
* @param engineDir - This checkout's engine directory
|
|
86
|
+
*/
|
|
87
|
+
export async function deferBusyPortToHarness(port, label, engineDir) {
|
|
88
|
+
const probe = await probeMarionettePort(port);
|
|
89
|
+
if (!probe.inUse || !probe.holder)
|
|
90
|
+
return false;
|
|
91
|
+
if (isThisCheckoutsHolder(probe.holder, engineDir))
|
|
92
|
+
return false;
|
|
93
|
+
notice(`${label} port ${port} is held by ${probe.holder.command} (PID ${probe.holder.pid}), which is ` +
|
|
94
|
+
"not this checkout's. Auto ports: the harness moves this run to a free port instead.");
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Filename of the history log at its pre-0.50 location, inside `patches/`. */
|
|
2
|
+
export declare const LEGACY_HISTORY_LOG_FILENAME = ".fireforge-history.jsonl";
|
|
3
|
+
/** Default history log location, relative to the project root. */
|
|
4
|
+
export declare const DEFAULT_HISTORY_LOG_PATH: string;
|
|
5
|
+
/** Environment variable a caller sets to tag every record of its session. */
|
|
6
|
+
export declare const HISTORY_SESSION_ENV = "FIREFORGE_SESSION_ID";
|
|
7
|
+
/** Who wrote a history record. */
|
|
8
|
+
export interface HistoryRecordIdentity {
|
|
9
|
+
/** One id per FireForge process: every record one command writes shares it. */
|
|
10
|
+
invocationId: string;
|
|
11
|
+
/** The caller's `FIREFORGE_SESSION_ID`, when set. */
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
pid: number;
|
|
14
|
+
ppid: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Identity fields stamped on every history record: one id per FireForge
|
|
18
|
+
* process (every record one command writes shares it), the process and its
|
|
19
|
+
* parent, and the caller's `FIREFORGE_SESSION_ID` when set. The parent PID
|
|
20
|
+
* is a weak hint (usually the shell); the environment variable is the
|
|
21
|
+
* reliable way for a session to tag its own lines.
|
|
22
|
+
*/
|
|
23
|
+
export declare function historyRecordIdentity(): HistoryRecordIdentity;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the history log path for a project.
|
|
26
|
+
*
|
|
27
|
+
* @param projectRoot - Project root containing `fireforge.json`
|
|
28
|
+
* @returns Absolute path of the history log
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveHistoryLogPath(projectRoot: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Moves a history log left at the legacy `patches/` path to `target`.
|
|
33
|
+
*
|
|
34
|
+
* Records are never dropped, whatever else is happening:
|
|
35
|
+
* - The legacy file is first claimed by renaming it aside, so of two
|
|
36
|
+
* commands migrating at once exactly one does the work and neither
|
|
37
|
+
* duplicates it.
|
|
38
|
+
* - When `target` does not exist yet (the first run), the claimed file is
|
|
39
|
+
* hard-linked into place, which cannot overwrite a log another command
|
|
40
|
+
* created in the meantime, and keeps a large log a constant-time move.
|
|
41
|
+
* - When both exist (an older FireForge appended to the legacy path after
|
|
42
|
+
* a migration, or `history.path` changed), the legacy records are
|
|
43
|
+
* appended to the target in one write. They then follow newer records;
|
|
44
|
+
* every record carries its own `ts`.
|
|
45
|
+
*
|
|
46
|
+
* A crash between the claim and the move leaves the records in a
|
|
47
|
+
* `.migrating-*` file next to the legacy path, never deleted.
|
|
48
|
+
*
|
|
49
|
+
* @param patchesDir - The patches directory that may hold a legacy log
|
|
50
|
+
* @param target - Absolute path of the history log
|
|
51
|
+
*/
|
|
52
|
+
export declare function migrateLegacyHistoryLog(patchesDir: string, target: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Appends one serialized record to the project's history log, migrating a
|
|
55
|
+
* legacy log first.
|
|
56
|
+
*
|
|
57
|
+
* @param patchesDir - The project's `patches/` directory; the project root is
|
|
58
|
+
* its parent, as every caller derives it from `getProjectPaths`
|
|
59
|
+
* @param line - One JSON record, without the trailing newline
|
|
60
|
+
*/
|
|
61
|
+
export declare function appendHistoryLine(patchesDir: string, line: string): Promise<void>;
|