@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Where the patch history log lives, and who wrote each record.
|
|
4
|
+
*
|
|
5
|
+
* The log used to be `patches/.fireforge-history.jsonl`, inside the tracked
|
|
6
|
+
* patch queue, so it was committed with every export until consumers
|
|
7
|
+
* gitignored it, and it grew to tens of megabytes. It now defaults to
|
|
8
|
+
* `.fireforge/history.jsonl` (FireForge's own untracked data directory), or
|
|
9
|
+
* `fireforge.json#history.path` when a project sets one. A log left at the
|
|
10
|
+
* old path is moved on the first append.
|
|
11
|
+
*
|
|
12
|
+
* Several sessions sharing one checkout all append to the same log, so each
|
|
13
|
+
* record carries the invocation that wrote it, plus a session hint, letting
|
|
14
|
+
* a session select its own lines mechanically.
|
|
15
|
+
*/
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import { appendFile, link, readFile, rename, rm, stat } from 'node:fs/promises';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
20
|
+
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
21
|
+
import { info, verbose } from '../utils/logger.js';
|
|
22
|
+
import { FIREFORGE_DIR } from './config-paths.js';
|
|
23
|
+
import { isValidHistoryPath } from './config-validate-history.js';
|
|
24
|
+
/** Filename of the history log at its pre-0.50 location, inside `patches/`. */
|
|
25
|
+
export const LEGACY_HISTORY_LOG_FILENAME = '.fireforge-history.jsonl';
|
|
26
|
+
/** Default history log location, relative to the project root. */
|
|
27
|
+
export const DEFAULT_HISTORY_LOG_PATH = join(FIREFORGE_DIR, 'history.jsonl');
|
|
28
|
+
/** Environment variable a caller sets to tag every record of its session. */
|
|
29
|
+
export const HISTORY_SESSION_ENV = 'FIREFORGE_SESSION_ID';
|
|
30
|
+
let invocationId;
|
|
31
|
+
/**
|
|
32
|
+
* Identity fields stamped on every history record: one id per FireForge
|
|
33
|
+
* process (every record one command writes shares it), the process and its
|
|
34
|
+
* parent, and the caller's `FIREFORGE_SESSION_ID` when set. The parent PID
|
|
35
|
+
* is a weak hint (usually the shell); the environment variable is the
|
|
36
|
+
* reliable way for a session to tag its own lines.
|
|
37
|
+
*/
|
|
38
|
+
export function historyRecordIdentity() {
|
|
39
|
+
invocationId ??= randomUUID();
|
|
40
|
+
const session = process.env[HISTORY_SESSION_ENV]?.trim();
|
|
41
|
+
return {
|
|
42
|
+
invocationId,
|
|
43
|
+
...(session !== undefined && session !== '' ? { sessionId: session } : {}),
|
|
44
|
+
pid: process.pid,
|
|
45
|
+
ppid: process.ppid,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reads `history.path` from the project's raw `fireforge.json`.
|
|
50
|
+
*
|
|
51
|
+
* Fail-open: the writer runs after a mutation has already committed, so a
|
|
52
|
+
* missing or unreadable config means "use the default", never an error. An
|
|
53
|
+
* invalid value is refused by config validation when the command loads its
|
|
54
|
+
* config; here it also falls back to the default rather than writing
|
|
55
|
+
* somewhere validation would not allow.
|
|
56
|
+
*/
|
|
57
|
+
async function readConfiguredHistoryPath(projectRoot) {
|
|
58
|
+
let data;
|
|
59
|
+
try {
|
|
60
|
+
data = JSON.parse(await readFile(join(projectRoot, 'fireforge.json'), 'utf-8'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (typeof data !== 'object' || data === null)
|
|
66
|
+
return undefined;
|
|
67
|
+
const history = data['history'];
|
|
68
|
+
if (typeof history !== 'object' || history === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
const path = history['path'];
|
|
71
|
+
if (path === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
if (!isValidHistoryPath(path)) {
|
|
74
|
+
verbose(`Ignoring invalid history.path in fireforge.json; using ${DEFAULT_HISTORY_LOG_PATH}`);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return path;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolves the history log path for a project.
|
|
81
|
+
*
|
|
82
|
+
* @param projectRoot - Project root containing `fireforge.json`
|
|
83
|
+
* @returns Absolute path of the history log
|
|
84
|
+
*/
|
|
85
|
+
export async function resolveHistoryLogPath(projectRoot) {
|
|
86
|
+
const configured = await readConfiguredHistoryPath(projectRoot);
|
|
87
|
+
return resolve(projectRoot, configured ?? DEFAULT_HISTORY_LOG_PATH);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Moves a history log left at the legacy `patches/` path to `target`.
|
|
91
|
+
*
|
|
92
|
+
* Records are never dropped, whatever else is happening:
|
|
93
|
+
* - The legacy file is first claimed by renaming it aside, so of two
|
|
94
|
+
* commands migrating at once exactly one does the work and neither
|
|
95
|
+
* duplicates it.
|
|
96
|
+
* - When `target` does not exist yet (the first run), the claimed file is
|
|
97
|
+
* hard-linked into place, which cannot overwrite a log another command
|
|
98
|
+
* created in the meantime, and keeps a large log a constant-time move.
|
|
99
|
+
* - When both exist (an older FireForge appended to the legacy path after
|
|
100
|
+
* a migration, or `history.path` changed), the legacy records are
|
|
101
|
+
* appended to the target in one write. They then follow newer records;
|
|
102
|
+
* every record carries its own `ts`.
|
|
103
|
+
*
|
|
104
|
+
* A crash between the claim and the move leaves the records in a
|
|
105
|
+
* `.migrating-*` file next to the legacy path, never deleted.
|
|
106
|
+
*
|
|
107
|
+
* @param patchesDir - The patches directory that may hold a legacy log
|
|
108
|
+
* @param target - Absolute path of the history log
|
|
109
|
+
*/
|
|
110
|
+
export async function migrateLegacyHistoryLog(patchesDir, target) {
|
|
111
|
+
const legacy = join(patchesDir, LEGACY_HISTORY_LOG_FILENAME);
|
|
112
|
+
if (resolve(legacy) === resolve(target))
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
await stat(legacy);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const claimed = `${legacy}.migrating-${process.pid}-${randomUUID()}`;
|
|
121
|
+
try {
|
|
122
|
+
await rename(legacy, claimed);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (getNodeErrorCode(error) === 'ENOENT')
|
|
126
|
+
return;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
await ensureDir(dirname(target));
|
|
130
|
+
try {
|
|
131
|
+
await link(claimed, target);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// Windows can report two racing renames of one file as both
|
|
135
|
+
// succeeding, the file ending under the other claimant's name. That
|
|
136
|
+
// claimant holds the records and moves them, so this one stands down.
|
|
137
|
+
if (getNodeErrorCode(error) === 'ENOENT' && !(await pathExists(claimed))) {
|
|
138
|
+
verbose('History log migration lost the claim to a concurrent command');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// EEXIST: both logs exist. Anything else (a filesystem without hard
|
|
142
|
+
// links, a target on another device): the append below creates the
|
|
143
|
+
// target when it is missing, so it covers that case too.
|
|
144
|
+
verbose(`History log migration falls back to appending (${toError(error).message})`);
|
|
145
|
+
await appendFile(target, await readFile(claimed));
|
|
146
|
+
}
|
|
147
|
+
await rm(claimed, { force: true });
|
|
148
|
+
info(`Moved the patch history log from ${legacy} to ${target}. ` +
|
|
149
|
+
'Nothing reads it back; it is an audit trail only.');
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Appends one serialized record to the project's history log, migrating a
|
|
153
|
+
* legacy log first.
|
|
154
|
+
*
|
|
155
|
+
* @param patchesDir - The project's `patches/` directory; the project root is
|
|
156
|
+
* its parent, as every caller derives it from `getProjectPaths`
|
|
157
|
+
* @param line - One JSON record, without the trailing newline
|
|
158
|
+
*/
|
|
159
|
+
export async function appendHistoryLine(patchesDir, line) {
|
|
160
|
+
const target = await resolveHistoryLogPath(dirname(resolve(patchesDir)));
|
|
161
|
+
await migrateLegacyHistoryLog(patchesDir, target);
|
|
162
|
+
await ensureDir(dirname(target));
|
|
163
|
+
await appendFile(target, `${line}\n`, 'utf-8');
|
|
164
|
+
}
|
|
@@ -10,7 +10,8 @@ import type { HistoryEntry } from './destructive.js';
|
|
|
10
10
|
* reject would report a completed, irreversible mutation as a failure and send
|
|
11
11
|
* the operator looking for damage that is not there. Because that reasoning is
|
|
12
12
|
* easy to lose in a copy, it now lives in one place.
|
|
13
|
-
* @param patchesDir - The `patches/` directory
|
|
13
|
+
* @param patchesDir - The project's `patches/` directory (the log itself lives
|
|
14
|
+
* under `.fireforge/` or `history.path`; see `history-log-file.ts`).
|
|
14
15
|
* @param entry - The entry to append.
|
|
15
16
|
* @param context - What just committed, rendered into
|
|
16
17
|
* `History log append failed after <context>: <reason>`, e.g.
|
|
@@ -13,7 +13,8 @@ import { appendHistory } from './destructive.js';
|
|
|
13
13
|
* reject would report a completed, irreversible mutation as a failure and send
|
|
14
14
|
* the operator looking for damage that is not there. Because that reasoning is
|
|
15
15
|
* easy to lose in a copy, it now lives in one place.
|
|
16
|
-
* @param patchesDir - The `patches/` directory
|
|
16
|
+
* @param patchesDir - The project's `patches/` directory (the log itself lives
|
|
17
|
+
* under `.fireforge/` or `history.path`; see `history-log-file.ts`).
|
|
17
18
|
* @param entry - The entry to append.
|
|
18
19
|
* @param context - What just committed, rendered into
|
|
19
20
|
* `History log append failed after <context>: <reason>`, e.g.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format-preserving rewrites of a JSON document.
|
|
3
|
+
*
|
|
4
|
+
* `fireforge source set`, `fireforge config` and `furnace init` change a
|
|
5
|
+
* few keys of `fireforge.json`. Re-serializing the whole document with
|
|
6
|
+
* `JSON.stringify` expands every compact object and array the project keeps
|
|
7
|
+
* on one line, so a one-key change becomes a diff across unrelated policy
|
|
8
|
+
* sections. This module diffs the document on disk against the wanted one
|
|
9
|
+
* and edits only the values that differ, in place, keeping every other byte
|
|
10
|
+
* (indentation, compact containers, key order, trailing newline).
|
|
11
|
+
*
|
|
12
|
+
* Inserted values follow the style of the container they land in: one line
|
|
13
|
+
* inside a one-line container, the container's own indentation otherwise.
|
|
14
|
+
*/
|
|
15
|
+
import type { JsonObject } from '../types/json.js';
|
|
16
|
+
/**
|
|
17
|
+
* Rewrites `text` so it parses to `next`, editing only the values that
|
|
18
|
+
* differ. Returns `undefined` when `text` is not a JSON object or the edit
|
|
19
|
+
* cannot be proven equivalent, so the caller can fall back to a full
|
|
20
|
+
* re-serialization.
|
|
21
|
+
*/
|
|
22
|
+
export declare function rewriteJsonPreservingFormat(text: string, next: JsonObject): string | undefined;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Format-preserving rewrites of a JSON document.
|
|
4
|
+
*
|
|
5
|
+
* `fireforge source set`, `fireforge config` and `furnace init` change a
|
|
6
|
+
* few keys of `fireforge.json`. Re-serializing the whole document with
|
|
7
|
+
* `JSON.stringify` expands every compact object and array the project keeps
|
|
8
|
+
* on one line, so a one-key change becomes a diff across unrelated policy
|
|
9
|
+
* sections. This module diffs the document on disk against the wanted one
|
|
10
|
+
* and edits only the values that differ, in place, keeping every other byte
|
|
11
|
+
* (indentation, compact containers, key order, trailing newline).
|
|
12
|
+
*
|
|
13
|
+
* Inserted values follow the style of the container they land in: one line
|
|
14
|
+
* inside a one-line container, the container's own indentation otherwise.
|
|
15
|
+
*/
|
|
16
|
+
import { applyEdits, findNodeAtLocation, modify, parseTree } from 'jsonc-parser';
|
|
17
|
+
import { isJsonObject } from '../utils/validation.js';
|
|
18
|
+
/** The same reviver `readJson` uses, so both sides of the diff agree. */
|
|
19
|
+
function parseJsonDocument(text) {
|
|
20
|
+
return JSON.parse(text, (key, value) => key === '__proto__' || key === 'constructor' || key === 'prototype' ? undefined : value);
|
|
21
|
+
}
|
|
22
|
+
function jsonEqual(a, b) {
|
|
23
|
+
if (a === b)
|
|
24
|
+
return true;
|
|
25
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
26
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
27
|
+
return false;
|
|
28
|
+
return a.every((value, index) => jsonEqual(value, b[index]));
|
|
29
|
+
}
|
|
30
|
+
if (!isJsonObject(a) || !isJsonObject(b))
|
|
31
|
+
return false;
|
|
32
|
+
const keysA = definedKeys(a);
|
|
33
|
+
const keysB = definedKeys(b);
|
|
34
|
+
return keysA.length === keysB.length && keysA.every((key) => jsonEqual(a[key], b[key]));
|
|
35
|
+
}
|
|
36
|
+
/** Keys whose value survives `JSON.stringify` (an `undefined` value is an absent key). */
|
|
37
|
+
function definedKeys(object) {
|
|
38
|
+
return Object.keys(object).filter((key) => object[key] !== undefined);
|
|
39
|
+
}
|
|
40
|
+
function collectChanges(path, before, after, changes) {
|
|
41
|
+
if (jsonEqual(before, after))
|
|
42
|
+
return;
|
|
43
|
+
if (isJsonObject(before) && isJsonObject(after)) {
|
|
44
|
+
for (const key of definedKeys(before)) {
|
|
45
|
+
if (after[key] === undefined)
|
|
46
|
+
changes.push({ kind: 'remove', path: [...path, key] });
|
|
47
|
+
}
|
|
48
|
+
for (const key of definedKeys(after)) {
|
|
49
|
+
const next = after[key];
|
|
50
|
+
const previous = before[key];
|
|
51
|
+
if (previous === undefined) {
|
|
52
|
+
changes.push({ kind: 'set', path: [...path, key], value: next });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
collectChanges([...path, key], previous, next, changes);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
61
|
+
const isAppend = after.length > before.length &&
|
|
62
|
+
before.every((value, index) => jsonEqual(value, after[index]));
|
|
63
|
+
if (isAppend) {
|
|
64
|
+
for (const value of after.slice(before.length))
|
|
65
|
+
changes.push({ kind: 'append', path, value });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (after.length === before.length) {
|
|
69
|
+
before.forEach((value, index) => {
|
|
70
|
+
collectChanges([...path, index], value, after[index], changes);
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
changes.push({ kind: 'set', path, value: after });
|
|
76
|
+
}
|
|
77
|
+
function detectStyle(text) {
|
|
78
|
+
const indent = /^([ \t]+)\S/m.exec(text)?.[1];
|
|
79
|
+
return {
|
|
80
|
+
eol: text.includes('\r\n') ? '\r\n' : '\n',
|
|
81
|
+
indentUnit: indent === undefined ? ' ' : indent.startsWith('\t') ? '\t' : indent,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function isMultiline(text, node) {
|
|
85
|
+
return node !== undefined && text.slice(node.offset, node.offset + node.length).includes('\n');
|
|
86
|
+
}
|
|
87
|
+
/** The object or array holding `node` (skipping the property node of an object member). */
|
|
88
|
+
function containerOf(node) {
|
|
89
|
+
const parent = node.parent;
|
|
90
|
+
return parent?.type === 'property' ? parent.parent : parent;
|
|
91
|
+
}
|
|
92
|
+
/** Leading whitespace of the line `offset` sits on. */
|
|
93
|
+
function lineIndent(text, offset) {
|
|
94
|
+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
|
|
95
|
+
return /^[ \t]*/.exec(text.slice(lineStart))?.[0] ?? '';
|
|
96
|
+
}
|
|
97
|
+
function renderCompact(value) {
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
return `[${value.map(renderCompact).join(', ')}]`;
|
|
100
|
+
}
|
|
101
|
+
if (isJsonObject(value)) {
|
|
102
|
+
const entries = definedKeys(value).map((key) => `${JSON.stringify(key)}: ${renderCompact(value[key])}`);
|
|
103
|
+
return entries.length === 0 ? '{}' : `{ ${entries.join(', ')} }`;
|
|
104
|
+
}
|
|
105
|
+
return JSON.stringify(value);
|
|
106
|
+
}
|
|
107
|
+
function renderValue(value, multiline, indent, style) {
|
|
108
|
+
if (!multiline)
|
|
109
|
+
return renderCompact(value);
|
|
110
|
+
return JSON.stringify(value, null, style.indentUnit)
|
|
111
|
+
.split('\n')
|
|
112
|
+
.join(style.eol + indent);
|
|
113
|
+
}
|
|
114
|
+
function replaceRange(text, offset, length, content) {
|
|
115
|
+
return text.slice(0, offset) + content + text.slice(offset + length);
|
|
116
|
+
}
|
|
117
|
+
/** Replaces an existing value, keeping a container's one-line or multi-line shape. */
|
|
118
|
+
function replaceNode(text, node, value, style) {
|
|
119
|
+
const isContainer = node.type === 'object' || node.type === 'array';
|
|
120
|
+
const multiline = isContainer ? isMultiline(text, node) : isMultiline(text, containerOf(node));
|
|
121
|
+
const rendered = renderValue(value, multiline, lineIndent(text, node.offset), style);
|
|
122
|
+
return replaceRange(text, node.offset, node.length, rendered);
|
|
123
|
+
}
|
|
124
|
+
/** Appends `entry` (a property or an array element) after the container's last child. */
|
|
125
|
+
function appendEntry(text, container, renderEntry, style) {
|
|
126
|
+
const children = container.children ?? [];
|
|
127
|
+
const last = children[children.length - 1];
|
|
128
|
+
const containerIndent = lineIndent(text, container.offset);
|
|
129
|
+
if (last === undefined) {
|
|
130
|
+
// Empty container: open it up the way its parent is laid out.
|
|
131
|
+
const multiline = isMultiline(text, containerOf(container));
|
|
132
|
+
const [open, close] = container.type === 'array' ? ['[', ']'] : ['{', '}'];
|
|
133
|
+
const childIndent = containerIndent + style.indentUnit;
|
|
134
|
+
const body = multiline
|
|
135
|
+
? `${open}${style.eol}${childIndent}${renderEntry(true, childIndent)}${style.eol}${containerIndent}${close}`
|
|
136
|
+
: container.type === 'array'
|
|
137
|
+
? `${open}${renderEntry(false, childIndent)}${close}`
|
|
138
|
+
: `${open} ${renderEntry(false, childIndent)} ${close}`;
|
|
139
|
+
return replaceRange(text, container.offset, container.length, body);
|
|
140
|
+
}
|
|
141
|
+
const multiline = isMultiline(text, container);
|
|
142
|
+
const indent = multiline ? lineIndent(text, last.offset) : containerIndent;
|
|
143
|
+
const separator = multiline ? `,${style.eol}${indent}` : ', ';
|
|
144
|
+
return replaceRange(text, last.offset + last.length, 0, separator + renderEntry(multiline, indent));
|
|
145
|
+
}
|
|
146
|
+
function applyChange(text, change, style) {
|
|
147
|
+
const root = parseTree(text);
|
|
148
|
+
if (root === undefined) {
|
|
149
|
+
throw new Error('document is not valid JSON');
|
|
150
|
+
}
|
|
151
|
+
if (change.kind === 'remove') {
|
|
152
|
+
return applyEdits(text, modify(text, change.path, undefined, {}));
|
|
153
|
+
}
|
|
154
|
+
const target = findNodeAtLocation(root, change.path);
|
|
155
|
+
if (change.kind === 'append') {
|
|
156
|
+
if (target?.type !== 'array')
|
|
157
|
+
throw new Error('append target is not an array');
|
|
158
|
+
return appendEntry(text, target, (multiline, indent) => renderValue(change.value, multiline, indent, style), style);
|
|
159
|
+
}
|
|
160
|
+
if (target !== undefined) {
|
|
161
|
+
return replaceNode(text, target, change.value, style);
|
|
162
|
+
}
|
|
163
|
+
const parent = findNodeAtLocation(root, change.path.slice(0, -1));
|
|
164
|
+
const key = change.path[change.path.length - 1];
|
|
165
|
+
if (parent?.type !== 'object' || typeof key !== 'string') {
|
|
166
|
+
throw new Error('insertion parent is not an object');
|
|
167
|
+
}
|
|
168
|
+
return appendEntry(text, parent, (multiline, indent) => `${JSON.stringify(key)}: ${renderValue(change.value, multiline, indent, style)}`, style);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Rewrites `text` so it parses to `next`, editing only the values that
|
|
172
|
+
* differ. Returns `undefined` when `text` is not a JSON object or the edit
|
|
173
|
+
* cannot be proven equivalent, so the caller can fall back to a full
|
|
174
|
+
* re-serialization.
|
|
175
|
+
*/
|
|
176
|
+
export function rewriteJsonPreservingFormat(text, next) {
|
|
177
|
+
let current;
|
|
178
|
+
try {
|
|
179
|
+
current = parseJsonDocument(text);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
if (!isJsonObject(current))
|
|
185
|
+
return undefined;
|
|
186
|
+
const changes = [];
|
|
187
|
+
collectChanges([], current, next, changes);
|
|
188
|
+
const style = detectStyle(text);
|
|
189
|
+
try {
|
|
190
|
+
let edited = text;
|
|
191
|
+
for (const change of changes)
|
|
192
|
+
edited = applyChange(edited, change, style);
|
|
193
|
+
// The edits are structural; prove the result before trusting it.
|
|
194
|
+
return jsonEqual(parseJsonDocument(edited), next) ? edited : undefined;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prefixes every line of a chunked stream, for output from several
|
|
3
|
+
* concurrent harness instances (`fireforge test --jobs`) that share one
|
|
4
|
+
* terminal and one run log. Without a prefix, interleaved lines from two
|
|
5
|
+
* browsers cannot be attributed after the fact.
|
|
6
|
+
*/
|
|
7
|
+
/** A stateful line prefixer for one stream. */
|
|
8
|
+
export interface LinePrefixer {
|
|
9
|
+
/** Returns `chunk` with the prefix inserted at the start of each line. */
|
|
10
|
+
transform(chunk: string): string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a prefixer. Line state carries across chunks, so a line split
|
|
14
|
+
* between two chunks gets the prefix exactly once.
|
|
15
|
+
*
|
|
16
|
+
* @param prefix - Text to insert, e.g. `[job 2] `
|
|
17
|
+
*/
|
|
18
|
+
export declare function createLinePrefixer(prefix: string): LinePrefixer;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Prefixes every line of a chunked stream, for output from several
|
|
4
|
+
* concurrent harness instances (`fireforge test --jobs`) that share one
|
|
5
|
+
* terminal and one run log. Without a prefix, interleaved lines from two
|
|
6
|
+
* browsers cannot be attributed after the fact.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Creates a prefixer. Line state carries across chunks, so a line split
|
|
10
|
+
* between two chunks gets the prefix exactly once.
|
|
11
|
+
*
|
|
12
|
+
* @param prefix - Text to insert, e.g. `[job 2] `
|
|
13
|
+
*/
|
|
14
|
+
export function createLinePrefixer(prefix) {
|
|
15
|
+
let atLineStart = true;
|
|
16
|
+
return {
|
|
17
|
+
transform(chunk) {
|
|
18
|
+
if (chunk.length === 0)
|
|
19
|
+
return chunk;
|
|
20
|
+
let out = '';
|
|
21
|
+
let start = 0;
|
|
22
|
+
for (let index = chunk.indexOf('\n'); index !== -1; index = chunk.indexOf('\n', start)) {
|
|
23
|
+
out += `${atLineStart ? prefix : ''}${chunk.slice(start, index + 1)}`;
|
|
24
|
+
atLineStart = true;
|
|
25
|
+
start = index + 1;
|
|
26
|
+
}
|
|
27
|
+
if (start < chunk.length) {
|
|
28
|
+
out += `${atLineStart ? prefix : ''}${chunk.slice(start)}`;
|
|
29
|
+
atLineStart = false;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/src/core/mach.d.ts
CHANGED
|
@@ -47,6 +47,11 @@ export interface MachOptions {
|
|
|
47
47
|
onProcessGroup?: (pgid: number) => void;
|
|
48
48
|
/** Exec-layer hook, see {@link ProcessGroupOptions.postCloseSweep}. Capture-only. */
|
|
49
49
|
postCloseSweep?: () => Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Prefix for every echoed and logged line (concurrent `test --jobs`
|
|
52
|
+
* instances). The capture and `onOutputChunk` stay raw. Capture-only.
|
|
53
|
+
*/
|
|
54
|
+
outputPrefix?: string | undefined;
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* Result of running a mach command while capturing streamed output.
|
|
@@ -263,6 +268,8 @@ export interface MachTestSuiteOptions {
|
|
|
263
268
|
* the harness launched. See {@link MachOptions}.
|
|
264
269
|
*/
|
|
265
270
|
teardown?: Pick<MachOptions, 'onOutputChunk' | 'onProcessGroup' | 'postCloseSweep'> | undefined;
|
|
271
|
+
/** Line prefix for echo and run log, see {@link MachOptions.outputPrefix}. */
|
|
272
|
+
outputPrefix?: string | undefined;
|
|
266
273
|
}
|
|
267
274
|
/**
|
|
268
275
|
* Runs one mach test command (`mach test` / `mach xpcshell-test` / `mach
|
package/dist/src/core/mach.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
3
|
import { MachNotFoundError } from '../errors/build.js';
|
|
4
4
|
import { pathExists } from '../utils/fs.js';
|
|
5
5
|
import { info, warn } from '../utils/logger.js';
|
|
6
6
|
import { exec, execInherit, execInheritCapture, execSmokeRun, execStream, } from '../utils/process.js';
|
|
7
|
+
import { withBuildRunRecord } from './build-run-record.js';
|
|
7
8
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
8
9
|
import { ensureFirefoxIgnorefileCompatibility } from './firefox-ignorefile.js';
|
|
10
|
+
import { createLinePrefixer } from './line-prefixer.js';
|
|
9
11
|
import { explainMachError } from './mach-error-hints.js';
|
|
10
12
|
import { createKnownTeardownNoiseFilter, createTeardownNoiseContext, hasKnownTeardownNoise, KNOWN_TEARDOWN_NOISE_BUILD_NOTE, } from './mach-known-noise-filter.js';
|
|
11
13
|
import { getPython } from './mach-python.js';
|
|
@@ -88,6 +90,15 @@ export async function runMachCapture(args, engineDir, options = {}) {
|
|
|
88
90
|
const noiseContext = options.annotateKnownTeardownNoise === true ? createTeardownNoiseContext() : undefined;
|
|
89
91
|
const stdoutFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
|
|
90
92
|
const stderrFilter = noiseContext && createKnownTeardownNoiseFilter(noiseContext);
|
|
93
|
+
const prefix = options.outputPrefix;
|
|
94
|
+
const prefixers = prefix === undefined
|
|
95
|
+
? undefined
|
|
96
|
+
: {
|
|
97
|
+
stdoutLog: createLinePrefixer(prefix),
|
|
98
|
+
stdoutEcho: createLinePrefixer(prefix),
|
|
99
|
+
stderrLog: createLinePrefixer(prefix),
|
|
100
|
+
stderrEcho: createLinePrefixer(prefix),
|
|
101
|
+
};
|
|
91
102
|
const exitCode = await execStream(python, [machPath, ...args], {
|
|
92
103
|
cwd: engineDir,
|
|
93
104
|
...(options.env ? { env: options.env } : {}),
|
|
@@ -109,8 +120,9 @@ export async function runMachCapture(args, engineDir, options = {}) {
|
|
|
109
120
|
// echo filter only exists to shorten what a human scrolls past.
|
|
110
121
|
// Collapsing a traceback in the artifact would reintroduce the loss
|
|
111
122
|
// this log exists to prevent.
|
|
112
|
-
writeToActiveRunLog(data);
|
|
113
|
-
|
|
123
|
+
writeToActiveRunLog(prefixers ? prefixers.stdoutLog.transform(data) : data);
|
|
124
|
+
const echo = stdoutFilter ? stdoutFilter.transform(data) : data;
|
|
125
|
+
process.stdout.write(prefixers ? prefixers.stdoutEcho.transform(echo) : echo);
|
|
114
126
|
},
|
|
115
127
|
onStderr: (data) => {
|
|
116
128
|
options.onOutputChunk?.('stderr', data);
|
|
@@ -118,19 +130,22 @@ export async function runMachCapture(args, engineDir, options = {}) {
|
|
|
118
130
|
if (stderr.length > CAPTURE_TAIL_LIMIT) {
|
|
119
131
|
stderr = stderr.slice(-CAPTURE_TAIL_LIMIT);
|
|
120
132
|
}
|
|
121
|
-
writeToActiveRunLog(data);
|
|
122
|
-
|
|
133
|
+
writeToActiveRunLog(prefixers ? prefixers.stderrLog.transform(data) : data);
|
|
134
|
+
const echo = stderrFilter ? stderrFilter.transform(data) : data;
|
|
135
|
+
process.stderr.write(prefixers ? prefixers.stderrEcho.transform(echo) : echo);
|
|
123
136
|
},
|
|
124
137
|
});
|
|
125
138
|
if (stdoutFilter) {
|
|
126
139
|
const residue = stdoutFilter.flush();
|
|
127
|
-
if (residue.length > 0)
|
|
128
|
-
process.stdout.write(residue);
|
|
140
|
+
if (residue.length > 0) {
|
|
141
|
+
process.stdout.write(prefixers ? prefixers.stdoutEcho.transform(residue) : residue);
|
|
142
|
+
}
|
|
129
143
|
}
|
|
130
144
|
if (stderrFilter) {
|
|
131
145
|
const residue = stderrFilter.flush();
|
|
132
|
-
if (residue.length > 0)
|
|
133
|
-
process.stderr.write(residue);
|
|
146
|
+
if (residue.length > 0) {
|
|
147
|
+
process.stderr.write(prefixers ? prefixers.stderrEcho.transform(residue) : residue);
|
|
148
|
+
}
|
|
134
149
|
}
|
|
135
150
|
return { stdout, stderr, exitCode };
|
|
136
151
|
}
|
|
@@ -227,6 +242,12 @@ export async function runProtectedMachBuild(kind, engineDir, options = {}) {
|
|
|
227
242
|
if (kind === 'full' && options.jobs !== undefined) {
|
|
228
243
|
args.push('-j', String(options.jobs));
|
|
229
244
|
}
|
|
245
|
+
// The run record lets any session see why a bundle is missing after this
|
|
246
|
+
// build dies part-way (see build-run-record.ts). The engine directory is
|
|
247
|
+
// always `<projectRoot>/engine`.
|
|
248
|
+
return withBuildRunRecord(dirname(engineDir), `mach ${args.join(' ')}`, () => runProtectedMachBuildAttempts(args, kind, engineDir, options));
|
|
249
|
+
}
|
|
250
|
+
async function runProtectedMachBuildAttempts(args, kind, engineDir, options) {
|
|
230
251
|
const maxAttempts = Math.max(1, (options.retries ?? DEFAULT_BUILD_CRASH_RETRIES) + 1);
|
|
231
252
|
for (let attempt = 1;; attempt += 1) {
|
|
232
253
|
const { env } = await installMachResourceGuard(engineDir);
|
|
@@ -411,13 +432,14 @@ function testVerbosityEnvUnset(fullOutput) {
|
|
|
411
432
|
* @param options - Engine checkout, test paths, extra args, env, verbosity
|
|
412
433
|
*/
|
|
413
434
|
export async function runMachTestSuite(kind, options) {
|
|
414
|
-
const { engineDir, testPaths = [], args = [], env, fullOutput, teardown } = options;
|
|
435
|
+
const { engineDir, testPaths = [], args = [], env, fullOutput, teardown, outputPrefix } = options;
|
|
415
436
|
const guard = await installMachResourceGuard(engineDir);
|
|
416
437
|
const envUnset = testVerbosityEnvUnset(fullOutput);
|
|
417
438
|
return runMachCapture([kind, ...testPaths, ...args], engineDir, {
|
|
418
439
|
env: { ...guard.env, ...env },
|
|
419
440
|
...(envUnset ? { envUnset } : {}),
|
|
420
441
|
...teardown,
|
|
442
|
+
...(outputPrefix === undefined ? {} : { outputPrefix }),
|
|
421
443
|
annotateKnownTeardownNoise: true,
|
|
422
444
|
});
|
|
423
445
|
}
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
* no owned file is an upstream Firefox module, which FireForge does not
|
|
19
19
|
* police and cannot cheaply enumerate. Policing only what the fork owns is
|
|
20
20
|
* both the actionable set and the one that produces real incidents.
|
|
21
|
+
*
|
|
22
|
+
* An unscoped `verify` widens that set with the engine worktree: changed
|
|
23
|
+
* files import, and untracked files are targets. A chrome script's lazy
|
|
24
|
+
* getter for a new, not yet exported `.sys.mjs` with no moz.build line
|
|
25
|
+
* SIGSEGVed every start of a shared objdir, and neither file was in the
|
|
26
|
+
* queue yet, so the queue-only scan could not see it.
|
|
21
27
|
*/
|
|
22
28
|
/** Why a queue-owned module import does not resolve to a packaged module. */
|
|
23
29
|
export type UnresolvedModuleReason = 'missing-file' | 'unregistered';
|
|
@@ -31,16 +37,26 @@ export interface UnresolvedModuleImport {
|
|
|
31
37
|
importers: string[];
|
|
32
38
|
reason: UnresolvedModuleReason;
|
|
33
39
|
}
|
|
40
|
+
/** Engine worktree files the preflight also judges, beside the queue's. */
|
|
41
|
+
export interface WorktreeModuleFiles {
|
|
42
|
+
/** Changed or untracked files: scanned for imports. */
|
|
43
|
+
changed: readonly string[];
|
|
44
|
+
/** Untracked files: new modules the fork has not exported yet. */
|
|
45
|
+
untracked: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/** Lists the engine's changed and untracked files for the worktree scan. */
|
|
48
|
+
export declare function collectWorktreeModuleFiles(engineDir: string): Promise<WorktreeModuleFiles>;
|
|
34
49
|
/**
|
|
35
50
|
* Resolves every `resource://` system-module import made by the
|
|
36
|
-
* queue-owned files and
|
|
37
|
-
* load.
|
|
51
|
+
* queue-owned files (and, given `worktree`, by changed engine files) and
|
|
52
|
+
* reports the ones a packaged browser could not load.
|
|
38
53
|
*
|
|
39
54
|
* @param engineDir - Path to the engine checkout
|
|
40
55
|
* @param ownedFiles - Engine-relative paths the patch queue owns
|
|
56
|
+
* @param worktree - Changed and untracked engine files to judge as well
|
|
41
57
|
* @returns Findings sorted by module path. Empty when everything resolves
|
|
42
58
|
*/
|
|
43
|
-
export declare function findUnresolvedSystemModuleImports(engineDir: string, ownedFiles: readonly string[]): Promise<UnresolvedModuleImport[]>;
|
|
59
|
+
export declare function findUnresolvedSystemModuleImports(engineDir: string, ownedFiles: readonly string[], worktree?: WorktreeModuleFiles): Promise<UnresolvedModuleImport[]>;
|
|
44
60
|
/**
|
|
45
61
|
* Renders findings as operator-facing lines, naming the unregistered or
|
|
46
62
|
* unresolvable module, so the failure is a sentence instead of a bare
|