@hominis/fireforge 0.44.4 → 0.46.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 +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Decides whether a changed `jar.mn` really forces the pre-test build to
|
|
4
|
+
* escalate from `mach build faster` to a full `mach build`.
|
|
5
|
+
*
|
|
6
|
+
* The 0.41.0 rule was `any changed jar.mn ⇒ full build`, which costs ~10
|
|
7
|
+
* minutes against ~20 s for a chrome-only slice and is paid TWICE per
|
|
8
|
+
* green/no-change pair on a Furnace-touching slice. A downstream experiment
|
|
9
|
+
* (two clean runs, 2026-08-29, against 0.44.4) showed the rule is over-broad
|
|
10
|
+
* for the common case: a jar-only registration of a NEW content file added
|
|
11
|
+
* to an EXISTING `dist/bin` manifest was installed by a plain
|
|
12
|
+
* `fireforge build --ui` both times — `faster/install_dist_bin_browser` named
|
|
13
|
+
* the destination, the file landed in `dist/bin/…/content/browser/` and in
|
|
14
|
+
* the `.app` bundle, and a plain `fireforge test` fetched it over
|
|
15
|
+
* `chrome://` at runtime. Neither run escalated, and neither needed to.
|
|
16
|
+
*
|
|
17
|
+
* What the experiment did NOT exercise, and what therefore still escalates:
|
|
18
|
+
*
|
|
19
|
+
* - a NEW `jar.mn` file. The install manifest for a manifest the backend
|
|
20
|
+
* has never seen does not exist yet, and `mach build faster` consumes
|
|
21
|
+
* install manifests rather than regenerating the backend that writes
|
|
22
|
+
* them.
|
|
23
|
+
* - a manifest whose jar declaration carries a bracketed base-directory
|
|
24
|
+
* prefix (`[some/dir] name.jar:`), which redirects the install
|
|
25
|
+
* destination away from the default chrome root. That redirect is the
|
|
26
|
+
* "non-dist/bin target" half nobody has run.
|
|
27
|
+
*
|
|
28
|
+
* Everything else about the rule is unchanged, and every failure to read or
|
|
29
|
+
* parse a manifest escalates: this narrows a conservative rule, so its
|
|
30
|
+
* error direction stays the slow-but-correct one.
|
|
31
|
+
*/
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { toError } from '../utils/errors.js';
|
|
34
|
+
import { readText } from '../utils/fs.js';
|
|
35
|
+
import { verbose } from '../utils/logger.js';
|
|
36
|
+
import { isJarManifestPath } from './build-audit.js';
|
|
37
|
+
import { getUntrackedFilesInDir } from './git-status.js';
|
|
38
|
+
/**
|
|
39
|
+
* A jar declaration line: an optional bracketed base-directory prefix, then
|
|
40
|
+
* the jar name. Matched at column zero because entry lines inside a section
|
|
41
|
+
* are indented — an indented `foo.jar:` is content, not a declaration.
|
|
42
|
+
*/
|
|
43
|
+
const JAR_DECLARATION_PATTERN = /^(\[[^\]]*\]\s*)?[^\s[\]]+\.jar:\s*$/;
|
|
44
|
+
/** True when the jar declaration redirects the install base directory. */
|
|
45
|
+
function declaresNonDefaultBase(line) {
|
|
46
|
+
const match = JAR_DECLARATION_PATTERN.exec(line);
|
|
47
|
+
if (!match)
|
|
48
|
+
return false;
|
|
49
|
+
return match[1] !== undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* True when a changed `jar.mn` is NEW to the last successful build.
|
|
53
|
+
*
|
|
54
|
+
* Untracked in the engine repo is the reliable tell: a fork's engine tree is
|
|
55
|
+
* a checkout of upstream with patches applied to the WORKTREE, so a manifest
|
|
56
|
+
* a patch created is untracked and one that shipped upstream is tracked. The
|
|
57
|
+
* baseline's fingerprints cannot answer this on their own — they record only
|
|
58
|
+
* the paths that were DIRTY at the last build, so a clean pre-existing
|
|
59
|
+
* manifest has no entry either.
|
|
60
|
+
*/
|
|
61
|
+
async function isNewJarManifest(engineDir, path) {
|
|
62
|
+
const untracked = await getUntrackedFilesInDir(engineDir, path);
|
|
63
|
+
return untracked.includes(path);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Evaluates every changed `jar.mn` against the narrowed rule.
|
|
67
|
+
*
|
|
68
|
+
* @param engineDir - Absolute engine directory
|
|
69
|
+
* @param changedPaths - Engine-relative paths changed since the last
|
|
70
|
+
* successful build (non-`jar.mn` entries are ignored)
|
|
71
|
+
* @param baseline - Last successful build's baseline (reserved: the
|
|
72
|
+
* fingerprint map already gated which paths reach here)
|
|
73
|
+
* @returns The escalation decision with per-manifest causes
|
|
74
|
+
*/
|
|
75
|
+
export async function evaluateJarManifestEscalation(engineDir, changedPaths, baseline) {
|
|
76
|
+
void baseline;
|
|
77
|
+
const causes = [];
|
|
78
|
+
const cleared = [];
|
|
79
|
+
for (const path of changedPaths.filter(isJarManifestPath)) {
|
|
80
|
+
let isNew;
|
|
81
|
+
try {
|
|
82
|
+
isNew = await isNewJarManifest(engineDir, path);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Fail closed: an unanswerable "is this new?" keeps the old rule.
|
|
86
|
+
verbose(`jar escalation: could not classify engine/${path} (${toError(error).message}); escalating.`);
|
|
87
|
+
causes.push({ path, reason: 'could not determine whether the manifest is new' });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (isNew) {
|
|
91
|
+
causes.push({ path, reason: 'new jar.mn (no install manifest exists for it yet)' });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
let body;
|
|
95
|
+
try {
|
|
96
|
+
body = await readText(join(engineDir, path));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
verbose(`jar escalation: could not read engine/${path} (${toError(error).message}); escalating.`);
|
|
100
|
+
causes.push({ path, reason: 'manifest could not be read' });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const redirected = body
|
|
104
|
+
.split(/\r?\n/)
|
|
105
|
+
.filter((line) => declaresNonDefaultBase(line))
|
|
106
|
+
.map((line) => line.trim());
|
|
107
|
+
if (redirected.length > 0) {
|
|
108
|
+
causes.push({
|
|
109
|
+
path,
|
|
110
|
+
reason: `jar declaration redirects the install base directory (${redirected[0] ?? ''})`,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
cleared.push(path);
|
|
115
|
+
}
|
|
116
|
+
return { escalate: causes.length > 0, causes, cleared };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Renders the operator-facing escalation notice. Names the manifest AND the
|
|
120
|
+
* reason: "a jar.mn changed" is the sentence that made the previous rule
|
|
121
|
+
* impossible to argue with from outside.
|
|
122
|
+
*/
|
|
123
|
+
export function formatJarEscalationNotice(decision) {
|
|
124
|
+
const detail = decision.causes.map((c) => `engine/${c.path} (${c.reason})`).join('; ');
|
|
125
|
+
return (`Escalating this pre-test build to a full mach build so new install-manifest destinations ` +
|
|
126
|
+
`are created: ${detail}. A changed jar.mn that merely adds entries to an existing ` +
|
|
127
|
+
`dist/bin manifest no longer escalates.`);
|
|
128
|
+
}
|
|
@@ -143,7 +143,7 @@ export function formatUnexportedDriftWarning(files) {
|
|
|
143
143
|
const owner = entry.owner !== undefined ? ` (owned by ${entry.owner})` : '';
|
|
144
144
|
return ` ${entry.file} [${entry.classification}]${owner}`;
|
|
145
145
|
});
|
|
146
|
-
return (`This build is about to rewrite ${
|
|
146
|
+
return (`This build is about to rewrite ${files.length} engine file(s) from FireForge-owned ` +
|
|
147
147
|
'sources, and their current content matches NEITHER a patch body NOR the pristine ' +
|
|
148
148
|
'baseline — so the edits below are recorded nowhere and the build will destroy them:\n' +
|
|
149
149
|
`${rows.join('\n')}\n\n` +
|
|
@@ -14,6 +14,11 @@ export interface BuildPreparation {
|
|
|
14
14
|
reconfigured: boolean;
|
|
15
15
|
/** A changed packaging manifest cannot be safely handled by `mach build faster`. */
|
|
16
16
|
fullBuildRequired?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Operator-facing explanation for {@link fullBuildRequired}, naming the
|
|
19
|
+
* manifest and why it still escalates. Absent when no escalation applies.
|
|
20
|
+
*/
|
|
21
|
+
fullBuildReason?: string;
|
|
17
22
|
}
|
|
18
23
|
/** Options for {@link prepareBuildEnvironment}. */
|
|
19
24
|
export interface PrepareBuildOptions {
|
|
@@ -31,8 +36,6 @@ export interface PrepareBuildOptions {
|
|
|
31
36
|
*/
|
|
32
37
|
refuseUnexportedDrift?: boolean | undefined;
|
|
33
38
|
}
|
|
34
|
-
/** Packaging manifests whose graph/destination directories need a full build. */
|
|
35
|
-
export declare function requiresFullBuildForIncrementalTest(path: string): boolean;
|
|
36
39
|
/**
|
|
37
40
|
* Describes an exit code in the shell's 128+N signal convention. Truncated
|
|
38
41
|
* configure/build logs with a signal-shaped exit (e.g. 144 = 128+16, SIGURG
|
|
@@ -12,6 +12,7 @@ import { pathExists } from '../utils/fs.js';
|
|
|
12
12
|
import { info, notice, spinner, verbose, warn } from '../utils/logger.js';
|
|
13
13
|
import { isBrandingSetup, setupBranding } from './branding.js';
|
|
14
14
|
import { isBuildInputPath, isJarManifestPath } from './build-audit.js';
|
|
15
|
+
import { evaluateJarManifestEscalation, formatJarEscalationNotice, } from './build-jar-escalation.js';
|
|
15
16
|
import { findUnexportedDriftAtRisk, formatUnexportedDriftWarning, } from './build-overwrite-guard.js';
|
|
16
17
|
import { collectChangedEnginePaths, dropPathsMatchingFingerprints } from './engine-changes.js';
|
|
17
18
|
import { applyAllComponents } from './furnace-apply.js';
|
|
@@ -23,10 +24,6 @@ import { generateMozconfig, runMachCapture } from './mach.js';
|
|
|
23
24
|
import { explainMachError } from './mach-error-hints.js';
|
|
24
25
|
/** Path fragments of files whose edits invalidate the recursive-make backend. */
|
|
25
26
|
const BACKEND_INVALIDATING_SUFFIXES = ['moz.build', 'moz.configure', 'Makefile.in'];
|
|
26
|
-
/** Packaging manifests whose graph/destination directories need a full build. */
|
|
27
|
-
export function requiresFullBuildForIncrementalTest(path) {
|
|
28
|
-
return isJarManifestPath(path);
|
|
29
|
-
}
|
|
30
27
|
/**
|
|
31
28
|
* Build-input manifests changed since the last SUCCESSFUL build — not
|
|
32
29
|
* merely dirty against HEAD. `collectChangedEnginePaths` unions the
|
|
@@ -107,6 +104,29 @@ function buildConfigureFailureError(captured) {
|
|
|
107
104
|
(hints.length > 0 ? `\n\n${hints.map((hint) => `Hint: ${hint}`).join('\n')}` : '') +
|
|
108
105
|
'\n\nBuild stopped because continuing would hide the real configure failure.', 'mach configure');
|
|
109
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Runs the narrowed `jar.mn` escalation rule over this run's changed build
|
|
109
|
+
* inputs and returns the operator-facing reason when a full build is still
|
|
110
|
+
* required, or undefined when the narrowing cleared every changed manifest.
|
|
111
|
+
*
|
|
112
|
+
* Extracted so `prepareBuildEnvironment` stays under the complexity budget.
|
|
113
|
+
*
|
|
114
|
+
* @param engineDir - Absolute engine directory
|
|
115
|
+
* @param changed - Build inputs changed since the last successful build
|
|
116
|
+
* @param baseline - Last successful build's baseline
|
|
117
|
+
*/
|
|
118
|
+
async function decideJarEscalation(engineDir, changed, baseline) {
|
|
119
|
+
// Packaging manifests whose graph/destination directories MAY need a full
|
|
120
|
+
// build. Path-shape only: `evaluateJarManifestEscalation` decides whether a
|
|
121
|
+
// given changed manifest actually forces one (a new manifest, or one that
|
|
122
|
+
// redirects its install base directory), so an entry added to an existing
|
|
123
|
+
// `dist/bin` manifest no longer costs a ~10-minute build.
|
|
124
|
+
const decision = await evaluateJarManifestEscalation(engineDir, changed.filter(isJarManifestPath), baseline);
|
|
125
|
+
if (decision.cleared.length > 0) {
|
|
126
|
+
verbose(`jar escalation: ${decision.cleared.length} changed jar.mn add entries to an existing dist/bin manifest; not escalating for ${decision.cleared.join(', ')}.`);
|
|
127
|
+
}
|
|
128
|
+
return decision.escalate ? formatJarEscalationNotice(decision) : undefined;
|
|
129
|
+
}
|
|
110
130
|
/**
|
|
111
131
|
* Returns true when the file path matches a pattern that forces
|
|
112
132
|
* `mach configure` to regenerate the backend. Exported for testing.
|
|
@@ -177,10 +197,13 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
177
197
|
// work against a stale recursive-make backend.
|
|
178
198
|
let reconfigured = false;
|
|
179
199
|
let fullBuildRequired = false;
|
|
200
|
+
let fullBuildReason;
|
|
180
201
|
if (options.previousBaseline) {
|
|
181
202
|
const changed = await collectChangedBuildInputs(paths.engine, options.previousBaseline);
|
|
182
203
|
const invalidating = changed.filter(isBackendInvalidatingFile);
|
|
183
|
-
|
|
204
|
+
const jarEscalation = await decideJarEscalation(paths.engine, changed, options.previousBaseline);
|
|
205
|
+
fullBuildRequired = jarEscalation !== undefined;
|
|
206
|
+
fullBuildReason = jarEscalation;
|
|
184
207
|
if (invalidating.length > 0) {
|
|
185
208
|
// Warning severity: this is the "why is this run slow" explanation,
|
|
186
209
|
// and an info-level line does not survive an agent output filter that
|
|
@@ -300,5 +323,10 @@ export async function prepareBuildEnvironment(projectRoot, paths, config, option
|
|
|
300
323
|
mozconfigSpinner.error('Failed to generate mozconfig');
|
|
301
324
|
throw error;
|
|
302
325
|
}
|
|
303
|
-
return {
|
|
326
|
+
return {
|
|
327
|
+
furnaceApplied,
|
|
328
|
+
reconfigured,
|
|
329
|
+
fullBuildRequired,
|
|
330
|
+
...(fullBuildReason !== undefined ? { fullBuildReason } : {}),
|
|
331
|
+
};
|
|
304
332
|
}
|
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
* Project path derivation from a root directory.
|
|
3
3
|
*/
|
|
4
4
|
import type { ProjectPaths } from '../types/config.js';
|
|
5
|
-
/** Name of the configuration file */
|
|
6
|
-
export declare const CONFIG_FILENAME = "fireforge.json";
|
|
7
5
|
/** Name of the fireforge data directory */
|
|
8
6
|
export declare const FIREFORGE_DIR = ".fireforge";
|
|
9
|
-
/** Name of the state file */
|
|
10
|
-
export declare const STATE_FILENAME = "state.json";
|
|
11
7
|
/** Supported top-level fireforge.json keys backed by the current schema. */
|
|
12
|
-
export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
|
|
8
|
+
export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "buildAudit", "test", "externalToolchains", "license", "wire", "patchLint", "patchPolicy", "typecheck", "markerComment"];
|
|
13
9
|
/** Supported config paths that can be read or set without --force. */
|
|
14
|
-
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
|
|
10
|
+
export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "firefox.sha256", "firefox.candidate", "firefox.allowUnverifiedDownload", "build", "build.jobs", "test", "test.canaryPath", "test.canaryTimeoutSeconds", "externalToolchains", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.checkJsTestFiles", "patchLint.checkJsTestShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "patchPolicy", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
|
|
15
11
|
/**
|
|
16
12
|
* Gets all project paths based on a root directory.
|
|
17
13
|
* @param root - Root directory of the project
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
/** Name of the configuration file */
|
|
7
|
-
|
|
7
|
+
const CONFIG_FILENAME = 'fireforge.json';
|
|
8
8
|
/** Name of the fireforge data directory */
|
|
9
9
|
export const FIREFORGE_DIR = '.fireforge';
|
|
10
10
|
/** Name of the state file */
|
|
11
|
-
|
|
11
|
+
const STATE_FILENAME = 'state.json';
|
|
12
12
|
/** Name of the engine directory */
|
|
13
13
|
const ENGINE_DIR = 'engine';
|
|
14
14
|
/** Name of the patches directory */
|
|
@@ -25,6 +25,7 @@ export const SUPPORTED_CONFIG_ROOT_KEYS = [
|
|
|
25
25
|
'binaryName',
|
|
26
26
|
'firefox',
|
|
27
27
|
'build',
|
|
28
|
+
'buildAudit',
|
|
28
29
|
'test',
|
|
29
30
|
'externalToolchains',
|
|
30
31
|
'license',
|
|
@@ -46,6 +47,7 @@ export const SUPPORTED_CONFIG_PATHS = [
|
|
|
46
47
|
'firefox.product',
|
|
47
48
|
'firefox.sha256',
|
|
48
49
|
'firefox.candidate',
|
|
50
|
+
'firefox.allowUnverifiedDownload',
|
|
49
51
|
'build',
|
|
50
52
|
'build.jobs',
|
|
51
53
|
'test',
|
|
@@ -8,12 +8,6 @@ import type { FireForgeState } from '../types/config.js';
|
|
|
8
8
|
* @returns FireForge state
|
|
9
9
|
*/
|
|
10
10
|
export declare function loadState(root: string): Promise<FireForgeState>;
|
|
11
|
-
/**
|
|
12
|
-
* Saves the fireforge state.
|
|
13
|
-
* @param root - Root directory of the project
|
|
14
|
-
* @param state - State to save
|
|
15
|
-
*/
|
|
16
|
-
export declare function saveState(root: string, state: FireForgeState): Promise<void>;
|
|
17
11
|
/**
|
|
18
12
|
* Updates specific fields in the fireforge state.
|
|
19
13
|
* @param root - Root directory of the project
|
|
@@ -24,13 +24,7 @@ function sanitizeProjectState(data) {
|
|
|
24
24
|
const state = {};
|
|
25
25
|
const issues = [];
|
|
26
26
|
const recoveredFields = [];
|
|
27
|
-
const stringFields = [
|
|
28
|
-
'brand',
|
|
29
|
-
'buildMode',
|
|
30
|
-
'lastBuild',
|
|
31
|
-
'downloadedVersion',
|
|
32
|
-
'baseCommit',
|
|
33
|
-
];
|
|
27
|
+
const stringFields = ['brand', 'buildMode', 'downloadedVersion', 'baseCommit'];
|
|
34
28
|
for (const key of stringFields) {
|
|
35
29
|
const value = data[key];
|
|
36
30
|
if (value === undefined) {
|
|
@@ -132,18 +126,6 @@ export async function loadState(root) {
|
|
|
132
126
|
const paths = getProjectPaths(root);
|
|
133
127
|
return loadStateFromPath(paths.state);
|
|
134
128
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Saves the fireforge state.
|
|
137
|
-
* @param root - Root directory of the project
|
|
138
|
-
* @param state - State to save
|
|
139
|
-
*/
|
|
140
|
-
export async function saveState(root, state) {
|
|
141
|
-
const paths = getProjectPaths(root);
|
|
142
|
-
const validatedState = validateFireForgeState(state);
|
|
143
|
-
await withStateFileLock(paths.state, async () => {
|
|
144
|
-
await writeJson(paths.state, validatedState);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
129
|
/**
|
|
148
130
|
* Updates specific fields in the fireforge state.
|
|
149
131
|
* @param root - Root directory of the project
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BuildAuditConfig } from '../types/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses the `buildAudit` block.
|
|
4
|
+
*
|
|
5
|
+
* @param raw - The raw `buildAudit` value from fireforge.json
|
|
6
|
+
* @returns The validated block
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseBuildAuditConfig(raw: unknown): BuildAuditConfig;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Validation for the `buildAudit` block.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `config-validate.ts`, which is at its per-file line budget,
|
|
6
|
+
* following the `config-validate-file-size.ts` precedent.
|
|
7
|
+
*
|
|
8
|
+
* The rules here are stricter than the schema strictly needs, and
|
|
9
|
+
* deliberately so: every entry is a standing suppression of a warning the
|
|
10
|
+
* audit would otherwise raise. A malformed one must fail loudly at config
|
|
11
|
+
* load rather than quietly admit nothing (leaving the operator believing a
|
|
12
|
+
* path is carved out) or quietly admit everything.
|
|
13
|
+
*/
|
|
14
|
+
import { ConfigError } from '../errors/config.js';
|
|
15
|
+
import { isContainedRelativePath } from '../utils/paths.js';
|
|
16
|
+
import { isObject } from '../utils/validation.js';
|
|
17
|
+
/** Parses one `buildAudit.unpackaged[]` entry. */
|
|
18
|
+
function parseUnpackagedEntry(raw, field) {
|
|
19
|
+
if (!isObject(raw)) {
|
|
20
|
+
throw new ConfigError(`Config field "${field}" must be a plain object with "path" and "reason"`);
|
|
21
|
+
}
|
|
22
|
+
for (const key of Object.keys(raw)) {
|
|
23
|
+
if (key !== 'path' && key !== 'reason') {
|
|
24
|
+
throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const path = raw['path'];
|
|
28
|
+
if (typeof path !== 'string' || path.trim().length === 0) {
|
|
29
|
+
throw new ConfigError(`Config field "${field}.path" must be a non-empty string`);
|
|
30
|
+
}
|
|
31
|
+
if (!isContainedRelativePath(path)) {
|
|
32
|
+
throw new ConfigError(`Config field "${field}.path" must be an engine-relative path`);
|
|
33
|
+
}
|
|
34
|
+
if (path.includes('**')) {
|
|
35
|
+
// A `**` carve-out admits an entire subtree, which is how a reviewed
|
|
36
|
+
// exception quietly becomes a blanket one. `*` inside a single segment
|
|
37
|
+
// is enough for the shapes this exists for.
|
|
38
|
+
throw new ConfigError(`Config field "${field}.path" must not use "**" — a carve-out may glob within one path segment only`);
|
|
39
|
+
}
|
|
40
|
+
const reason = raw['reason'];
|
|
41
|
+
if (typeof reason !== 'string' || reason.trim().length === 0) {
|
|
42
|
+
// Required on purpose: this is the one audit class FireForge cannot
|
|
43
|
+
// derive from the tree, so the declaration IS the evidence.
|
|
44
|
+
throw new ConfigError(`Config field "${field}.reason" must be a non-empty string explaining why the file is never packaged`);
|
|
45
|
+
}
|
|
46
|
+
return { path, reason };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parses the `buildAudit` block.
|
|
50
|
+
*
|
|
51
|
+
* @param raw - The raw `buildAudit` value from fireforge.json
|
|
52
|
+
* @returns The validated block
|
|
53
|
+
*/
|
|
54
|
+
export function parseBuildAuditConfig(raw) {
|
|
55
|
+
if (!isObject(raw)) {
|
|
56
|
+
throw new ConfigError('Config field "buildAudit" must be a plain object');
|
|
57
|
+
}
|
|
58
|
+
for (const key of Object.keys(raw)) {
|
|
59
|
+
if (key !== 'unpackaged') {
|
|
60
|
+
throw new ConfigError(`Config field "buildAudit" has unknown key "${key}"`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const unpackaged = raw['unpackaged'];
|
|
64
|
+
if (unpackaged === undefined)
|
|
65
|
+
return {};
|
|
66
|
+
if (!Array.isArray(unpackaged)) {
|
|
67
|
+
throw new ConfigError('Config field "buildAudit.unpackaged" must be an array');
|
|
68
|
+
}
|
|
69
|
+
const entries = unpackaged.map((entry, index) => parseUnpackagedEntry(entry, `buildAudit.unpackaged[${String(index)}]`));
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (seen.has(entry.path)) {
|
|
73
|
+
// Two rows for one path means one of the two reasons is not the
|
|
74
|
+
// operative one, and nothing says which.
|
|
75
|
+
throw new ConfigError(`Config field "buildAudit.unpackaged" declares "${entry.path}" more than once`);
|
|
76
|
+
}
|
|
77
|
+
seen.add(entry.path);
|
|
78
|
+
}
|
|
79
|
+
return { unpackaged: entries };
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation for `patchLint.fileSizeThresholds`.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `config-validate.ts`, which is at its per-file line budget.
|
|
5
|
+
*/
|
|
6
|
+
import type { PatchLintFileSizeThresholds } from '../types/config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Parses the `patchLint.fileSizeThresholds` block. Ordering is validated
|
|
9
|
+
* against the MERGED triple (overrides on top of the defaults), so setting
|
|
10
|
+
* only `warning` cannot land below the default `notice`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parsePatchLintFileSizeThresholds(raw: unknown): PatchLintFileSizeThresholds;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Validation for `patchLint.fileSizeThresholds`.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `config-validate.ts`, which is at its per-file line budget.
|
|
6
|
+
*/
|
|
7
|
+
import { ConfigError } from '../errors/config.js';
|
|
8
|
+
import { isObject } from '../utils/validation.js';
|
|
9
|
+
import { DEFAULT_FILE_SIZE_THRESHOLDS } from './patch-lint-file-size.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parses one `patchLint.fileSizeThresholds.<tier>` triple. Every field is
|
|
12
|
+
* optional and merges over the built-in default; values must be positive
|
|
13
|
+
* integers and stay ordered `notice <= warning <= error`, because an
|
|
14
|
+
* out-of-order triple would silently disable a band instead of failing.
|
|
15
|
+
*/
|
|
16
|
+
function parseFileSizeTier(raw, field) {
|
|
17
|
+
if (!isObject(raw)) {
|
|
18
|
+
throw new ConfigError(`Config field "${field}" must be a plain object`);
|
|
19
|
+
}
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const key of ['notice', 'warning', 'error']) {
|
|
22
|
+
const value = raw[key];
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value <= 0) {
|
|
26
|
+
throw new ConfigError(`Config field "${field}.${key}" must be a positive integer`);
|
|
27
|
+
}
|
|
28
|
+
out[key] = value;
|
|
29
|
+
}
|
|
30
|
+
for (const key of Object.keys(raw)) {
|
|
31
|
+
if (!['notice', 'warning', 'error'].includes(key)) {
|
|
32
|
+
throw new ConfigError(`Config field "${field}" has unknown key "${key}"`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parses the `patchLint.fileSizeThresholds` block. Ordering is validated
|
|
39
|
+
* against the MERGED triple (overrides on top of the defaults), so setting
|
|
40
|
+
* only `warning` cannot land below the default `notice`.
|
|
41
|
+
*/
|
|
42
|
+
export function parsePatchLintFileSizeThresholds(raw) {
|
|
43
|
+
if (!isObject(raw)) {
|
|
44
|
+
throw new ConfigError('Config field "patchLint.fileSizeThresholds" must be a plain object');
|
|
45
|
+
}
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const tier of ['general', 'test']) {
|
|
48
|
+
const value = raw[tier];
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
const parsed = parseFileSizeTier(value, `patchLint.fileSizeThresholds.${tier}`);
|
|
52
|
+
const merged = { ...DEFAULT_FILE_SIZE_THRESHOLDS[tier], ...parsed };
|
|
53
|
+
if (merged.notice > merged.warning || merged.warning > merged.error) {
|
|
54
|
+
throw new ConfigError(`Config field "patchLint.fileSizeThresholds.${tier}" must satisfy notice <= warning <= error ` +
|
|
55
|
+
`(resolved: ${merged.notice}/${merged.warning}/${merged.error})`);
|
|
56
|
+
}
|
|
57
|
+
out[tier] = parsed;
|
|
58
|
+
}
|
|
59
|
+
for (const key of Object.keys(raw)) {
|
|
60
|
+
if (key !== 'general' && key !== 'test') {
|
|
61
|
+
throw new ConfigError(`Config field "patchLint.fileSizeThresholds" has unknown key "${key}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Validation helpers for `fireforge.json#patchPolicy`.
|
|
3
3
|
*/
|
|
4
4
|
import type { PatchPolicyConfig } from '../types/config.js';
|
|
5
|
-
import {
|
|
5
|
+
import { type ParsedRecord } from '../utils/parse.js';
|
|
6
6
|
/** Parses and validates the optional patch policy config block. */
|
|
7
|
-
export declare function parsePatchPolicyBlock(rec:
|
|
7
|
+
export declare function parsePatchPolicyBlock(rec: ParsedRecord): PatchPolicyConfig;
|
|
@@ -108,7 +108,7 @@ function parsePatchPolicyReservedRange(raw, label) {
|
|
|
108
108
|
return {
|
|
109
109
|
from,
|
|
110
110
|
to,
|
|
111
|
-
allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${
|
|
111
|
+
allowed: allowedRaw.map((entry, index) => parseReservedAllowedPatch(entry, `${label}.allowed[${index}]`)),
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
function assertPolicyRangesDoNotOverlap(ranges, label) {
|
|
@@ -160,14 +160,14 @@ export function parsePatchPolicyBlock(rec) {
|
|
|
160
160
|
if (!Array.isArray(rangesRaw) || rangesRaw.length === 0) {
|
|
161
161
|
throw new ConfigError('Config field "patchPolicy.ranges" must be a non-empty array');
|
|
162
162
|
}
|
|
163
|
-
out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${
|
|
163
|
+
out.ranges = rangesRaw.map((entry, index) => parsePatchPolicyRange(entry, `patchPolicy.ranges[${index}]`));
|
|
164
164
|
assertPolicyRangesDoNotOverlap(out.ranges, 'patchPolicy.ranges');
|
|
165
165
|
const reservedRangesRaw = rec.raw('reservedRanges');
|
|
166
166
|
if (reservedRangesRaw !== undefined) {
|
|
167
167
|
if (!Array.isArray(reservedRangesRaw)) {
|
|
168
168
|
throw new ConfigError('Config field "patchPolicy.reservedRanges" must be an array');
|
|
169
169
|
}
|
|
170
|
-
out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${
|
|
170
|
+
out.reservedRanges = reservedRangesRaw.map((entry, index) => parsePatchPolicyReservedRange(entry, `patchPolicy.reservedRanges[${index}]`));
|
|
171
171
|
assertPolicyRangesDoNotOverlap(out.reservedRanges, 'patchPolicy.reservedRanges');
|
|
172
172
|
}
|
|
173
173
|
return out;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FireForgeConfig } from '../types/config.js';
|
|
2
|
-
import {
|
|
3
|
-
type ConfigRecord =
|
|
2
|
+
import type { ParsedRecord } from '../utils/parse.js';
|
|
3
|
+
type ConfigRecord = ParsedRecord;
|
|
4
4
|
/** Parses the optional `test` block. */
|
|
5
5
|
export declare function parseTestBlock(rec: ConfigRecord, config: FireForgeConfig): void;
|
|
6
6
|
/** Parses optional project-specific external toolchain doctor probes. */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { ConfigError } from '../errors/config.js';
|
|
3
|
-
import { parseObject } from '../utils/parse.js';
|
|
4
3
|
import { isContainedRelativePath } from '../utils/paths.js';
|
|
5
4
|
import { isObject } from '../utils/validation.js';
|
|
6
5
|
function optionalConfigString(rec, key, label) {
|
|
@@ -58,16 +57,16 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
58
57
|
}
|
|
59
58
|
config.externalToolchains = raw.map((entry, index) => {
|
|
60
59
|
if (!isObject(entry)) {
|
|
61
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
60
|
+
throw new ConfigError(`Config field "externalToolchains[${index}]" must be an object`);
|
|
62
61
|
}
|
|
63
62
|
const item = entry;
|
|
64
63
|
const name = item['name'];
|
|
65
64
|
if (typeof name !== 'string' || name.trim() === '') {
|
|
66
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
65
|
+
throw new ConfigError(`Config field "externalToolchains[${index}].name" must be a non-empty string`);
|
|
67
66
|
}
|
|
68
67
|
const tools = item['tools'];
|
|
69
68
|
if (!Array.isArray(tools) || tools.length === 0) {
|
|
70
|
-
throw new ConfigError(`Config field "externalToolchains[${
|
|
69
|
+
throw new ConfigError(`Config field "externalToolchains[${index}].tools" must be a non-empty array`);
|
|
71
70
|
}
|
|
72
71
|
return {
|
|
73
72
|
name,
|
|
@@ -76,7 +75,7 @@ export function parseExternalToolchainsBlock(rec, config) {
|
|
|
76
75
|
});
|
|
77
76
|
}
|
|
78
77
|
function parseExternalToolRequirement(tool, index, toolIndex) {
|
|
79
|
-
const label = `externalToolchains[${
|
|
78
|
+
const label = `externalToolchains[${index}].tools[${toolIndex}]`;
|
|
80
79
|
if (!isObject(tool)) {
|
|
81
80
|
throw new ConfigError(`Config field "${label}" must be an object`);
|
|
82
81
|
}
|