@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
|
@@ -20,18 +20,20 @@
|
|
|
20
20
|
* (`refreshQueueCtxEntry`), so later iterations of an `--all` run lint
|
|
21
21
|
* against the just-refreshed body instead of the stale one.
|
|
22
22
|
*/
|
|
23
|
-
import { buildPatchQueueContext,
|
|
23
|
+
import { buildPatchQueueContext, detectNewFilesInDiff, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, measurePatchSize, resolvePatchSizeTier, } from '../core/patch-lint.js';
|
|
24
24
|
import { buildPerPatchLintCacheKey, getCachedPerPatchLintIssues, getPerPatchLintCacheHeadSha, loadPerPatchLintCache, savePerPatchLintCache, setCachedPerPatchLintIssues, } from '../core/patch-lint-cache.js';
|
|
25
25
|
import { invalidateNewFileCreatorsCache } from '../core/patch-lint-cross.js';
|
|
26
|
+
import { lintIgnoreCacheTokens } from '../core/patch-lint-ignore.js';
|
|
26
27
|
import { resolvePatchOwnedSysMjs } from '../core/patch-lint-ownership.js';
|
|
27
28
|
import { computeProjectedLintRegressions } from '../core/patch-lint-projection.js';
|
|
28
29
|
import { GeneralError } from '../errors/base.js';
|
|
29
30
|
import { pathExists } from '../utils/fs.js';
|
|
30
31
|
import { info, warn } from '../utils/logger.js';
|
|
31
32
|
import { reportPatchLintOutcome } from './export-shared.js';
|
|
33
|
+
import { formatForwardImportRemedy } from './forward-import-remedy.js';
|
|
32
34
|
import { buildPerRunCheckJs } from './lint-per-run-checkjs.js';
|
|
33
35
|
import { projectEntryBody } from './patch/entry-projection.js';
|
|
34
|
-
import {
|
|
36
|
+
import { categoryOf, manifestFromQueue } from './re-export-scan.js';
|
|
35
37
|
/**
|
|
36
38
|
* Builds the once-per-invocation lint context.
|
|
37
39
|
*
|
|
@@ -135,7 +137,7 @@ export async function lintReExportedPatch(args) {
|
|
|
135
137
|
queueContext: patchQueueCtx,
|
|
136
138
|
...(lintCtx.engineHeadSha === undefined ? {} : { engineHeadSha: lintCtx.engineHeadSha }),
|
|
137
139
|
});
|
|
138
|
-
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, args.ignoreChecks);
|
|
140
|
+
const cached = getCachedPerPatchLintIssues(cache, patch.filename, cacheKey, lintIgnoreCacheTokens(args.ignoreChecks));
|
|
139
141
|
if (cached) {
|
|
140
142
|
lintCtx.reusedCacheEntries++;
|
|
141
143
|
reportPatchLintOutcome([...globalIssues, ...cached.issues], args.skipLint);
|
|
@@ -167,8 +169,10 @@ export async function lintReExportedPatch(args) {
|
|
|
167
169
|
else if (patchQueueCtx) {
|
|
168
170
|
lintOptions.checkJsReportScope = resolvePatchOwnedSysMjs(detectNewFilesInDiff(diffContent));
|
|
169
171
|
}
|
|
172
|
+
const patchSize = await measurePatchSize(paths.engine, diffContent, existingFiles, config);
|
|
170
173
|
const issues = await lintExportedPatch(paths.engine, existingFiles, diffContent, config, {
|
|
171
174
|
...lintOptions,
|
|
175
|
+
patchSizeMeasurement: patchSize,
|
|
172
176
|
...(patchQueueCtx ? { patchQueueCtx } : {}),
|
|
173
177
|
...(args.ignoreChecks ? { ignoreChecks: args.ignoreChecks } : {}),
|
|
174
178
|
...(args.patchTier ? { patchTier: args.patchTier } : {}),
|
|
@@ -177,8 +181,8 @@ export async function lintReExportedPatch(args) {
|
|
|
177
181
|
return {
|
|
178
182
|
issues,
|
|
179
183
|
suppressed: suppressedIssues,
|
|
180
|
-
lineCount:
|
|
181
|
-
lintIgnore:
|
|
184
|
+
lineCount: patchSize.lines,
|
|
185
|
+
lintIgnore: lintIgnoreCacheTokens(args.ignoreChecks),
|
|
182
186
|
};
|
|
183
187
|
}
|
|
184
188
|
/**
|
|
@@ -236,10 +240,11 @@ export function assertRefreshedBodyHasNoNewQueueErrors(args) {
|
|
|
236
240
|
const projectedEntries = ctx.entries.map((entry) => entry.filename === patchFilename ? { ...entry, ...projection } : entry);
|
|
237
241
|
const isError = (issue) => issue.severity === 'error';
|
|
238
242
|
const baseline = lintPatchQueue(ctx).filter(isError);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
+
// The whole context carries over (patchPolicy and the
|
|
244
|
+
// `patchLint.forwardRegistration` scope): dropping the scope here let a
|
|
245
|
+
// forward jar.mn registration pass this gate and surface only in the
|
|
246
|
+
// later queue audit, separately from the forward import it packages.
|
|
247
|
+
const projected = lintPatchQueue({ ...ctx, entries: projectedEntries }).filter(isError);
|
|
243
248
|
const regressions = computeProjectedLintRegressions(baseline, projected);
|
|
244
249
|
if (regressions.length === 0) {
|
|
245
250
|
if (baseline.length > 0 && !lintCtx.queueBaselineWarned) {
|
|
@@ -253,7 +258,15 @@ export function assertRefreshedBodyHasNoNewQueueErrors(args) {
|
|
|
253
258
|
const hasForwardImport = regressions.some((issue) => issue.check === 'forward-import');
|
|
254
259
|
const message = `Refusing to re-export ${patchFilename} because the refreshed body introduces ` +
|
|
255
260
|
`${regressions.length} new cross-patch lint error${regressions.length === 1 ? '' : 's'}:\n${details}` +
|
|
256
|
-
(hasForwardImport
|
|
261
|
+
(hasForwardImport
|
|
262
|
+
? `\n${formatForwardImportRemedy({
|
|
263
|
+
reRunHint: 're-running the re-export',
|
|
264
|
+
issues: regressions,
|
|
265
|
+
manifest: manifestFromQueue(ctx),
|
|
266
|
+
config: lintCtx.config,
|
|
267
|
+
...categoryOf(ctx, patchFilename),
|
|
268
|
+
})}`
|
|
269
|
+
: '');
|
|
257
270
|
if (forceUnsafe) {
|
|
258
271
|
warn(message);
|
|
259
272
|
warn('Proceeding because --force-unsafe was provided.');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { InvalidArgumentError } from '../errors/base.js';
|
|
3
3
|
import { info } from '../utils/logger.js';
|
|
4
|
+
import { validateScanFileOwners } from './re-export-scan-owner.js';
|
|
4
5
|
/**
|
|
5
6
|
* Heuristic for `--files` positional folding: an extra positional that
|
|
6
7
|
* contains a path separator and is not a `.patch` filename is an engine
|
|
@@ -62,6 +63,39 @@ function validateWhitelistFlagDependencies(options) {
|
|
|
62
63
|
options.refuseAdjacentUnmanaged !== true) {
|
|
63
64
|
throw new InvalidArgumentError('--expect-unmanaged names approved exceptions to --refuse-adjacent-unmanaged and has no effect without it. Pass --refuse-adjacent-unmanaged, or drop --expect-unmanaged.', '--expect-unmanaged');
|
|
64
65
|
}
|
|
66
|
+
for (const [flag, values] of [
|
|
67
|
+
['--expect-new', options.expectNew],
|
|
68
|
+
['--adjacent-ignore', options.adjacentIgnore],
|
|
69
|
+
]) {
|
|
70
|
+
if (values !== undefined && values.length > 0 && options.refuseAdjacentUnmanaged !== true) {
|
|
71
|
+
throw new InvalidArgumentError(`${flag} relaxes --refuse-adjacent-unmanaged and has no effect without it. Pass --refuse-adjacent-unmanaged, or drop ${flag}.`, flag);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (options.adjacentIgnore?.some((entry) => entry.includes('**'))) {
|
|
75
|
+
throw new InvalidArgumentError('--adjacent-ignore takes a path or a glob whose * stays within one path segment; ** is refused, because a subtree carve-out is how a reviewed exception becomes a blanket one.', '--adjacent-ignore');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Refuses a path-valued flag whose value starts with `-`. Commander hands a
|
|
80
|
+
* required option value whatever follows it, so a list that ended early
|
|
81
|
+
* (`--expect-unmanaged --dry-run`) silently turned the next flag into a path
|
|
82
|
+
* and the dry run into a write. No engine path starts with a dash.
|
|
83
|
+
*/
|
|
84
|
+
function validatePathFlagValues(options) {
|
|
85
|
+
for (const [flag, values] of [
|
|
86
|
+
['--expect', options.expect],
|
|
87
|
+
['--expect-unmanaged', options.expectUnmanaged],
|
|
88
|
+
['--expect-new', options.expectNew],
|
|
89
|
+
['--expect-removed', options.expectRemoved],
|
|
90
|
+
['--adjacent-ignore', options.adjacentIgnore],
|
|
91
|
+
['--scan-file', options.scanFiles],
|
|
92
|
+
]) {
|
|
93
|
+
const swallowed = (values ?? []).filter((value) => value.trim().startsWith('-'));
|
|
94
|
+
if (swallowed.length > 0) {
|
|
95
|
+
throw new InvalidArgumentError(`${flag} was given ${swallowed.map((value) => `"${value}"`).join(', ')}, which is a flag, not an engine path: ` +
|
|
96
|
+
`the ${flag} before it had no value, so it took the next argument. Give every ${flag} a path.`, flag);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
65
99
|
}
|
|
66
100
|
function validateExpectedRemovalUsage(patches, options) {
|
|
67
101
|
if (options.expectRemoved?.length &&
|
|
@@ -69,9 +103,37 @@ function validateExpectedRemovalUsage(patches, options) {
|
|
|
69
103
|
throw new InvalidArgumentError('--expect-removed requires explicit selected patches and the scan-less path (no --scan, --files, or --all).', '--expect-removed');
|
|
70
104
|
}
|
|
71
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* `--hunks-from` and `--exclude-hunks` rebuild one patch's files from named
|
|
108
|
+
* hunks, so they take exactly one explicit patch and no flag that sets the
|
|
109
|
+
* file list another way.
|
|
110
|
+
*/
|
|
111
|
+
function validateHunkSelectionUsage(patches, options) {
|
|
112
|
+
if (options.hunksFrom !== undefined && options.excludeHunks !== undefined) {
|
|
113
|
+
throw new InvalidArgumentError('--hunks-from and --exclude-hunks are alternatives; pass one.', '--hunks-from');
|
|
114
|
+
}
|
|
115
|
+
const flag = options.hunksFrom !== undefined
|
|
116
|
+
? '--hunks-from'
|
|
117
|
+
: options.excludeHunks !== undefined
|
|
118
|
+
? '--exclude-hunks'
|
|
119
|
+
: undefined;
|
|
120
|
+
if (flag === undefined)
|
|
121
|
+
return;
|
|
122
|
+
if (patches.length !== 1 ||
|
|
123
|
+
options.all ||
|
|
124
|
+
options.scan ||
|
|
125
|
+
options.files !== undefined ||
|
|
126
|
+
options.scanFilesManifest !== undefined) {
|
|
127
|
+
throw new InvalidArgumentError(`${flag} rebuilds one patch's files from named hunks: pass exactly one patch, without --scan, --files or --all.`, flag);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
72
130
|
/** Validates mutually exclusive `re-export` targeting and metadata options. */
|
|
73
131
|
export function validateReExportOptionCombinations(patches, options) {
|
|
132
|
+
// First: a swallowed flag would otherwise surface as whichever
|
|
133
|
+
// combination error it happens to trip, naming the wrong problem.
|
|
134
|
+
validatePathFlagValues(options);
|
|
74
135
|
validateExpectedRemovalUsage(patches, options);
|
|
136
|
+
validateHunkSelectionUsage(patches, options);
|
|
75
137
|
if (options.files !== undefined) {
|
|
76
138
|
if (options.all || options.scan) {
|
|
77
139
|
throw new InvalidArgumentError('--files cannot be combined with --scan or --all.', '--files');
|
|
@@ -91,9 +153,10 @@ export function validateReExportOptionCombinations(patches, options) {
|
|
|
91
153
|
if (options.scanFilesManifest !== undefined) {
|
|
92
154
|
throw new InvalidArgumentError('--scan-file cannot be combined with --scan-files.', '--scan-file');
|
|
93
155
|
}
|
|
94
|
-
if (options.all
|
|
95
|
-
throw new InvalidArgumentError('--scan-file
|
|
156
|
+
if (options.all) {
|
|
157
|
+
throw new InvalidArgumentError('--scan-file cannot be combined with --all. Pass the target patch identifiers.', '--scan-file');
|
|
96
158
|
}
|
|
159
|
+
validateScanFileOwners(patches, options.scanFiles);
|
|
97
160
|
}
|
|
98
161
|
if (options.scanFilesManifest !== undefined) {
|
|
99
162
|
if (!options.scan)
|
|
@@ -102,11 +165,16 @@ export function validateReExportOptionCombinations(patches, options) {
|
|
|
102
165
|
throw new InvalidArgumentError('--scan-files selects patches from its manifest and cannot be combined with positional patches or --all.', '--scan-files');
|
|
103
166
|
}
|
|
104
167
|
}
|
|
105
|
-
|
|
106
|
-
|
|
168
|
+
// Beside --scan the belt judges the neighbours the scan did not adopt;
|
|
169
|
+
// --files replaces the file list outright, so there is nothing to judge.
|
|
170
|
+
if (options.refuseAdjacentUnmanaged === true && options.files !== undefined) {
|
|
171
|
+
throw new InvalidArgumentError('--refuse-adjacent-unmanaged cannot be combined with --files (it sets filesAffected explicitly).', '--refuse-adjacent-unmanaged');
|
|
107
172
|
}
|
|
108
|
-
|
|
109
|
-
|
|
173
|
+
// Beside --scan the belt covers the files each patch already owns; the
|
|
174
|
+
// files a scan adopts are the run's stated intent. --files replaces the
|
|
175
|
+
// file list outright, so there is no owned set left to guard.
|
|
176
|
+
if (options.refuseForeignDrift === true && options.files !== undefined) {
|
|
177
|
+
throw new InvalidArgumentError('--refuse-foreign-drift cannot be combined with --files (it sets filesAffected explicitly and captures the current engine state).', '--refuse-foreign-drift');
|
|
110
178
|
}
|
|
111
179
|
validateWhitelistFlagDependencies(options);
|
|
112
180
|
const usingTierFlag = options.tier !== undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReExportOptions } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads a path list file: one engine-relative path per line, blank lines
|
|
4
|
+
* and `#` comment lines ignored. Backs `--expect-from`, which exists because
|
|
5
|
+
* a list held in one shell variable reaches the CLI as one argument under
|
|
6
|
+
* zsh, and splitting it by hand left every path after the first as a
|
|
7
|
+
* positional patch name.
|
|
8
|
+
*
|
|
9
|
+
* @param file - The list file, relative to the working directory
|
|
10
|
+
* @param flag - Flag name for error messages
|
|
11
|
+
*/
|
|
12
|
+
export declare function readPathListFile(file: string, flag: string): Promise<string[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Runs every lock-free argument check: empty or unknown patch names
|
|
15
|
+
* (naming an engine file passed by mistake), option combinations, path
|
|
16
|
+
* flag values that are flags, and path flag values that name no changed
|
|
17
|
+
* file. Throws the refusal the locked run would have thrown, only sooner.
|
|
18
|
+
*
|
|
19
|
+
* @param projectRoot - Project root
|
|
20
|
+
* @param patches - Positional patch arguments as given
|
|
21
|
+
* @param options - Options as the command layer builds them
|
|
22
|
+
*/
|
|
23
|
+
export declare function preflightReExportArguments(projectRoot: string, patches: readonly string[], options: ReExportOptions): Promise<void>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Argument checks `fireforge re-export` runs before it queues for the
|
|
4
|
+
* engine session lock.
|
|
5
|
+
*
|
|
6
|
+
* Under contention the lock wait runs to many minutes, and an argument the
|
|
7
|
+
* run was always going to refuse (an empty patch name from an unquoted
|
|
8
|
+
* `"$@"`, a changed-file path passed as a positional, a path flag that
|
|
9
|
+
* swallowed the next flag) used to be reported only after that wait. These
|
|
10
|
+
* checks read the manifest and a path-scoped `git status` without the lock.
|
|
11
|
+
* The command repeats its own resolution under the lock, so a manifest that
|
|
12
|
+
* changes in between is still read fresh there.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { join, resolve } from 'node:path';
|
|
16
|
+
import { getProjectPaths } from '../core/config.js';
|
|
17
|
+
import { isGitRepository } from '../core/git.js';
|
|
18
|
+
import { chunkPathspecs } from '../core/git-base.js';
|
|
19
|
+
import { getDirtyFiles } from '../core/git-status.js';
|
|
20
|
+
import { formatPatchNotFoundError } from '../core/patch-identifier-suggest.js';
|
|
21
|
+
import { loadPatchesManifest, resolvePatchIdentifier } from '../core/patch-manifest.js';
|
|
22
|
+
import { InvalidArgumentError } from '../errors/base.js';
|
|
23
|
+
import { toError } from '../utils/errors.js';
|
|
24
|
+
import { pathExists } from '../utils/fs.js';
|
|
25
|
+
import { normalizeReExportFilesPositionals, validateReExportOptionCombinations, } from './re-export-options.js';
|
|
26
|
+
import { normalizeEngineRelativeInput } from './re-export-scan.js';
|
|
27
|
+
import { splitScanFileOwner } from './re-export-scan-owner.js';
|
|
28
|
+
/**
|
|
29
|
+
* Reads a path list file: one engine-relative path per line, blank lines
|
|
30
|
+
* and `#` comment lines ignored. Backs `--expect-from`, which exists because
|
|
31
|
+
* a list held in one shell variable reaches the CLI as one argument under
|
|
32
|
+
* zsh, and splitting it by hand left every path after the first as a
|
|
33
|
+
* positional patch name.
|
|
34
|
+
*
|
|
35
|
+
* @param file - The list file, relative to the working directory
|
|
36
|
+
* @param flag - Flag name for error messages
|
|
37
|
+
*/
|
|
38
|
+
export async function readPathListFile(file, flag) {
|
|
39
|
+
let text;
|
|
40
|
+
try {
|
|
41
|
+
text = await readFile(resolve(file), 'utf8');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new InvalidArgumentError(`${flag} could not read ${file}: ${toError(error).message}`, flag);
|
|
45
|
+
}
|
|
46
|
+
const paths = text
|
|
47
|
+
.split(/\r?\n/)
|
|
48
|
+
.map((line) => line.trim())
|
|
49
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'));
|
|
50
|
+
if (paths.length === 0) {
|
|
51
|
+
throw new InvalidArgumentError(`${flag} file ${file} lists no paths. An empty list is usually a generation step that ` +
|
|
52
|
+
`failed; write one engine-relative path per line, or drop ${flag}.`, flag);
|
|
53
|
+
}
|
|
54
|
+
return paths;
|
|
55
|
+
}
|
|
56
|
+
/** Engine-relative form of a positional, or undefined when it cannot be one. */
|
|
57
|
+
function asEnginePath(value) {
|
|
58
|
+
try {
|
|
59
|
+
return normalizeEngineRelativeInput(value, 'patch');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function resolvePositionals(engineDir, patches, manifest) {
|
|
66
|
+
for (const identifier of patches) {
|
|
67
|
+
if (resolvePatchIdentifier(identifier, manifest.patches))
|
|
68
|
+
continue;
|
|
69
|
+
const enginePath = asEnginePath(identifier);
|
|
70
|
+
if (enginePath !== undefined && (await pathExists(join(engineDir, enginePath)))) {
|
|
71
|
+
throw new InvalidArgumentError(`"${identifier}" is a file in engine/, not a patch. re-export takes patch identifiers ` +
|
|
72
|
+
`as positionals. To name a file whose drift this run expects, pass --expect ` +
|
|
73
|
+
`${enginePath} (one --expect per path), or list the paths one per line in a file ` +
|
|
74
|
+
'and pass --expect-from <file>.', identifier);
|
|
75
|
+
}
|
|
76
|
+
throw new InvalidArgumentError(formatPatchNotFoundError(identifier, manifest.patches), identifier);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Refuses a path flag value that names no changed file in engine/. Every
|
|
81
|
+
* one of these flags is about a changed file (drift to expect, an unmanaged
|
|
82
|
+
* file to admit, a file to adopt), so a clean or absent path is a typo or a
|
|
83
|
+
* swallowed argument, and the run would otherwise treat it as a no-op.
|
|
84
|
+
*/
|
|
85
|
+
async function assertFlagPathsInStatus(engineDir, options, manifest) {
|
|
86
|
+
const normalize = (flag, values) => [
|
|
87
|
+
flag,
|
|
88
|
+
(values ?? []).map((value) => normalizeEngineRelativeInput(value, flag)),
|
|
89
|
+
];
|
|
90
|
+
const expectNew = normalize('--expect-new', options.expectNew);
|
|
91
|
+
const byFlag = [
|
|
92
|
+
normalize('--expect', options.expect),
|
|
93
|
+
normalize('--expect-unmanaged', options.expectUnmanaged),
|
|
94
|
+
expectNew,
|
|
95
|
+
normalize('--scan-file', options.scanFiles?.map((value) => splitScanFileOwner(value).path)),
|
|
96
|
+
];
|
|
97
|
+
const all = [...new Set(byFlag.flatMap(([, values]) => values))];
|
|
98
|
+
if (all.length === 0 || !(await isGitRepository(engineDir)))
|
|
99
|
+
return;
|
|
100
|
+
const changed = new Set();
|
|
101
|
+
try {
|
|
102
|
+
for (const chunk of chunkPathspecs(all)) {
|
|
103
|
+
for (const file of await getDirtyFiles(engineDir, chunk))
|
|
104
|
+
changed.add(file);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// An engine git cannot answer for (no HEAD yet, a broken checkout) is
|
|
109
|
+
// the locked run's engine precondition to report, with its own remedy.
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const [flag, values] of byFlag) {
|
|
113
|
+
const absent = values.filter((value) => !changed.has(value));
|
|
114
|
+
if (absent.length > 0) {
|
|
115
|
+
throw new InvalidArgumentError(`${flag} names no changed file in engine/: ${absent.join(', ')}. Each value must be a ` +
|
|
116
|
+
'path git status lists (modified, deleted or untracked); a clean or missing path is a ' +
|
|
117
|
+
'typo or a list that ended early, and the run would have ignored it.', flag);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const owners = new Map();
|
|
121
|
+
for (const patch of manifest.patches) {
|
|
122
|
+
for (const file of patch.filesAffected)
|
|
123
|
+
owners.set(file, patch.filename);
|
|
124
|
+
}
|
|
125
|
+
const owned = expectNew[1].filter((file) => owners.has(file));
|
|
126
|
+
if (owned.length > 0) {
|
|
127
|
+
throw new InvalidArgumentError(`--expect-new names a file a patch already owns: ` +
|
|
128
|
+
`${owned.map((file) => `${file} (${owners.get(file) ?? ''})`).join(', ')}. ` +
|
|
129
|
+
'--expect-new is for files no patch owns yet; drop it for these.', '--expect-new');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Runs every lock-free argument check: empty or unknown patch names
|
|
134
|
+
* (naming an engine file passed by mistake), option combinations, path
|
|
135
|
+
* flag values that are flags, and path flag values that name no changed
|
|
136
|
+
* file. Throws the refusal the locked run would have thrown, only sooner.
|
|
137
|
+
*
|
|
138
|
+
* @param projectRoot - Project root
|
|
139
|
+
* @param patches - Positional patch arguments as given
|
|
140
|
+
* @param options - Options as the command layer builds them
|
|
141
|
+
*/
|
|
142
|
+
export async function preflightReExportArguments(projectRoot, patches, options) {
|
|
143
|
+
const empty = patches.filter((patch) => patch.trim().length === 0).length;
|
|
144
|
+
if (empty > 0) {
|
|
145
|
+
throw new InvalidArgumentError(`re-export was given ${empty} empty patch argument(s). An empty argument is usually a ` +
|
|
146
|
+
'shell expansion that produced nothing (an unquoted "$@" in a heredoc, an unset ' +
|
|
147
|
+
'variable); pass patch identifiers, or --all.', 'patches');
|
|
148
|
+
}
|
|
149
|
+
const normalized = normalizeReExportFilesPositionals(patches, options);
|
|
150
|
+
validateReExportOptionCombinations(normalized.patches, normalized.options);
|
|
151
|
+
const paths = getProjectPaths(projectRoot);
|
|
152
|
+
const manifest = await loadPatchesManifest(paths.patches);
|
|
153
|
+
// No manifest: the locked run reports that with its own remedy.
|
|
154
|
+
if (!manifest || manifest.patches.length === 0)
|
|
155
|
+
return;
|
|
156
|
+
if (options.all !== true && options.scanFilesManifest === undefined) {
|
|
157
|
+
await resolvePositionals(paths.engine, normalized.patches, manifest);
|
|
158
|
+
}
|
|
159
|
+
await assertFlagPathsInStatus(paths.engine, normalized.options, manifest);
|
|
160
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
/** What a scoped dry run could touch: the patches it was asked about. */
|
|
3
|
+
export interface DryRunPurityScope {
|
|
4
|
+
/** The selected patches. */
|
|
5
|
+
patches: readonly PatchMetadata[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Fingerprints exactly the state a scoped `re-export --dry-run` could
|
|
9
|
+
* disturb. Every key names what it covers, so a violation reads as the
|
|
10
|
+
* path that moved.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fingerprintScopedDryRunState(engineDir: string, patchesDir: string, scope: DryRunPurityScope): Promise<Map<string, string>>;
|
|
13
|
+
/**
|
|
14
|
+
* Names whoever else wrote during the dry run: history records from other
|
|
15
|
+
* invocations since it started, and a live holder of the engine-session
|
|
16
|
+
* lock (which a dry run no longer takes). Empty when there is no evidence.
|
|
17
|
+
*/
|
|
18
|
+
export declare function describeForeignWriters(patchesDir: string, sinceMs: number): Promise<string[]>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The scoped half of the `re-export --dry-run` purity guard.
|
|
4
|
+
*
|
|
5
|
+
* A dry run takes no engine-session lock since 0.51.0, so peers keep
|
|
6
|
+
* writing while it runs. The whole-tree fingerprint then reported every
|
|
7
|
+
* peer's write as "invariant violated … This is a FireForge bug", and an
|
|
8
|
+
* immediate repeat was clean. The scoped fingerprint covers only what the
|
|
9
|
+
* dry run could touch: the selected patches' bodies and manifest rows, the
|
|
10
|
+
* engine files they own, the engine HEAD and the engine index. A mismatch
|
|
11
|
+
* that remains is attributed to a foreign writer when the history log or
|
|
12
|
+
* the engine lock shows one in the window.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { readEngineSessionLockStatus } from '../core/engine-session-lock.js';
|
|
17
|
+
import { git } from '../core/git-base.js';
|
|
18
|
+
import { historyRecordIdentity, resolveHistoryLogPath } from '../core/history-log-file.js';
|
|
19
|
+
import { PATCHES_MANIFEST } from '../core/patch-manifest-io.js';
|
|
20
|
+
import { GeneralError } from '../errors/base.js';
|
|
21
|
+
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
22
|
+
import { sha256Hex } from '../utils/hash.js';
|
|
23
|
+
import { isArray, isObject } from '../utils/validation.js';
|
|
24
|
+
/** Reads a file's bytes as a hash, `absent` when it does not exist. */
|
|
25
|
+
async function hashOrAbsent(path, label) {
|
|
26
|
+
try {
|
|
27
|
+
return sha256Hex(await readFile(path));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const code = getNodeErrorCode(error);
|
|
31
|
+
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
32
|
+
return 'absent';
|
|
33
|
+
throw new GeneralError(`[dry-run] cannot fingerprint ${label} for the purity guard: ${toError(error).message}`, error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The selected rows of `patches.json`, so a peer's write to another row is not a violation. */
|
|
37
|
+
async function selectedRowsFingerprint(patchesDir, filenames) {
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = await readFile(join(patchesDir, PATCHES_MANIFEST), 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (getNodeErrorCode(error) === 'ENOENT')
|
|
44
|
+
return 'absent';
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
const rows = isObject(parsed) && isArray(parsed['patches']) ? parsed['patches'] : [];
|
|
50
|
+
const selected = rows.filter((row) => isObject(row) && typeof row['filename'] === 'string' && filenames.has(row['filename']));
|
|
51
|
+
return sha256Hex(JSON.stringify(selected));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// A manifest that no longer parses changed, whoever wrote it.
|
|
55
|
+
return sha256Hex(raw);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Fingerprints exactly the state a scoped `re-export --dry-run` could
|
|
60
|
+
* disturb. Every key names what it covers, so a violation reads as the
|
|
61
|
+
* path that moved.
|
|
62
|
+
*/
|
|
63
|
+
export async function fingerprintScopedDryRunState(engineDir, patchesDir, scope) {
|
|
64
|
+
const fingerprint = new Map();
|
|
65
|
+
try {
|
|
66
|
+
fingerprint.set('engine HEAD', (await git(['rev-parse', 'HEAD'], engineDir)).trim());
|
|
67
|
+
// The index as a whole: the historic purity defect was a transient
|
|
68
|
+
// `--intent-to-add` entry, which may land on any path a scan reads.
|
|
69
|
+
fingerprint.set('the engine index', sha256Hex(await git(['ls-files', '--stage', '-z'], engineDir)));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new GeneralError(`[dry-run] cannot fingerprint the engine for the purity guard: ${toError(error).message}`, error);
|
|
73
|
+
}
|
|
74
|
+
const filenames = new Set(scope.patches.map((patch) => patch.filename));
|
|
75
|
+
fingerprint.set(`${PATCHES_MANIFEST} rows of ${[...filenames].sort().join(', ')}`, await selectedRowsFingerprint(patchesDir, filenames));
|
|
76
|
+
for (const filename of [...filenames].sort()) {
|
|
77
|
+
fingerprint.set(`patches/${filename}`, await hashOrAbsent(join(patchesDir, filename), `patches/${filename}`));
|
|
78
|
+
}
|
|
79
|
+
const owned = [...new Set(scope.patches.flatMap((patch) => patch.filesAffected))].sort();
|
|
80
|
+
for (const file of owned) {
|
|
81
|
+
fingerprint.set(`engine/${file}`, await hashOrAbsent(join(engineDir, file), `engine/${file}`));
|
|
82
|
+
}
|
|
83
|
+
return fingerprint;
|
|
84
|
+
}
|
|
85
|
+
/** History records another invocation wrote at or after `sinceMs`. */
|
|
86
|
+
async function foreignHistoryRecords(projectRoot, sinceMs) {
|
|
87
|
+
let text;
|
|
88
|
+
try {
|
|
89
|
+
text = await readFile(await resolveHistoryLogPath(projectRoot), 'utf8');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const own = historyRecordIdentity().invocationId;
|
|
95
|
+
const writers = [];
|
|
96
|
+
for (const line of text.split('\n').slice(-500)) {
|
|
97
|
+
let record;
|
|
98
|
+
try {
|
|
99
|
+
record = JSON.parse(line);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const ts = typeof record.ts === 'string' ? Date.parse(record.ts) : Number.NaN;
|
|
105
|
+
if (!(ts >= sinceMs) || record.invocationId === own)
|
|
106
|
+
continue;
|
|
107
|
+
const session = typeof record.sessionId === 'string' ? `, session ${record.sessionId}` : '';
|
|
108
|
+
writers.push(`${String(record.operation)} (PID ${String(record.pid)}${session})`);
|
|
109
|
+
}
|
|
110
|
+
return writers;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Names whoever else wrote during the dry run: history records from other
|
|
114
|
+
* invocations since it started, and a live holder of the engine-session
|
|
115
|
+
* lock (which a dry run no longer takes). Empty when there is no evidence.
|
|
116
|
+
*/
|
|
117
|
+
export async function describeForeignWriters(patchesDir, sinceMs) {
|
|
118
|
+
const projectRoot = dirname(resolve(patchesDir));
|
|
119
|
+
const writers = await foreignHistoryRecords(projectRoot, sinceMs);
|
|
120
|
+
try {
|
|
121
|
+
const lock = await readEngineSessionLockStatus(projectRoot);
|
|
122
|
+
if (lock.held && lock.holder !== undefined && lock.holder.pid !== process.pid) {
|
|
123
|
+
const detail = lock.holder.metadata.length > 0 ? ` (${lock.holder.metadata.join(', ')})` : '';
|
|
124
|
+
writers.push(`the engine-session lock holder, PID ${lock.holder.pid}${detail}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Attribution is best effort; the violation is reported either way.
|
|
129
|
+
}
|
|
130
|
+
return writers;
|
|
131
|
+
}
|