@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
|
@@ -19,12 +19,19 @@
|
|
|
19
19
|
* no owned file is an upstream Firefox module, which FireForge does not
|
|
20
20
|
* police and cannot cheaply enumerate. Policing only what the fork owns is
|
|
21
21
|
* both the actionable set and the one that produces real incidents.
|
|
22
|
+
*
|
|
23
|
+
* An unscoped `verify` widens that set with the engine worktree: changed
|
|
24
|
+
* files import, and untracked files are targets. A chrome script's lazy
|
|
25
|
+
* getter for a new, not yet exported `.sys.mjs` with no moz.build line
|
|
26
|
+
* SIGSEGVed every start of a shared objdir, and neither file was in the
|
|
27
|
+
* queue yet, so the queue-only scan could not see it.
|
|
22
28
|
*/
|
|
23
29
|
import { dirname, join } from 'node:path';
|
|
24
30
|
import { toError } from '../utils/errors.js';
|
|
25
31
|
import { pathExists, readText } from '../utils/fs.js';
|
|
26
32
|
import { verbose } from '../utils/logger.js';
|
|
27
33
|
import { escapeRegex } from '../utils/regex.js';
|
|
34
|
+
import { expandUntrackedDirectoryEntries, getWorkingTreeStatus } from './git-status.js';
|
|
28
35
|
import { extractResourceModuleSpecifiers, moduleSourceSuffix, } from './patch-lint-module-registration.js';
|
|
29
36
|
/** Extensions whose contents are scanned for resource-module imports. */
|
|
30
37
|
const IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
@@ -79,20 +86,31 @@ async function isRegisteredInAncestorMozBuild(engineDir, moduleRelPath, mozBuild
|
|
|
79
86
|
dir = parent;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
89
|
+
/** Lists the engine's changed and untracked files for the worktree scan. */
|
|
90
|
+
export async function collectWorktreeModuleFiles(engineDir) {
|
|
91
|
+
const entries = await expandUntrackedDirectoryEntries(engineDir, await getWorkingTreeStatus(engineDir));
|
|
92
|
+
const present = entries.filter((entry) => entry.worktreeStatus !== 'D');
|
|
93
|
+
return {
|
|
94
|
+
changed: present.map((entry) => entry.file),
|
|
95
|
+
untracked: present.filter((entry) => entry.status === '??').map((entry) => entry.file),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
82
98
|
/**
|
|
83
99
|
* Resolves every `resource://` system-module import made by the
|
|
84
|
-
* queue-owned files and
|
|
85
|
-
* load.
|
|
100
|
+
* queue-owned files (and, given `worktree`, by changed engine files) and
|
|
101
|
+
* reports the ones a packaged browser could not load.
|
|
86
102
|
*
|
|
87
103
|
* @param engineDir - Path to the engine checkout
|
|
88
104
|
* @param ownedFiles - Engine-relative paths the patch queue owns
|
|
105
|
+
* @param worktree - Changed and untracked engine files to judge as well
|
|
89
106
|
* @returns Findings sorted by module path. Empty when everything resolves
|
|
90
107
|
*/
|
|
91
|
-
export async function findUnresolvedSystemModuleImports(engineDir, ownedFiles) {
|
|
92
|
-
const owned = [...new Set(ownedFiles)];
|
|
93
|
-
|
|
108
|
+
export async function findUnresolvedSystemModuleImports(engineDir, ownedFiles, worktree) {
|
|
109
|
+
const owned = [...new Set([...ownedFiles, ...(worktree?.untracked ?? [])])];
|
|
110
|
+
const importing = [...new Set([...owned, ...(worktree?.changed ?? [])])];
|
|
111
|
+
/** specifier → importing files. */
|
|
94
112
|
const importersBySpecifier = new Map();
|
|
95
|
-
for (const file of
|
|
113
|
+
for (const file of importing) {
|
|
96
114
|
if (!IMPORTABLE_EXTENSIONS.some((extension) => file.endsWith(extension)))
|
|
97
115
|
continue;
|
|
98
116
|
const content = await readIfPresent(join(engineDir, file));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which negative-control targets only a build can deliver.
|
|
3
|
+
*
|
|
4
|
+
* A control is applied after the pre-test build and right before dispatch,
|
|
5
|
+
* which is right for a file the runtime reads from the source tree or the
|
|
6
|
+
* objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
|
|
7
|
+
* never compiled: the run tests the unmutated binary and reports
|
|
8
|
+
* `control=missed`, which reads as a test that cannot fail. Such a target
|
|
9
|
+
* is now refused without `--build`, and with `--build` the control is
|
|
10
|
+
* applied before the build so the binary under test carries it.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The engine-relative targets that reach the runtime only through a
|
|
14
|
+
* compile: a compiled-source extension and no install-manifest entry that
|
|
15
|
+
* ships the file itself.
|
|
16
|
+
*
|
|
17
|
+
* @param engineDir - Absolute engine directory
|
|
18
|
+
* @param enginePaths - The control's engine-relative targets
|
|
19
|
+
*/
|
|
20
|
+
export declare function findCompiledControlTargets(engineDir: string, enginePaths: readonly string[]): Promise<string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* The refusal for a compiled control without `--build`, naming the manual
|
|
23
|
+
* procedure for a run that wants to keep its build out of FireForge.
|
|
24
|
+
*
|
|
25
|
+
* @param compiled - Engine-relative compiled targets
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatCompiledControlRefusal(compiled: readonly string[]): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Which negative-control targets only a build can deliver.
|
|
4
|
+
*
|
|
5
|
+
* A control is applied after the pre-test build and right before dispatch,
|
|
6
|
+
* which is right for a file the runtime reads from the source tree or the
|
|
7
|
+
* objdir links to. A C++ (or Rust, IDL, …) source edited at that point is
|
|
8
|
+
* never compiled: the run tests the unmutated binary and reports
|
|
9
|
+
* `control=missed`, which reads as a test that cannot fail. Such a target
|
|
10
|
+
* is now refused without `--build`, and with `--build` the control is
|
|
11
|
+
* applied before the build so the binary under test carries it.
|
|
12
|
+
*/
|
|
13
|
+
import { extname } from 'node:path';
|
|
14
|
+
import { classifyRuntimeDelivery } from './objdir-install-manifests.js';
|
|
15
|
+
import { loadEngineInstallIndex } from './test-stale-exempt.js';
|
|
16
|
+
/** Extensions of sources the build compiles or code-generates into the binary. */
|
|
17
|
+
const COMPILED_EXTENSIONS = new Set([
|
|
18
|
+
'.c',
|
|
19
|
+
'.cc',
|
|
20
|
+
'.cpp',
|
|
21
|
+
'.cxx',
|
|
22
|
+
'.h',
|
|
23
|
+
'.hh',
|
|
24
|
+
'.hpp',
|
|
25
|
+
'.m',
|
|
26
|
+
'.mm',
|
|
27
|
+
'.rs',
|
|
28
|
+
'.idl',
|
|
29
|
+
'.webidl',
|
|
30
|
+
'.ipdl',
|
|
31
|
+
'.ipdlh',
|
|
32
|
+
'.yaml',
|
|
33
|
+
'.yml',
|
|
34
|
+
]);
|
|
35
|
+
/**
|
|
36
|
+
* The engine-relative targets that reach the runtime only through a
|
|
37
|
+
* compile: a compiled-source extension and no install-manifest entry that
|
|
38
|
+
* ships the file itself.
|
|
39
|
+
*
|
|
40
|
+
* @param engineDir - Absolute engine directory
|
|
41
|
+
* @param enginePaths - The control's engine-relative targets
|
|
42
|
+
*/
|
|
43
|
+
export async function findCompiledControlTargets(engineDir, enginePaths) {
|
|
44
|
+
const candidates = enginePaths.filter((path) => COMPILED_EXTENSIONS.has(extname(path)));
|
|
45
|
+
if (candidates.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
const index = await loadEngineInstallIndex(engineDir);
|
|
48
|
+
if (index === undefined)
|
|
49
|
+
return candidates;
|
|
50
|
+
const compiled = [];
|
|
51
|
+
for (const path of candidates) {
|
|
52
|
+
if ((await classifyRuntimeDelivery(index, engineDir, path)) === 'unlisted') {
|
|
53
|
+
compiled.push(path);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return compiled;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The refusal for a compiled control without `--build`, naming the manual
|
|
60
|
+
* procedure for a run that wants to keep its build out of FireForge.
|
|
61
|
+
*
|
|
62
|
+
* @param compiled - Engine-relative compiled targets
|
|
63
|
+
*/
|
|
64
|
+
export function formatCompiledControlRefusal(compiled) {
|
|
65
|
+
const files = compiled.map((path) => `engine/${path}`).join(', ');
|
|
66
|
+
return (`This negative control edits compiled source (${files}). Applied right before dispatch, ` +
|
|
67
|
+
'it would never be compiled: the run would test the unmutated binary and report ' +
|
|
68
|
+
'control=missed. Add --build: the control is then applied before the pre-test build, so ' +
|
|
69
|
+
'the binary under test carries it, and restored afterwards with a fresh mtime so the next ' +
|
|
70
|
+
'build recompiles the original. To run it by hand instead: copy the mutated file in, ' +
|
|
71
|
+
'`touch` it, `fireforge build`, `fireforge test`, restore the file byte-exact, `touch` it ' +
|
|
72
|
+
'again and rebuild.');
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsing and strict application of a `fireforge test --negative-control`
|
|
3
|
+
* diff.
|
|
4
|
+
*
|
|
5
|
+
* A control is a reviewed mutation, so the applier is deliberately strict:
|
|
6
|
+
* every context and removed line must match exactly, the only tolerance is
|
|
7
|
+
* a line offset (the same one `git apply` allows, never fuzz), and a hunk
|
|
8
|
+
* that does not apply refuses the whole control before any file is
|
|
9
|
+
* written. Only plain text modifications of existing files are accepted: a
|
|
10
|
+
* created, deleted, renamed or binary file has no byte-exact restore story
|
|
11
|
+
* that is worth its risk for a neuter.
|
|
12
|
+
*/
|
|
13
|
+
import { type ParsedHunk } from './patch-parse.js';
|
|
14
|
+
/** One file of a control diff. */
|
|
15
|
+
export interface ControlDiffFile {
|
|
16
|
+
/** The path the diff names, without git's `a/`/`b/` prefix. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** The hunks to apply, in file order. */
|
|
19
|
+
hunks: ParsedHunk[];
|
|
20
|
+
}
|
|
21
|
+
/** Options for {@link parseControlDiff}. */
|
|
22
|
+
export interface ParseControlDiffOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Called for a section whose old and new paths differ. Returns the path
|
|
25
|
+
* the control targets (a `git diff --no-index <file> <edited copy>` names
|
|
26
|
+
* the copy on its new side), or undefined for a real rename, which is
|
|
27
|
+
* refused.
|
|
28
|
+
*/
|
|
29
|
+
resolveRename?: (sourcePath: string, targetPath: string) => string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parses a control diff. Accepts `git diff` output (a `diff --git` header
|
|
33
|
+
* per file), which is what `git -C engine diff -- <file>` or a project-root
|
|
34
|
+
* `git diff` writes, a plain unified diff (`diff -u`), and, through
|
|
35
|
+
* `resolveRename`, a `git diff --no-index` against an edited copy.
|
|
36
|
+
*
|
|
37
|
+
* @param content - The diff file's text
|
|
38
|
+
* @param options - See {@link ParseControlDiffOptions}
|
|
39
|
+
* @returns One entry per file, in diff order
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseControlDiff(content: string, options?: ParseControlDiffOptions): ControlDiffFile[];
|
|
42
|
+
/**
|
|
43
|
+
* Applies `hunks` to `original`, strictly.
|
|
44
|
+
*
|
|
45
|
+
* @param original - The file's current text
|
|
46
|
+
* @param hunks - Hunks parsed from the control diff
|
|
47
|
+
* @param label - File label for refusal messages
|
|
48
|
+
* @returns The mutated text
|
|
49
|
+
* @throws GeneralError when any hunk does not apply
|
|
50
|
+
*/
|
|
51
|
+
export declare function applyControlHunks(original: string, hunks: readonly ParsedHunk[], label: string): string;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Parsing and strict application of a `fireforge test --negative-control`
|
|
4
|
+
* diff.
|
|
5
|
+
*
|
|
6
|
+
* A control is a reviewed mutation, so the applier is deliberately strict:
|
|
7
|
+
* every context and removed line must match exactly, the only tolerance is
|
|
8
|
+
* a line offset (the same one `git apply` allows, never fuzz), and a hunk
|
|
9
|
+
* that does not apply refuses the whole control before any file is
|
|
10
|
+
* written. Only plain text modifications of existing files are accepted: a
|
|
11
|
+
* created, deleted, renamed or binary file has no byte-exact restore story
|
|
12
|
+
* that is worth its risk for a neuter.
|
|
13
|
+
*/
|
|
14
|
+
import { GeneralError } from '../errors/base.js';
|
|
15
|
+
import { isContainedRelativePath, normalizePathSlashes } from '../utils/paths.js';
|
|
16
|
+
import { parseDiffSections } from './patch-parse.js';
|
|
17
|
+
function refuse(message) {
|
|
18
|
+
throw new GeneralError(`Negative control diff refused: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
/** A `---`/`+++` file name, without a trailing timestamp or git's `a/`/`b/`. */
|
|
21
|
+
function plainDiffPath(raw) {
|
|
22
|
+
const name = raw.split('\t')[0]?.trim() ?? '';
|
|
23
|
+
return name.startsWith('a/') || name.startsWith('b/') ? name.slice(2) : name;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gives a plain unified diff (`diff -u`, no `diff --git` lines) the git
|
|
27
|
+
* header the section parser keys on, one per `---`/`+++` pair. A diff that
|
|
28
|
+
* already carries `diff --git` headers is returned unchanged.
|
|
29
|
+
*
|
|
30
|
+
* @param content - The diff file's text
|
|
31
|
+
*/
|
|
32
|
+
function withGitHeaders(content) {
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
if (lines.some((line) => line.startsWith('diff --git ')))
|
|
35
|
+
return content;
|
|
36
|
+
const out = [];
|
|
37
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
38
|
+
const line = lines[index] ?? '';
|
|
39
|
+
const next = lines[index + 1] ?? '';
|
|
40
|
+
if (line.startsWith('--- ') && next.startsWith('+++ ')) {
|
|
41
|
+
const oldPath = plainDiffPath(line.slice(4));
|
|
42
|
+
const newPath = plainDiffPath(next.slice(4));
|
|
43
|
+
index += 1;
|
|
44
|
+
if (oldPath === '/dev/null' || newPath === '/dev/null') {
|
|
45
|
+
// Kept as a create/delete, which the parser below refuses by name.
|
|
46
|
+
const path = oldPath === '/dev/null' ? newPath : oldPath;
|
|
47
|
+
out.push(`diff --git a/${path} b/${path}`, oldPath === '/dev/null' ? 'new file mode 100644' : 'deleted file mode 100644', oldPath === '/dev/null' ? '--- /dev/null' : `--- a/${path}`, newPath === '/dev/null' ? '+++ /dev/null' : `+++ b/${path}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
out.push(`diff --git a/${oldPath} b/${newPath}`, `--- a/${oldPath}`, `+++ b/${newPath}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
out.push(line);
|
|
54
|
+
}
|
|
55
|
+
return out.join('\n');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parses a control diff. Accepts `git diff` output (a `diff --git` header
|
|
59
|
+
* per file), which is what `git -C engine diff -- <file>` or a project-root
|
|
60
|
+
* `git diff` writes, a plain unified diff (`diff -u`), and, through
|
|
61
|
+
* `resolveRename`, a `git diff --no-index` against an edited copy.
|
|
62
|
+
*
|
|
63
|
+
* @param content - The diff file's text
|
|
64
|
+
* @param options - See {@link ParseControlDiffOptions}
|
|
65
|
+
* @returns One entry per file, in diff order
|
|
66
|
+
*/
|
|
67
|
+
export function parseControlDiff(content, options = {}) {
|
|
68
|
+
const sections = parseDiffSections(withGitHeaders(content));
|
|
69
|
+
if (sections.length === 0) {
|
|
70
|
+
refuse('no diff sections found. Write the control with git, e.g. ' +
|
|
71
|
+
'`git -C engine diff -- <file> > control.diff` after making the edit, then revert the edit.');
|
|
72
|
+
}
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
return sections.map((section) => {
|
|
75
|
+
let path = normalizePathSlashes(section.targetPath);
|
|
76
|
+
if (section.isBinary)
|
|
77
|
+
refuse(`${path} is a binary section.`);
|
|
78
|
+
if (section.isNewFile || section.isDeletedFile) {
|
|
79
|
+
refuse(`${path} creates or deletes a file; a control may only modify existing text files.`);
|
|
80
|
+
}
|
|
81
|
+
if (section.sourcePath !== section.targetPath) {
|
|
82
|
+
const resolved = options.resolveRename?.(section.sourcePath, section.targetPath);
|
|
83
|
+
if (resolved === undefined) {
|
|
84
|
+
refuse(`${section.sourcePath} -> ${path} is a rename; a control may only modify files.`);
|
|
85
|
+
}
|
|
86
|
+
path = normalizePathSlashes(resolved);
|
|
87
|
+
}
|
|
88
|
+
if (!isContainedRelativePath(path)) {
|
|
89
|
+
refuse(`${path} is not a contained relative path.`);
|
|
90
|
+
}
|
|
91
|
+
if (section.hunks.length === 0)
|
|
92
|
+
refuse(`${path} has no hunks.`);
|
|
93
|
+
if (seen.has(path))
|
|
94
|
+
refuse(`${path} appears in more than one section.`);
|
|
95
|
+
seen.add(path);
|
|
96
|
+
return { path, hunks: section.hunks };
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function hunkSides(hunk, label, index) {
|
|
100
|
+
const oldLines = [];
|
|
101
|
+
const newLines = [];
|
|
102
|
+
for (const line of hunk.lines) {
|
|
103
|
+
const body = line.slice(1);
|
|
104
|
+
if (line.startsWith(' ') || line.startsWith('-'))
|
|
105
|
+
oldLines.push(body);
|
|
106
|
+
if (line.startsWith(' ') || line.startsWith('+'))
|
|
107
|
+
newLines.push(body);
|
|
108
|
+
}
|
|
109
|
+
if (oldLines.length !== hunk.oldCount || newLines.length !== hunk.newCount) {
|
|
110
|
+
refuse(`hunk ${index + 1} of ${label} is malformed: its header counts ` +
|
|
111
|
+
`(-${hunk.oldCount} +${hunk.newCount}) do not match its body ` +
|
|
112
|
+
`(-${oldLines.length} +${newLines.length}).`);
|
|
113
|
+
}
|
|
114
|
+
return { oldLines, newLines };
|
|
115
|
+
}
|
|
116
|
+
function matchesAt(lines, block, position) {
|
|
117
|
+
if (position < 0 || position + block.length > lines.length)
|
|
118
|
+
return false;
|
|
119
|
+
return block.every((line, offset) => lines[position + offset] === line);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The position nearest `anchor`, at or after `floor`, where `block`
|
|
123
|
+
* matches, or -1.
|
|
124
|
+
*/
|
|
125
|
+
function findBlock(lines, block, anchor, floor) {
|
|
126
|
+
const start = Math.max(anchor, floor);
|
|
127
|
+
for (let delta = 0; delta <= lines.length; delta += 1) {
|
|
128
|
+
if (matchesAt(lines, block, start + delta))
|
|
129
|
+
return start + delta;
|
|
130
|
+
const below = anchor - delta;
|
|
131
|
+
if (delta > 0 && below >= floor && matchesAt(lines, block, below))
|
|
132
|
+
return below;
|
|
133
|
+
}
|
|
134
|
+
return -1;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Applies `hunks` to `original`, strictly.
|
|
138
|
+
*
|
|
139
|
+
* @param original - The file's current text
|
|
140
|
+
* @param hunks - Hunks parsed from the control diff
|
|
141
|
+
* @param label - File label for refusal messages
|
|
142
|
+
* @returns The mutated text
|
|
143
|
+
* @throws GeneralError when any hunk does not apply
|
|
144
|
+
*/
|
|
145
|
+
export function applyControlHunks(original, hunks, label) {
|
|
146
|
+
const hasFinalNewline = original.endsWith('\n');
|
|
147
|
+
const lines = original === '' ? [] : (hasFinalNewline ? original.slice(0, -1) : original).split('\n');
|
|
148
|
+
const result = [];
|
|
149
|
+
let cursor = 0;
|
|
150
|
+
let finalNewline = hasFinalNewline;
|
|
151
|
+
hunks.forEach((hunk, index) => {
|
|
152
|
+
const { oldLines, newLines } = hunkSides(hunk, label, index);
|
|
153
|
+
const anchor = hunk.oldCount === 0 ? hunk.oldStart : hunk.oldStart - 1;
|
|
154
|
+
const position = oldLines.length === 0
|
|
155
|
+
? anchor >= cursor && anchor <= lines.length
|
|
156
|
+
? anchor
|
|
157
|
+
: -1
|
|
158
|
+
: findBlock(lines, oldLines, anchor, cursor);
|
|
159
|
+
const reachesEnd = position + oldLines.length === lines.length;
|
|
160
|
+
const endMatches = position >= 0 &&
|
|
161
|
+
(reachesEnd && oldLines.length > 0
|
|
162
|
+
? hunk.noNewlineAtEndOld === !hasFinalNewline
|
|
163
|
+
: !hunk.noNewlineAtEndOld);
|
|
164
|
+
if (position < 0 || !endMatches) {
|
|
165
|
+
const alreadyApplied = newLines.length > 0 && findBlock(lines, newLines, Math.max(0, hunk.newStart - 1), 0) >= 0;
|
|
166
|
+
refuse(`hunk ${index + 1} of ${label} (@@ -${hunk.oldStart},${hunk.oldCount} ` +
|
|
167
|
+
`+${hunk.newStart},${hunk.newCount} @@) does not apply to the current file.` +
|
|
168
|
+
(alreadyApplied
|
|
169
|
+
? ' Its new side is already present: the mutation looks applied already (an earlier control that was not restored?).'
|
|
170
|
+
: ' The file changed since the diff was written; regenerate the control against it.'));
|
|
171
|
+
}
|
|
172
|
+
result.push(...lines.slice(cursor, position), ...newLines);
|
|
173
|
+
cursor = position + oldLines.length;
|
|
174
|
+
if (reachesEnd)
|
|
175
|
+
finalNewline = !hunk.noNewlineAtEndNew;
|
|
176
|
+
});
|
|
177
|
+
result.push(...lines.slice(cursor));
|
|
178
|
+
return result.join('\n') + (finalNewline && result.length > 0 ? '\n' : '');
|
|
179
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireforge test --negative-control <diff>`: apply a reviewed mutation to
|
|
3
|
+
* engine sources for exactly one harness dispatch, then put every byte back.
|
|
4
|
+
*
|
|
5
|
+
* Safety model (also stated in docs/testing.md):
|
|
6
|
+
*
|
|
7
|
+
* - The control is applied and restored only while the caller holds the
|
|
8
|
+
* engine-session lock, after the pre-test build has packaged and before
|
|
9
|
+
* the harness spawns, so no peer build or test can observe it and a
|
|
10
|
+
* Furnace deploy cannot overwrite it.
|
|
11
|
+
* - Before the first mutated byte is written, the original bytes of every
|
|
12
|
+
* target go to `.fireforge/negative-control/` together with a journal
|
|
13
|
+
* recording each file's original and mutated SHA-256, mode and
|
|
14
|
+
* timestamps. A killed FireForge therefore leaves enough on disk to undo
|
|
15
|
+
* the control, and the next `fireforge test` does so before anything else.
|
|
16
|
+
* - Restore writes the original bytes back atomically, re-applies the
|
|
17
|
+
* original mode and timestamps, and reads the file back to prove the
|
|
18
|
+
* bytes match. It only overwrites a file that still holds exactly the
|
|
19
|
+
* bytes the control wrote: anything else means someone edited the file
|
|
20
|
+
* while the control ran, and that edit is kept, never clobbered, with the
|
|
21
|
+
* original left in the journal directory and the conflict reported.
|
|
22
|
+
* - A SIGINT or SIGTERM restores through {@link restoreActiveNegativeControlsForSignal},
|
|
23
|
+
* which the entry point runs after the harness has shut down and before
|
|
24
|
+
* it releases the held locks.
|
|
25
|
+
*/
|
|
26
|
+
import type { ParsedHunk } from './patch-parse.js';
|
|
27
|
+
/** One file a control mutates. */
|
|
28
|
+
export interface NegativeControlTarget {
|
|
29
|
+
/** The path as the diff names it. */
|
|
30
|
+
diffPath: string;
|
|
31
|
+
/** The engine-relative file the control mutates. */
|
|
32
|
+
enginePath: string;
|
|
33
|
+
/** Hunks to apply to the engine file. */
|
|
34
|
+
hunks: ParsedHunk[];
|
|
35
|
+
/** True when the diff named a `components/` source mapped to its deployed copy. */
|
|
36
|
+
fromComponents: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A parsed, resolved control, ready to apply. */
|
|
39
|
+
export interface NegativeControlPlan {
|
|
40
|
+
/** The diff file, as the operator passed it. */
|
|
41
|
+
diffFile: string;
|
|
42
|
+
targets: NegativeControlTarget[];
|
|
43
|
+
/**
|
|
44
|
+
* Engine-relative targets only a build delivers (compiled sources). A
|
|
45
|
+
* control with any must be applied before the pre-test build, and is
|
|
46
|
+
* restored with a fresh mtime. See `negative-control-compiled.ts`.
|
|
47
|
+
*/
|
|
48
|
+
compiled?: string[];
|
|
49
|
+
}
|
|
50
|
+
/** Locations a control works against. */
|
|
51
|
+
export interface NegativeControlPaths {
|
|
52
|
+
engineDir: string;
|
|
53
|
+
/** The project's `.fireforge/` directory. */
|
|
54
|
+
fireforgeDir: string;
|
|
55
|
+
}
|
|
56
|
+
interface JournalEntry {
|
|
57
|
+
enginePath: string;
|
|
58
|
+
backup: string;
|
|
59
|
+
originalSha256: string;
|
|
60
|
+
mutatedSha256: string;
|
|
61
|
+
mode: number;
|
|
62
|
+
atimeMs: number;
|
|
63
|
+
mtimeMs: number;
|
|
64
|
+
/**
|
|
65
|
+
* Restore with the current time as mtime, not the original: a compiled
|
|
66
|
+
* source restored with its old mtime is skipped by the incremental build,
|
|
67
|
+
* which would leave the mutated code in the binary.
|
|
68
|
+
*/
|
|
69
|
+
freshMtime?: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface Journal {
|
|
72
|
+
version: 1;
|
|
73
|
+
diffFile: string;
|
|
74
|
+
appliedAt: string;
|
|
75
|
+
entries: JournalEntry[];
|
|
76
|
+
}
|
|
77
|
+
/** A control currently applied to the engine. */
|
|
78
|
+
export interface ActiveNegativeControl {
|
|
79
|
+
paths: NegativeControlPaths;
|
|
80
|
+
journal: Journal;
|
|
81
|
+
/**
|
|
82
|
+
* The one restore in flight, set synchronously by whichever path (the
|
|
83
|
+
* run's own `finally`, or the signal handler) gets there first, so the
|
|
84
|
+
* other awaits the same restore instead of racing or skipping it.
|
|
85
|
+
*/
|
|
86
|
+
restoring?: Promise<NegativeControlRestoreResult>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reads, parses and resolves a control diff, and (unless `deferApplyCheck`)
|
|
90
|
+
* proves every hunk applies to the current engine file, so a broken control
|
|
91
|
+
* is refused before any build time is spent.
|
|
92
|
+
*
|
|
93
|
+
* `deferApplyCheck` is for a run that builds first: Furnace redeploys a
|
|
94
|
+
* component's engine copy during the build, so a `components/` control can
|
|
95
|
+
* only be checked against what the build left behind.
|
|
96
|
+
*
|
|
97
|
+
* @param projectRoot - Project root; the diff path resolves against it
|
|
98
|
+
* @param engineDir - Absolute engine directory
|
|
99
|
+
* @param diffFile - The `--negative-control` argument
|
|
100
|
+
* @param options - `deferApplyCheck` skips the up-front apply check for
|
|
101
|
+
* `components/` targets
|
|
102
|
+
*/
|
|
103
|
+
export declare function planNegativeControl(projectRoot: string, engineDir: string, diffFile: string, options?: {
|
|
104
|
+
deferApplyCheck?: boolean;
|
|
105
|
+
}): Promise<NegativeControlPlan>;
|
|
106
|
+
/**
|
|
107
|
+
* Applies the control: every target is computed first (a hunk that does
|
|
108
|
+
* not apply refuses with nothing written), the originals and the journal
|
|
109
|
+
* are persisted, then the mutated bytes are written. Runs as a signal
|
|
110
|
+
* critical section, so a Ctrl+C mid-apply waits for the apply to finish
|
|
111
|
+
* and the signal path then restores a complete journal.
|
|
112
|
+
*
|
|
113
|
+
* @returns The handle {@link restoreNegativeControl} takes
|
|
114
|
+
*/
|
|
115
|
+
export declare function applyNegativeControl(paths: NegativeControlPaths, plan: NegativeControlPlan): Promise<ActiveNegativeControl>;
|
|
116
|
+
/** What happened to each file of a restored control. */
|
|
117
|
+
export interface NegativeControlRestoreResult {
|
|
118
|
+
/** Engine-relative files put back to their original bytes. */
|
|
119
|
+
restored: string[];
|
|
120
|
+
/** Engine-relative files that held neither the control's bytes nor the original. */
|
|
121
|
+
conflicts: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Restores a control applied by {@link applyNegativeControl}. Idempotent:
|
|
125
|
+
* a second caller (the signal handler racing the run's own `finally`)
|
|
126
|
+
* receives the restore already in flight.
|
|
127
|
+
*
|
|
128
|
+
* @throws GeneralError when a file changed under the control (the edit is
|
|
129
|
+
* kept and the original stays in the journal directory) or did not read
|
|
130
|
+
* back byte-exact
|
|
131
|
+
*/
|
|
132
|
+
export declare function restoreNegativeControl(control: ActiveNegativeControl): Promise<NegativeControlRestoreResult>;
|
|
133
|
+
/**
|
|
134
|
+
* Restores every applied control on the way out of a SIGINT/SIGTERM. Called
|
|
135
|
+
* by the entry point after child shutdown and before the held locks are
|
|
136
|
+
* force-released. Never throws: a failure is reported on stderr and the
|
|
137
|
+
* journal stays for the next run's recovery.
|
|
138
|
+
*/
|
|
139
|
+
export declare function restoreActiveNegativeControlsForSignal(): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Undoes a control a killed FireForge left applied. Called at the start of
|
|
142
|
+
* every `fireforge test`, under the engine-session lock. A file that still
|
|
143
|
+
* holds the control's bytes is restored; a file somebody changed since is
|
|
144
|
+
* refused rather than overwritten.
|
|
145
|
+
*
|
|
146
|
+
* @throws PreflightRefusalError (`note=negative-control-pending`) when a
|
|
147
|
+
* journal entry cannot be restored safely
|
|
148
|
+
*/
|
|
149
|
+
export declare function recoverPendingNegativeControl(paths: NegativeControlPaths): Promise<void>;
|
|
150
|
+
export {};
|