@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,214 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Recognizes the one shape where the undefined-identifier hint sends the
|
|
4
|
+
* operator at the wrong remedy — and where taking that remedy would MASK a
|
|
5
|
+
* real bug.
|
|
6
|
+
*
|
|
7
|
+
* The shape: helpers are hoisted out of duplicated test files into a new
|
|
8
|
+
* `head_settings.js` that a managed `head.js` pulls in with
|
|
9
|
+
* `loadSubScript`. Until that companion is exported it belongs to no patch,
|
|
10
|
+
* so the per-patch checkJs program — which only ever loads patch-owned
|
|
11
|
+
* roots — cannot see any of its declarations. Every use of every hoisted
|
|
12
|
+
* helper is then reported as `Cannot find name`, and the generic hint
|
|
13
|
+
* offers three remedies of which the shim is the easiest: adding the
|
|
14
|
+
* globals to `extraShim` makes all of the warnings disappear AND makes a
|
|
15
|
+
* genuinely missing helper undetectable forever after.
|
|
16
|
+
*
|
|
17
|
+
* The warnings themselves are correct — the per-patch program's honest view
|
|
18
|
+
* of a file that is in no patch. Only the advice is wrong. FireForge
|
|
19
|
+
* already knows ownership, so when every undefined name in a file is
|
|
20
|
+
* declared at the top level of an UNMANAGED file that a managed head loads,
|
|
21
|
+
* it can name that file and the adoption command instead.
|
|
22
|
+
*
|
|
23
|
+
* Detection is deliberately conservative on both halves: a companion must
|
|
24
|
+
* be reachable from an actual `loadSubScript` call, and EVERY undefined
|
|
25
|
+
* name must resolve inside it. One unresolved name means the file may
|
|
26
|
+
* genuinely be missing a helper, and the generic hint stays.
|
|
27
|
+
*/
|
|
28
|
+
import { readFile } from 'node:fs/promises';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { UNDEFINED_IDENTIFIER_HINT } from './typecheck-shim.js';
|
|
31
|
+
/**
|
|
32
|
+
* String literals passed anywhere inside a `loadSubScript(...)` call.
|
|
33
|
+
*
|
|
34
|
+
* The mochitest idiom is
|
|
35
|
+
* `Services.scriptloader.loadSubScript(getRootDirectory(gTestPath) + "head_settings.js", this)`,
|
|
36
|
+
* so the useful signal is the `.js` literal inside the call, not a
|
|
37
|
+
* resolvable URL — the path half is computed at runtime.
|
|
38
|
+
*/
|
|
39
|
+
export function findLoadSubScriptTargets(source) {
|
|
40
|
+
const targets = [];
|
|
41
|
+
// Scanned to the statement terminator rather than to a closing paren:
|
|
42
|
+
// the idiomatic argument is itself a call (`getRootDirectory(gTestPath) +
|
|
43
|
+
// "head_settings.js"`), so a non-greedy `\)` stops on the INNER paren and
|
|
44
|
+
// never reaches the literal.
|
|
45
|
+
for (const call of source.matchAll(/loadSubScript\s*\(/g)) {
|
|
46
|
+
const from = call.index + call[0].length;
|
|
47
|
+
const semicolon = source.indexOf(';', from);
|
|
48
|
+
const to = semicolon === -1 ? from + CALL_SCAN_LIMIT : Math.min(semicolon, from + CALL_SCAN_LIMIT);
|
|
49
|
+
for (const literal of source.slice(from, to).matchAll(/["'`]([^"'`]*\.js)["'`]/g)) {
|
|
50
|
+
const value = literal[1];
|
|
51
|
+
if (value !== undefined && value.length > 0)
|
|
52
|
+
targets.push(value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return targets;
|
|
56
|
+
}
|
|
57
|
+
/** How far past a `loadSubScript(` the argument scan looks. */
|
|
58
|
+
const CALL_SCAN_LIMIT = 400;
|
|
59
|
+
/**
|
|
60
|
+
* Top-level declaration names in a script-scope file.
|
|
61
|
+
*
|
|
62
|
+
* Regex rather than a TS program on purpose: this runs only to decide the
|
|
63
|
+
* WORDING of a hint, the file is by definition outside every program the
|
|
64
|
+
* pass built, and a missed declaration degrades to the generic hint rather
|
|
65
|
+
* than to a wrong answer. Matching is anchored at column zero so a nested
|
|
66
|
+
* declaration inside a function body is not mistaken for a global.
|
|
67
|
+
*/
|
|
68
|
+
export function collectTopLevelDeclarations(source) {
|
|
69
|
+
const names = new Set();
|
|
70
|
+
const patterns = [
|
|
71
|
+
/^(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/gm,
|
|
72
|
+
/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)/gm,
|
|
73
|
+
/^class\s+([A-Za-z_$][\w$]*)/gm,
|
|
74
|
+
];
|
|
75
|
+
for (const pattern of patterns) {
|
|
76
|
+
for (const match of source.matchAll(pattern)) {
|
|
77
|
+
const name = match[1];
|
|
78
|
+
if (name !== undefined)
|
|
79
|
+
names.add(name);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return names;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extracts the identifier from a `Cannot find name 'X'` diagnostic message,
|
|
86
|
+
* or undefined when the message is not one.
|
|
87
|
+
*/
|
|
88
|
+
function extractUndefinedIdentifier(message) {
|
|
89
|
+
const match = /Cannot find name '([^']+)'/.exec(message);
|
|
90
|
+
return match?.[1];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolves the unmanaged companions reachable from a set of managed head
|
|
94
|
+
* files, given a reader for repo-relative file content.
|
|
95
|
+
*
|
|
96
|
+
* A candidate qualifies only when it is NOT in `ownedFiles` and its content
|
|
97
|
+
* can be read — an unreadable or absent target is simply not a companion,
|
|
98
|
+
* and the generic hint applies.
|
|
99
|
+
*
|
|
100
|
+
* @param heads - Repo-relative managed head files, with their sources
|
|
101
|
+
* @param ownedFiles - Every patch-owned repo-relative path
|
|
102
|
+
* @param readFile - Reads a repo-relative file, or returns undefined
|
|
103
|
+
*/
|
|
104
|
+
export async function resolveUnmanagedCompanions(heads, ownedFiles, readFile) {
|
|
105
|
+
const companions = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const head of heads) {
|
|
108
|
+
const dir = head.file.slice(0, head.file.lastIndexOf('/') + 1);
|
|
109
|
+
for (const target of findLoadSubScriptTargets(head.source)) {
|
|
110
|
+
// Only same-directory targets: the runtime path is computed from the
|
|
111
|
+
// test's own directory, and a bare basename is what that idiom yields.
|
|
112
|
+
const candidate = `${dir}${target.slice(target.lastIndexOf('/') + 1)}`;
|
|
113
|
+
if (candidate === head.file || ownedFiles.has(candidate) || seen.has(candidate))
|
|
114
|
+
continue;
|
|
115
|
+
seen.add(candidate);
|
|
116
|
+
const source = await readFile(candidate);
|
|
117
|
+
if (source === undefined)
|
|
118
|
+
continue;
|
|
119
|
+
companions.push({
|
|
120
|
+
file: candidate,
|
|
121
|
+
loadedBy: head.file,
|
|
122
|
+
declarations: collectTopLevelDeclarations(source),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return companions;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The replacement hint, when every undefined name resolves in one
|
|
130
|
+
* unmanaged companion.
|
|
131
|
+
*
|
|
132
|
+
* States the ownership fact first (that is the diagnosis), then the
|
|
133
|
+
* adoption command, and explicitly rules the shim OUT — the shim is what an
|
|
134
|
+
* operator reaches for by default here, and it is the one remedy that
|
|
135
|
+
* permanently hides a genuinely missing helper.
|
|
136
|
+
*/
|
|
137
|
+
export function formatUnmanagedCompanionHint(companion) {
|
|
138
|
+
return (`(undefined identifier — every one of them is declared in ${companion.file}, which ` +
|
|
139
|
+
`${companion.loadedBy} loads via loadSubScript but which NO patch owns yet, so the ` +
|
|
140
|
+
`per-patch program cannot see it. Adopt it with ` +
|
|
141
|
+
`"fireforge re-export <patch> --scan --scan-file ${companion.file}". Do NOT add these ` +
|
|
142
|
+
`globals to the extra shim: that silences these warnings and every future genuinely ` +
|
|
143
|
+
`missing helper with them.)`);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Picks the companion that explains EVERY undefined identifier in
|
|
147
|
+
* `messages`, or undefined when none does.
|
|
148
|
+
*
|
|
149
|
+
* @param messages - Diagnostic messages reported for one file
|
|
150
|
+
* @param companions - Candidates from {@link resolveUnmanagedCompanions}
|
|
151
|
+
*/
|
|
152
|
+
export function explainUndefinedIdentifiers(messages, companions) {
|
|
153
|
+
const names = messages
|
|
154
|
+
.map((message) => extractUndefinedIdentifier(message))
|
|
155
|
+
.filter((name) => name !== undefined);
|
|
156
|
+
if (names.length === 0)
|
|
157
|
+
return undefined;
|
|
158
|
+
// Only the undefined-identifier diagnostics are explained here; a file
|
|
159
|
+
// carrying OTHER type errors still gets those reported untouched.
|
|
160
|
+
return companions.find((companion) => names.every((name) => companion.declarations.has(name)));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reads the head files in the owned test set and resolves the UNMANAGED
|
|
164
|
+
* companions they pull in with `loadSubScript`. Hoisting shared helpers
|
|
165
|
+
* into such a companion leaves it unowned until export, and every use of
|
|
166
|
+
* every helper then reports `Cannot find name` from an honest per-patch
|
|
167
|
+
* program — see `patch-lint-unmanaged-companion.ts` for why the generic
|
|
168
|
+
* hint is actively harmful there.
|
|
169
|
+
*
|
|
170
|
+
* Best-effort: an unreadable head simply contributes no companions.
|
|
171
|
+
*/
|
|
172
|
+
export async function collectUnmanagedCompanions(repoDir, files, ownedFiles) {
|
|
173
|
+
const heads = [];
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
const base = file.split('/').pop() ?? '';
|
|
176
|
+
if (!/^head(?:_.*)?\.js$/.test(base))
|
|
177
|
+
continue;
|
|
178
|
+
try {
|
|
179
|
+
heads.push({ file, source: await readFile(join(repoDir, file), 'utf8') });
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (heads.length === 0)
|
|
186
|
+
return [];
|
|
187
|
+
return resolveUnmanagedCompanions(heads, ownedFiles, async (rel) => {
|
|
188
|
+
try {
|
|
189
|
+
return await readFile(join(repoDir, rel), 'utf8');
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Swaps the generic undefined-identifier hint for the ownership hint when
|
|
198
|
+
* one unmanaged companion explains EVERY undefined name in the file. Any
|
|
199
|
+
* other issue in the list is returned untouched.
|
|
200
|
+
*/
|
|
201
|
+
export function retargetUnmanagedCompanionHints(issues, companions) {
|
|
202
|
+
if (companions.length === 0)
|
|
203
|
+
return [...issues];
|
|
204
|
+
const undefinedIssues = issues.filter((issue) => issue.message.includes(UNDEFINED_IDENTIFIER_HINT));
|
|
205
|
+
if (undefinedIssues.length === 0)
|
|
206
|
+
return [...issues];
|
|
207
|
+
const companion = explainUndefinedIdentifiers(undefinedIssues.map((issue) => issue.message), companions);
|
|
208
|
+
if (!companion)
|
|
209
|
+
return [...issues];
|
|
210
|
+
const hint = formatUnmanagedCompanionHint(companion);
|
|
211
|
+
return issues.map((issue) => issue.message.includes(UNDEFINED_IDENTIFIER_HINT)
|
|
212
|
+
? { ...issue, message: issue.message.replace(UNDEFINED_IDENTIFIER_HINT, hint) }
|
|
213
|
+
: issue);
|
|
214
|
+
}
|
|
@@ -2,7 +2,8 @@ import type { PatchLintIssue } from '../types/commands/index.js';
|
|
|
2
2
|
import type { FireForgeConfig, ProjectLicense } from '../types/config.js';
|
|
3
3
|
import { type CommentStyle } from './license-headers.js';
|
|
4
4
|
import { lintPatchedCss } from './patch-lint-css.js';
|
|
5
|
-
export * from './patch-lint-
|
|
5
|
+
export * from './patch-lint-cross.js';
|
|
6
|
+
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
6
7
|
export { lintPatchedCss };
|
|
7
8
|
/**
|
|
8
9
|
* Canonical `[check] file: message` rendering of a patch-lint issue — the
|
|
@@ -132,6 +133,17 @@ export type PatchSizeTierDecision = {
|
|
|
132
133
|
* defensive-for-tests choice.
|
|
133
134
|
*/
|
|
134
135
|
export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string>, patchTier?: 'branding'): PatchSizeTierDecision;
|
|
136
|
+
/** Line and file-count thresholds governing one patch-size tier. */
|
|
137
|
+
export interface PatchSizeThresholds {
|
|
138
|
+
/** Added-line counts at which notice/warning/error issues fire. */
|
|
139
|
+
lines: {
|
|
140
|
+
notice: number;
|
|
141
|
+
warning: number;
|
|
142
|
+
error: number;
|
|
143
|
+
};
|
|
144
|
+
/** Maximum number of files a patch may touch before an issue fires. */
|
|
145
|
+
maxFiles: number;
|
|
146
|
+
}
|
|
135
147
|
/**
|
|
136
148
|
* Read-only view of the size thresholds a tier enforces.
|
|
137
149
|
* Public-API companion to {@link resolvePatchSizeTier} /
|
|
@@ -139,14 +151,7 @@ export declare function resolvePatchSizeTier(filesAffected: ReadonlyArray<string
|
|
|
139
151
|
* checks against the SAME numbers `large-patch-lines` /
|
|
140
152
|
* `large-patch-files` fire on instead of mirroring them.
|
|
141
153
|
*/
|
|
142
|
-
export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'):
|
|
143
|
-
lines: {
|
|
144
|
-
notice: number;
|
|
145
|
-
warning: number;
|
|
146
|
-
error: number;
|
|
147
|
-
};
|
|
148
|
-
maxFiles: number;
|
|
149
|
-
};
|
|
154
|
+
export declare function getPatchSizeThresholds(tier: 'general' | 'test' | 'branding'): PatchSizeThresholds;
|
|
150
155
|
/**
|
|
151
156
|
* Checks patch size and emits advisory warnings.
|
|
152
157
|
*
|
|
@@ -8,11 +8,13 @@ import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
|
|
|
8
8
|
import { lintPatchedCss } from './patch-lint-css.js';
|
|
9
9
|
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
10
10
|
import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
|
|
11
|
+
import { formatFileTooLargeMessage, resolveFileSizeThresholds } from './patch-lint-file-size.js';
|
|
11
12
|
import { hasRelativeImport } from './patch-lint-imports.js';
|
|
12
13
|
import { validateExportJsDoc } from './patch-lint-jsdoc.js';
|
|
13
14
|
import { lintMozBuildSortedLists } from './patch-lint-mozbuild.js';
|
|
14
15
|
import { lintObserverTopics } from './patch-lint-observer.js';
|
|
15
16
|
import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOwnedTestScripts, } from './patch-lint-ownership.js';
|
|
17
|
+
import { invokePatchLintPrettier } from './patch-lint-prettier.js';
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
// Cross-patch lint re-exports
|
|
18
20
|
// ---------------------------------------------------------------------------
|
|
@@ -20,9 +22,10 @@ import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOw
|
|
|
20
22
|
// The cross-patch lint infrastructure (queue context builder,
|
|
21
23
|
// duplicate-creation and forward-import rules, ignore marker) lives in
|
|
22
24
|
// `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies each
|
|
23
|
-
// stay within the per-file line budget.
|
|
24
|
-
//
|
|
25
|
-
export * from './patch-lint-
|
|
25
|
+
// stay within the per-file line budget. Its surface is re-exported here so
|
|
26
|
+
// callers keep importing from one module.
|
|
27
|
+
export * from './patch-lint-cross.js';
|
|
28
|
+
export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
|
|
26
29
|
// The CSS rule bodies live in `patch-lint-css.ts` (same per-file-budget
|
|
27
30
|
// split as the other rule families); re-export the imported binding so
|
|
28
31
|
// callers and tests keep importing `lintPatchedCss` from this module.
|
|
@@ -31,10 +34,6 @@ export { lintPatchedCss };
|
|
|
31
34
|
// Helpers
|
|
32
35
|
// ---------------------------------------------------------------------------
|
|
33
36
|
const JS_EXTENSIONS = ['.js', '.mjs', '.jsm'];
|
|
34
|
-
const FILE_SIZE_THRESHOLDS = {
|
|
35
|
-
general: { notice: 500, warning: 750, error: 900 },
|
|
36
|
-
test: { notice: 1200, warning: 1400, error: 1600 },
|
|
37
|
-
};
|
|
38
37
|
/**
|
|
39
38
|
* Counts the lines of `content` the way `wc -l` reports them: a trailing
|
|
40
39
|
* newline terminates the last line rather than starting an empty extra one.
|
|
@@ -407,31 +406,22 @@ export async function lintPatchedJs(repoDir, affectedFiles, newFiles, config, pa
|
|
|
407
406
|
if (isNew) {
|
|
408
407
|
const lineCount = countContentLines(content);
|
|
409
408
|
const isTest = isTestFile(file);
|
|
410
|
-
const thresholds =
|
|
409
|
+
const thresholds = resolveFileSizeThresholds(config.patchLint?.fileSizeThresholds, isTest);
|
|
411
410
|
const label = isTest ? 'Test file' : 'New file';
|
|
412
411
|
const verb = isTest ? 'splitting' : 'decomposing';
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
else if (lineCount > thresholds.warning) {
|
|
412
|
+
const band = lineCount > thresholds.error
|
|
413
|
+
? 'error'
|
|
414
|
+
: lineCount > thresholds.warning
|
|
415
|
+
? 'warning'
|
|
416
|
+
: lineCount > thresholds.notice
|
|
417
|
+
? 'notice'
|
|
418
|
+
: undefined;
|
|
419
|
+
if (band !== undefined) {
|
|
422
420
|
issues.push({
|
|
423
421
|
file,
|
|
424
422
|
check: 'file-too-large',
|
|
425
|
-
message:
|
|
426
|
-
severity:
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
else if (lineCount > thresholds.notice) {
|
|
430
|
-
issues.push({
|
|
431
|
-
file,
|
|
432
|
-
check: 'file-too-large',
|
|
433
|
-
message: `${label} has ${lineCount} lines (soft limit: ${thresholds.warning}, hard limit: ${thresholds.error}). Consider ${verb}.`,
|
|
434
|
-
severity: 'notice',
|
|
423
|
+
message: formatFileTooLargeMessage({ label, lineCount, thresholds, band, verb }),
|
|
424
|
+
severity: band,
|
|
435
425
|
});
|
|
436
426
|
}
|
|
437
427
|
}
|
|
@@ -715,6 +705,16 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
|
|
|
715
705
|
}
|
|
716
706
|
}
|
|
717
707
|
}
|
|
708
|
+
// Prettier over patch-owned .sys.mjs. Deliberately NOT part of the
|
|
709
|
+
// precomputedCheckJs short-circuit: it is a separate tool with its own
|
|
710
|
+
// gate, and a queue-wide program has no equivalent to share. Scoped to
|
|
711
|
+
// the files THIS patch owns, run from engine/ so the engine's own config
|
|
712
|
+
// and ignore file decide (see `patch-lint-prettier.ts`).
|
|
713
|
+
const prettierGate = config.patchLint?.prettier ?? 'off';
|
|
714
|
+
if (prettierGate !== 'off' && patchOwnedFiles.size > 0) {
|
|
715
|
+
const scoped = [...patchOwnedFiles].filter((file) => !options?.checkJsReportScope || options.checkJsReportScope.has(file));
|
|
716
|
+
issues.push(...(await invokePatchLintPrettier(repoDir, dirname(repoDir), scoped.sort(), prettierGate)));
|
|
717
|
+
}
|
|
718
718
|
// Filter out ignored checks last so every rule still runs (keeps the
|
|
719
719
|
// implementation uniform) but suppressed rules do not surface. We do not
|
|
720
720
|
// reclassify severities — an ignored error simply drops, mirroring how
|
|
@@ -16,12 +16,12 @@ const PATCH_DIRECTORY_LOCK = '.fireforge-patches.lock';
|
|
|
16
16
|
* session lock's progress line so operators read one shape for both locks.
|
|
17
17
|
*/
|
|
18
18
|
function formatWaitProgressLine(waitedMs, timeoutMs, holder) {
|
|
19
|
-
const progress = `${
|
|
19
|
+
const progress = `${Math.round(waitedMs / 1000)}s of up to ${Math.round(timeoutMs / 1000)}s`;
|
|
20
20
|
if (holder === undefined) {
|
|
21
21
|
return `Waiting for the FireForge patch directory lock — ${progress}.`;
|
|
22
22
|
}
|
|
23
23
|
const details = holder.metadata.length > 0 ? ` (${holder.metadata.join(', ')})` : '';
|
|
24
|
-
return `Waiting for the FireForge patch directory lock held by PID ${
|
|
24
|
+
return `Waiting for the FireForge patch directory lock held by PID ${holder.pid}${details} — ${progress}.`;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Asserts that this process is inside {@link withPatchDirectoryLock} for the
|
|
@@ -47,7 +47,7 @@ export async function assertPatchDirectoryLockHeld(patchesDir, context) {
|
|
|
47
47
|
const status = await readLockStatus(join(patchesDir, PATCH_DIRECTORY_LOCK));
|
|
48
48
|
assert(status.held, () => `patch directory lock is held before ${context}`);
|
|
49
49
|
assert(status.holder === undefined || status.holder.pid === process.pid, () => `patch directory lock is owned by this process before ${context} ` +
|
|
50
|
-
`(held by PID ${
|
|
50
|
+
`(held by PID ${status.holder?.pid}, we are ${process.pid})`);
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* Runs a patch directory mutation while holding an exclusive filesystem lock.
|
|
@@ -98,17 +98,18 @@ export async function mutatePatchRowsInManifest(patchesDir, filenames, mutator)
|
|
|
98
98
|
if (!(await pathExistsStrict(manifestPath)))
|
|
99
99
|
return null;
|
|
100
100
|
const rawManifest = await readJson(manifestPath);
|
|
101
|
+
// `validatePatchesManifest` has already rejected a manifest that is not an
|
|
102
|
+
// object with an array of object rows (it throws the corruption error the
|
|
103
|
+
// caller expects), so the shape re-checks below are FireForge's own
|
|
104
|
+
// invariants, not input validation: failing them means the validator and
|
|
105
|
+
// this walker disagree about the manifest schema, which is a bug here.
|
|
101
106
|
const beforeManifest = validatePatchesManifest(rawManifest);
|
|
102
|
-
|
|
103
|
-
throw new Error('patches.json must be a JSON object with a patches array');
|
|
104
|
-
}
|
|
107
|
+
assert(isObject(rawManifest) && isArray(rawManifest['patches']), 'validated patches.json is a JSON object with a patches array');
|
|
105
108
|
const filenameSet = new Set(filenames);
|
|
106
109
|
if (filenameSet.size === 0)
|
|
107
110
|
return [];
|
|
108
|
-
const rawPatches = rawManifest['patches'].map((entry) => {
|
|
109
|
-
|
|
110
|
-
throw new Error('patches.json patches entries must be objects');
|
|
111
|
-
}
|
|
111
|
+
const rawPatches = rawManifest['patches'].map((entry, index) => {
|
|
112
|
+
assert(isObject(entry), () => `validated patches.json row ${index} is an object`);
|
|
112
113
|
return { ...entry };
|
|
113
114
|
});
|
|
114
115
|
const changedIndexes = [];
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Renders a patch order as the zero-padded prefix used in filenames and in
|
|
3
|
+
* every message that names an order.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Eleven call sites across `patch-policy`, `patch-export`, `export-flow`,
|
|
6
|
+
* `export-placement-policy` and `patch move-files` spelled this as
|
|
7
|
+
* `String(order).padStart(3, '0')`, so the padding width lived in eleven
|
|
8
|
+
* places while the filenames it has to agree with are produced in one.
|
|
9
|
+
* @param order - Numeric patch order.
|
|
10
|
+
* @param width - Prefix width; defaults to the on-disk convention.
|
|
11
|
+
* @returns The zero-padded order, e.g. `007`.
|
|
11
12
|
*/
|
|
13
|
+
export declare function formatPatchOrder(order: number, width?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Renders an inclusive order range the way `patchPolicy` diagnostics name it.
|
|
16
|
+
* @param range - Inclusive `from`/`to` order bounds.
|
|
17
|
+
* @returns The range label, e.g. `100-199`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatOrderRange(range: {
|
|
20
|
+
from: number;
|
|
21
|
+
to: number;
|
|
22
|
+
}): string;
|
|
12
23
|
/**
|
|
13
24
|
* Extracts the order number from a patch filename.
|
|
14
25
|
* Expects format like "001-description.patch"
|
|
@@ -11,6 +11,31 @@ import { assert } from '../utils/assert.js';
|
|
|
11
11
|
* captures that mis-split any path containing ` b/`. New diff-shaped parsing
|
|
12
12
|
* must build on {@link parseDiffSections} rather than re-walking lines.
|
|
13
13
|
*/
|
|
14
|
+
/** Width of the zero-padded numeric prefix on a patch filename ("001-…"). */
|
|
15
|
+
const PATCH_ORDER_WIDTH = 3;
|
|
16
|
+
/**
|
|
17
|
+
* Renders a patch order as the zero-padded prefix used in filenames and in
|
|
18
|
+
* every message that names an order.
|
|
19
|
+
*
|
|
20
|
+
* Eleven call sites across `patch-policy`, `patch-export`, `export-flow`,
|
|
21
|
+
* `export-placement-policy` and `patch move-files` spelled this as
|
|
22
|
+
* `String(order).padStart(3, '0')`, so the padding width lived in eleven
|
|
23
|
+
* places while the filenames it has to agree with are produced in one.
|
|
24
|
+
* @param order - Numeric patch order.
|
|
25
|
+
* @param width - Prefix width; defaults to the on-disk convention.
|
|
26
|
+
* @returns The zero-padded order, e.g. `007`.
|
|
27
|
+
*/
|
|
28
|
+
export function formatPatchOrder(order, width = PATCH_ORDER_WIDTH) {
|
|
29
|
+
return String(order).padStart(width, '0');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Renders an inclusive order range the way `patchPolicy` diagnostics name it.
|
|
33
|
+
* @param range - Inclusive `from`/`to` order bounds.
|
|
34
|
+
* @returns The range label, e.g. `100-199`.
|
|
35
|
+
*/
|
|
36
|
+
export function formatOrderRange(range) {
|
|
37
|
+
return `${formatPatchOrder(range.from)}-${formatPatchOrder(range.to)}`;
|
|
38
|
+
}
|
|
14
39
|
/**
|
|
15
40
|
* Extracts the order number from a patch filename.
|
|
16
41
|
* Expects format like "001-description.patch"
|
|
@@ -11,6 +11,7 @@ import { InvalidArgumentError } from '../errors/base.js';
|
|
|
11
11
|
import { warn } from '../utils/logger.js';
|
|
12
12
|
import { PATCH_CATEGORIES } from '../utils/validation.js';
|
|
13
13
|
import { rewriteStagedDependencyOwners } from './patch-manifest-io.js';
|
|
14
|
+
import { formatOrderRange, formatPatchOrder } from './patch-parse.js';
|
|
14
15
|
/** Default patch filename contract used when a policy omits `filenamePattern`. */
|
|
15
16
|
const DEFAULT_PATCH_POLICY_FILENAME_PATTERN = '^(?<order>\\d{3})-(?<category>[a-z][a-z0-9-]*)-(?<slug>[a-z0-9-]+)\\.patch$';
|
|
16
17
|
function policy(config) {
|
|
@@ -19,13 +20,6 @@ function policy(config) {
|
|
|
19
20
|
function mutationMode(config) {
|
|
20
21
|
return policy(config)?.mutationMode ?? 'error';
|
|
21
22
|
}
|
|
22
|
-
function issueSeverity(config) {
|
|
23
|
-
return mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
24
|
-
}
|
|
25
|
-
/** Returns true when the loaded config includes an opt-in patch policy. */
|
|
26
|
-
function hasPatchPolicy(config) {
|
|
27
|
-
return policy(config) !== undefined;
|
|
28
|
-
}
|
|
29
23
|
/** Returns valid categories for prompts and CLI validation under the config. */
|
|
30
24
|
export function getPatchPolicyCategories(config) {
|
|
31
25
|
const cfg = policy(config);
|
|
@@ -42,9 +36,6 @@ export function isCategoryAllowedByConfig(config, category) {
|
|
|
42
36
|
return PATCH_CATEGORIES.includes(category);
|
|
43
37
|
return cfg.ranges.some((range) => range.category === category);
|
|
44
38
|
}
|
|
45
|
-
function rangeLabel(range) {
|
|
46
|
-
return `${String(range.from).padStart(3, '0')}-${String(range.to).padStart(3, '0')}`;
|
|
47
|
-
}
|
|
48
39
|
/**
|
|
49
40
|
* Human-readable label for a category's configured ranges, e.g. `300-399`.
|
|
50
41
|
* Exported for the forward-import hint, which must render the
|
|
@@ -54,7 +45,7 @@ export function categoryRangeLabel(ranges, category) {
|
|
|
54
45
|
const matches = ranges.filter((range) => range.category === category);
|
|
55
46
|
if (matches.length === 0)
|
|
56
47
|
return '(no configured range)';
|
|
57
|
-
return matches.map(
|
|
48
|
+
return matches.map(formatOrderRange).join(', ');
|
|
58
49
|
}
|
|
59
50
|
function reservedRangeForOrder(cfg, order) {
|
|
60
51
|
return cfg.reservedRanges?.find((range) => order >= range.from && order <= range.to) ?? null;
|
|
@@ -73,9 +64,6 @@ export function categoryRangeForOrder(cfg, category, order) {
|
|
|
73
64
|
function anyRangeForOrder(cfg, order) {
|
|
74
65
|
return cfg.ranges.find((range) => order >= range.from && order <= range.to) ?? null;
|
|
75
66
|
}
|
|
76
|
-
function compileFilenamePattern(cfg) {
|
|
77
|
-
return new RegExp(cfg.filenamePattern ?? DEFAULT_PATCH_POLICY_FILENAME_PATTERN);
|
|
78
|
-
}
|
|
79
67
|
function parseFilenameWithPolicy(cfg, patch, severity) {
|
|
80
68
|
if (cfg.filenamePattern === undefined) {
|
|
81
69
|
const defaultMatch = /^(?<order>\d{3})-(?<rest>[a-z0-9-]+)\.patch$/.exec(patch.filename);
|
|
@@ -117,7 +105,7 @@ function parseFilenameWithPolicy(cfg, patch, severity) {
|
|
|
117
105
|
}
|
|
118
106
|
return { order: Number.parseInt(orderRaw, 10), category };
|
|
119
107
|
}
|
|
120
|
-
const pattern =
|
|
108
|
+
const pattern = new RegExp(cfg.filenamePattern ?? DEFAULT_PATCH_POLICY_FILENAME_PATTERN);
|
|
121
109
|
const match = pattern.exec(patch.filename);
|
|
122
110
|
if (!match) {
|
|
123
111
|
return [
|
|
@@ -188,7 +176,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
188
176
|
code: 'reserved-range',
|
|
189
177
|
filename: patch.filename,
|
|
190
178
|
severity,
|
|
191
|
-
message: `${patch.filename} is in reserved range ${
|
|
179
|
+
message: `${patch.filename} is in reserved range ${formatOrderRange(reserved)}. ` +
|
|
192
180
|
'Reserved ranges require an exact patchPolicy.reservedRanges[].allowed filename exception.',
|
|
193
181
|
});
|
|
194
182
|
return issues;
|
|
@@ -198,7 +186,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
198
186
|
code: 'reserved-documentation',
|
|
199
187
|
filename: patch.filename,
|
|
200
188
|
severity,
|
|
201
|
-
message: `${patch.filename} is allowlisted for reserved range ${
|
|
189
|
+
message: `${patch.filename} is allowlisted for reserved range ${formatOrderRange(reserved)}, ` +
|
|
202
190
|
'but the allowlist entry must include either "adr" or "documentation".',
|
|
203
191
|
});
|
|
204
192
|
}
|
|
@@ -210,7 +198,7 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
210
198
|
code: 'reserved-files',
|
|
211
199
|
filename: patch.filename,
|
|
212
200
|
severity,
|
|
213
|
-
message: `${patch.filename} is allowlisted for reserved range ${
|
|
201
|
+
message: `${patch.filename} is allowlisted for reserved range ${formatOrderRange(reserved)}, ` +
|
|
214
202
|
`but touches file(s) outside its reserved allowlist: ${extraFiles.join(', ')}.`,
|
|
215
203
|
});
|
|
216
204
|
}
|
|
@@ -222,8 +210,8 @@ function evaluatePatchMetadata(cfg, patch, severity) {
|
|
|
222
210
|
const owner = anyRangeForOrder(cfg, patch.order);
|
|
223
211
|
const expected = categoryRangeLabel(cfg.ranges, patch.category);
|
|
224
212
|
const actual = owner !== null
|
|
225
|
-
? `${
|
|
226
|
-
: `${
|
|
213
|
+
? `${formatPatchOrder(patch.order)} is configured for ${owner.category} (${formatOrderRange(owner)})`
|
|
214
|
+
: `${formatPatchOrder(patch.order)} is outside all configured ranges`;
|
|
227
215
|
issues.push({
|
|
228
216
|
code: 'category-range',
|
|
229
217
|
filename: patch.filename,
|
|
@@ -262,10 +250,10 @@ function evaluateGaps(cfg, patches, severity) {
|
|
|
262
250
|
if (missing.length > 0) {
|
|
263
251
|
issues.push({
|
|
264
252
|
code: 'numeric-gap',
|
|
265
|
-
filename: `${range.category}:${
|
|
253
|
+
filename: `${range.category}:${formatOrderRange(range)}`,
|
|
266
254
|
severity,
|
|
267
|
-
message: `${range.category} range ${
|
|
268
|
-
missing.map((order) =>
|
|
255
|
+
message: `${range.category} range ${formatOrderRange(range)} has numeric gap(s): ` +
|
|
256
|
+
missing.map((order) => formatPatchOrder(order)).join(', ') +
|
|
269
257
|
'. patchPolicy.allowGaps is false.',
|
|
270
258
|
});
|
|
271
259
|
}
|
|
@@ -286,9 +274,9 @@ function evaluateOrderCollisions(patches, severity) {
|
|
|
286
274
|
const filenames = matches.map((patch) => patch.filename).sort((a, b) => a.localeCompare(b));
|
|
287
275
|
issues.push({
|
|
288
276
|
code: 'order-collision',
|
|
289
|
-
filename:
|
|
277
|
+
filename: formatPatchOrder(order),
|
|
290
278
|
severity,
|
|
291
|
-
message: `patchPolicy requires unique numeric orders; order ${
|
|
279
|
+
message: `patchPolicy requires unique numeric orders; order ${formatPatchOrder(order)} ` +
|
|
292
280
|
`is used by: ${filenames.join(', ')}.`,
|
|
293
281
|
});
|
|
294
282
|
}
|
|
@@ -299,7 +287,7 @@ export function evaluatePatchPolicy(config, manifest) {
|
|
|
299
287
|
const cfg = policy(config);
|
|
300
288
|
if (!cfg)
|
|
301
289
|
return [];
|
|
302
|
-
const severity =
|
|
290
|
+
const severity = mutationMode(config) === 'warn' ? 'warning' : 'error';
|
|
303
291
|
const issues = manifest.patches.flatMap((patch) => evaluatePatchMetadata(cfg, patch, severity));
|
|
304
292
|
issues.push(...evaluateOrderCollisions(manifest.patches, severity));
|
|
305
293
|
issues.push(...evaluateGaps(cfg, manifest.patches, severity));
|
|
@@ -335,7 +323,7 @@ export function applyRenameMapToManifest(manifest, renameMap) {
|
|
|
335
323
|
}
|
|
336
324
|
/** Enforces patch policy according to the configured mutation mode. */
|
|
337
325
|
export function enforcePatchPolicy(input) {
|
|
338
|
-
if (
|
|
326
|
+
if (policy(input.config) === undefined)
|
|
339
327
|
return;
|
|
340
328
|
const issues = evaluatePatchPolicy(input.config, input.manifest);
|
|
341
329
|
if (issues.length === 0)
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { PatchMetadata } from '../types/commands/index.js';
|
|
2
|
-
import type { FirefoxConfig
|
|
2
|
+
import type { FirefoxConfig } from '../types/config.js';
|
|
3
3
|
/** Metadata fields stamped on new or refreshed patch entries. */
|
|
4
4
|
export declare function buildPatchSourceMetadata(firefox: Pick<FirefoxConfig, 'product' | 'version'>): Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceProduct' | 'sourceVersion'>;
|
|
5
5
|
/** Backward-compatible source version reader for legacy manifests. */
|
|
6
6
|
export declare function getPatchSourceVersion(patch: Pick<PatchMetadata, 'sourceEsrVersion' | 'sourceVersion'>): string;
|
|
7
|
-
/** Backward-compatible source product reader for legacy manifests. */
|
|
8
|
-
export declare function getPatchSourceProduct(patch: Pick<PatchMetadata, 'sourceProduct'>): FirefoxProduct | undefined;
|
|
@@ -10,7 +10,3 @@ export function buildPatchSourceMetadata(firefox) {
|
|
|
10
10
|
export function getPatchSourceVersion(patch) {
|
|
11
11
|
return patch.sourceVersion ?? patch.sourceEsrVersion;
|
|
12
12
|
}
|
|
13
|
-
/** Backward-compatible source product reader for legacy manifests. */
|
|
14
|
-
export function getPatchSourceProduct(patch) {
|
|
15
|
-
return patch.sourceProduct;
|
|
16
|
-
}
|