@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
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
/**
|
|
3
|
-
* Validation for `patchLint
|
|
3
|
+
* Validation for the size-rule keys of `patchLint`: `fileSizeThresholds`,
|
|
4
|
+
* `fileSizeMetric`, `patchSizeThresholds`, `patchSizeMetric` and
|
|
5
|
+
* `patchSizeRegistrationFiles`.
|
|
4
6
|
*
|
|
5
7
|
* Split out of `config-validate.ts`, which is at its per-file line budget.
|
|
6
8
|
*/
|
|
7
9
|
import { ConfigError } from '../errors/config.js';
|
|
8
10
|
import { isObject } from '../utils/validation.js';
|
|
9
11
|
import { DEFAULT_FILE_SIZE_THRESHOLDS } from './patch-lint-file-size.js';
|
|
12
|
+
import { resolvePatchSizeThresholds, } from './patch-lint-size-thresholds.js';
|
|
10
13
|
/**
|
|
11
14
|
* Parses one `patchLint.fileSizeThresholds.<tier>` triple. Every field is
|
|
12
15
|
* optional and merges over the built-in default. Values must be positive
|
|
@@ -39,7 +42,7 @@ function parseFileSizeTier(raw, field) {
|
|
|
39
42
|
* against the merged triple (overrides on top of the defaults), so setting
|
|
40
43
|
* only `warning` cannot land below the default `notice`.
|
|
41
44
|
*/
|
|
42
|
-
|
|
45
|
+
function parsePatchLintFileSizeThresholds(raw) {
|
|
43
46
|
if (!isObject(raw)) {
|
|
44
47
|
throw new ConfigError('Config field "patchLint.fileSizeThresholds" must be a plain object');
|
|
45
48
|
}
|
|
@@ -63,3 +66,89 @@ export function parsePatchLintFileSizeThresholds(raw) {
|
|
|
63
66
|
}
|
|
64
67
|
return out;
|
|
65
68
|
}
|
|
69
|
+
/** Throws unless `merged` is ordered `notice <= warning <= error` (a `null` error is unbounded). */
|
|
70
|
+
function assertOrderedTriple(merged, field) {
|
|
71
|
+
if (merged.notice > merged.warning || (merged.error !== null && merged.warning > merged.error)) {
|
|
72
|
+
throw new ConfigError(`Config field "${field}" must satisfy notice <= warning <= error ` +
|
|
73
|
+
`(resolved: ${merged.notice}/${merged.warning}/${merged.error ?? 'none'})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parses one `patchLint.patchSizeThresholds.<tier>` entry: optional
|
|
78
|
+
* `lines` and `files` triples, each validated against its merged result.
|
|
79
|
+
*/
|
|
80
|
+
function parsePatchSizeTier(raw, tier) {
|
|
81
|
+
const field = `patchLint.patchSizeThresholds.${tier}`;
|
|
82
|
+
if (!isObject(raw)) {
|
|
83
|
+
throw new ConfigError(`Config field "${field}" must be a plain object`);
|
|
84
|
+
}
|
|
85
|
+
const out = {};
|
|
86
|
+
for (const key of Object.keys(raw)) {
|
|
87
|
+
if (key !== 'lines' && key !== 'files') {
|
|
88
|
+
throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
|
|
89
|
+
}
|
|
90
|
+
out[key] = parseFileSizeTier(raw[key], `${field}.${key}`);
|
|
91
|
+
}
|
|
92
|
+
const merged = resolvePatchSizeThresholds(tier, { [tier]: out });
|
|
93
|
+
assertOrderedTriple(merged.lines, `${field}.lines`);
|
|
94
|
+
assertOrderedTriple(merged.files, `${field}.files`);
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Parses the `patchLint.patchSizeThresholds` block. */
|
|
98
|
+
function parsePatchLintPatchSizeThresholds(raw) {
|
|
99
|
+
if (!isObject(raw)) {
|
|
100
|
+
throw new ConfigError('Config field "patchLint.patchSizeThresholds" must be a plain object');
|
|
101
|
+
}
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const key of Object.keys(raw)) {
|
|
104
|
+
if (key !== 'general' && key !== 'test' && key !== 'branding') {
|
|
105
|
+
throw new ConfigError(`Config field "patchLint.patchSizeThresholds" has unknown key "${key}"`);
|
|
106
|
+
}
|
|
107
|
+
out[key] = parsePatchSizeTier(raw[key], key);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
/** Parses an enum-valued `patchLint` key, naming the allowed values on failure. */
|
|
112
|
+
function parseEnum(raw, field, values) {
|
|
113
|
+
if (typeof raw !== 'string' || !values.includes(raw)) {
|
|
114
|
+
throw new ConfigError(`Config field "${field}" must be one of: ${values.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
return raw;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parses every size-rule key of the `patchLint` block.
|
|
120
|
+
*
|
|
121
|
+
* @param read - Reads one raw `patchLint` field by key
|
|
122
|
+
* @returns The parsed fields, only those present
|
|
123
|
+
*/
|
|
124
|
+
export function parsePatchLintSizeFields(read) {
|
|
125
|
+
const out = {};
|
|
126
|
+
const fileSizeThresholds = read('fileSizeThresholds');
|
|
127
|
+
if (fileSizeThresholds !== undefined) {
|
|
128
|
+
out.fileSizeThresholds = parsePatchLintFileSizeThresholds(fileSizeThresholds);
|
|
129
|
+
}
|
|
130
|
+
const fileSizeMetric = read('fileSizeMetric');
|
|
131
|
+
if (fileSizeMetric !== undefined) {
|
|
132
|
+
out.fileSizeMetric = parseEnum(fileSizeMetric, 'patchLint.fileSizeMetric', ['raw', 'code']);
|
|
133
|
+
}
|
|
134
|
+
const patchSizeThresholds = read('patchSizeThresholds');
|
|
135
|
+
if (patchSizeThresholds !== undefined) {
|
|
136
|
+
out.patchSizeThresholds = parsePatchLintPatchSizeThresholds(patchSizeThresholds);
|
|
137
|
+
}
|
|
138
|
+
const patchSizeMetric = read('patchSizeMetric');
|
|
139
|
+
if (patchSizeMetric !== undefined) {
|
|
140
|
+
out.patchSizeMetric = parseEnum(patchSizeMetric, 'patchLint.patchSizeMetric', [
|
|
141
|
+
'diff',
|
|
142
|
+
'addedCode',
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
const registration = read('patchSizeRegistrationFiles');
|
|
146
|
+
if (registration !== undefined) {
|
|
147
|
+
if (!Array.isArray(registration) ||
|
|
148
|
+
registration.some((v) => typeof v !== 'string' || v.trim() === '')) {
|
|
149
|
+
throw new ConfigError('Config field "patchLint.patchSizeRegistrationFiles" must be an array of non-empty strings');
|
|
150
|
+
}
|
|
151
|
+
out.patchSizeRegistrationFiles = registration;
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
2
|
+
import type { ParsedRecord } from '../utils/parse.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a `history.path` value is usable: a non-empty, project-relative
|
|
5
|
+
* path that stays inside the project and names a file, not the root.
|
|
6
|
+
* Shared with the history writer, which reads the raw config and must agree
|
|
7
|
+
* with validation about what it may write to.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isValidHistoryPath(value: unknown): value is string;
|
|
10
|
+
/** Parses the optional `history` block. */
|
|
11
|
+
export declare function parseHistoryBlock(rec: ParsedRecord, config: FireForgeConfig): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { ConfigError } from '../errors/config.js';
|
|
3
|
+
import { isContainedRelativePath } from '../utils/paths.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whether a `history.path` value is usable: a non-empty, project-relative
|
|
6
|
+
* path that stays inside the project and names a file, not the root.
|
|
7
|
+
* Shared with the history writer, which reads the raw config and must agree
|
|
8
|
+
* with validation about what it may write to.
|
|
9
|
+
*/
|
|
10
|
+
export function isValidHistoryPath(value) {
|
|
11
|
+
return (typeof value === 'string' &&
|
|
12
|
+
value.trim() !== '' &&
|
|
13
|
+
value.trim() !== '.' &&
|
|
14
|
+
!/[\n\r]/.test(value) &&
|
|
15
|
+
isContainedRelativePath(value));
|
|
16
|
+
}
|
|
17
|
+
/** Parses the optional `history` block. */
|
|
18
|
+
export function parseHistoryBlock(rec, config) {
|
|
19
|
+
const raw = rec.raw('history');
|
|
20
|
+
if (raw === undefined)
|
|
21
|
+
return;
|
|
22
|
+
let historyRec;
|
|
23
|
+
try {
|
|
24
|
+
historyRec = rec.object('history');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new ConfigError('Config field "history" must be an object');
|
|
28
|
+
}
|
|
29
|
+
const out = {};
|
|
30
|
+
const path = historyRec.raw('path');
|
|
31
|
+
if (path !== undefined) {
|
|
32
|
+
if (!isValidHistoryPath(path)) {
|
|
33
|
+
throw new ConfigError('Config field "history.path" must be a project-relative file path inside the project');
|
|
34
|
+
}
|
|
35
|
+
out.path = path;
|
|
36
|
+
}
|
|
37
|
+
config.history = out;
|
|
38
|
+
}
|
|
@@ -121,6 +121,105 @@ function assertPolicyRangesDoNotOverlap(ranges, label) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
+
function parseNonEmptyString(raw, label) {
|
|
125
|
+
if (typeof raw !== 'string' || raw.trim() === '') {
|
|
126
|
+
throw new ConfigError(`Config field "${label}" must be a non-empty string`);
|
|
127
|
+
}
|
|
128
|
+
return raw;
|
|
129
|
+
}
|
|
130
|
+
function parseGlobList(raw, label) {
|
|
131
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
132
|
+
throw new ConfigError(`Config field "${label}" must be a non-empty array of path globs`);
|
|
133
|
+
}
|
|
134
|
+
return raw.map((entry, index) => parseNonEmptyString(entry, `${label}[${index}]`));
|
|
135
|
+
}
|
|
136
|
+
function parseUpstreamAllowEntry(raw, label, defaultMarker) {
|
|
137
|
+
if (typeof raw === 'string')
|
|
138
|
+
return parseNonEmptyString(raw, label);
|
|
139
|
+
let rec;
|
|
140
|
+
try {
|
|
141
|
+
rec = parseObject(raw, label);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
throw new ConfigError(`Config field "${label}" must be a path glob or an object`);
|
|
145
|
+
}
|
|
146
|
+
const entry = {
|
|
147
|
+
path: parseNonEmptyString(rec.raw('path'), `${label}.path`),
|
|
148
|
+
};
|
|
149
|
+
const addOnly = rec.raw('addOnly');
|
|
150
|
+
if (addOnly !== undefined) {
|
|
151
|
+
if (typeof addOnly !== 'boolean') {
|
|
152
|
+
throw new ConfigError(`Config field "${label}.addOnly" must be a boolean`);
|
|
153
|
+
}
|
|
154
|
+
entry.addOnly = addOnly;
|
|
155
|
+
}
|
|
156
|
+
const marker = rec.raw('marker');
|
|
157
|
+
if (marker !== undefined)
|
|
158
|
+
entry.marker = parseNonEmptyString(marker, `${label}.marker`);
|
|
159
|
+
if (entry.addOnly === true && entry.marker === undefined && defaultMarker === undefined) {
|
|
160
|
+
throw new ConfigError(`Config field "${label}" sets addOnly but no marker: set "${label}.marker" or "patchPolicy.addOnlyMarker"`);
|
|
161
|
+
}
|
|
162
|
+
return entry;
|
|
163
|
+
}
|
|
164
|
+
function parseScopeRule(raw, label) {
|
|
165
|
+
let rec;
|
|
166
|
+
try {
|
|
167
|
+
rec = parseObject(raw, label);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
throw new ConfigError(`Config field "${label}" must be an object`);
|
|
171
|
+
}
|
|
172
|
+
const rule = { files: parseGlobList(rec.raw('files'), `${label}.files`) };
|
|
173
|
+
const category = rec.raw('category');
|
|
174
|
+
if (category !== undefined) {
|
|
175
|
+
rule.category = parsePatchPolicyCategory(category, `${label}.category`);
|
|
176
|
+
}
|
|
177
|
+
const filenamePattern = optionalConfigString(rec, 'filenamePattern', `${label}.filenamePattern`);
|
|
178
|
+
if (filenamePattern !== undefined) {
|
|
179
|
+
try {
|
|
180
|
+
new RegExp(filenamePattern);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
throw new ConfigError(`Config field "${label}.filenamePattern" must be a valid regular expression: ${toError(error).message}`);
|
|
184
|
+
}
|
|
185
|
+
rule.filenamePattern = filenamePattern;
|
|
186
|
+
}
|
|
187
|
+
if (rule.category === undefined && rule.filenamePattern === undefined) {
|
|
188
|
+
throw new ConfigError(`Config field "${label}" must set "category" or "filenamePattern" to select the patches it scopes`);
|
|
189
|
+
}
|
|
190
|
+
return rule;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Parses the registration-only policy fields: `upstreamAllowlist`,
|
|
194
|
+
* `addOnlyMarker`, `scopeRules` and `verifyDocumentPaths`. All opt-in.
|
|
195
|
+
*/
|
|
196
|
+
function parsePatchPolicyContentRules(rec, out) {
|
|
197
|
+
const addOnlyMarker = rec.raw('addOnlyMarker');
|
|
198
|
+
if (addOnlyMarker !== undefined) {
|
|
199
|
+
out.addOnlyMarker = parseNonEmptyString(addOnlyMarker, 'patchPolicy.addOnlyMarker');
|
|
200
|
+
}
|
|
201
|
+
const allowlist = rec.raw('upstreamAllowlist');
|
|
202
|
+
if (allowlist !== undefined) {
|
|
203
|
+
if (!Array.isArray(allowlist)) {
|
|
204
|
+
throw new ConfigError('Config field "patchPolicy.upstreamAllowlist" must be an array');
|
|
205
|
+
}
|
|
206
|
+
out.upstreamAllowlist = allowlist.map((entry, index) => parseUpstreamAllowEntry(entry, `patchPolicy.upstreamAllowlist[${index}]`, out.addOnlyMarker));
|
|
207
|
+
}
|
|
208
|
+
const scopeRules = rec.raw('scopeRules');
|
|
209
|
+
if (scopeRules !== undefined) {
|
|
210
|
+
if (!Array.isArray(scopeRules)) {
|
|
211
|
+
throw new ConfigError('Config field "patchPolicy.scopeRules" must be an array');
|
|
212
|
+
}
|
|
213
|
+
out.scopeRules = scopeRules.map((entry, index) => parseScopeRule(entry, `patchPolicy.scopeRules[${index}]`));
|
|
214
|
+
}
|
|
215
|
+
const verifyDocumentPaths = rec.raw('verifyDocumentPaths');
|
|
216
|
+
if (verifyDocumentPaths !== undefined) {
|
|
217
|
+
if (typeof verifyDocumentPaths !== 'boolean') {
|
|
218
|
+
throw new ConfigError('Config field "patchPolicy.verifyDocumentPaths" must be a boolean');
|
|
219
|
+
}
|
|
220
|
+
out.verifyDocumentPaths = verifyDocumentPaths;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
124
223
|
/** Parses and validates the optional patch policy config block. */
|
|
125
224
|
export function parsePatchPolicyBlock(rec) {
|
|
126
225
|
const out = { ranges: [] };
|
|
@@ -170,5 +269,6 @@ export function parsePatchPolicyBlock(rec) {
|
|
|
170
269
|
out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${index}]`));
|
|
171
270
|
assertPolicyRangesDoNotOverlap(out.reservedRanges, 'patchPolicy.reservedRanges');
|
|
172
271
|
}
|
|
272
|
+
parsePatchPolicyContentRules(rec, out);
|
|
173
273
|
return out;
|
|
174
274
|
}
|
|
@@ -52,6 +52,13 @@ export function parseTestBlock(rec, config) {
|
|
|
52
52
|
}
|
|
53
53
|
out.reapOrphans = reapOrphans;
|
|
54
54
|
}
|
|
55
|
+
const mochitestPorts = testRec.raw('mochitestPorts');
|
|
56
|
+
if (mochitestPorts !== undefined) {
|
|
57
|
+
if (mochitestPorts !== 'fixed' && mochitestPorts !== 'auto') {
|
|
58
|
+
throw new ConfigError('Config field "test.mochitestPorts" must be "fixed" or "auto"');
|
|
59
|
+
}
|
|
60
|
+
out.mochitestPorts = mochitestPorts;
|
|
61
|
+
}
|
|
55
62
|
config.test = out;
|
|
56
63
|
}
|
|
57
64
|
/** Parses optional project-specific external toolchain doctor probes. */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation for `fireforge.json#patchLint.tokenScales`.
|
|
3
|
+
*/
|
|
4
|
+
import type { PatchLintTokenScale } from '../types/config.js';
|
|
5
|
+
/** Parses and validates `patchLint.tokenScales`. */
|
|
6
|
+
export declare function parsePatchLintTokenScales(raw: unknown): PatchLintTokenScale[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Validation for `fireforge.json#patchLint.tokenScales`.
|
|
4
|
+
*/
|
|
5
|
+
import { ConfigError } from '../errors/config.js';
|
|
6
|
+
import { parseObject } from '../utils/parse.js';
|
|
7
|
+
function parseNames(raw, label, required) {
|
|
8
|
+
if (raw === undefined && !required)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (!Array.isArray(raw) ||
|
|
11
|
+
(required && raw.length === 0) ||
|
|
12
|
+
raw.some((value) => typeof value !== 'string' || value.trim() === '')) {
|
|
13
|
+
throw new ConfigError(`Config field "${label}" must be ${required ? 'a non-empty' : 'an'} array of non-empty strings`);
|
|
14
|
+
}
|
|
15
|
+
return raw;
|
|
16
|
+
}
|
|
17
|
+
function parseTokenScale(raw, label) {
|
|
18
|
+
let rec;
|
|
19
|
+
try {
|
|
20
|
+
rec = parseObject(raw, label);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new ConfigError(`Config field "${label}" must be an object`);
|
|
24
|
+
}
|
|
25
|
+
const properties = parseNames(rec.raw('properties'), `${label}.properties`, true) ?? [];
|
|
26
|
+
const prefix = rec.raw('prefix');
|
|
27
|
+
if (typeof prefix !== 'string' || !/^--[\w-]+$/.test(prefix)) {
|
|
28
|
+
throw new ConfigError(`Config field "${label}.prefix" must be a custom-property prefix such as "--mybrowser-space-"`);
|
|
29
|
+
}
|
|
30
|
+
const scale = { properties, prefix };
|
|
31
|
+
const allowLiterals = parseNames(rec.raw('allowLiterals'), `${label}.allowLiterals`, false);
|
|
32
|
+
if (allowLiterals !== undefined)
|
|
33
|
+
scale.allowLiterals = allowLiterals;
|
|
34
|
+
const allow = parseNames(rec.raw('allow'), `${label}.allow`, false);
|
|
35
|
+
if (allow !== undefined) {
|
|
36
|
+
const malformed = allow.find((name) => !name.startsWith('--'));
|
|
37
|
+
if (malformed !== undefined) {
|
|
38
|
+
throw new ConfigError(`Config field "${label}.allow" entries must be custom-property names starting with "--" (got "${malformed}")`);
|
|
39
|
+
}
|
|
40
|
+
scale.allow = allow;
|
|
41
|
+
}
|
|
42
|
+
return scale;
|
|
43
|
+
}
|
|
44
|
+
/** Parses and validates `patchLint.tokenScales`. */
|
|
45
|
+
export function parsePatchLintTokenScales(raw) {
|
|
46
|
+
if (!Array.isArray(raw)) {
|
|
47
|
+
throw new ConfigError('Config field "patchLint.tokenScales" must be an array');
|
|
48
|
+
}
|
|
49
|
+
return raw.map((entry, index) => parseTokenScale(entry, `patchLint.tokenScales[${index}]`));
|
|
50
|
+
}
|
|
@@ -9,9 +9,12 @@ import { isContainedRelativePath, isExplicitAbsolutePath } from '../utils/paths.
|
|
|
9
9
|
import { describeProductVersionIncompatibility, FIREFOX_PRODUCTS, isObject, isValidAppId, isValidFirefoxCandidate, isValidFirefoxProduct, isValidFirefoxVersion, isValidProjectLicense, PROJECT_LICENSES, } from '../utils/validation.js';
|
|
10
10
|
import { SUPPORTED_CONFIG_ROOT_KEYS } from './config-paths.js';
|
|
11
11
|
import { parseBuildAuditConfig } from './config-validate-build-audit.js';
|
|
12
|
-
import {
|
|
12
|
+
import { parseCheckConfig } from './config-validate-check.js';
|
|
13
|
+
import { parsePatchLintSizeFields } from './config-validate-file-size.js';
|
|
14
|
+
import { parseHistoryBlock } from './config-validate-history.js';
|
|
13
15
|
import { parsePatchPolicyBlock } from './config-validate-patch-policy.js';
|
|
14
16
|
import { parseExternalToolchainsBlock, parseTestBlock } from './config-validate-test-toolchains.js';
|
|
17
|
+
import { parsePatchLintTokenScales } from './config-validate-token-scales.js';
|
|
15
18
|
/**
|
|
16
19
|
* Parses and validates the four required identity fields (`name`,
|
|
17
20
|
* `vendor`, `appId`, `binaryName`): all non-empty strings, with
|
|
@@ -167,6 +170,7 @@ export function validateConfig(data) {
|
|
|
167
170
|
config.buildAudit = parseBuildAuditConfig(buildAuditRaw);
|
|
168
171
|
}
|
|
169
172
|
parseTestBlock(rec, config);
|
|
173
|
+
parseHistoryBlock(rec, config);
|
|
170
174
|
parseExternalToolchainsBlock(rec, config);
|
|
171
175
|
parseWireBlock(rec, config);
|
|
172
176
|
parseLicenseField(rec, config);
|
|
@@ -190,6 +194,11 @@ export function validateConfig(data) {
|
|
|
190
194
|
if (typecheckRec) {
|
|
191
195
|
config.typecheck = parseTypecheckBlock(typecheckRec);
|
|
192
196
|
}
|
|
197
|
+
// `fireforge check` policy and project steps.
|
|
198
|
+
const checkRaw = rec.raw('check');
|
|
199
|
+
if (checkRaw !== undefined) {
|
|
200
|
+
config.check = parseCheckConfig(checkRaw);
|
|
201
|
+
}
|
|
193
202
|
// Warn on unknown root keys
|
|
194
203
|
const knownRootKeys = new Set(SUPPORTED_CONFIG_ROOT_KEYS);
|
|
195
204
|
for (const key of rec.keys()) {
|
|
@@ -372,10 +381,7 @@ function parsePatchLintBlock(rec) {
|
|
|
372
381
|
}
|
|
373
382
|
out.rawColorAllowlist = rawColorAllowlist;
|
|
374
383
|
}
|
|
375
|
-
|
|
376
|
-
if (fileSizeThresholds !== undefined) {
|
|
377
|
-
out.fileSizeThresholds = parsePatchLintFileSizeThresholds(fileSizeThresholds);
|
|
378
|
-
}
|
|
384
|
+
Object.assign(out, parsePatchLintSizeFields((key) => rec.raw(key)));
|
|
379
385
|
const forwardRegistration = rec.raw('forwardRegistration');
|
|
380
386
|
if (forwardRegistration !== undefined) {
|
|
381
387
|
if (forwardRegistration !== 'support-files' && forwardRegistration !== 'extended') {
|
|
@@ -383,6 +389,9 @@ function parsePatchLintBlock(rec) {
|
|
|
383
389
|
}
|
|
384
390
|
out.forwardRegistration = forwardRegistration;
|
|
385
391
|
}
|
|
392
|
+
const tokenScales = rec.raw('tokenScales');
|
|
393
|
+
if (tokenScales !== undefined)
|
|
394
|
+
out.tokenScales = parsePatchLintTokenScales(tokenScales);
|
|
386
395
|
for (const key of PATCH_LINT_SEVERITY_GATE_KEYS) {
|
|
387
396
|
const gate = parseSeverityGate(rec.raw(key), `patchLint.${key}`);
|
|
388
397
|
if (gate !== undefined)
|
|
@@ -54,6 +54,11 @@ export declare function writeConfig(root: string, config: FireForgeConfig): Prom
|
|
|
54
54
|
* This is used by CLI `config --force`, where callers may intentionally write
|
|
55
55
|
* keys or value shapes outside the validated FireForgeConfig schema.
|
|
56
56
|
*
|
|
57
|
+
* An existing file is edited in place: only the values that differ from the
|
|
58
|
+
* document on disk are rewritten, so a one-key change keeps the file's
|
|
59
|
+
* formatting (compact objects and arrays, indentation, key order) and stays
|
|
60
|
+
* a one-hunk diff. A missing or unparseable file is written in full.
|
|
61
|
+
*
|
|
57
62
|
* Individual writes are atomic via {@link writeJson} (temp file + rename),
|
|
58
63
|
* but atomicity alone does not prevent lost updates across concurrent
|
|
59
64
|
* writers: each writer reads an old copy, mutates its own in-memory view,
|
package/dist/src/core/config.js
CHANGED
|
@@ -16,6 +16,7 @@ import { isObject } from '../utils/validation.js';
|
|
|
16
16
|
import { getProjectPaths } from './config-paths.js';
|
|
17
17
|
import { validateConfig } from './config-validate.js';
|
|
18
18
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
19
|
+
import { rewriteJsonPreservingFormat } from './json-preserving-edit.js';
|
|
19
20
|
// ---- re-exports ----
|
|
20
21
|
export { mutateConfig } from './config-mutate.js';
|
|
21
22
|
export { FIREFORGE_DIR, getProjectPaths, SUPPORTED_CONFIG_PATHS, SUPPORTED_CONFIG_ROOT_KEYS, } from './config-paths.js';
|
|
@@ -109,6 +110,11 @@ export async function writeConfig(root, config) {
|
|
|
109
110
|
* This is used by CLI `config --force`, where callers may intentionally write
|
|
110
111
|
* keys or value shapes outside the validated FireForgeConfig schema.
|
|
111
112
|
*
|
|
113
|
+
* An existing file is edited in place: only the values that differ from the
|
|
114
|
+
* document on disk are rewritten, so a one-key change keeps the file's
|
|
115
|
+
* formatting (compact objects and arrays, indentation, key order) and stays
|
|
116
|
+
* a one-hunk diff. A missing or unparseable file is written in full.
|
|
117
|
+
*
|
|
112
118
|
* Individual writes are atomic via {@link writeJson} (temp file + rename),
|
|
113
119
|
* but atomicity alone does not prevent lost updates across concurrent
|
|
114
120
|
* writers: each writer reads an old copy, mutates its own in-memory view,
|
|
@@ -119,6 +125,16 @@ export async function writeConfig(root, config) {
|
|
|
119
125
|
*/
|
|
120
126
|
export async function writeConfigDocument(root, config) {
|
|
121
127
|
const paths = getProjectPaths(root);
|
|
128
|
+
if (await fsUtils.pathExists(paths.config)) {
|
|
129
|
+
const current = await fsUtils.readText(paths.config);
|
|
130
|
+
// `FireForgeConfig` is plain parsed-config data, so it is a JSON object.
|
|
131
|
+
const edited = rewriteJsonPreservingFormat(current, config);
|
|
132
|
+
if (edited !== undefined) {
|
|
133
|
+
if (edited !== current)
|
|
134
|
+
await fsUtils.writeText(paths.config, edited);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
122
138
|
await fsUtils.writeJson(paths.config, config);
|
|
123
139
|
}
|
|
124
140
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch-time half of per-path coverage. A scoped build that followed a
|
|
3
|
+
* full build carries the full build's anchor forward instead of collapsing
|
|
4
|
+
* the claim to its own paths. A requested path outside the scoped list is
|
|
5
|
+
* then still covered while its own staging inputs are unchanged against
|
|
6
|
+
* that anchor, and a refusal names the inputs that did change.
|
|
7
|
+
*/
|
|
8
|
+
import type { BuildBaseline } from './build-baseline-types.js';
|
|
9
|
+
/** A requested path no recorded claim covers, with the inputs that explain it. */
|
|
10
|
+
export interface UncoveredRequest {
|
|
11
|
+
path: string;
|
|
12
|
+
/** Staging inputs changed since the carried full build. Empty when unknown. */
|
|
13
|
+
changed: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Re-checks the paths the scoped list does not cover against the carried
|
|
17
|
+
* full-build anchor. Returns the paths that stay uncovered.
|
|
18
|
+
*
|
|
19
|
+
* @param engineDir - Path to the engine directory
|
|
20
|
+
* @param baseline - Last successful build's baseline
|
|
21
|
+
* @param uncovered - Request paths outside the scoped list
|
|
22
|
+
* @param fullSuite - The sentinel standing for a path-less (full-suite) request
|
|
23
|
+
*/
|
|
24
|
+
export declare function filterByCarriedFullCoverage(engineDir: string, baseline: BuildBaseline | undefined, uncovered: readonly string[], fullSuite: string): Promise<UncoveredRequest[]>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Dispatch-time half of per-path coverage. A scoped build that followed a
|
|
4
|
+
* full build carries the full build's anchor forward instead of collapsing
|
|
5
|
+
* the claim to its own paths. A requested path outside the scoped list is
|
|
6
|
+
* then still covered while its own staging inputs are unchanged against
|
|
7
|
+
* that anchor, and a refusal names the inputs that did change.
|
|
8
|
+
*/
|
|
9
|
+
import { toError } from '../utils/errors.js';
|
|
10
|
+
import { verbose } from '../utils/logger.js';
|
|
11
|
+
import { collectStagingInputs, findChangedInputs, hashEngineFile, readEngineContentState, } from './coverage-records.js';
|
|
12
|
+
/**
|
|
13
|
+
* Re-checks the paths the scoped list does not cover against the carried
|
|
14
|
+
* full-build anchor. Returns the paths that stay uncovered.
|
|
15
|
+
*
|
|
16
|
+
* @param engineDir - Path to the engine directory
|
|
17
|
+
* @param baseline - Last successful build's baseline
|
|
18
|
+
* @param uncovered - Request paths outside the scoped list
|
|
19
|
+
* @param fullSuite - The sentinel standing for a path-less (full-suite) request
|
|
20
|
+
*/
|
|
21
|
+
export async function filterByCarriedFullCoverage(engineDir, baseline, uncovered, fullSuite) {
|
|
22
|
+
const unresolved = uncovered.map((path) => ({ path, changed: [] }));
|
|
23
|
+
const anchor = baseline?.fullCoverageAnchor;
|
|
24
|
+
if (anchor === undefined || uncovered.length === 0)
|
|
25
|
+
return unresolved;
|
|
26
|
+
if ((await hashEngineFile(engineDir, 'mozconfig')) !== anchor.mozconfigHash)
|
|
27
|
+
return unresolved;
|
|
28
|
+
let state;
|
|
29
|
+
try {
|
|
30
|
+
state = await readEngineContentState(engineDir);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
verbose(`Coverage check: engine state unreadable — ${toError(error).message}`);
|
|
34
|
+
return unresolved;
|
|
35
|
+
}
|
|
36
|
+
const remaining = [];
|
|
37
|
+
for (const path of uncovered) {
|
|
38
|
+
const inputs = path === fullSuite ? undefined : await collectStagingInputs(engineDir, path);
|
|
39
|
+
const changed = await findChangedInputs(state, anchor, inputs);
|
|
40
|
+
if (changed === undefined || changed.length > 0)
|
|
41
|
+
remaining.push({ path, changed: changed ?? [] });
|
|
42
|
+
}
|
|
43
|
+
return remaining;
|
|
44
|
+
}
|