@hominis/fireforge 0.49.2 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -1
- package/README.md +1 -1
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/commands/build-prelock.d.ts +20 -0
- package/dist/src/commands/build-prelock.js +50 -0
- package/dist/src/commands/build.d.ts +3 -1
- package/dist/src/commands/build.js +13 -6
- package/dist/src/commands/check-report.d.ts +41 -0
- package/dist/src/commands/check-report.js +91 -0
- package/dist/src/commands/check-run.d.ts +33 -0
- package/dist/src/commands/check-run.js +112 -0
- package/dist/src/commands/check-steps.d.ts +75 -0
- package/dist/src/commands/check-steps.js +200 -0
- package/dist/src/commands/check.d.ts +32 -0
- package/dist/src/commands/check.js +160 -0
- package/dist/src/commands/config.js +5 -6
- package/dist/src/commands/declarations-report.d.ts +43 -0
- package/dist/src/commands/declarations-report.js +76 -0
- package/dist/src/commands/doctor-engine-eslint.js +6 -1
- package/dist/src/commands/doctor-source-pin.js +4 -1
- package/dist/src/commands/export-adopt-check.d.ts +38 -0
- package/dist/src/commands/export-adopt-check.js +98 -0
- package/dist/src/commands/export-flow.js +29 -14
- package/dist/src/commands/export-placement-gate.d.ts +6 -2
- package/dist/src/commands/export-placement-gate.js +8 -0
- package/dist/src/commands/export-shared.d.ts +2 -1
- package/dist/src/commands/export-staged-registration.d.ts +46 -0
- package/dist/src/commands/export-staged-registration.js +91 -0
- package/dist/src/commands/forward-import-remedy.d.ts +32 -0
- package/dist/src/commands/forward-import-remedy.js +63 -0
- package/dist/src/commands/furnace/apply.js +3 -3
- package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
- package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
- package/dist/src/commands/furnace/chrome-doc.js +2 -2
- package/dist/src/commands/furnace/create.js +2 -2
- package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
- package/dist/src/commands/furnace/deploy-named.js +107 -0
- package/dist/src/commands/furnace/deploy.d.ts +3 -2
- package/dist/src/commands/furnace/deploy.js +50 -86
- package/dist/src/commands/furnace/diff.js +26 -3
- package/dist/src/commands/furnace/index.js +66 -41
- package/dist/src/commands/furnace/init.js +13 -6
- package/dist/src/commands/furnace/override.js +4 -2
- package/dist/src/commands/furnace/preview.js +4 -3
- package/dist/src/commands/furnace/refresh.js +2 -2
- package/dist/src/commands/furnace/remove.js +4 -3
- package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
- package/dist/src/commands/furnace/rename-dry-run.js +127 -0
- package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
- package/dist/src/commands/furnace/rename-helpers.js +15 -1
- package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
- package/dist/src/commands/furnace/rename-registrations.js +98 -0
- package/dist/src/commands/furnace/rename.d.ts +2 -1
- package/dist/src/commands/furnace/rename.js +28 -66
- package/dist/src/commands/furnace/scan.d.ts +9 -4
- package/dist/src/commands/furnace/scan.js +11 -9
- package/dist/src/commands/furnace/sync.js +3 -2
- package/dist/src/commands/furnace/validate-report.d.ts +45 -0
- package/dist/src/commands/furnace/validate-report.js +91 -0
- package/dist/src/commands/furnace/validate.d.ts +1 -1
- package/dist/src/commands/furnace/validate.js +151 -113
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +36 -61
- package/dist/src/commands/lint-per-run-checkjs.js +2 -2
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +20 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +4 -0
- package/dist/src/commands/park.d.ts +22 -0
- package/dist/src/commands/park.js +232 -0
- package/dist/src/commands/patch/delete-gap-notice.js +3 -1
- package/dist/src/commands/patch/delete.d.ts +1 -1
- package/dist/src/commands/patch/delete.js +24 -104
- package/dist/src/commands/patch/index.d.ts +3 -3
- package/dist/src/commands/patch/index.js +10 -4
- package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
- package/dist/src/commands/patch/lint-ignore-from.js +142 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +78 -31
- package/dist/src/commands/patch/move-files-create.js +34 -18
- package/dist/src/commands/patch/move-files-into.js +15 -7
- package/dist/src/commands/patch/move-files.js +6 -2
- package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
- package/dist/src/commands/patch/removal-created-files.js +83 -0
- package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
- package/dist/src/commands/patch/removal-dependents.js +133 -0
- package/dist/src/commands/patch/rename.js +20 -1
- package/dist/src/commands/patch/reserve.d.ts +33 -0
- package/dist/src/commands/patch/reserve.js +152 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +133 -0
- package/dist/src/commands/patch/split-plan.d.ts +17 -8
- package/dist/src/commands/patch/split-plan.js +60 -28
- package/dist/src/commands/patch/split-staged.d.ts +108 -0
- package/dist/src/commands/patch/split-staged.js +202 -0
- package/dist/src/commands/patch/split.d.ts +5 -0
- package/dist/src/commands/patch/split.js +62 -17
- package/dist/src/commands/patch/stage.d.ts +34 -0
- package/dist/src/commands/patch/stage.js +153 -0
- package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
- package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
- package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
- package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
- package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
- package/dist/src/commands/patch/staged-dependency-from.js +108 -0
- package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
- package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
- package/dist/src/commands/patch/staged-dependency.js +54 -139
- package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
- package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
- package/dist/src/commands/re-export-adjacent.d.ts +50 -2
- package/dist/src/commands/re-export-adjacent.js +160 -40
- package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
- package/dist/src/commands/re-export-bulk-scan.js +22 -7
- package/dist/src/commands/re-export-drift.d.ts +36 -8
- package/dist/src/commands/re-export-drift.js +73 -49
- package/dist/src/commands/re-export-files.js +11 -8
- package/dist/src/commands/re-export-hunks.d.ts +35 -0
- package/dist/src/commands/re-export-hunks.js +220 -0
- package/dist/src/commands/re-export-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +74 -6
- package/dist/src/commands/re-export-preflight.d.ts +23 -0
- package/dist/src/commands/re-export-preflight.js +160 -0
- package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
- package/dist/src/commands/re-export-purity-scope.js +131 -0
- package/dist/src/commands/re-export-register.js +56 -17
- package/dist/src/commands/re-export-removed.d.ts +13 -2
- package/dist/src/commands/re-export-removed.js +57 -12
- package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
- package/dist/src/commands/re-export-scan-owner.js +116 -0
- package/dist/src/commands/re-export-scan.d.ts +19 -9
- package/dist/src/commands/re-export-scan.js +60 -24
- package/dist/src/commands/re-export-single.js +122 -46
- package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
- package/dist/src/commands/re-export-stale-registrations.js +62 -0
- package/dist/src/commands/re-export-summary.d.ts +28 -0
- package/dist/src/commands/re-export-summary.js +36 -0
- package/dist/src/commands/re-export.js +71 -57
- package/dist/src/commands/rebase/index.js +17 -17
- package/dist/src/commands/run.js +16 -2
- package/dist/src/commands/status-check.d.ts +55 -16
- package/dist/src/commands/status-check.js +92 -37
- package/dist/src/commands/status-json.d.ts +22 -9
- package/dist/src/commands/status-json.js +47 -32
- package/dist/src/commands/status-output.js +3 -1
- package/dist/src/commands/status-scope.d.ts +37 -0
- package/dist/src/commands/status-scope.js +54 -0
- package/dist/src/commands/status.js +66 -23
- package/dist/src/commands/test-browser-preflight.js +19 -3
- package/dist/src/commands/test-build-furnace.d.ts +22 -0
- package/dist/src/commands/test-build-furnace.js +54 -0
- package/dist/src/commands/test-build-phase.js +64 -41
- package/dist/src/commands/test-checkjs.js +10 -2
- package/dist/src/commands/test-diagnose.js +7 -0
- package/dist/src/commands/test-dispatch.d.ts +39 -0
- package/dist/src/commands/test-dispatch.js +211 -0
- package/dist/src/commands/test-furnace-reach.d.ts +37 -0
- package/dist/src/commands/test-furnace-reach.js +132 -0
- package/dist/src/commands/test-modes.d.ts +16 -1
- package/dist/src/commands/test-modes.js +24 -1
- package/dist/src/commands/test-multi-verdict.d.ts +61 -0
- package/dist/src/commands/test-multi-verdict.js +111 -0
- package/dist/src/commands/test-orchestrate.d.ts +52 -0
- package/dist/src/commands/test-orchestrate.js +240 -0
- package/dist/src/commands/test-perf-scope.d.ts +50 -0
- package/dist/src/commands/test-perf-scope.js +138 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +273 -0
- package/dist/src/commands/test-register.js +69 -10
- package/dist/src/commands/test-results.d.ts +62 -0
- package/dist/src/commands/test-results.js +62 -0
- package/dist/src/commands/test-run.d.ts +13 -55
- package/dist/src/commands/test-run.js +74 -99
- package/dist/src/commands/test-shards.d.ts +110 -0
- package/dist/src/commands/test-shards.js +255 -0
- package/dist/src/commands/test-stale-gate.d.ts +5 -1
- package/dist/src/commands/test-stale-gate.js +60 -28
- package/dist/src/commands/test-verdict.d.ts +11 -1
- package/dist/src/commands/test-verdict.js +28 -1
- package/dist/src/commands/test.d.ts +6 -1
- package/dist/src/commands/test.js +120 -124
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +151 -0
- package/dist/src/commands/token.js +10 -0
- package/dist/src/commands/verify-scope.d.ts +28 -0
- package/dist/src/commands/verify-scope.js +67 -0
- package/dist/src/commands/verify.d.ts +10 -2
- package/dist/src/commands/verify.js +67 -36
- package/dist/src/core/build-baseline-types.d.ts +46 -0
- package/dist/src/core/build-baseline-validate.js +19 -0
- package/dist/src/core/build-baseline.d.ts +5 -1
- package/dist/src/core/build-baseline.js +3 -1
- package/dist/src/core/build-overwrite-guard.d.ts +50 -0
- package/dist/src/core/build-overwrite-guard.js +84 -4
- package/dist/src/core/build-prepare.d.ts +13 -0
- package/dist/src/core/build-prepare.js +42 -10
- package/dist/src/core/build-run-record.d.ts +74 -0
- package/dist/src/core/build-run-record.js +136 -0
- package/dist/src/core/checkjs-support-heads.d.ts +41 -0
- package/dist/src/core/checkjs-support-heads.js +104 -0
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +10 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +219 -0
- package/dist/src/core/config-validate-file-size.d.ts +9 -6
- package/dist/src/core/config-validate-file-size.js +91 -2
- package/dist/src/core/config-validate-history.d.ts +11 -0
- package/dist/src/core/config-validate-history.js +38 -0
- package/dist/src/core/config-validate-patch-policy.js +100 -0
- package/dist/src/core/config-validate-test-toolchains.js +21 -0
- package/dist/src/core/config-validate-token-scales.d.ts +9 -0
- package/dist/src/core/config-validate-token-scales.js +92 -0
- package/dist/src/core/config-validate.js +20 -5
- package/dist/src/core/config.d.ts +5 -0
- package/dist/src/core/config.js +16 -0
- package/dist/src/core/coverage-dispatch.d.ts +24 -0
- package/dist/src/core/coverage-dispatch.js +44 -0
- package/dist/src/core/coverage-extend.d.ts +70 -45
- package/dist/src/core/coverage-extend.js +199 -144
- package/dist/src/core/coverage-records.d.ts +108 -0
- package/dist/src/core/coverage-records.js +384 -0
- package/dist/src/core/destructive.d.ts +7 -4
- package/dist/src/core/destructive.js +10 -10
- package/dist/src/core/engine-session-lock.d.ts +16 -1
- package/dist/src/core/engine-session-lock.js +38 -10
- package/dist/src/core/export-moved-code-shape.d.ts +49 -11
- package/dist/src/core/export-moved-code-shape.js +135 -44
- package/dist/src/core/file-lock-queue.d.ts +67 -0
- package/dist/src/core/file-lock-queue.js +221 -0
- package/dist/src/core/file-lock-shared.d.ts +57 -0
- package/dist/src/core/file-lock-shared.js +117 -0
- package/dist/src/core/file-lock.d.ts +13 -25
- package/dist/src/core/file-lock.js +163 -140
- package/dist/src/core/furnace-apply-output.d.ts +10 -0
- package/dist/src/core/furnace-apply-output.js +31 -1
- package/dist/src/core/furnace-apply.js +22 -12
- package/dist/src/core/furnace-composed-source.d.ts +76 -0
- package/dist/src/core/furnace-composed-source.js +167 -0
- package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
- package/dist/src/core/furnace-config-validate.js +14 -0
- package/dist/src/core/furnace-css-fragments.d.ts +10 -0
- package/dist/src/core/furnace-css-fragments.js +26 -0
- package/dist/src/core/furnace-operation.d.ts +3 -3
- package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
- package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
- package/dist/src/core/furnace-registration-remove.d.ts +22 -1
- package/dist/src/core/furnace-registration-remove.js +53 -17
- package/dist/src/core/furnace-registration-rename.d.ts +26 -0
- package/dist/src/core/furnace-registration-rename.js +77 -0
- package/dist/src/core/furnace-registration.d.ts +14 -4
- package/dist/src/core/furnace-registration.js +44 -10
- package/dist/src/core/furnace-staleness.d.ts +7 -0
- package/dist/src/core/furnace-staleness.js +34 -24
- package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
- package/dist/src/core/furnace-validate-accessibility.js +71 -28
- package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
- package/dist/src/core/furnace-validate-exceptions.js +80 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +78 -0
- package/dist/src/core/furnace-validate.js +1 -1
- package/dist/src/core/harness-port-fallback.d.ts +60 -0
- package/dist/src/core/harness-port-fallback.js +96 -0
- package/dist/src/core/history-log-file.d.ts +61 -0
- package/dist/src/core/history-log-file.js +164 -0
- package/dist/src/core/history-log.d.ts +2 -1
- package/dist/src/core/history-log.js +2 -1
- package/dist/src/core/json-preserving-edit.d.ts +22 -0
- package/dist/src/core/json-preserving-edit.js +199 -0
- package/dist/src/core/line-prefixer.d.ts +18 -0
- package/dist/src/core/line-prefixer.js +34 -0
- package/dist/src/core/mach.d.ts +7 -0
- package/dist/src/core/mach.js +32 -10
- package/dist/src/core/module-resolution-preflight.d.ts +19 -3
- package/dist/src/core/module-resolution-preflight.js +24 -6
- package/dist/src/core/negative-control-compiled.d.ts +27 -0
- package/dist/src/core/negative-control-compiled.js +73 -0
- package/dist/src/core/negative-control-diff.d.ts +51 -0
- package/dist/src/core/negative-control-diff.js +179 -0
- package/dist/src/core/negative-control.d.ts +150 -0
- package/dist/src/core/negative-control.js +399 -0
- package/dist/src/core/objdir-install-manifests.d.ts +73 -0
- package/dist/src/core/objdir-install-manifests.js +226 -0
- package/dist/src/core/park-store.d.ts +63 -0
- package/dist/src/core/park-store.js +216 -0
- package/dist/src/core/patch-export-update.d.ts +6 -0
- package/dist/src/core/patch-export-update.js +13 -3
- package/dist/src/core/patch-export.d.ts +3 -2
- package/dist/src/core/patch-export.js +22 -16
- package/dist/src/core/patch-files.js +26 -8
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
- package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
- package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
- package/dist/src/core/patch-lint-checkjs.js +20 -13
- package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
- package/dist/src/core/patch-lint-code-lines.js +105 -0
- package/dist/src/core/patch-lint-cross.d.ts +3 -1
- package/dist/src/core/patch-lint-cross.js +17 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +24 -14
- package/dist/src/core/patch-lint-file-size.d.ts +12 -5
- package/dist/src/core/patch-lint-file-size.js +22 -11
- package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +196 -0
- package/dist/src/core/patch-lint-ignore.js +429 -0
- package/dist/src/core/patch-lint-ownership.d.ts +10 -0
- package/dist/src/core/patch-lint-ownership.js +26 -0
- package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
- package/dist/src/core/patch-lint-size-thresholds.js +110 -0
- package/dist/src/core/patch-lint-size.d.ts +117 -0
- package/dist/src/core/patch-lint-size.js +331 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
- package/dist/src/core/patch-lint-staged-registration.js +20 -0
- package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
- package/dist/src/core/patch-lint-token-scale.js +286 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +66 -288
- package/dist/src/core/patch-lock.js +7 -6
- package/dist/src/core/patch-manifest-consistency.js +7 -6
- package/dist/src/core/patch-manifest-io.d.ts +6 -3
- package/dist/src/core/patch-manifest-io.js +27 -9
- package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
- package/dist/src/core/patch-manifest-key-order.js +61 -0
- package/dist/src/core/patch-manifest-stage.d.ts +46 -0
- package/dist/src/core/patch-manifest-stage.js +96 -0
- package/dist/src/core/patch-manifest-validate.js +61 -3
- package/dist/src/core/patch-order-legal.d.ts +26 -0
- package/dist/src/core/patch-order-legal.js +88 -0
- package/dist/src/core/patch-policy-content.d.ts +40 -0
- package/dist/src/core/patch-policy-content.js +216 -0
- package/dist/src/core/patch-policy.d.ts +22 -5
- package/dist/src/core/patch-policy.js +26 -12
- package/dist/src/core/patch-retired.d.ts +57 -0
- package/dist/src/core/patch-retired.js +92 -0
- package/dist/src/core/patch-subset.d.ts +16 -0
- package/dist/src/core/patch-subset.js +66 -0
- package/dist/src/core/preflight-wait.d.ts +20 -0
- package/dist/src/core/preflight-wait.js +38 -0
- package/dist/src/core/rebase-from-version.d.ts +46 -0
- package/dist/src/core/rebase-from-version.js +71 -0
- package/dist/src/core/run-log.d.ts +26 -0
- package/dist/src/core/run-log.js +106 -4
- package/dist/src/core/run-profile.d.ts +7 -0
- package/dist/src/core/run-profile.js +7 -1
- package/dist/src/core/smoke-patterns.d.ts +5 -0
- package/dist/src/core/smoke-patterns.js +13 -0
- package/dist/src/core/test-harness-crash.d.ts +19 -1
- package/dist/src/core/test-harness-crash.js +8 -9
- package/dist/src/core/test-harness-signature.d.ts +33 -0
- package/dist/src/core/test-harness-signature.js +23 -1
- package/dist/src/core/test-raw-log.d.ts +66 -0
- package/dist/src/core/test-raw-log.js +122 -0
- package/dist/src/core/test-run-completeness.d.ts +90 -0
- package/dist/src/core/test-run-completeness.js +184 -0
- package/dist/src/core/test-stale-check.d.ts +27 -1
- package/dist/src/core/test-stale-check.js +39 -49
- package/dist/src/core/test-stale-exempt.d.ts +40 -0
- package/dist/src/core/test-stale-exempt.js +70 -0
- package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
- package/dist/src/core/test-xpcshell-retry.js +2 -1
- package/dist/src/core/token-lint.d.ts +53 -0
- package/dist/src/core/token-lint.js +131 -0
- package/dist/src/core/token-sibling-sheets.d.ts +46 -0
- package/dist/src/core/token-sibling-sheets.js +119 -0
- package/dist/src/core/tree-guard.js +5 -0
- package/dist/src/core/typecheck-shim.d.ts +6 -2
- package/dist/src/core/typecheck-shim.js +205 -4
- package/dist/src/core/typecheck.js +3 -1
- package/dist/src/index.d.ts +14 -3
- package/dist/src/index.js +6 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +212 -24
- package/dist/src/types/commands/patches.d.ts +118 -1
- package/dist/src/types/config.d.ts +264 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +63 -1
- package/dist/src/types/furnace.js +7 -0
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/utils/options.d.ts +36 -7
- package/dist/src/utils/options.js +94 -8
- package/dist/src/utils/path-glob.d.ts +15 -0
- package/dist/src/utils/path-glob.js +47 -0
- package/dist/src/utils/regex.d.ts +15 -3
- package/dist/src/utils/regex.js +37 -16
- package/docs/configuration.md +369 -11
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +139 -3
- package/docs/lifecycle-invariants.md +65 -30
- package/docs/machine-output.md +120 -12
- package/docs/patch-workflow.md +458 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +346 -21
- package/package.json +3 -3
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
* the orchestrator would go the wrong way.
|
|
9
9
|
*/
|
|
10
10
|
import { Command, Option } from 'commander';
|
|
11
|
+
import { getProjectPaths } from '../core/config.js';
|
|
11
12
|
import { withEngineSessionLock } from '../core/engine-session-lock.js';
|
|
13
|
+
import { withPrivateGitIndex } from '../core/git-readonly-index.js';
|
|
12
14
|
import { addWaitLockOption, pickDefined, resolveWaitLockSeconds, stringListOption, } from '../utils/options.js';
|
|
13
15
|
import { reExportCommand } from './re-export.js';
|
|
16
|
+
import { preflightReExportArguments, readPathListFile } from './re-export-preflight.js';
|
|
14
17
|
/** Registers the re-export command on the CLI program. */
|
|
15
18
|
export function registerReExport(program, { getProjectRoot, withErrorHandling }) {
|
|
16
19
|
const reExport = program
|
|
@@ -20,41 +23,77 @@ export function registerReExport(program, { getProjectRoot, withErrorHandling })
|
|
|
20
23
|
'rebase for source metadata stamping.')
|
|
21
24
|
.option('-a, --all', 'Re-export all patches')
|
|
22
25
|
.option('-s, --scan', 'Scan directories for new/removed files and update filesAffected')
|
|
23
|
-
.option('--scan-file <path>', 'With --scan, add this explicit engine-relative file to one
|
|
26
|
+
.option('--scan-file <path>', 'With --scan, add this explicit engine-relative file without collecting adjacent files. Prefix the owning patch (<patch>:<path>, required with several target patches) to adopt into one of them; selected patches without a --scan-file refresh scan-less. Repeatable.', ...stringListOption())
|
|
24
27
|
.option('--scan-files <manifest>', 'With --scan, bulk-assign generated files from a JSON manifest: {"assignments":[{"patch":"002-name.patch","files":["path"]}]}. Selects patches from the manifest.')
|
|
25
28
|
.option('--files <paths>', 'Restrict the re-exported filesAffected to this comma-separated list (single target patch only)', (value) => value
|
|
26
29
|
.split(',')
|
|
27
30
|
.map((v) => v.trim())
|
|
28
31
|
.filter((v) => v.length > 0))
|
|
29
|
-
.option('--refuse-adjacent-unmanaged', 'Refuse a
|
|
30
|
-
.option('--refuse-foreign-drift', "Refuse a
|
|
32
|
+
.option('--refuse-adjacent-unmanaged', 'Refuse a re-export (non-zero exit, patch not written) when unmanaged files exist adjacent to the patch ownership, instead of warning. Adjacent means the same directory as an owned file (not a sibling or nested directory), or named by a registration line the patch adds. Beside --scan it judges the files the scan did not adopt. Mutually exclusive with --files.')
|
|
33
|
+
.option('--refuse-foreign-drift', "Refuse a re-export (non-zero exit, patch not written) when the refreshed body would add or drop lines, in a file the patch already owns, that the old patch body did not carry — protects multi-session checkouts from silently capturing another session's uncommitted edits. Beside --scan it guards each patch's existing files (adopted files count as expected). Mutually exclusive with --files.")
|
|
31
34
|
.option('--expect <path>', "With --refuse-foreign-drift, name an engine-relative file whose drift is this session's intended edit: drift confined to --expect files proceeds, drift anywhere else still refuses. Repeatable, and the whitelist applies across EVERY patch in this invocation, not per patch — so pass all the patches and all their --expect paths in one call rather than looping one patch at a time.", ...stringListOption())
|
|
32
35
|
.option('--expect-unmanaged <path>', 'With --refuse-adjacent-unmanaged, name an engine-relative file that is a REVIEWED, recorded exception to the adjacency refusal: an approved unmanaged path is still listed, but no longer refuses. Repeatable, and the carve-out applies across EVERY patch in this invocation, not per patch. Use it to keep the belt armed for everything else instead of dropping --refuse-adjacent-unmanaged entirely.', ...stringListOption())
|
|
33
|
-
.option('--expect-
|
|
36
|
+
.option('--expect-from <file>', 'Read --expect paths from a file, one engine-relative path per line (blank lines and # comments ignored). Adds to any --expect flags.')
|
|
37
|
+
.option('--expect-new <path>', 'With --refuse-adjacent-unmanaged, name a file this session created and will adopt with its next export (or --scan-file): it passes the belt and is reported, while every other adjacent file still refuses. Must be changed in engine/ and owned by no patch. Repeatable.', ...stringListOption())
|
|
38
|
+
.option('--adjacent-ignore <path|glob>', 'With --refuse-adjacent-unmanaged, a reviewed ignore-list entry: an engine-relative path, or a glob whose * stays within one path segment (** is refused). Matches are reported, never refused. Repeatable.', ...stringListOption())
|
|
39
|
+
.option('--expect-removed <path>', 'Retire a file from the selected patch while keeping scan-less refusals armed: an absent file the patch created, or a patch-modified upstream file whose engine copy is back at HEAD. Repeatable.', ...stringListOption())
|
|
34
40
|
.option('--dry-run', 'Show what would change without writing')
|
|
35
41
|
.option('--skip-lint', 'Skip patch lint checks (downgrade errors to warnings)')
|
|
36
42
|
.option('--no-cache', 'Bypass per-patch lint result cache reads and writes for this re-export')
|
|
37
43
|
.option('--allow-stale-furnace', 'Export the deployed engine copy even when the components/ source changed since the last furnace apply')
|
|
38
44
|
.option('--allow-shrink', 'Allow --files to remove paths currently owned by the patch. Required before --yes can bypass the shrink confirmation.')
|
|
45
|
+
.option('--declarations-out <path>', 'Where a refused --scan adoption writes its one --from-ready staged-declaration report (default: .fireforge/declarations/<timestamp>.json).')
|
|
46
|
+
.option('--hunks-from <diff>', "Export each file the diff names as the patch's current body plus only the diff's hunks, so another session's edits in the same file stay out. One explicit patch; not with --scan, --files or --all.")
|
|
47
|
+
.option('--exclude-hunks <diff>', "Export each file the diff names as the working tree minus the diff's hunks. One explicit patch; not with --scan, --files or --all.")
|
|
39
48
|
.option('-y, --yes', 'Skip confirmation prompts (required for non-TTY destructive writes)')
|
|
40
49
|
.option('--force-unsafe', 'Bypass cross-patch lint refusal when --files shrinks a patch')
|
|
41
50
|
.option('--stamp', "After every selected patch refreshes cleanly, stamp each re-exported patch's sourceVersion/sourceProduct in patches.json to firefox.version/firefox.product from fireforge.json. No effect on a partial run.")
|
|
42
51
|
.addOption(new Option('--tier <tier>', 'Force a tier override on the selected patch (only "branding" recognised). Mutually exclusive with --all.').choices(['branding']))
|
|
43
52
|
.option('--lint-ignore <check-id>', 'Append a lint check ID to the patch\'s PatchMetadata.lintIgnore (union, de-duped, repeatable). Mutually exclusive with --all. Use "fireforge patch lint-ignore" for --remove / --clear.', ...stringListOption());
|
|
44
53
|
addWaitLockOption(reExport).action(withErrorHandling(async (patches, options) => {
|
|
45
|
-
const { tier, lintIgnore, scanFile, scanFiles, cache, expect, expectUnmanaged, ...rest } = options;
|
|
46
54
|
const projectRoot = getProjectRoot();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const reExportOptions = await toReExportOptions(options);
|
|
56
|
+
// Refuse what the locked run would refuse before queueing for the
|
|
57
|
+
// lock, which can be a long wait under contention.
|
|
58
|
+
await preflightReExportArguments(projectRoot, patches, reExportOptions);
|
|
59
|
+
// A dry run writes nothing, so it does not queue behind peers' builds
|
|
60
|
+
// and test runs for the engine lock. Its git plumbing runs on a
|
|
61
|
+
// private index, so not even git's stat-cache refresh reaches the
|
|
62
|
+
// shared `.git/index`; dry runs still serialize among themselves.
|
|
63
|
+
if (reExportOptions.dryRun === true) {
|
|
64
|
+
const engineDir = getProjectPaths(projectRoot).engine;
|
|
65
|
+
await withPrivateGitIndex(engineDir, () => reExportCommand(projectRoot, patches, reExportOptions));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await withEngineSessionLock(projectRoot, 're-export', () => reExportCommand(projectRoot, patches, reExportOptions), { waitLockSeconds: resolveWaitLockSeconds(options.waitLock) });
|
|
59
69
|
}));
|
|
60
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Maps commander's option names onto {@link ReExportOptions}, reading the
|
|
73
|
+
* `--expect-from` list into `expect` and dropping empty repeatables.
|
|
74
|
+
*/
|
|
75
|
+
async function toReExportOptions(options) {
|
|
76
|
+
const { tier, lintIgnore, scanFile, scanFiles, cache, expect: expectFlags, expectFrom, expectUnmanaged, expectNew, adjacentIgnore, ...rest } = options;
|
|
77
|
+
const expect = [
|
|
78
|
+
...(expectFlags ?? []),
|
|
79
|
+
...(expectFrom !== undefined ? await readPathListFile(expectFrom, '--expect-from') : []),
|
|
80
|
+
];
|
|
81
|
+
return {
|
|
82
|
+
...pickDefined({
|
|
83
|
+
...rest,
|
|
84
|
+
scanFiles: nonEmpty(scanFile),
|
|
85
|
+
scanFilesManifest: scanFiles,
|
|
86
|
+
tier: tier,
|
|
87
|
+
lintIgnore: nonEmpty(lintIgnore),
|
|
88
|
+
expect: nonEmpty(expect),
|
|
89
|
+
expectUnmanaged: nonEmpty(expectUnmanaged),
|
|
90
|
+
expectNew: nonEmpty(expectNew),
|
|
91
|
+
adjacentIgnore: nonEmpty(adjacentIgnore),
|
|
92
|
+
}),
|
|
93
|
+
...(cache === false ? { noCache: true } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** The list when it has entries, else undefined (which `pickDefined` drops). */
|
|
97
|
+
function nonEmpty(values) {
|
|
98
|
+
return values !== undefined && values.length > 0 ? values : undefined;
|
|
99
|
+
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
2
2
|
import type { ProjectPaths } from '../types/config.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Validates every retirement before any patch is written; typos never
|
|
5
|
+
* silently grant a carve-out. Two shapes retire a file from its one
|
|
6
|
+
* selected owner:
|
|
7
|
+
*
|
|
8
|
+
* - a file the patch created (`new file mode` section), now absent from
|
|
9
|
+
* disk and untracked in engine HEAD;
|
|
10
|
+
* - an upstream file the patch modified or deleted, whose engine copy is
|
|
11
|
+
* back at HEAD (present, tracked, no diff). A scan-less re-export keeps
|
|
12
|
+
* `filesAffected` as it is, so without this the stale entry would stay
|
|
13
|
+
* in the manifest and in the reserved-range policy check.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateExpectedRemovals(paths: ProjectPaths, patches: PatchMetadata[], options: ReExportOptions, queue?: readonly PatchMetadata[]): Promise<string[]>;
|
|
@@ -2,32 +2,45 @@
|
|
|
2
2
|
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { listTrackedInHead } from '../core/git-file-ops.js';
|
|
5
|
+
import { getDirtyFiles } from '../core/git-status.js';
|
|
5
6
|
import { parseDiffSections } from '../core/patch-parse.js';
|
|
6
7
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
7
8
|
import { getNodeErrorCode } from '../utils/errors.js';
|
|
8
9
|
import { readText } from '../utils/fs.js';
|
|
9
10
|
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Validates every retirement before any patch is written; typos never
|
|
13
|
+
* silently grant a carve-out. Two shapes retire a file from its one
|
|
14
|
+
* selected owner:
|
|
15
|
+
*
|
|
16
|
+
* - a file the patch created (`new file mode` section), now absent from
|
|
17
|
+
* disk and untracked in engine HEAD;
|
|
18
|
+
* - an upstream file the patch modified or deleted, whose engine copy is
|
|
19
|
+
* back at HEAD (present, tracked, no diff). A scan-less re-export keeps
|
|
20
|
+
* `filesAffected` as it is, so without this the stale entry would stay
|
|
21
|
+
* in the manifest and in the reserved-range policy check.
|
|
22
|
+
*/
|
|
23
|
+
export async function validateExpectedRemovals(paths, patches, options, queue = patches) {
|
|
12
24
|
const removed = [
|
|
13
25
|
...new Set((options.expectRemoved ?? []).map((file) => normalizeEngineRelativeInput(file, '--expect-removed'))),
|
|
14
26
|
];
|
|
15
27
|
if (removed.length === 0)
|
|
16
28
|
return removed;
|
|
17
29
|
const tracked = await listTrackedInHead(paths.engine, removed);
|
|
30
|
+
const dirty = new Set(await getDirtyFiles(paths.engine, [...tracked]));
|
|
18
31
|
for (const file of removed) {
|
|
19
32
|
const owners = patches.filter((patch) => patch.filesAffected.includes(file));
|
|
20
|
-
if (owners.length !== 1 ||
|
|
21
|
-
tracked.has(file) ||
|
|
22
|
-
(await removalPathPresent(join(paths.engine, file)))) {
|
|
23
|
-
throw new InvalidArgumentError(`--expect-removed ${file} must be absent, untracked in engine HEAD, and owned by exactly one selected patch.`, '--expect-removed');
|
|
24
|
-
}
|
|
25
33
|
const owner = owners[0];
|
|
26
|
-
if (
|
|
27
|
-
throw new InvalidArgumentError(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
if (owners.length !== 1 || owner === undefined) {
|
|
35
|
+
throw new InvalidArgumentError(`--expect-removed ${file} must be owned by exactly one selected patch.` +
|
|
36
|
+
unselectedOwnerHint(file, patches, queue), '--expect-removed');
|
|
37
|
+
}
|
|
38
|
+
const section = parseDiffSections(await readText(join(paths.patches, owner.filename))).find((candidate) => candidate.targetPath === file);
|
|
39
|
+
if (tracked.has(file)) {
|
|
40
|
+
assertUpstreamFileBackAtHead(file, owner, section !== undefined && !section.isNewFile, dirty);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await assertCreatedFileAbsent(paths, file, owner, section?.isNewFile === true);
|
|
31
44
|
}
|
|
32
45
|
if (owner.filesAffected.every((path) => removed.includes(path))) {
|
|
33
46
|
throw new InvalidArgumentError('Retiring every file would empty the patch; use patch delete instead.', '--expect-removed');
|
|
@@ -35,6 +48,38 @@ export async function validateExpectedRemovals(paths, patches, options) {
|
|
|
35
48
|
}
|
|
36
49
|
return removed;
|
|
37
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Each path retires from its own owner, so one run may retire paths from
|
|
53
|
+
* several selected patches. A path whose owner was left out of the
|
|
54
|
+
* selection names that owner, since adding it is the usual fix.
|
|
55
|
+
*/
|
|
56
|
+
function unselectedOwnerHint(file, selected, queue) {
|
|
57
|
+
const selectedNames = new Set(selected.map((patch) => patch.filename));
|
|
58
|
+
const outside = queue
|
|
59
|
+
.filter((patch) => !selectedNames.has(patch.filename) && patch.filesAffected.includes(file))
|
|
60
|
+
.map((patch) => patch.filename);
|
|
61
|
+
if (outside.length === 0)
|
|
62
|
+
return '';
|
|
63
|
+
return ` It is owned by ${outside.join(', ')}, which this run did not select; add it to the selection to retire both in one run.`;
|
|
64
|
+
}
|
|
65
|
+
async function assertCreatedFileAbsent(paths, file, owner, createdByOwner) {
|
|
66
|
+
if (await removalPathPresent(join(paths.engine, file))) {
|
|
67
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is untracked in engine HEAD and still present on disk; ` +
|
|
68
|
+
'a patch-created file can be retired only once it is deleted.', '--expect-removed');
|
|
69
|
+
}
|
|
70
|
+
if (!createdByOwner) {
|
|
71
|
+
throw new InvalidArgumentError(`--expect-removed ${file} was not created by its selected patch ${owner.filename}.`, '--expect-removed');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function assertUpstreamFileBackAtHead(file, owner, changedByOwner, dirty) {
|
|
75
|
+
if (dirty.has(file)) {
|
|
76
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is tracked in engine HEAD, but its engine copy still differs ` +
|
|
77
|
+
'from HEAD. Restore the HEAD copy first, then retire it.', '--expect-removed');
|
|
78
|
+
}
|
|
79
|
+
if (!changedByOwner) {
|
|
80
|
+
throw new InvalidArgumentError(`--expect-removed ${file} is not changed by its selected patch ${owner.filename}.`, '--expect-removed');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
38
83
|
/** A dangling symlink is still an existing owned path, not an approved absence. */
|
|
39
84
|
async function removalPathPresent(path) {
|
|
40
85
|
try {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
|
|
3
|
+
*
|
|
4
|
+
* One change often spans a patch that creates files and a patch that
|
|
5
|
+
* registers or imports them. With `--scan-file` bound to a single target
|
|
6
|
+
* patch, that change took two invocations, and the adopting one could not
|
|
7
|
+
* carry the belts the other did. Naming the owner per file lets one call
|
|
8
|
+
* adopt each file into its patch while every other selected patch
|
|
9
|
+
* refreshes scan-less, all under the same `--refuse-foreign-drift` belt
|
|
10
|
+
* and in a single engine-lock hold.
|
|
11
|
+
*/
|
|
12
|
+
import type { PatchesManifest, PatchMetadata, ReExportOptions } from '../types/commands/index.js';
|
|
13
|
+
/** A `--scan-file` value split into its optional owner and its path. */
|
|
14
|
+
export interface ScanFileOwnerSpec {
|
|
15
|
+
owner: string | undefined;
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Splits `<patch>:<path>` into owner and path. The owner is everything
|
|
20
|
+
* before the first `:`, and only when that part holds no path separator, so
|
|
21
|
+
* a plain path (even one with a later `:`) stays a path. A one-letter
|
|
22
|
+
* prefix before `:/` or `:\` reads as a Windows drive, not a patch.
|
|
23
|
+
*/
|
|
24
|
+
export declare function splitScanFileOwner(raw: string): ScanFileOwnerSpec;
|
|
25
|
+
/** Rejoins a split value, so normalization can rewrite the path half only. */
|
|
26
|
+
export declare function joinScanFileOwner(spec: ScanFileOwnerSpec): string;
|
|
27
|
+
/**
|
|
28
|
+
* Argument-shape check, before any manifest is read: a multi-patch call has
|
|
29
|
+
* to say which patch each file joins.
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateScanFileOwners(patches: readonly string[], scanFiles: readonly string[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves each `--scan-file` value to its owning selected patch. Returns
|
|
34
|
+
* `undefined` when no `--scan-file` was given. An unprefixed path belongs
|
|
35
|
+
* to the single selected patch (the historical form).
|
|
36
|
+
*/
|
|
37
|
+
export declare function assignScanFileOwners(scanFiles: readonly string[] | undefined, selectedPatches: readonly PatchMetadata[], manifest: PatchesManifest): Map<string, string[]> | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The options one patch of the loop runs with. A patch with assigned files
|
|
40
|
+
* adopts exactly those; a selected patch without any refreshes scan-less,
|
|
41
|
+
* because `--scan` beside per-file owners scopes adoption to the named
|
|
42
|
+
* files rather than arming a broad directory scan for every other patch.
|
|
43
|
+
*/
|
|
44
|
+
export declare function scanOptionsForPatch(options: ReExportOptions, patchFilename: string, assignments: ReadonlyMap<string, string[]> | undefined): ReExportOptions;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Per-file owners for `re-export --scan --scan-file <patch>:<path>`.
|
|
4
|
+
*
|
|
5
|
+
* One change often spans a patch that creates files and a patch that
|
|
6
|
+
* registers or imports them. With `--scan-file` bound to a single target
|
|
7
|
+
* patch, that change took two invocations, and the adopting one could not
|
|
8
|
+
* carry the belts the other did. Naming the owner per file lets one call
|
|
9
|
+
* adopt each file into its patch while every other selected patch
|
|
10
|
+
* refreshes scan-less, all under the same `--refuse-foreign-drift` belt
|
|
11
|
+
* and in a single engine-lock hold.
|
|
12
|
+
*/
|
|
13
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
14
|
+
import { resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
15
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
16
|
+
/**
|
|
17
|
+
* Splits `<patch>:<path>` into owner and path. The owner is everything
|
|
18
|
+
* before the first `:`, and only when that part holds no path separator, so
|
|
19
|
+
* a plain path (even one with a later `:`) stays a path. A one-letter
|
|
20
|
+
* prefix before `:/` or `:\` reads as a Windows drive, not a patch.
|
|
21
|
+
*/
|
|
22
|
+
export function splitScanFileOwner(raw) {
|
|
23
|
+
const colon = raw.indexOf(':');
|
|
24
|
+
if (colon <= 0)
|
|
25
|
+
return { owner: undefined, path: raw };
|
|
26
|
+
const owner = raw.slice(0, colon).trim();
|
|
27
|
+
const path = raw.slice(colon + 1);
|
|
28
|
+
if (owner.length === 0 || /[/\\]/.test(owner))
|
|
29
|
+
return { owner: undefined, path: raw };
|
|
30
|
+
if (/^[A-Za-z]$/.test(owner) && /^[/\\]/.test(path))
|
|
31
|
+
return { owner: undefined, path: raw };
|
|
32
|
+
return { owner, path };
|
|
33
|
+
}
|
|
34
|
+
/** Rejoins a split value, so normalization can rewrite the path half only. */
|
|
35
|
+
export function joinScanFileOwner(spec) {
|
|
36
|
+
return spec.owner === undefined ? spec.path : `${spec.owner}:${spec.path}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Argument-shape check, before any manifest is read: a multi-patch call has
|
|
40
|
+
* to say which patch each file joins.
|
|
41
|
+
*/
|
|
42
|
+
export function validateScanFileOwners(patches, scanFiles) {
|
|
43
|
+
if (patches.length === 0) {
|
|
44
|
+
throw new InvalidArgumentError('--scan-file needs explicit target patch identifiers.', '--scan-file');
|
|
45
|
+
}
|
|
46
|
+
if (patches.length === 1)
|
|
47
|
+
return;
|
|
48
|
+
const unowned = scanFiles.filter((file) => splitScanFileOwner(file).owner === undefined);
|
|
49
|
+
if (unowned.length > 0) {
|
|
50
|
+
throw new InvalidArgumentError(`--scan-file in a call with ${patches.length} patches must name each file's owning patch ` +
|
|
51
|
+
`as <patch>:<path> (for example --scan-file ${patches[0] ?? '201'}:${unowned[0] ?? '<path>'}). ` +
|
|
52
|
+
`Missing an owner: ${unowned.join(', ')}.`, '--scan-file');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolves each `--scan-file` value to its owning selected patch. Returns
|
|
57
|
+
* `undefined` when no `--scan-file` was given. An unprefixed path belongs
|
|
58
|
+
* to the single selected patch (the historical form).
|
|
59
|
+
*/
|
|
60
|
+
export function assignScanFileOwners(scanFiles, selectedPatches, manifest) {
|
|
61
|
+
if (scanFiles === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const selected = new Set(selectedPatches.map((patch) => patch.filename));
|
|
64
|
+
const byPatch = new Map();
|
|
65
|
+
const ownerByFile = new Map();
|
|
66
|
+
for (const raw of scanFiles) {
|
|
67
|
+
const { owner, path } = splitScanFileOwner(raw);
|
|
68
|
+
const filename = resolveOwner(owner, selectedPatches, manifest);
|
|
69
|
+
if (!selected.has(filename)) {
|
|
70
|
+
throw new InvalidArgumentError(`--scan-file ${raw}: ${filename} is not among the selected patches. ` +
|
|
71
|
+
'Pass it as a positional patch too.', '--scan-file');
|
|
72
|
+
}
|
|
73
|
+
const previous = ownerByFile.get(path);
|
|
74
|
+
if (previous !== undefined && previous !== filename) {
|
|
75
|
+
throw new InvalidArgumentError(`--scan-file path is assigned to more than one patch: ${path} (${previous}, ${filename})`, '--scan-file');
|
|
76
|
+
}
|
|
77
|
+
ownerByFile.set(path, filename);
|
|
78
|
+
const files = byPatch.get(filename) ?? [];
|
|
79
|
+
if (!files.includes(path))
|
|
80
|
+
files.push(path);
|
|
81
|
+
byPatch.set(filename, files);
|
|
82
|
+
}
|
|
83
|
+
for (const files of byPatch.values())
|
|
84
|
+
files.sort();
|
|
85
|
+
return byPatch;
|
|
86
|
+
}
|
|
87
|
+
function resolveOwner(owner, selectedPatches, manifest) {
|
|
88
|
+
if (owner === undefined) {
|
|
89
|
+
const only = selectedPatches.length === 1 ? selectedPatches[0] : undefined;
|
|
90
|
+
if (only === undefined) {
|
|
91
|
+
throw new InvalidArgumentError('--scan-file in a multi-patch call must name its owning patch as <patch>:<path>.', '--scan-file');
|
|
92
|
+
}
|
|
93
|
+
return only.filename;
|
|
94
|
+
}
|
|
95
|
+
const match = resolvePatchIdentifier(owner, manifest.patches);
|
|
96
|
+
if (!match) {
|
|
97
|
+
throw new InvalidArgumentError(`--scan-file: ${formatPatchNotFoundError(owner, manifest.patches)}`, '--scan-file');
|
|
98
|
+
}
|
|
99
|
+
return match.filename;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The options one patch of the loop runs with. A patch with assigned files
|
|
103
|
+
* adopts exactly those; a selected patch without any refreshes scan-less,
|
|
104
|
+
* because `--scan` beside per-file owners scopes adoption to the named
|
|
105
|
+
* files rather than arming a broad directory scan for every other patch.
|
|
106
|
+
*/
|
|
107
|
+
export function scanOptionsForPatch(options, patchFilename, assignments) {
|
|
108
|
+
if (assignments === undefined)
|
|
109
|
+
return options;
|
|
110
|
+
const assigned = assignments.get(patchFilename);
|
|
111
|
+
if (assigned !== undefined)
|
|
112
|
+
return { ...options, scanFiles: assigned };
|
|
113
|
+
const scanLess = { ...options, scan: false };
|
|
114
|
+
delete scanLess.scanFiles;
|
|
115
|
+
return scanLess;
|
|
116
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { type PatchQueueContext } from '../core/patch-lint.js';
|
|
1
2
|
import type { PatchesManifest } from '../types/commands/index.js';
|
|
3
|
+
import type { FireForgeConfig } from '../types/config.js';
|
|
2
4
|
export interface ScanResult {
|
|
3
5
|
updated: string[];
|
|
4
6
|
added: string[];
|
|
5
7
|
removed: string[];
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes repeatable `--scan-file` inputs into safe engine-relative
|
|
11
|
+
* paths, keeping an optional `<patch>:` owner prefix intact.
|
|
12
|
+
*/
|
|
8
13
|
export declare function normalizeScanFiles(scanFiles: readonly string[] | undefined): string[] | undefined;
|
|
9
14
|
/** Normalizes one CLI-provided path into a safe engine-relative path. */
|
|
10
15
|
export declare function normalizeEngineRelativeInput(rawPath: string, flagName: string): string;
|
|
@@ -44,15 +49,20 @@ export declare function assertScanAdoptionsHaveNoForwardImports(args: {
|
|
|
44
49
|
engineDir: string;
|
|
45
50
|
patchFilename: string;
|
|
46
51
|
added: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Threads `patchLint.forwardRegistration` so jar.mn registrations are judged
|
|
54
|
+
* too, and `patchPolicy`, so the remedy names a legal order.
|
|
55
|
+
*/
|
|
56
|
+
config?: FireForgeConfig;
|
|
57
|
+
/** `--declarations-out`: where the one `--from`-ready report goes. */
|
|
58
|
+
declarationsOut?: string | undefined;
|
|
47
59
|
}): Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
export declare function formatForwardImportRemedy(reRunHint: string): string;
|
|
60
|
+
/** The manifest rows a queue context carries, for remedy computations. */
|
|
61
|
+
export declare function manifestFromQueue(ctx: PatchQueueContext): PatchesManifest;
|
|
62
|
+
/** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
|
|
63
|
+
export declare function categoryOf(ctx: PatchQueueContext, filename: string): {
|
|
64
|
+
category?: string;
|
|
65
|
+
};
|
|
56
66
|
/** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
|
|
57
67
|
export declare function assertScanFileAdditionsHaveDiffHunks(args: {
|
|
58
68
|
diffContent: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { confirm } from '@clack/prompts';
|
|
4
4
|
import { getDiffForFilesAgainstHead } from '../core/git-diff.js';
|
|
5
5
|
import { listTrackedInHead } from '../core/git-file-ops.js';
|
|
@@ -15,15 +15,27 @@ import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
|
15
15
|
import { pathExists } from '../utils/fs.js';
|
|
16
16
|
import { cancel, info, isCancel, warn } from '../utils/logger.js';
|
|
17
17
|
import { isContainedRelativePath, normalizePathSlashes, stripEnginePrefix, } from '../utils/paths.js';
|
|
18
|
+
import { formatDeclarationsReport, writeDeclarationsReport } from './declarations-report.js';
|
|
19
|
+
import { formatForwardImportRemedy } from './forward-import-remedy.js';
|
|
20
|
+
import { joinScanFileOwner, splitScanFileOwner } from './re-export-scan-owner.js';
|
|
18
21
|
const SCAN_ADD_COUNT_THRESHOLD = 3;
|
|
19
22
|
/** Concurrency bound for existence probes (matches the classify/lint pools). */
|
|
20
23
|
const PATH_PROBE_CONCURRENCY = 8;
|
|
21
24
|
const SCAN_DIR_COUNT_THRESHOLD = 2;
|
|
22
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Normalizes repeatable `--scan-file` inputs into safe engine-relative
|
|
27
|
+
* paths, keeping an optional `<patch>:` owner prefix intact.
|
|
28
|
+
*/
|
|
23
29
|
export function normalizeScanFiles(scanFiles) {
|
|
24
|
-
const normalized = [
|
|
25
|
-
.map((file) =>
|
|
26
|
-
|
|
30
|
+
const normalized = [
|
|
31
|
+
...new Set((scanFiles ?? []).map((file) => {
|
|
32
|
+
const spec = splitScanFileOwner(file);
|
|
33
|
+
return joinScanFileOwner({
|
|
34
|
+
owner: spec.owner,
|
|
35
|
+
path: normalizeEngineRelativeInput(spec.path, '--scan-file'),
|
|
36
|
+
});
|
|
37
|
+
})),
|
|
38
|
+
].sort();
|
|
27
39
|
return normalized.length > 0 ? normalized : undefined;
|
|
28
40
|
}
|
|
29
41
|
/** Normalizes one CLI-provided path into a safe engine-relative path. */
|
|
@@ -171,7 +183,7 @@ function scanAdditionsNeedConfirmation(added) {
|
|
|
171
183
|
* (all adopt through the same scan result), including dry-run.
|
|
172
184
|
*/
|
|
173
185
|
export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
174
|
-
const { patchesDir, engineDir, patchFilename, added } = args;
|
|
186
|
+
const { patchesDir, engineDir, patchFilename, added, config } = args;
|
|
175
187
|
if (added.length === 0)
|
|
176
188
|
return;
|
|
177
189
|
const candidateDiff = await getDiffForFilesAgainstHead(engineDir, [...added]);
|
|
@@ -180,7 +192,7 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
|
180
192
|
const candidateNewFiles = buildNewFileTextProjection(candidateDiff);
|
|
181
193
|
const candidateCreations = detectNewFilesInDiff(candidateDiff);
|
|
182
194
|
const candidateAdditions = buildModifiedFileAdditionsFromDiff(candidateDiff);
|
|
183
|
-
const baseCtx = await buildPatchQueueContext(patchesDir);
|
|
195
|
+
const baseCtx = await buildPatchQueueContext(patchesDir, config);
|
|
184
196
|
const projectedEntries = baseCtx.entries.map((entry) => {
|
|
185
197
|
if (entry.filename !== patchFilename)
|
|
186
198
|
return entry;
|
|
@@ -193,27 +205,51 @@ export async function assertScanAdoptionsHaveNoForwardImports(args) {
|
|
|
193
205
|
};
|
|
194
206
|
});
|
|
195
207
|
const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
|
|
196
|
-
const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
208
|
+
const projectedIssues = lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
197
209
|
const addedSet = new Set(added);
|
|
198
|
-
const
|
|
199
|
-
|
|
210
|
+
const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
|
|
211
|
+
const offending = regressions.filter((issue) => issue.check === 'forward-import' && addedSet.has(issue.file));
|
|
212
|
+
// A forward registration (jar.mn packaging, a test manifest, ...) only
|
|
213
|
+
// regresses here because the candidates moved the registered file's
|
|
214
|
+
// creation to this patch, or because a candidate registers it: either way
|
|
215
|
+
// the adoption caused it, and the operator should declare it in the same
|
|
216
|
+
// pass as the import instead of meeting it later in the queue audit.
|
|
217
|
+
const registrations = regressions.filter((issue) => issue.check === 'forward-registration');
|
|
218
|
+
if (offending.length === 0 && registrations.length === 0)
|
|
200
219
|
return;
|
|
201
220
|
const details = offending.map((issue) => ` - ${issue.file}: ${issue.message}`).join('\n');
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
221
|
+
const registrationDetails = registrations
|
|
222
|
+
.map((issue) => ` - ${issue.file}: ${issue.message}`)
|
|
223
|
+
.join('\n');
|
|
224
|
+
const parts = [];
|
|
225
|
+
if (offending.length > 0) {
|
|
226
|
+
parts.push(`${offending.length} scanned file${offending.length === 1 ? '' : 's'} import modules created by later patches:\n${details}`);
|
|
227
|
+
}
|
|
228
|
+
if (registrations.length > 0) {
|
|
229
|
+
parts.push(`${registrations.length} forward registration${registrations.length === 1 ? '' : 's'} the adoption would leave dangling:\n${registrationDetails}`);
|
|
230
|
+
}
|
|
231
|
+
const report = await writeDeclarationsReport(dirname(resolve(patchesDir)), [...offending, ...registrations], args.declarationsOut);
|
|
232
|
+
throw new GeneralError(`Refusing to adopt scanned files into ${patchFilename}: ${parts.join('\n')}\n` +
|
|
233
|
+
(report !== undefined ? `${formatDeclarationsReport(report).join('\n')}\n` : '') +
|
|
234
|
+
formatForwardImportRemedy({
|
|
235
|
+
reRunHint: 're-running the scan',
|
|
236
|
+
issues: [...offending, ...registrations],
|
|
237
|
+
manifest: manifestFromQueue(baseCtx),
|
|
238
|
+
...(args.config !== undefined ? { config: args.config } : {}),
|
|
239
|
+
...categoryOf(baseCtx, patchFilename),
|
|
240
|
+
}));
|
|
205
241
|
}
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
/** The manifest rows a queue context carries, for remedy computations. */
|
|
243
|
+
export function manifestFromQueue(ctx) {
|
|
244
|
+
return {
|
|
245
|
+
version: 1,
|
|
246
|
+
patches: ctx.entries.flatMap((entry) => (entry.metadata ? [entry.metadata] : [])),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** `{ category }` of the named queue entry, or `{}` when it has no metadata. */
|
|
250
|
+
export function categoryOf(ctx, filename) {
|
|
251
|
+
const category = ctx.entries.find((entry) => entry.filename === filename)?.metadata?.category;
|
|
252
|
+
return category !== undefined ? { category } : {};
|
|
217
253
|
}
|
|
218
254
|
/** Refuses explicit `--scan-file` additions that did not produce patch hunks. */
|
|
219
255
|
export function assertScanFileAdditionsHaveDiffHunks(args) {
|