@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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (`re-export-adjacent.ts`) warns about unmanaged neighbour files but is
|
|
10
10
|
* structurally blind to drift inside owned files. This module compares
|
|
11
11
|
* the old body against the refreshed body per file and reports the
|
|
12
|
-
* payload lines that are about to enter the body. Under
|
|
12
|
+
* payload lines that are about to enter or leave the body. Under
|
|
13
13
|
* `--refuse-foreign-drift` the patch is skipped and the run exits
|
|
14
14
|
* non-zero, mirroring `--refuse-adjacent-unmanaged`.
|
|
15
15
|
*
|
|
@@ -74,8 +74,15 @@ function multisetExcess(next, previous) {
|
|
|
74
74
|
}
|
|
75
75
|
return excess;
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Summarizes the hunks of `payload` that carry the budgeted `+`/`-` lines.
|
|
79
|
+
* `describe` renders the per-hunk counts, so the old body's dropped lines
|
|
80
|
+
* read in the same engine-edit direction as the new body's absorbed ones.
|
|
81
|
+
*/
|
|
82
|
+
function summarizeForeignHunks(payload, foreignAdded, foreignRemoved, describe) {
|
|
78
83
|
const summaries = [];
|
|
84
|
+
if (payload === undefined)
|
|
85
|
+
return summaries;
|
|
79
86
|
const addedBudget = new Map(foreignAdded);
|
|
80
87
|
const removedBudget = new Map(foreignRemoved);
|
|
81
88
|
for (const section of payload.sections) {
|
|
@@ -101,7 +108,7 @@ function summarizeForeignHunks(payload, foreignAdded, foreignRemoved) {
|
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
if (added > 0 || removed > 0) {
|
|
104
|
-
summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ (
|
|
111
|
+
summaries.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ ${describe(added, removed)}`);
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
}
|
|
@@ -113,47 +120,71 @@ function countExcess(excess) {
|
|
|
113
120
|
total += count;
|
|
114
121
|
return total;
|
|
115
122
|
}
|
|
123
|
+
const EMPTY_PAYLOAD_LINES = new Map();
|
|
116
124
|
/**
|
|
117
125
|
* Pure comparison: per owned file, which `+`/`-` payload lines the
|
|
118
|
-
* refreshed body carries that the old body did not
|
|
119
|
-
*
|
|
120
|
-
*
|
|
126
|
+
* refreshed body carries that the old body did not, and which the old body
|
|
127
|
+
* carried that the refreshed one dropped. Both directions are drift: a
|
|
128
|
+
* concurrent session deleting a line the patch added changes the patch as
|
|
129
|
+
* surely as one adding a line, and a belt that only saw absorbed lines let
|
|
130
|
+
* such a deletion through silently. Files not in `previousFilesAffected`
|
|
131
|
+
* are skipped, since those are intentional adoptions (`--scan`) or
|
|
132
|
+
* retirements (`--expect-removed`) rather than drift.
|
|
121
133
|
*/
|
|
122
134
|
export function computeForeignDrift(oldBody, newBody, previousFilesAffected) {
|
|
123
135
|
const owned = new Set(previousFilesAffected);
|
|
124
136
|
const oldByFile = collectPayloadByFile(oldBody);
|
|
125
137
|
const newByFile = collectPayloadByFile(newBody);
|
|
126
138
|
const drift = [];
|
|
127
|
-
|
|
139
|
+
// New-body order first, then files only the old body carried.
|
|
140
|
+
const files = new Set([...newByFile.keys(), ...oldByFile.keys()]);
|
|
141
|
+
for (const file of files) {
|
|
128
142
|
if (!owned.has(file))
|
|
129
143
|
continue;
|
|
144
|
+
const next = newByFile.get(file);
|
|
130
145
|
const previous = oldByFile.get(file);
|
|
131
|
-
if (next
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
if (next?.isBinary === true || previous?.isBinary === true) {
|
|
147
|
+
if (binaryContentChanged(next, previous)) {
|
|
148
|
+
drift.push({
|
|
149
|
+
file,
|
|
150
|
+
addedLines: 0,
|
|
151
|
+
removedLines: 0,
|
|
152
|
+
hunkSummaries: [],
|
|
153
|
+
binaryChanged: true,
|
|
154
|
+
});
|
|
138
155
|
}
|
|
139
156
|
continue;
|
|
140
157
|
}
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
158
|
+
const absorbedAdded = multisetExcess(next?.added ?? EMPTY_PAYLOAD_LINES, previous?.added ?? EMPTY_PAYLOAD_LINES);
|
|
159
|
+
const absorbedRemoved = multisetExcess(next?.removed ?? EMPTY_PAYLOAD_LINES, previous?.removed ?? EMPTY_PAYLOAD_LINES);
|
|
160
|
+
const droppedAdded = multisetExcess(previous?.added ?? EMPTY_PAYLOAD_LINES, next?.added ?? EMPTY_PAYLOAD_LINES);
|
|
161
|
+
const droppedRemoved = multisetExcess(previous?.removed ?? EMPTY_PAYLOAD_LINES, next?.removed ?? EMPTY_PAYLOAD_LINES);
|
|
162
|
+
const addedLines = countExcess(absorbedAdded) + countExcess(droppedRemoved);
|
|
163
|
+
const removedLines = countExcess(absorbedRemoved) + countExcess(droppedAdded);
|
|
145
164
|
if (addedLines === 0 && removedLines === 0)
|
|
146
165
|
continue;
|
|
147
166
|
drift.push({
|
|
148
167
|
file,
|
|
149
168
|
addedLines,
|
|
150
169
|
removedLines,
|
|
151
|
-
hunkSummaries:
|
|
170
|
+
hunkSummaries: [
|
|
171
|
+
...summarizeForeignHunks(next, absorbedAdded, absorbedRemoved, (plus, minus) => `(+${plus}/-${minus})`),
|
|
172
|
+
// A dropped `+` line is one the engine lost; a dropped `-` line is an
|
|
173
|
+
// upstream line the engine has back.
|
|
174
|
+
...summarizeForeignHunks(previous, droppedAdded, droppedRemoved, (plus, minus) => `in the old body, dropped (+${minus}/-${plus})`),
|
|
175
|
+
].slice(0, MAX_HUNK_SUMMARIES_PER_FILE),
|
|
152
176
|
binaryChanged: false,
|
|
153
177
|
});
|
|
154
178
|
}
|
|
155
179
|
return drift;
|
|
156
180
|
}
|
|
181
|
+
/** True when a binary section appeared, vanished, or its new-side blob hash moved. */
|
|
182
|
+
function binaryContentChanged(next, previous) {
|
|
183
|
+
if (next?.binaryNewHash === undefined || previous?.binaryNewHash === undefined)
|
|
184
|
+
return true;
|
|
185
|
+
return !(next.binaryNewHash.startsWith(previous.binaryNewHash) ||
|
|
186
|
+
previous.binaryNewHash.startsWith(next.binaryNewHash));
|
|
187
|
+
}
|
|
157
188
|
/**
|
|
158
189
|
* Reads the old body, prints the always-on drift preview (including under
|
|
159
190
|
* dry-run and `--scan`), and, when `ctx.refuseForeignDrift` is set,
|
|
@@ -161,6 +192,10 @@ export function computeForeignDrift(oldBody, newBody, previousFilesAffected) {
|
|
|
161
192
|
*/
|
|
162
193
|
export async function reportForeignDrift(args) {
|
|
163
194
|
const { patch, patchesDir, engineDir, newDiffContent, ctx } = args;
|
|
195
|
+
for (const file of args.adoptedFiles ?? []) {
|
|
196
|
+
if (ctx.expectedDriftFiles.has(file))
|
|
197
|
+
ctx.expectedSeen.add(file);
|
|
198
|
+
}
|
|
164
199
|
const oldPath = join(patchesDir, patch.filename);
|
|
165
200
|
let oldBody;
|
|
166
201
|
try {
|
|
@@ -172,7 +207,7 @@ export async function reportForeignDrift(args) {
|
|
|
172
207
|
catch (error) {
|
|
173
208
|
return refuseUnreadableBaseline(ctx, patch.filename, `old body unreadable (${toError(error).message})`);
|
|
174
209
|
}
|
|
175
|
-
const drift = computeForeignDrift(oldBody, newDiffContent, patch.filesAffected);
|
|
210
|
+
const drift = computeForeignDrift(oldBody, newDiffContent, args.ownedFiles ?? patch.filesAffected);
|
|
176
211
|
ctx.evaluationRuns += 1;
|
|
177
212
|
if (drift.length === 0)
|
|
178
213
|
return false;
|
|
@@ -184,7 +219,7 @@ export async function reportForeignDrift(args) {
|
|
|
184
219
|
const recentlyEdited = await findFilesEditedSinceLastExport(engineDir, oldPath, drift.map((d) => d.file));
|
|
185
220
|
const totalLines = drift.reduce((sum, d) => sum + d.addedLines + d.removedLines, 0);
|
|
186
221
|
const lineNoun = totalLines === 1 ? 'line' : 'lines';
|
|
187
|
-
warn(`${patch.filename}: refreshed body
|
|
222
|
+
warn(`${patch.filename}: refreshed body changes ${totalLines} ${lineNoun} against the old patch body (${drift.length} file(s)):`);
|
|
188
223
|
for (const entry of drift.slice(0, MAX_FILES_REPORTED)) {
|
|
189
224
|
const expectedTag = ctx.expectedDriftFiles.has(entry.file) ? ' (expected via --expect)' : '';
|
|
190
225
|
const authorshipTag = recentlyEdited.has(entry.file)
|
|
@@ -194,7 +229,7 @@ export async function reportForeignDrift(args) {
|
|
|
194
229
|
info(` ${entry.file}: binary content changed${expectedTag}${authorshipTag}`);
|
|
195
230
|
continue;
|
|
196
231
|
}
|
|
197
|
-
info(` ${entry.file}: +${entry.addedLines}
|
|
232
|
+
info(` ${entry.file}: +${entry.addedLines} added / -${entry.removedLines} removed line(s)${expectedTag}${authorshipTag}`);
|
|
198
233
|
for (const summary of entry.hunkSummaries) {
|
|
199
234
|
info(` ${summary}`);
|
|
200
235
|
}
|
|
@@ -274,31 +309,24 @@ function refuseUnreadableBaseline(ctx, patchFilename, detail) {
|
|
|
274
309
|
/**
|
|
275
310
|
* States, verbatim in the notice, what the `--expect` whitelist is compared
|
|
276
311
|
* against and when. This notice is the only feedback an operator gets that
|
|
277
|
-
* the drift belt looked at the files they named,
|
|
278
|
-
*
|
|
279
|
-
* over an edit that was captured correctly, and none of the causes it named
|
|
280
|
-
* held, so the operator's only recourse was `grep -c` on the written body.
|
|
281
|
-
*
|
|
282
|
-
* The measurement, stated exactly:
|
|
312
|
+
* the drift belt looked at the files they named, so it has to describe the
|
|
313
|
+
* measurement exactly:
|
|
283
314
|
*
|
|
284
315
|
* - Compared: for each file already in the patch's `filesAffected`, the
|
|
285
316
|
* `+`/`-` payload lines the refreshed body carries that the old patch
|
|
286
|
-
* body did not
|
|
287
|
-
*
|
|
317
|
+
* body did not, and the ones the old body carried that the refreshed body
|
|
318
|
+
* dropped (`computeForeignDrift`). It is a content comparison in both
|
|
319
|
+
* directions, so a deletion-only edit is drift too.
|
|
288
320
|
* - When: per patch, on the body about to be written, before the write.
|
|
289
321
|
* Never against post-write state.
|
|
290
322
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* is absorbed, so there is no drift to see. The belt exists to catch a
|
|
295
|
-
* concurrent session's edits being silently swallowed into a body, not
|
|
296
|
-
* every difference between two bodies. Stating that is the fix. Widening
|
|
297
|
-
* the comparison would turn every ordinary line removal into a refusal.
|
|
323
|
+
* An earlier copy compared one direction only, so a correctly captured
|
|
324
|
+
* deletion read as an `--expect` that matched nothing, and a peer's
|
|
325
|
+
* deletion in a file outside `--expect` passed the belt silently.
|
|
298
326
|
*/
|
|
299
327
|
const EXPECT_MEASUREMENT_NOTE = '--expect is evaluated against CONTENT, before the write: for each file already in the ' +
|
|
300
|
-
"patch's filesAffected, the payload lines the refreshed body
|
|
301
|
-
'body
|
|
328
|
+
"patch's filesAffected, the payload lines the refreshed body adds or drops against the old " +
|
|
329
|
+
'patch body. It never looks at post-write state.';
|
|
302
330
|
/**
|
|
303
331
|
* Names `--expect` paths that never drifted this run. A typo'd
|
|
304
332
|
* `--expect` path silently degrades the flag back to refusing the slice it
|
|
@@ -318,17 +346,13 @@ export function warnUnseenExpectedDrift(driftCtx) {
|
|
|
318
346
|
`${unseen.join(', ')}.\n` +
|
|
319
347
|
`${EXPECT_MEASUREMENT_NOTE}\n` +
|
|
320
348
|
'Causes, in rough order of likelihood:\n' +
|
|
321
|
-
|
|
322
|
-
'
|
|
323
|
-
'
|
|
324
|
-
'with "grep -c" on the written patch body.\n' +
|
|
325
|
-
" 2. A typo in the path, or a path not in the patch's filesAffected (files adopted " +
|
|
326
|
-
'this run via --scan/--scan-file are excluded from the comparison by design).\n' +
|
|
327
|
-
' 3. Adoption already captured it: a "re-export <p> --scan --scan-file <f>" run also ' +
|
|
349
|
+
" 1. A typo in the path, or a path in none of the selected patches' filesAffected and " +
|
|
350
|
+
'not adopted by this run.\n' +
|
|
351
|
+
' 2. Adoption already captured it: a "re-export <p> --scan --scan-file <f>" run also ' +
|
|
328
352
|
"captures that patch's other drifted files, so a scan-less re-export afterwards " +
|
|
329
|
-
'legitimately sees nothing left to
|
|
353
|
+
'legitimately sees nothing left to change. Informational — a chain that treats this ' +
|
|
330
354
|
'warning as an error will false-red on it.\n' +
|
|
331
|
-
'
|
|
332
|
-
'
|
|
355
|
+
' 3. The slice was already captured by an earlier export in this session.\n' +
|
|
356
|
+
' 4. The engine content already matches the patch body — a concurrent session may have ' +
|
|
333
357
|
'exported the same change, so the drift converged before this run looked.');
|
|
334
358
|
}
|
|
@@ -7,6 +7,7 @@ import { listTrackedInHead } from '../core/git-file-ops.js';
|
|
|
7
7
|
import { extractAffectedFiles } from '../core/patch-apply.js';
|
|
8
8
|
import { updatePatchAndMetadata } from '../core/patch-export.js';
|
|
9
9
|
import { buildPatchQueueContext, formatPatchLintIssue, lintPatchQueue, } from '../core/patch-lint.js';
|
|
10
|
+
import { buildLintIgnoreChecks, mergeLintIgnoreIds } from '../core/patch-lint-ignore.js';
|
|
10
11
|
import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
|
|
11
12
|
import { loadPatchesManifest } from '../core/patch-manifest.js';
|
|
12
13
|
import { buildProjectedManifest, enforcePatchPolicy } from '../core/patch-policy.js';
|
|
@@ -25,10 +26,8 @@ import { projectEntryBody } from './patch/entry-projection.js';
|
|
|
25
26
|
* so the caller can drop the field rather than write an empty array.
|
|
26
27
|
*/
|
|
27
28
|
function resolveEffectiveTierAndLintIgnore(target, options) {
|
|
28
|
-
const existingIgnoreSet = new Set(target.lintIgnore ?? []);
|
|
29
29
|
const flagIgnoreSet = new Set(options.lintIgnore ?? []);
|
|
30
|
-
const
|
|
31
|
-
const effectiveLintIgnore = mergedIgnoreSet.size > 0 ? [...mergedIgnoreSet] : undefined;
|
|
30
|
+
const effectiveLintIgnore = mergeLintIgnoreIds(target.lintIgnore, [...flagIgnoreSet]);
|
|
32
31
|
const effectiveTier = options.tier ?? target.tier;
|
|
33
32
|
return { effectiveTier, effectiveLintIgnore, flagIgnoreSet };
|
|
34
33
|
}
|
|
@@ -39,8 +38,10 @@ function resolveEffectiveTierAndLintIgnore(target, options) {
|
|
|
39
38
|
* shrink. Pre-existing cross-patch errors are surfaced as a non-blocking
|
|
40
39
|
* warning so the user does not walk away thinking the queue is clean.
|
|
41
40
|
*/
|
|
42
|
-
async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff) {
|
|
43
|
-
|
|
41
|
+
async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedDiff, config) {
|
|
42
|
+
// The config threads `patchLint.forwardRegistration` through, so a forward
|
|
43
|
+
// jar.mn registration is judged the way `lint --per-patch` judges it.
|
|
44
|
+
const baseCtx = await buildPatchQueueContext(patchesDir, config);
|
|
44
45
|
const projection = projectEntryBody(projectedDiff);
|
|
45
46
|
const projectedEntries = baseCtx.entries.map((entry) => {
|
|
46
47
|
if (entry.filename !== targetFilename)
|
|
@@ -48,7 +49,7 @@ async function runProjectedCrossPatchLint(patchesDir, targetFilename, projectedD
|
|
|
48
49
|
return { ...entry, ...projection };
|
|
49
50
|
});
|
|
50
51
|
const baselineIssues = lintPatchQueue(baseCtx).filter((i) => i.severity === 'error');
|
|
51
|
-
const projectedIssues = lintPatchQueue({ entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
52
|
+
const projectedIssues = lintPatchQueue({ ...baseCtx, entries: projectedEntries }).filter((i) => i.severity === 'error');
|
|
52
53
|
const regressions = computeProjectedLintRegressions(baselineIssues, projectedIssues);
|
|
53
54
|
if (baselineIssues.length > 0 && regressions.length === 0) {
|
|
54
55
|
warn(`Note: projected queue still has ${baselineIssues.length} pre-existing ` +
|
|
@@ -223,7 +224,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
|
|
|
223
224
|
// `--tier` and `--lint-ignore` participate too, with append/union
|
|
224
225
|
// semantics on the lint-ignore list.
|
|
225
226
|
const { effectiveTier, effectiveLintIgnore, flagIgnoreSet } = resolveEffectiveTierAndLintIgnore(target, options);
|
|
226
|
-
const ignoreChecks =
|
|
227
|
+
const ignoreChecks = buildLintIgnoreChecks(effectiveLintIgnore);
|
|
227
228
|
const patchQueueCtx = (await pathExists(paths.patches))
|
|
228
229
|
? await buildPatchQueueContext(paths.patches)
|
|
229
230
|
: undefined;
|
|
@@ -237,7 +238,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
|
|
|
237
238
|
ignoreChecks,
|
|
238
239
|
patchTier: effectiveTier,
|
|
239
240
|
});
|
|
240
|
-
const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff);
|
|
241
|
+
const conflicts = await runProjectedCrossPatchLint(paths.patches, target.filename, projectedDiff, config);
|
|
241
242
|
const filesUpdates = buildFilesModeMetadataUpdates(actualProjectedFiles, options, effectiveLintIgnore, flagIgnoreSet);
|
|
242
243
|
const manifest = await loadPatchesManifest(paths.patches);
|
|
243
244
|
if (manifest) {
|
|
@@ -246,6 +247,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
|
|
|
246
247
|
manifest: buildProjectedManifest(manifest, manifest.patches.map((entry) => entry.filename === target.filename ? { ...entry, ...filesUpdates } : entry)),
|
|
247
248
|
command: 're-export --files',
|
|
248
249
|
forceUnsafe: options.forceUnsafe === true,
|
|
250
|
+
scope: [target.filename],
|
|
249
251
|
});
|
|
250
252
|
}
|
|
251
253
|
const decision = await confirmFilesModeProjection({
|
|
@@ -297,6 +299,7 @@ export async function reExportFilesInPlace(paths, selectedPatches, options, conf
|
|
|
297
299
|
config,
|
|
298
300
|
command: 're-export --files',
|
|
299
301
|
forceUnsafe: options.forceUnsafe === true,
|
|
302
|
+
scope: [target.filename],
|
|
300
303
|
},
|
|
301
304
|
});
|
|
302
305
|
success(`Re-exported ${target.filename}`);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ParsedHunk } from '../core/patch-parse.js';
|
|
2
|
+
/** Which hunk mode a re-export runs in, and the diff naming the hunks. */
|
|
3
|
+
export interface HunkSelection {
|
|
4
|
+
mode: 'include' | 'exclude';
|
|
5
|
+
diffPath: string;
|
|
6
|
+
flag: '--hunks-from' | '--exclude-hunks';
|
|
7
|
+
}
|
|
8
|
+
/** The selection a re-export's options ask for, if any. */
|
|
9
|
+
export declare function hunkSelectionOf(options: {
|
|
10
|
+
hunksFrom?: string;
|
|
11
|
+
excludeHunks?: string;
|
|
12
|
+
}): HunkSelection | undefined;
|
|
13
|
+
/** Splits a diff into its raw per-file sections, keyed by target path. */
|
|
14
|
+
export declare function splitRawSections(diff: string): Map<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Applies `hunks` to `content` by context, forward or reversed. A hunk
|
|
17
|
+
* whose context is not found refuses, naming it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyHunksByContext(content: string, hunks: readonly ParsedHunk[], reverse: boolean, file: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the re-export body for a hunk-selected run: named files from the
|
|
22
|
+
* selected hunks, the rest from the old body (`--hunks-from`) or the normal
|
|
23
|
+
* working-tree diff (`--exclude-hunks`).
|
|
24
|
+
*
|
|
25
|
+
* @param args.normalDiff - The diff a plain re-export would write
|
|
26
|
+
* @param args.oldBody - The patch's current body
|
|
27
|
+
* @param args.files - The files the refreshed patch owns
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildHunkSelectedDiff(args: {
|
|
30
|
+
engineDir: string;
|
|
31
|
+
selection: HunkSelection;
|
|
32
|
+
oldBody: string;
|
|
33
|
+
normalDiff: string;
|
|
34
|
+
files: readonly string[];
|
|
35
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `re-export --hunks-from <diff>` and `--exclude-hunks <diff>`: export a
|
|
4
|
+
* file by hunk rather than as a whole.
|
|
5
|
+
*
|
|
6
|
+
* Re-export captures a file's working-tree content against engine HEAD, so
|
|
7
|
+
* on a shared engine a peer's unexported line in the same manifest or chrome
|
|
8
|
+
* document landed in the exporting patch, and the only way out was for the
|
|
9
|
+
* peer to park its hunk by hand. These two modes build the file's new
|
|
10
|
+
* content from the hunks a session names:
|
|
11
|
+
*
|
|
12
|
+
* - `--hunks-from`: the patch's current body for the file (HEAD with the
|
|
13
|
+
* old section applied) plus only the named hunks. Files the diff does
|
|
14
|
+
* not name keep their old section.
|
|
15
|
+
* - `--exclude-hunks`: the working tree minus the named hunks. Files the
|
|
16
|
+
* diff does not name export as usual.
|
|
17
|
+
*
|
|
18
|
+
* Hunks are placed by their context, searched outward from the line their
|
|
19
|
+
* header names, so a diff taken before a peer's edit above it still applies.
|
|
20
|
+
* The synthetic content is diffed against HEAD by git itself, on a
|
|
21
|
+
* throwaway index, so the section is byte-for-byte what a plain re-export
|
|
22
|
+
* would write for that content.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
25
|
+
import { tmpdir } from 'node:os';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import { git } from '../core/git-base.js';
|
|
28
|
+
import { getFileContentAtRef, listTrackedInHead } from '../core/git-file-ops.js';
|
|
29
|
+
import { mintDisposableGitIndex } from '../core/git-readonly-index.js';
|
|
30
|
+
import { parseDiffGitHeader, parseDiffSections, } from '../core/patch-parse.js';
|
|
31
|
+
import { applyPatchTextToContent } from '../core/patch-transform.js';
|
|
32
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
33
|
+
import { PatchError } from '../errors/patch.js';
|
|
34
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
35
|
+
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
36
|
+
/** The selection a re-export's options ask for, if any. */
|
|
37
|
+
export function hunkSelectionOf(options) {
|
|
38
|
+
if (options.hunksFrom !== undefined) {
|
|
39
|
+
return { mode: 'include', diffPath: options.hunksFrom, flag: '--hunks-from' };
|
|
40
|
+
}
|
|
41
|
+
if (options.excludeHunks !== undefined) {
|
|
42
|
+
return { mode: 'exclude', diffPath: options.excludeHunks, flag: '--exclude-hunks' };
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Splits a diff into its raw per-file sections, keyed by target path. */
|
|
47
|
+
export function splitRawSections(diff) {
|
|
48
|
+
const sections = new Map();
|
|
49
|
+
let path;
|
|
50
|
+
let lines = [];
|
|
51
|
+
const flush = () => {
|
|
52
|
+
if (path !== undefined)
|
|
53
|
+
sections.set(path, `${lines.join('\n')}\n`);
|
|
54
|
+
};
|
|
55
|
+
for (const line of diff.split('\n')) {
|
|
56
|
+
const header = parseDiffGitHeader(line);
|
|
57
|
+
if (header) {
|
|
58
|
+
flush();
|
|
59
|
+
path = header.targetPath;
|
|
60
|
+
lines = [line];
|
|
61
|
+
}
|
|
62
|
+
else if (path !== undefined) {
|
|
63
|
+
lines.push(line);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
flush();
|
|
67
|
+
for (const [key, text] of sections)
|
|
68
|
+
sections.set(key, text.replace(/\n+$/, '\n'));
|
|
69
|
+
return sections;
|
|
70
|
+
}
|
|
71
|
+
/** The old and new line blocks a hunk replaces, swapped when reversed. */
|
|
72
|
+
function hunkBlocks(hunk, reverse) {
|
|
73
|
+
const oldLines = [];
|
|
74
|
+
const newLines = [];
|
|
75
|
+
for (const line of hunk.lines) {
|
|
76
|
+
const marker = line[0];
|
|
77
|
+
const text = line.slice(1);
|
|
78
|
+
if (marker === ' ' || marker === '-')
|
|
79
|
+
oldLines.push(text);
|
|
80
|
+
if (marker === ' ' || marker === '+')
|
|
81
|
+
newLines.push(text);
|
|
82
|
+
}
|
|
83
|
+
return reverse ? { from: newLines, to: oldLines } : { from: oldLines, to: newLines };
|
|
84
|
+
}
|
|
85
|
+
function matchesAt(lines, block, at) {
|
|
86
|
+
if (at < 0 || at + block.length > lines.length)
|
|
87
|
+
return false;
|
|
88
|
+
return block.every((line, index) => lines[at + index] === line);
|
|
89
|
+
}
|
|
90
|
+
/** The match of `block` nearest `expected`, at or after `floor`. */
|
|
91
|
+
function findBlock(lines, block, expected, floor) {
|
|
92
|
+
for (let distance = 0; distance <= lines.length; distance++) {
|
|
93
|
+
for (const at of distance === 0 ? [expected] : [expected - distance, expected + distance]) {
|
|
94
|
+
if (at >= floor && matchesAt(lines, block, at))
|
|
95
|
+
return at;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Applies `hunks` to `content` by context, forward or reversed. A hunk
|
|
102
|
+
* whose context is not found refuses, naming it.
|
|
103
|
+
*/
|
|
104
|
+
export function applyHunksByContext(content, hunks, reverse, file) {
|
|
105
|
+
const endsWithNewline = content.endsWith('\n') || content === '';
|
|
106
|
+
const lines = content === '' ? [] : content.replace(/\n$/, '').split('\n');
|
|
107
|
+
let offset = 0;
|
|
108
|
+
let floor = 0;
|
|
109
|
+
for (const hunk of hunks) {
|
|
110
|
+
const { from, to } = hunkBlocks(hunk, reverse);
|
|
111
|
+
const start = (reverse ? hunk.newStart : hunk.oldStart) - (from.length === 0 ? 0 : 1);
|
|
112
|
+
const expected = Math.max(0, start + offset);
|
|
113
|
+
const at = from.length === 0 ? expected : findBlock(lines, from, expected, floor);
|
|
114
|
+
if (at === undefined) {
|
|
115
|
+
throw new PatchError(`The hunk @@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@ ` +
|
|
116
|
+
`does not ${reverse ? 'match the working tree' : 'apply to the patch body'} of ${file}: ` +
|
|
117
|
+
`its ${reverse ? 'new' : 'old'}-side lines were not found.`);
|
|
118
|
+
}
|
|
119
|
+
lines.splice(at, from.length, ...to);
|
|
120
|
+
offset += to.length - from.length + (at - expected);
|
|
121
|
+
floor = at + to.length;
|
|
122
|
+
}
|
|
123
|
+
if (lines.length === 0)
|
|
124
|
+
return '';
|
|
125
|
+
return `${lines.join('\n')}${endsWithNewline ? '\n' : ''}`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Diffs `content` for `file` against engine HEAD the way a re-export would,
|
|
129
|
+
* on a throwaway index so the shared one never moves.
|
|
130
|
+
*/
|
|
131
|
+
async function diffContentAgainstHead(engineDir, file, content, tracked) {
|
|
132
|
+
const index = await mintDisposableGitIndex(engineDir);
|
|
133
|
+
if (index === undefined) {
|
|
134
|
+
throw new GeneralError(`Cannot build a private git index in ${engineDir} to diff the hunk-selected ${file}.`);
|
|
135
|
+
}
|
|
136
|
+
const scratch = await mkdtemp(join(tmpdir(), 'fireforge-hunks-'));
|
|
137
|
+
try {
|
|
138
|
+
const temp = join(scratch, 'content');
|
|
139
|
+
await writeFile(temp, content);
|
|
140
|
+
const blob = (await git(['hash-object', '-w', '--path', file, '--', temp], engineDir)).trim();
|
|
141
|
+
const mode = tracked
|
|
142
|
+
? ((await git(['ls-tree', 'HEAD', '--', file], engineDir)).split(/\s/)[0] ?? '100644')
|
|
143
|
+
: '100644';
|
|
144
|
+
await git(['update-index', '--add', '--cacheinfo', `${mode},${blob},${file}`], engineDir, {
|
|
145
|
+
env: index.env,
|
|
146
|
+
});
|
|
147
|
+
return await git(['diff', '--binary', '--cached', 'HEAD', '--', file], engineDir, {
|
|
148
|
+
env: index.env,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await rm(scratch, { recursive: true, force: true });
|
|
153
|
+
await index.dispose();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Reads and checks the hunk diff: text sections for files the patch owns. */
|
|
157
|
+
async function readSelection(selection, owned) {
|
|
158
|
+
if (!(await pathExists(selection.diffPath))) {
|
|
159
|
+
throw new InvalidArgumentError(`${selection.flag} ${selection.diffPath} does not exist.`, selection.flag);
|
|
160
|
+
}
|
|
161
|
+
const sections = parseDiffSections(await readText(selection.diffPath));
|
|
162
|
+
if (sections.length === 0) {
|
|
163
|
+
throw new InvalidArgumentError(`${selection.flag} ${selection.diffPath} holds no "diff --git" section.`, selection.flag);
|
|
164
|
+
}
|
|
165
|
+
const ownedSet = new Set(owned);
|
|
166
|
+
const byFile = new Map();
|
|
167
|
+
for (const section of sections) {
|
|
168
|
+
const file = normalizeEngineRelativeInput(section.targetPath, selection.flag);
|
|
169
|
+
if (section.isBinary || section.sourcePath !== section.targetPath) {
|
|
170
|
+
throw new InvalidArgumentError(`${selection.flag}: the section for ${file} is binary or a rename; only text hunks can be selected.`, selection.flag);
|
|
171
|
+
}
|
|
172
|
+
if (!ownedSet.has(file)) {
|
|
173
|
+
throw new InvalidArgumentError(`${selection.flag}: ${file} is not a file this patch owns.`, selection.flag);
|
|
174
|
+
}
|
|
175
|
+
byFile.set(file, section);
|
|
176
|
+
}
|
|
177
|
+
return byFile;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Builds the re-export body for a hunk-selected run: named files from the
|
|
181
|
+
* selected hunks, the rest from the old body (`--hunks-from`) or the normal
|
|
182
|
+
* working-tree diff (`--exclude-hunks`).
|
|
183
|
+
*
|
|
184
|
+
* @param args.normalDiff - The diff a plain re-export would write
|
|
185
|
+
* @param args.oldBody - The patch's current body
|
|
186
|
+
* @param args.files - The files the refreshed patch owns
|
|
187
|
+
*/
|
|
188
|
+
export async function buildHunkSelectedDiff(args) {
|
|
189
|
+
const { engineDir, selection, oldBody, normalDiff, files } = args;
|
|
190
|
+
const named = await readSelection(selection, files);
|
|
191
|
+
const tracked = await listTrackedInHead(engineDir, [...named.keys()]);
|
|
192
|
+
const fallback = splitRawSections(selection.mode === 'include' ? oldBody : normalDiff);
|
|
193
|
+
const out = [];
|
|
194
|
+
for (const file of [...files].sort()) {
|
|
195
|
+
const section = named.get(file);
|
|
196
|
+
if (section === undefined) {
|
|
197
|
+
const raw = fallback.get(file);
|
|
198
|
+
if (raw !== undefined)
|
|
199
|
+
out.push(raw);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const head = tracked.has(file) ? await getFileContentAtRef(engineDir, file) : null;
|
|
203
|
+
let content;
|
|
204
|
+
if (selection.mode === 'include') {
|
|
205
|
+
const base = applyPatchTextToContent(head, oldBody, file);
|
|
206
|
+
content = applyHunksByContext(base, section.hunks, false, file);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
const worktree = join(engineDir, file);
|
|
210
|
+
const current = (await pathExists(worktree)) ? await readText(worktree) : '';
|
|
211
|
+
content = applyHunksByContext(current, section.hunks, true, file);
|
|
212
|
+
}
|
|
213
|
+
if (head !== null && content === head)
|
|
214
|
+
continue;
|
|
215
|
+
const diff = await diffContentAgainstHead(engineDir, file, content, tracked.has(file));
|
|
216
|
+
if (diff.trim())
|
|
217
|
+
out.push(diff.endsWith('\n') ? diff : `${diff}\n`);
|
|
218
|
+
}
|
|
219
|
+
return out.join('');
|
|
220
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { type PatchQueueContext } from '../core/patch-lint.js';
|
|
23
23
|
import { type PerPatchLintCacheFile } from '../core/patch-lint-cache.js';
|
|
24
|
+
import { type LintIgnoreChecks } from '../core/patch-lint-ignore.js';
|
|
24
25
|
import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
25
26
|
import type { FireForgeConfig, ProjectPaths } from '../types/config.js';
|
|
26
27
|
import { type PerRunCheckJs } from './lint-per-run-checkjs.js';
|
|
@@ -92,7 +93,7 @@ export declare function lintReExportedPatch(args: {
|
|
|
92
93
|
existingFiles: string[];
|
|
93
94
|
diffContent: string;
|
|
94
95
|
skipLint?: boolean;
|
|
95
|
-
ignoreChecks?:
|
|
96
|
+
ignoreChecks?: LintIgnoreChecks;
|
|
96
97
|
patchTier?: 'branding';
|
|
97
98
|
}): Promise<ReExportLintResult | null>;
|
|
98
99
|
/**
|