@hominis/fireforge 0.49.1 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create-templates.d.ts +8 -3
- package/dist/src/commands/furnace/create-templates.js +8 -23
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +93 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +51 -15
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency.js +14 -41
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +37 -24
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +59 -35
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +97 -0
- package/dist/src/core/coverage-records.js +350 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +12 -2
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +157 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +323 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +20 -0
- package/dist/src/core/run-log.js +89 -3
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +34 -6
- package/dist/src/utils/options.js +86 -5
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +2 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type FurnaceOptInCheck, type FurnaceValidationException } from '../types/furnace.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses `validationExceptions[]`: `{ component, check, reason }` objects,
|
|
4
|
+
* no other keys, every field a non-empty string, and no two entries for the
|
|
5
|
+
* same component and check.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseValidationExceptions(raw: unknown): FurnaceValidationException[];
|
|
8
|
+
/** Parses `optInChecks[]`: names from {@link FURNACE_OPT_IN_CHECKS}. */
|
|
9
|
+
export declare function parseOptInChecks(raw: unknown): FurnaceOptInCheck[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Validators for the two `furnace validate` policy fields of furnace.json:
|
|
4
|
+
* `validationExceptions[]` and `optInChecks[]`.
|
|
5
|
+
*
|
|
6
|
+
* Strict on purpose, like `buildAudit.unpackaged`: every exception is a
|
|
7
|
+
* standing suppression of a finding, so a malformed one must fail at load
|
|
8
|
+
* rather than quietly suppress nothing (or everything).
|
|
9
|
+
*/
|
|
10
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
11
|
+
import { FURNACE_OPT_IN_CHECKS, } from '../types/furnace.js';
|
|
12
|
+
import { isArray, isObject, isString } from '../utils/validation.js';
|
|
13
|
+
const EXCEPTION_KEYS = new Set(['component', 'check', 'reason']);
|
|
14
|
+
function requireNonEmptyString(entry, key, field) {
|
|
15
|
+
const value = entry[key];
|
|
16
|
+
if (!isString(value) || value.trim().length === 0) {
|
|
17
|
+
throw new FurnaceError(`Furnace config: "${field}.${key}" must be a non-empty string`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parses `validationExceptions[]`: `{ component, check, reason }` objects,
|
|
23
|
+
* no other keys, every field a non-empty string, and no two entries for the
|
|
24
|
+
* same component and check.
|
|
25
|
+
*/
|
|
26
|
+
export function parseValidationExceptions(raw) {
|
|
27
|
+
if (!isArray(raw)) {
|
|
28
|
+
throw new FurnaceError('Furnace config: "validationExceptions" must be an array');
|
|
29
|
+
}
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
return raw.map((entry, index) => {
|
|
32
|
+
const field = `validationExceptions[${String(index)}]`;
|
|
33
|
+
if (!isObject(entry)) {
|
|
34
|
+
throw new FurnaceError(`Furnace config: "${field}" must be an object with "component", "check" and "reason"`);
|
|
35
|
+
}
|
|
36
|
+
for (const key of Object.keys(entry)) {
|
|
37
|
+
if (!EXCEPTION_KEYS.has(key)) {
|
|
38
|
+
throw new FurnaceError(`Furnace config: "${field}" has unknown key "${key}"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const exception = {
|
|
42
|
+
component: requireNonEmptyString(entry, 'component', field),
|
|
43
|
+
check: requireNonEmptyString(entry, 'check', field),
|
|
44
|
+
reason: requireNonEmptyString(entry, 'reason', field),
|
|
45
|
+
};
|
|
46
|
+
const identity = `${exception.component}\u0000${exception.check}`;
|
|
47
|
+
if (seen.has(identity)) {
|
|
48
|
+
throw new FurnaceError(`Furnace config: "${field}" repeats the exception for ${exception.component} [${exception.check}]`);
|
|
49
|
+
}
|
|
50
|
+
seen.add(identity);
|
|
51
|
+
return exception;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function isOptInCheck(value) {
|
|
55
|
+
return FURNACE_OPT_IN_CHECKS.includes(value);
|
|
56
|
+
}
|
|
57
|
+
/** Parses `optInChecks[]`: names from {@link FURNACE_OPT_IN_CHECKS}. */
|
|
58
|
+
export function parseOptInChecks(raw) {
|
|
59
|
+
if (!isArray(raw)) {
|
|
60
|
+
throw new FurnaceError('Furnace config: "optInChecks" must be an array');
|
|
61
|
+
}
|
|
62
|
+
const checks = [];
|
|
63
|
+
for (const value of raw) {
|
|
64
|
+
if (!isString(value) || !isOptInCheck(value)) {
|
|
65
|
+
throw new FurnaceError(`Furnace config: "optInChecks" entry ${JSON.stringify(value)} is not an opt-in check. Valid: ${FURNACE_OPT_IN_CHECKS.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
if (!checks.includes(value))
|
|
68
|
+
checks.push(value);
|
|
69
|
+
}
|
|
70
|
+
return checks;
|
|
71
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { FurnaceError } from '../errors/furnace.js';
|
|
9
9
|
import { isJsonObject, isString } from '../utils/validation.js';
|
|
10
10
|
import { parseStringArray } from './furnace-config-array-utils.js';
|
|
11
|
+
import { parseOptInChecks, parseValidationExceptions, } from './furnace-config-validate-exceptions.js';
|
|
11
12
|
/**
|
|
12
13
|
* Validates an override component config object.
|
|
13
14
|
* @param data - Raw data to validate
|
|
@@ -129,4 +130,11 @@ export function applyOptionalFurnaceFields(migrated, config) {
|
|
|
129
130
|
}
|
|
130
131
|
config.scanPaths = paths;
|
|
131
132
|
}
|
|
133
|
+
// `furnace validate` policy: reviewed exceptions and opt-in checks.
|
|
134
|
+
if (migrated['validationExceptions'] !== undefined) {
|
|
135
|
+
config.validationExceptions = parseValidationExceptions(migrated['validationExceptions']);
|
|
136
|
+
}
|
|
137
|
+
if (migrated['optInChecks'] !== undefined) {
|
|
138
|
+
config.optInChecks = parseOptInChecks(migrated['optInChecks']);
|
|
139
|
+
}
|
|
132
140
|
}
|
|
@@ -35,6 +35,16 @@ export declare function listFragmentIncludes(css: string): string[];
|
|
|
35
35
|
* against its workspace source and to re-expand idempotently.
|
|
36
36
|
*/
|
|
37
37
|
export declare function stripExpandedFragments(css: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Marks the lines of a stylesheet that belong to a fenced fragment
|
|
40
|
+
* expansion: the directive, the inlined body and the end marker. Index `i`
|
|
41
|
+
* is line `i + 1` of `css`. Fences are found exactly as
|
|
42
|
+
* {@link stripExpandedFragments} finds them, so a bare directive or an
|
|
43
|
+
* unterminated fence marks nothing. The patch-size metric reads this
|
|
44
|
+
* off a file's post-image, because a hunk can start inside a span and
|
|
45
|
+
* carry no directive of its own.
|
|
46
|
+
*/
|
|
47
|
+
export declare function expandedFragmentLineMask(css: string): boolean[];
|
|
38
48
|
/**
|
|
39
49
|
* Expands every `@fireforge-include` directive in `css` with the current
|
|
40
50
|
* content of its fragment file from `sharedDir`, fencing each expansion
|
|
@@ -78,6 +78,32 @@ export function stripExpandedFragments(css) {
|
|
|
78
78
|
}
|
|
79
79
|
return out.join('\n');
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Marks the lines of a stylesheet that belong to a fenced fragment
|
|
83
|
+
* expansion: the directive, the inlined body and the end marker. Index `i`
|
|
84
|
+
* is line `i + 1` of `css`. Fences are found exactly as
|
|
85
|
+
* {@link stripExpandedFragments} finds them, so a bare directive or an
|
|
86
|
+
* unterminated fence marks nothing. The patch-size metric reads this
|
|
87
|
+
* off a file's post-image, because a hunk can start inside a span and
|
|
88
|
+
* carry no directive of its own.
|
|
89
|
+
*/
|
|
90
|
+
export function expandedFragmentLineMask(css) {
|
|
91
|
+
const lines = css.split('\n');
|
|
92
|
+
const mask = lines.map(() => false);
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
const inc = INCLUDE_PATTERN.exec(lines[i] ?? '');
|
|
95
|
+
if (!inc?.[1])
|
|
96
|
+
continue;
|
|
97
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
98
|
+
if (END_INCLUDE_PATTERN.exec(lines[j] ?? '')?.[1] !== inc[1])
|
|
99
|
+
continue;
|
|
100
|
+
mask.fill(true, i, j + 1);
|
|
101
|
+
i = j;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return mask;
|
|
106
|
+
}
|
|
81
107
|
/**
|
|
82
108
|
* Expands every `@fireforge-include` directive in `css` with the current
|
|
83
109
|
* content of its fragment file from `sharedDir`, fencing each expansion
|
|
@@ -63,9 +63,9 @@ export interface RunFurnaceMutationOptions {
|
|
|
63
63
|
* never passed: `furnace deploy --wait-lock 1800` waited 1800 s for the
|
|
64
64
|
* engine session lock and then met `.fireforge/furnace.lock` with a fixed
|
|
65
65
|
* 30 s, which is the worst shape a budget can have: it pays the whole
|
|
66
|
-
* wait and still refuses.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
66
|
+
* wait and still refuses. Every furnace leaf that takes this lock threads
|
|
67
|
+
* its `--wait-lock` here. When absent, {@link sessionWaitLockMs} supplies
|
|
68
|
+
* the session-wide budget (`FIREFORGE_WAIT_LOCK`).
|
|
69
69
|
*/
|
|
70
70
|
lockTimeoutMs?: number;
|
|
71
71
|
/**
|
|
@@ -15,11 +15,32 @@
|
|
|
15
15
|
* - Two registration shapes are recognised:
|
|
16
16
|
* (A) Standalone statement:
|
|
17
17
|
* customElements.setElementCreationCallback("tag", ...);
|
|
18
|
-
* (B) Entry inside a `for (... of [ ... ])` registration array
|
|
18
|
+
* (B) Entry inside a `for (... of [ ... ])` registration array, or an
|
|
19
|
+
* array declared by name and iterated with `for (... of NAME)`:
|
|
19
20
|
* ["tag", "chrome://..."]
|
|
20
21
|
* Both are deleted together with any trailing comma and newline so the
|
|
21
22
|
* resulting file is still valid JavaScript.
|
|
22
23
|
*/
|
|
24
|
+
import type * as estree from 'estree';
|
|
25
|
+
import { type AcornESTreeNode } from './ast-utils.js';
|
|
26
|
+
/** A `[start, end)` source range in customElements.js. */
|
|
27
|
+
export interface RemovalRange {
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Walks the AST and returns the source range of every registration of
|
|
33
|
+
* `tagName`: each `[tag, module]` entry of a registration list, and each
|
|
34
|
+
* statement enclosing a `setElementCreationCallback(tag, ...)` call.
|
|
35
|
+
* Ancestor tracking lets us tell an "entry" array from a random
|
|
36
|
+
* `["moz-foo", ...]` literal (only direct children of a registration list
|
|
37
|
+
* are entries). Shared by removal and the in-place rename.
|
|
38
|
+
*
|
|
39
|
+
* @param ast - Parsed customElements.js
|
|
40
|
+
* @param tagName - Tag whose registrations to find
|
|
41
|
+
* @returns Unexpanded `[start, end)` ranges, in source order
|
|
42
|
+
*/
|
|
43
|
+
export declare function findRegistrationRanges(ast: AcornESTreeNode<estree.Program>, tagName: string): RemovalRange[];
|
|
23
44
|
/**
|
|
24
45
|
* Removes a custom element registration from customElements.js.
|
|
25
46
|
*
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* - Two registration shapes are recognised:
|
|
17
17
|
* (A) Standalone statement:
|
|
18
18
|
* customElements.setElementCreationCallback("tag", ...);
|
|
19
|
-
* (B) Entry inside a `for (... of [ ... ])` registration array
|
|
19
|
+
* (B) Entry inside a `for (... of [ ... ])` registration array, or an
|
|
20
|
+
* array declared by name and iterated with `for (... of NAME)`:
|
|
20
21
|
* ["tag", "chrome://..."]
|
|
21
22
|
* Both are deleted together with any trailing comma and newline so the
|
|
22
23
|
* resulting file is still valid JavaScript.
|
|
@@ -120,28 +121,63 @@ function isStandaloneCallbackCallFor(call, tagName) {
|
|
|
120
121
|
return tagArg.value === tagName;
|
|
121
122
|
}
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
124
|
+
* Names of the identifiers used as the right-hand side of a `for (... of X)`
|
|
125
|
+
* loop. A registration list declared as `const ELEMENTS = [...]` and
|
|
126
|
+
* iterated by name is a registration array just like an inline literal: the
|
|
127
|
+
* add path (`addCustomElementRegistration`) has always written into
|
|
128
|
+
* it, so the remove and rename paths must find entries there too or a
|
|
129
|
+
* rename leaves the old entry behind.
|
|
128
130
|
*/
|
|
129
|
-
function
|
|
131
|
+
function collectForOfIdentifiers(ast) {
|
|
132
|
+
const names = new Set();
|
|
133
|
+
walkAST(ast, {
|
|
134
|
+
enter(node) {
|
|
135
|
+
if (node.type !== 'ForOfStatement')
|
|
136
|
+
return;
|
|
137
|
+
const right = asEstree(node).right;
|
|
138
|
+
if (right.type === 'Identifier')
|
|
139
|
+
names.add(right.name);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return names;
|
|
143
|
+
}
|
|
144
|
+
/** True when `parent`/`grandparent` make an array a registration list. */
|
|
145
|
+
function isRegistrationList(parent, grandparent, forOfIdentifiers) {
|
|
146
|
+
if (parent?.type !== 'ArrayExpression' || !grandparent)
|
|
147
|
+
return false;
|
|
148
|
+
if (grandparent.type === 'ForOfStatement')
|
|
149
|
+
return true;
|
|
150
|
+
if (grandparent.type !== 'VariableDeclarator')
|
|
151
|
+
return false;
|
|
152
|
+
const id = asEstree(grandparent).id;
|
|
153
|
+
return id.type === 'Identifier' && forOfIdentifiers.has(id.name);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Walks the AST and returns the source range of every registration of
|
|
157
|
+
* `tagName`: each `[tag, module]` entry of a registration list, and each
|
|
158
|
+
* statement enclosing a `setElementCreationCallback(tag, ...)` call.
|
|
159
|
+
* Ancestor tracking lets us tell an "entry" array from a random
|
|
160
|
+
* `["moz-foo", ...]` literal (only direct children of a registration list
|
|
161
|
+
* are entries). Shared by removal and the in-place rename.
|
|
162
|
+
*
|
|
163
|
+
* @param ast - Parsed customElements.js
|
|
164
|
+
* @param tagName - Tag whose registrations to find
|
|
165
|
+
* @returns Unexpanded `[start, end)` ranges, in source order
|
|
166
|
+
*/
|
|
167
|
+
export function findRegistrationRanges(ast, tagName) {
|
|
130
168
|
const ranges = [];
|
|
131
169
|
const ancestors = [];
|
|
170
|
+
const forOfIdentifiers = collectForOfIdentifiers(ast);
|
|
132
171
|
walkAST(ast, {
|
|
133
172
|
enter(node) {
|
|
134
|
-
// Entry array: [tag, module] inside
|
|
135
|
-
//
|
|
136
|
-
//
|
|
173
|
+
// Entry array: [tag, module] inside a registration list, either the
|
|
174
|
+
// for-of array literal itself or an array declared by name and
|
|
175
|
+
// iterated by a for-of loop.
|
|
137
176
|
if (node.type === 'ArrayExpression' && isEntryArrayFor(node, tagName)) {
|
|
138
177
|
const parent = ancestors[ancestors.length - 1];
|
|
139
178
|
const grandparent = ancestors[ancestors.length - 2];
|
|
140
|
-
if (parent
|
|
141
|
-
|
|
142
|
-
grandparent &&
|
|
143
|
-
grandparent.type === 'ForOfStatement') {
|
|
144
|
-
ranges.push(expandRemovalRange(content, node.start, node.end));
|
|
179
|
+
if (isRegistrationList(parent, grandparent, forOfIdentifiers)) {
|
|
180
|
+
ranges.push({ start: node.start, end: node.end });
|
|
145
181
|
}
|
|
146
182
|
}
|
|
147
183
|
// Standalone call: customElements.setElementCreationCallback(tag, …)
|
|
@@ -162,7 +198,7 @@ function collectRemovalRanges(ast, content, tagName) {
|
|
|
162
198
|
}
|
|
163
199
|
}
|
|
164
200
|
const target = enclosing ?? call;
|
|
165
|
-
ranges.push(
|
|
201
|
+
ranges.push({ start: target.start, end: target.end });
|
|
166
202
|
}
|
|
167
203
|
}
|
|
168
204
|
ancestors.push(node);
|
|
@@ -207,7 +243,7 @@ export async function removeCustomElementRegistration(engineDir, tagName) {
|
|
|
207
243
|
// risks corrupting it. The caller's regex fallback handles the retry.
|
|
208
244
|
return;
|
|
209
245
|
}
|
|
210
|
-
const ranges =
|
|
246
|
+
const ranges = findRegistrationRanges(ast, tagName).map((range) => expandRemovalRange(content, range.start, range.end));
|
|
211
247
|
if (ranges.length === 0) {
|
|
212
248
|
return;
|
|
213
249
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-place rename of a custom element registration in customElements.js.
|
|
3
|
+
*
|
|
4
|
+
* `furnace rename` used to remove the old entry and add a new one. On a
|
|
5
|
+
* registration list the remove path could not see, that left the old entry
|
|
6
|
+
* beside the new one, and the new entry was written without the project
|
|
7
|
+
* marker every sibling line carries (`// <MARKER>:`), so the next lint
|
|
8
|
+
* flagged it as an unmarked engine edit. Rewriting the tag and module
|
|
9
|
+
* literals inside the existing entry keeps the entry's position, layout and
|
|
10
|
+
* trailing marker comments exactly as they were.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Renames `oldTag`'s registration in customElements.js in place.
|
|
14
|
+
*
|
|
15
|
+
* Returns false, writing nothing, when the file is missing or unparsable,
|
|
16
|
+
* holds no registration for `oldTag`, or already registers `newTag` (an
|
|
17
|
+
* in-place rewrite would then register it twice); the caller then falls
|
|
18
|
+
* back to remove-and-add, whose add is idempotent. Never parses with a line-based heuristic: an unparsable
|
|
19
|
+
* file is a doctor problem, not something a rename edits around.
|
|
20
|
+
*
|
|
21
|
+
* @param engineDir - Path to the Firefox engine source root
|
|
22
|
+
* @param oldTag - Tag being renamed away from
|
|
23
|
+
* @param newTag - Tag being renamed to
|
|
24
|
+
* @returns True when at least one registration was rewritten
|
|
25
|
+
*/
|
|
26
|
+
export declare function renameCustomElementRegistration(engineDir: string, oldTag: string, newTag: string): Promise<boolean>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* In-place rename of a custom element registration in customElements.js.
|
|
4
|
+
*
|
|
5
|
+
* `furnace rename` used to remove the old entry and add a new one. On a
|
|
6
|
+
* registration list the remove path could not see, that left the old entry
|
|
7
|
+
* beside the new one, and the new entry was written without the project
|
|
8
|
+
* marker every sibling line carries (`// <MARKER>:`), so the next lint
|
|
9
|
+
* flagged it as an unmarked engine edit. Rewriting the tag and module
|
|
10
|
+
* literals inside the existing entry keeps the entry's position, layout and
|
|
11
|
+
* trailing marker comments exactly as they were.
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import MagicString from 'magic-string';
|
|
15
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
16
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
17
|
+
import { parseScript } from './ast-utils.js';
|
|
18
|
+
import { CUSTOM_ELEMENTS_JS } from './furnace-constants.js';
|
|
19
|
+
import { isTagAlreadyRegistered } from './furnace-registration-ast.js';
|
|
20
|
+
import { findRegistrationRanges } from './furnace-registration-remove.js';
|
|
21
|
+
/**
|
|
22
|
+
* Rewrites the old tag's literals inside one registration's source: the
|
|
23
|
+
* quoted tag itself and the module file name in its chrome:// URI.
|
|
24
|
+
* Everything else in the range (indentation, line breaks, comments between
|
|
25
|
+
* elements) is left byte-for-byte.
|
|
26
|
+
*/
|
|
27
|
+
function renameRegistrationSource(source, oldTag, newTag) {
|
|
28
|
+
const escaped = escapeRegex(oldTag);
|
|
29
|
+
return source
|
|
30
|
+
.replace(new RegExp(`(["'])${escaped}\\1`, 'g'), `$1${newTag}$1`)
|
|
31
|
+
.replace(new RegExp(`/${escaped}(\\.m?js["'])`, 'g'), `/${newTag}$1`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Renames `oldTag`'s registration in customElements.js in place.
|
|
35
|
+
*
|
|
36
|
+
* Returns false, writing nothing, when the file is missing or unparsable,
|
|
37
|
+
* holds no registration for `oldTag`, or already registers `newTag` (an
|
|
38
|
+
* in-place rewrite would then register it twice); the caller then falls
|
|
39
|
+
* back to remove-and-add, whose add is idempotent. Never parses with a line-based heuristic: an unparsable
|
|
40
|
+
* file is a doctor problem, not something a rename edits around.
|
|
41
|
+
*
|
|
42
|
+
* @param engineDir - Path to the Firefox engine source root
|
|
43
|
+
* @param oldTag - Tag being renamed away from
|
|
44
|
+
* @param newTag - Tag being renamed to
|
|
45
|
+
* @returns True when at least one registration was rewritten
|
|
46
|
+
*/
|
|
47
|
+
export async function renameCustomElementRegistration(engineDir, oldTag, newTag) {
|
|
48
|
+
const filePath = join(engineDir, CUSTOM_ELEMENTS_JS);
|
|
49
|
+
if (!(await pathExists(filePath)))
|
|
50
|
+
return false;
|
|
51
|
+
const content = await readText(filePath);
|
|
52
|
+
if (!content.includes(`"${oldTag}"`) && !content.includes(`'${oldTag}'`))
|
|
53
|
+
return false;
|
|
54
|
+
if (isTagAlreadyRegistered(content, newTag))
|
|
55
|
+
return false;
|
|
56
|
+
let ranges;
|
|
57
|
+
try {
|
|
58
|
+
ranges = findRegistrationRanges(parseScript(content), oldTag);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (ranges.length === 0)
|
|
64
|
+
return false;
|
|
65
|
+
const ms = new MagicString(content);
|
|
66
|
+
for (const range of ranges) {
|
|
67
|
+
const source = content.slice(range.start, range.end);
|
|
68
|
+
const renamed = renameRegistrationSource(source, oldTag, newTag);
|
|
69
|
+
if (renamed !== source)
|
|
70
|
+
ms.overwrite(range.start, range.end, renamed);
|
|
71
|
+
}
|
|
72
|
+
const next = ms.toString();
|
|
73
|
+
if (next === content)
|
|
74
|
+
return false;
|
|
75
|
+
await writeText(filePath, next);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { addCustomElementRegistration, validateCustomElementRegistration, } from './furnace-registration-ast.js';
|
|
2
2
|
export { removeCustomElementRegistration } from './furnace-registration-remove.js';
|
|
3
|
+
export { renameCustomElementRegistration } from './furnace-registration-rename.js';
|
|
3
4
|
/**
|
|
4
5
|
* Adds jar.mn entries that map chrome:// URIs to on-disk paths for a
|
|
5
6
|
* component's files.
|
|
@@ -21,9 +22,9 @@ export { removeCustomElementRegistration } from './furnace-registration-remove.j
|
|
|
21
22
|
* in alphabetical order relative to these.
|
|
22
23
|
* - **Fallback**: if no `content/global/elements/` line exists (empty
|
|
23
24
|
* project), looks for any `content/global/` line and inserts after it.
|
|
24
|
-
* - **Idempotency**: entries already present
|
|
25
|
-
* on `content/global/elements/{file}
|
|
26
|
-
*
|
|
25
|
+
* - **Idempotency**: entries already present anywhere in the file (an exact
|
|
26
|
+
* target match on `content/global/elements/{file}`, or a source match on
|
|
27
|
+
* `(widgets/{tagName}/{file})`) are skipped, including hand-written ones.
|
|
27
28
|
*
|
|
28
29
|
* If Firefox upstream changes the jar.mn section ordering or switches to a
|
|
29
30
|
* different resource registration mechanism, the preflight validation in
|
|
@@ -57,11 +58,20 @@ export declare function addJarMnEntries(engineDir: string, tagName: string, file
|
|
|
57
58
|
* @returns Number of entries inserted (0 when already present, or jar.mn missing)
|
|
58
59
|
*/
|
|
59
60
|
export declare function addLocaleFtlJarMnEntry(engineDir: string, jarMnRelPath: string, tagName: string, chromeSubPath: string): Promise<number>;
|
|
61
|
+
/**
|
|
62
|
+
* True when the locale jar.mn carries a per-widget line for `tagName`.
|
|
63
|
+
* False when the jar.mn is missing. Used by `furnace rename --dry-run` to
|
|
64
|
+
* say whether a rename would re-point a line or leave the jar alone.
|
|
65
|
+
*/
|
|
66
|
+
export declare function hasLocaleFtlJarMnEntry(engineDir: string, jarMnRelPath: string, tagName: string, chromeSubPath: string): Promise<boolean>;
|
|
60
67
|
/**
|
|
61
68
|
* Removes a locale jar.mn entry previously written by `addLocaleFtlJarMnEntry`.
|
|
62
69
|
* Idempotent: if the entry is absent or the file is missing, nothing happens.
|
|
70
|
+
*
|
|
71
|
+
* @returns True when a line was removed, so a rename re-points only an
|
|
72
|
+
* entry that existed instead of inventing one
|
|
63
73
|
*/
|
|
64
|
-
export declare function removeLocaleFtlJarMnEntry(engineDir: string, jarMnRelPath: string, tagName: string, chromeSubPath: string): Promise<
|
|
74
|
+
export declare function removeLocaleFtlJarMnEntry(engineDir: string, jarMnRelPath: string, tagName: string, chromeSubPath: string): Promise<boolean>;
|
|
65
75
|
/**
|
|
66
76
|
* Removes all jar.mn entries for a given tag name.
|
|
67
77
|
*
|
|
@@ -27,8 +27,24 @@ function detectJarMnIndent(lines) {
|
|
|
27
27
|
// Re-export everything from the AST module so existing imports keep working
|
|
28
28
|
export { addCustomElementRegistration, validateCustomElementRegistration, } from './furnace-registration-ast.js';
|
|
29
29
|
export { removeCustomElementRegistration } from './furnace-registration-remove.js';
|
|
30
|
+
export { renameCustomElementRegistration } from './furnace-registration-rename.js';
|
|
30
31
|
import { escapeRegex } from '../utils/regex.js';
|
|
31
32
|
import { JAR_MN } from './furnace-constants.js';
|
|
33
|
+
/**
|
|
34
|
+
* True when jar.mn already registers `file` of component `tagName` anywhere
|
|
35
|
+
* in the file: either a line targeting `content/global/elements/<file>` or a
|
|
36
|
+
* line sourcing `(widgets/<tagName>/<file>)`. The position of the existing
|
|
37
|
+
* line is irrelevant. A hand-written line below an unrelated entry counts
|
|
38
|
+
* exactly like one Furnace wrote itself, so apply never adds a second line
|
|
39
|
+
* and mach never fails with "Item already in manifest". Boundary-aware so
|
|
40
|
+
* `moz-card.css` does not match `moz-card-group.css`.
|
|
41
|
+
*/
|
|
42
|
+
function isJarMnFileRegistered(content, tagName, file) {
|
|
43
|
+
const escapedFile = escapeRegex(file);
|
|
44
|
+
const target = new RegExp(`content/global/elements/${escapedFile}(?:\\s|$)`, 'm');
|
|
45
|
+
const source = new RegExp(`\\(widgets/${escapeRegex(tagName)}/${escapedFile}\\)`);
|
|
46
|
+
return target.test(content) || source.test(content);
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
49
|
* Adds jar.mn entries that map chrome:// URIs to on-disk paths for a
|
|
34
50
|
* component's files.
|
|
@@ -50,9 +66,9 @@ import { JAR_MN } from './furnace-constants.js';
|
|
|
50
66
|
* in alphabetical order relative to these.
|
|
51
67
|
* - **Fallback**: if no `content/global/elements/` line exists (empty
|
|
52
68
|
* project), looks for any `content/global/` line and inserts after it.
|
|
53
|
-
* - **Idempotency**: entries already present
|
|
54
|
-
* on `content/global/elements/{file}
|
|
55
|
-
*
|
|
69
|
+
* - **Idempotency**: entries already present anywhere in the file (an exact
|
|
70
|
+
* target match on `content/global/elements/{file}`, or a source match on
|
|
71
|
+
* `(widgets/{tagName}/{file})`) are skipped, including hand-written ones.
|
|
56
72
|
*
|
|
57
73
|
* If Firefox upstream changes the jar.mn section ordering or switches to a
|
|
58
74
|
* different resource registration mechanism, the preflight validation in
|
|
@@ -70,9 +86,8 @@ export async function addJarMnEntries(engineDir, tagName, files) {
|
|
|
70
86
|
}
|
|
71
87
|
let content = await readText(filePath);
|
|
72
88
|
const lines = content.split('\n');
|
|
73
|
-
// Filter to files not already registered
|
|
74
|
-
|
|
75
|
-
const newFiles = files.filter((f) => !new RegExp(`content/global/elements/${escapeRegex(f)}(?:\\s|$)`, 'm').test(content));
|
|
89
|
+
// Filter to files not already registered anywhere in the jar.
|
|
90
|
+
const newFiles = [...new Set(files)].filter((f) => !isJarMnFileRegistered(content, tagName, f));
|
|
76
91
|
if (newFiles.length === 0)
|
|
77
92
|
return 0;
|
|
78
93
|
// Build new entry lines using the indent detected from existing entries.
|
|
@@ -181,22 +196,41 @@ function detectLocaleJarMnIndent(lines, chromeSubPath) {
|
|
|
181
196
|
// Fall back to any existing `locale/...` indent before giving up.
|
|
182
197
|
return detectIndent(lines, exact) ?? detectIndent(lines, /^(\s+)locale\//) ?? ' ';
|
|
183
198
|
}
|
|
199
|
+
/** Matches a per-widget locale jar.mn line for `<chromeSubPath>/<tagName>.ftl`. */
|
|
200
|
+
function localeFtlJarMnPattern(tagName, chromeSubPath) {
|
|
201
|
+
return new RegExp(`locale\\/(?:@AB_CD@|[a-zA-Z-]+)\\/${escapeRegex(chromeSubPath)}\\/${escapeRegex(tagName)}\\.ftl`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* True when the locale jar.mn carries a per-widget line for `tagName`.
|
|
205
|
+
* False when the jar.mn is missing. Used by `furnace rename --dry-run` to
|
|
206
|
+
* say whether a rename would re-point a line or leave the jar alone.
|
|
207
|
+
*/
|
|
208
|
+
export async function hasLocaleFtlJarMnEntry(engineDir, jarMnRelPath, tagName, chromeSubPath) {
|
|
209
|
+
const filePath = join(engineDir, jarMnRelPath);
|
|
210
|
+
if (!(await pathExists(filePath)))
|
|
211
|
+
return false;
|
|
212
|
+
return localeFtlJarMnPattern(tagName, chromeSubPath).test(await readText(filePath));
|
|
213
|
+
}
|
|
184
214
|
/**
|
|
185
215
|
* Removes a locale jar.mn entry previously written by `addLocaleFtlJarMnEntry`.
|
|
186
216
|
* Idempotent: if the entry is absent or the file is missing, nothing happens.
|
|
217
|
+
*
|
|
218
|
+
* @returns True when a line was removed, so a rename re-points only an
|
|
219
|
+
* entry that existed instead of inventing one
|
|
187
220
|
*/
|
|
188
221
|
export async function removeLocaleFtlJarMnEntry(engineDir, jarMnRelPath, tagName, chromeSubPath) {
|
|
189
222
|
const filePath = join(engineDir, jarMnRelPath);
|
|
190
223
|
if (!(await pathExists(filePath))) {
|
|
191
|
-
return;
|
|
224
|
+
return false;
|
|
192
225
|
}
|
|
193
226
|
const content = await readText(filePath);
|
|
194
227
|
const lines = content.split('\n');
|
|
195
|
-
const pattern =
|
|
228
|
+
const pattern = localeFtlJarMnPattern(tagName, chromeSubPath);
|
|
196
229
|
const filtered = lines.filter((line) => !pattern.test(line));
|
|
197
230
|
if (filtered.length === lines.length)
|
|
198
|
-
return;
|
|
231
|
+
return false;
|
|
199
232
|
await writeText(filePath, filtered.join('\n'));
|
|
233
|
+
return true;
|
|
200
234
|
}
|
|
201
235
|
/**
|
|
202
236
|
* Removes all jar.mn entries for a given tag name.
|
|
@@ -284,7 +318,7 @@ export async function validateJarMnInsertionForFiles(engineDir, tagName, files)
|
|
|
284
318
|
}
|
|
285
319
|
const content = await readText(filePath);
|
|
286
320
|
const lines = content.split('\n');
|
|
287
|
-
const newFiles = files.filter((f) => !
|
|
321
|
+
const newFiles = files.filter((f) => !isJarMnFileRegistered(content, tagName, f));
|
|
288
322
|
if (newFiles.length === 0)
|
|
289
323
|
return;
|
|
290
324
|
const elementLinePattern = /^\s+content\/global\/elements\/([^.]+)\./;
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* The check is advisory only: errors (broken furnace config, partial
|
|
9
9
|
* state, transient filesystem failure) must never block the caller.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Names of the tracked override and custom components whose source changed
|
|
13
|
+
* on disk since the last apply, i.e. edits engine/ does not hold yet.
|
|
14
|
+
* Throws on a broken config or state; {@link warnIfFurnaceStale} downgrades
|
|
15
|
+
* that.
|
|
16
|
+
*/
|
|
17
|
+
export declare function listStaleFurnaceComponents(projectRoot: string): Promise<string[]>;
|
|
11
18
|
/**
|
|
12
19
|
* Emits a warning when any tracked override or custom component has
|
|
13
20
|
* changed on disk since the last apply. Safe to call from any build-time
|
|
@@ -13,6 +13,39 @@ import { pathExists } from '../utils/fs.js';
|
|
|
13
13
|
import { verbose, warn } from '../utils/logger.js';
|
|
14
14
|
import { extractComponentChecksums, hasComponentChanged } from './furnace-apply-helpers.js';
|
|
15
15
|
import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceState, } from './furnace-config.js';
|
|
16
|
+
/**
|
|
17
|
+
* Names of the tracked override and custom components whose source changed
|
|
18
|
+
* on disk since the last apply, i.e. edits engine/ does not hold yet.
|
|
19
|
+
* Throws on a broken config or state; {@link warnIfFurnaceStale} downgrades
|
|
20
|
+
* that.
|
|
21
|
+
*/
|
|
22
|
+
export async function listStaleFurnaceComponents(projectRoot) {
|
|
23
|
+
if (!(await furnaceConfigExists(projectRoot)))
|
|
24
|
+
return [];
|
|
25
|
+
const config = await loadFurnaceConfig(projectRoot);
|
|
26
|
+
const state = await loadFurnaceState(projectRoot);
|
|
27
|
+
const furnacePaths = getFurnacePaths(projectRoot);
|
|
28
|
+
if (!state.appliedChecksums)
|
|
29
|
+
return [];
|
|
30
|
+
const stale = [];
|
|
31
|
+
for (const name of Object.keys(config.overrides)) {
|
|
32
|
+
const dir = `${furnacePaths.overridesDir}/${name}`;
|
|
33
|
+
if (!(await pathExists(dir)))
|
|
34
|
+
continue;
|
|
35
|
+
const prev = extractComponentChecksums(state.appliedChecksums, 'override', name);
|
|
36
|
+
if (await hasComponentChanged(dir, prev))
|
|
37
|
+
stale.push(name);
|
|
38
|
+
}
|
|
39
|
+
for (const name of Object.keys(config.custom)) {
|
|
40
|
+
const dir = `${furnacePaths.customDir}/${name}`;
|
|
41
|
+
if (!(await pathExists(dir)))
|
|
42
|
+
continue;
|
|
43
|
+
const prev = extractComponentChecksums(state.appliedChecksums, 'custom', name);
|
|
44
|
+
if (await hasComponentChanged(dir, prev))
|
|
45
|
+
stale.push(name);
|
|
46
|
+
}
|
|
47
|
+
return stale;
|
|
48
|
+
}
|
|
16
49
|
/**
|
|
17
50
|
* Emits a warning when any tracked override or custom component has
|
|
18
51
|
* changed on disk since the last apply. Safe to call from any build-time
|
|
@@ -21,30 +54,7 @@ import { furnaceConfigExists, getFurnacePaths, loadFurnaceConfig, loadFurnaceSta
|
|
|
21
54
|
*/
|
|
22
55
|
export async function warnIfFurnaceStale(projectRoot) {
|
|
23
56
|
try {
|
|
24
|
-
|
|
25
|
-
return;
|
|
26
|
-
const config = await loadFurnaceConfig(projectRoot);
|
|
27
|
-
const state = await loadFurnaceState(projectRoot);
|
|
28
|
-
const furnacePaths = getFurnacePaths(projectRoot);
|
|
29
|
-
if (!state.appliedChecksums)
|
|
30
|
-
return;
|
|
31
|
-
const stale = [];
|
|
32
|
-
for (const name of Object.keys(config.overrides)) {
|
|
33
|
-
const dir = `${furnacePaths.overridesDir}/${name}`;
|
|
34
|
-
if (!(await pathExists(dir)))
|
|
35
|
-
continue;
|
|
36
|
-
const prev = extractComponentChecksums(state.appliedChecksums, 'override', name);
|
|
37
|
-
if (await hasComponentChanged(dir, prev))
|
|
38
|
-
stale.push(name);
|
|
39
|
-
}
|
|
40
|
-
for (const name of Object.keys(config.custom)) {
|
|
41
|
-
const dir = `${furnacePaths.customDir}/${name}`;
|
|
42
|
-
if (!(await pathExists(dir)))
|
|
43
|
-
continue;
|
|
44
|
-
const prev = extractComponentChecksums(state.appliedChecksums, 'custom', name);
|
|
45
|
-
if (await hasComponentChanged(dir, prev))
|
|
46
|
-
stale.push(name);
|
|
47
|
-
}
|
|
57
|
+
const stale = await listStaleFurnaceComponents(projectRoot);
|
|
48
58
|
if (stale.length > 0) {
|
|
49
59
|
warn(`Furnace component${stale.length === 1 ? '' : 's'} modified since last apply: ${stale.join(', ')}. ` +
|
|
50
60
|
'Run "fireforge furnace apply" (or "fireforge build" which auto-applies) to update the engine.');
|