@hominis/fireforge 0.49.2 → 0.50.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 +40 -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 +28 -0
- package/dist/src/commands/check-report.js +78 -0
- package/dist/src/commands/check-run.d.ts +28 -0
- package/dist/src/commands/check-run.js +104 -0
- package/dist/src/commands/check-steps.d.ts +67 -0
- package/dist/src/commands/check-steps.js +175 -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/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-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 +93 -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 +127 -114
- package/dist/src/commands/furnace/validation-output.js +14 -3
- package/dist/src/commands/lint-per-patch.js +31 -60
- package/dist/src/commands/lint-report.d.ts +8 -4
- package/dist/src/commands/lint-report.js +15 -5
- package/dist/src/commands/lint.js +15 -34
- package/dist/src/commands/manifest.js +2 -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 +13 -98
- package/dist/src/commands/patch/index.d.ts +1 -1
- package/dist/src/commands/patch/index.js +4 -2
- package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
- package/dist/src/commands/patch/lint-ignore.js +65 -28
- package/dist/src/commands/patch/move-files-create.js +26 -17
- package/dist/src/commands/patch/move-files-into.js +5 -3
- package/dist/src/commands/patch/move-files.js +3 -1
- 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 +7 -0
- package/dist/src/commands/patch/retire.d.ts +34 -0
- package/dist/src/commands/patch/retire.js +128 -0
- package/dist/src/commands/patch/split-plan.d.ts +7 -7
- package/dist/src/commands/patch/split-plan.js +45 -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 +51 -15
- 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.js +14 -41
- 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 +45 -2
- package/dist/src/commands/re-export-adjacent.js +156 -37
- 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-lint.d.ts +2 -1
- package/dist/src/commands/re-export-lint.js +23 -10
- package/dist/src/commands/re-export-options.js +45 -4
- 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-register.js +42 -17
- package/dist/src/commands/re-export-removed.d.ts +12 -1
- package/dist/src/commands/re-export-removed.js +41 -11
- 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 +17 -9
- package/dist/src/commands/re-export-scan.js +56 -23
- package/dist/src/commands/re-export-single.js +22 -6
- 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 +37 -24
- 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 +21 -0
- package/dist/src/commands/test-build-furnace.js +48 -0
- package/dist/src/commands/test-build-phase.js +47 -41
- package/dist/src/commands/test-checkjs.js +7 -0
- package/dist/src/commands/test-diagnose.js +7 -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 +43 -0
- package/dist/src/commands/test-orchestrate.js +222 -0
- package/dist/src/commands/test-perf-scope.d.ts +29 -0
- package/dist/src/commands/test-perf-scope.js +83 -0
- package/dist/src/commands/test-prelock.d.ts +51 -0
- package/dist/src/commands/test-prelock.js +177 -0
- package/dist/src/commands/test-register.js +54 -10
- package/dist/src/commands/test-run.js +4 -1
- package/dist/src/commands/test-stale-gate.js +57 -27
- package/dist/src/commands/test-verdict.d.ts +10 -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 +66 -60
- package/dist/src/commands/token-lint.d.ts +14 -0
- package/dist/src/commands/token-lint.js +111 -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 +59 -35
- 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 +7 -0
- package/dist/src/core/build-prepare.js +14 -8
- 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 +8 -0
- package/dist/src/core/config-validate-check.d.ts +8 -0
- package/dist/src/core/config-validate-check.js +143 -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 +7 -0
- package/dist/src/core/config-validate-token-scales.d.ts +6 -0
- package/dist/src/core/config-validate-token-scales.js +50 -0
- package/dist/src/core/config-validate.js +14 -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 +97 -0
- package/dist/src/core/coverage-records.js +350 -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 +10 -1
- package/dist/src/core/engine-session-lock.js +28 -8
- 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.d.ts +4 -25
- package/dist/src/core/file-lock.js +105 -138
- 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 +12 -2
- 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 +9 -0
- package/dist/src/core/furnace-config-validate-exceptions.js +71 -0
- package/dist/src/core/furnace-config-validate.js +8 -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-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 +4 -2
- package/dist/src/core/furnace-validate-accessibility.js +10 -1
- package/dist/src/core/furnace-validate-exceptions.d.ts +40 -0
- package/dist/src/core/furnace-validate-exceptions.js +43 -0
- package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
- package/dist/src/core/furnace-validate-icon-button.js +49 -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 +157 -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/mach.js +8 -1
- package/dist/src/core/negative-control-diff.d.ts +39 -0
- package/dist/src/core/negative-control-diff.js +136 -0
- package/dist/src/core/negative-control.d.ts +138 -0
- package/dist/src/core/negative-control.js +356 -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/patch-export-update.d.ts +2 -0
- package/dist/src/core/patch-export-update.js +1 -0
- package/dist/src/core/patch-export.js +3 -4
- package/dist/src/core/patch-lint-checkjs-any-import.d.ts +37 -0
- package/dist/src/core/patch-lint-checkjs-any-import.js +61 -0
- package/dist/src/core/patch-lint-checkjs.js +8 -11
- 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.js +12 -4
- package/dist/src/core/patch-lint-css.d.ts +8 -2
- package/dist/src/core/patch-lint-css.js +10 -4
- 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 +18 -0
- package/dist/src/core/patch-lint-forward-registration-extended.js +35 -10
- package/dist/src/core/patch-lint-forward-registration.js +10 -0
- package/dist/src/core/patch-lint-ignore.d.ts +152 -0
- package/dist/src/core/patch-lint-ignore.js +299 -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 +323 -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 +39 -0
- package/dist/src/core/patch-lint-token-scale.js +109 -0
- package/dist/src/core/patch-lint.d.ts +11 -83
- package/dist/src/core/patch-lint.js +64 -286
- 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 +19 -8
- package/dist/src/core/patch-manifest-validate.js +34 -2
- package/dist/src/core/patch-order-legal.d.ts +25 -0
- package/dist/src/core/patch-order-legal.js +83 -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 +16 -3
- package/dist/src/core/patch-policy.js +16 -9
- package/dist/src/core/patch-retired.d.ts +35 -0
- package/dist/src/core/patch-retired.js +54 -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 +20 -0
- package/dist/src/core/run-log.js +89 -3
- 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 +18 -1
- package/dist/src/core/test-harness-crash.js +6 -4
- package/dist/src/core/test-harness-signature.d.ts +17 -0
- package/dist/src/core/test-harness-signature.js +4 -0
- package/dist/src/core/test-run-completeness.d.ts +57 -0
- package/dist/src/core/test-run-completeness.js +144 -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/token-lint.d.ts +50 -0
- package/dist/src/core/token-lint.js +126 -0
- package/dist/src/core/tree-guard.js +3 -0
- package/dist/src/core/typecheck-shim.d.ts +1 -1
- package/dist/src/core/typecheck-shim.js +90 -2
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +3 -1
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +148 -21
- package/dist/src/types/commands/patches.d.ts +75 -1
- package/dist/src/types/config.d.ts +192 -0
- package/dist/src/types/config.js +9 -0
- package/dist/src/types/furnace.d.ts +43 -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 +34 -6
- package/dist/src/utils/options.js +86 -5
- 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 +282 -9
- package/docs/exit-codes.md +18 -1
- package/docs/furnace.md +97 -3
- package/docs/lifecycle-invariants.md +52 -19
- package/docs/machine-output.md +74 -12
- package/docs/patch-workflow.md +297 -22
- package/docs/run-logs.md +20 -1
- package/docs/testing.md +276 -20
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Retired order slots: the placeholders `patch retire` leaves in
|
|
4
|
+
* `patches.json#retired` so a patch can leave the queue without opening a
|
|
5
|
+
* numeric gap or renumbering its successors.
|
|
6
|
+
*
|
|
7
|
+
* A slot is not a patch. It has no `.patch` file and no row in `patches`,
|
|
8
|
+
* so everything that applies, exports, lints or rebases the queue walks past
|
|
9
|
+
* it without knowing it exists. The one reader is the
|
|
10
|
+
* `patchPolicy.allowGaps: false` gap check, which counts a retired order as
|
|
11
|
+
* occupied. A patch that later takes the same order fills the slot, and
|
|
12
|
+
* {@link carryRetiredSlots} drops it on that write, so the manifest never
|
|
13
|
+
* holds a slot and a patch at one order.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The retired slots of `current` that no patch in `patches` occupies, or
|
|
17
|
+
* undefined when none remain. Every manifest write that rebuilds the
|
|
18
|
+
* `patches` list routes the slots through here, so a slot survives the
|
|
19
|
+
* rewrite and a filled one does not.
|
|
20
|
+
*
|
|
21
|
+
* @param current - Manifest the slots come from (null or slot-free: none)
|
|
22
|
+
* @param patches - The rows the written manifest will hold
|
|
23
|
+
*/
|
|
24
|
+
export function carryRetiredSlots(current, patches) {
|
|
25
|
+
const slots = current?.retired;
|
|
26
|
+
if (slots === undefined || slots.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
const occupied = new Set(patches.map((patch) => patch.order));
|
|
29
|
+
const kept = slots
|
|
30
|
+
.filter((slot) => !occupied.has(slot.order))
|
|
31
|
+
.map((slot) => ({ ...slot }))
|
|
32
|
+
.sort((a, b) => a.order - b.order);
|
|
33
|
+
return kept.length > 0 ? kept : undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds a manifest from `patches` that keeps `current`'s unfilled retired
|
|
37
|
+
* slots. The `retired` key is omitted entirely when no slot remains, so a
|
|
38
|
+
* queue that never retired anything writes the same JSON as before.
|
|
39
|
+
*
|
|
40
|
+
* @param current - Manifest the slots come from
|
|
41
|
+
* @param patches - The rows the written manifest will hold
|
|
42
|
+
*/
|
|
43
|
+
export function manifestWithRetiredSlots(current, patches) {
|
|
44
|
+
const retired = carryRetiredSlots(current, patches);
|
|
45
|
+
return {
|
|
46
|
+
version: 1,
|
|
47
|
+
patches,
|
|
48
|
+
...(retired !== undefined ? { retired } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The set of order numbers held by retired slots. */
|
|
52
|
+
export function retiredOrderSet(manifest) {
|
|
53
|
+
return new Set((manifest.retired ?? []).map((slot) => slot.order));
|
|
54
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PatchesManifest, PatchMetadata } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a `--patches <name…>` subset against the manifest, matching each
|
|
4
|
+
* requested name tolerantly (exact filename, filename ± `.patch`, the bare
|
|
5
|
+
* order number, the manifest `name` field, or the slug). Accepts repeated
|
|
6
|
+
* values and comma lists. Throws listing the available patches when a
|
|
7
|
+
* requested name matches none, so a typo fails loud rather than silently
|
|
8
|
+
* judging nothing.
|
|
9
|
+
*
|
|
10
|
+
* @param manifest - The loaded patch manifest
|
|
11
|
+
* @param requested - Raw `--patches` values (each may be a comma list)
|
|
12
|
+
* @param refusalHint - Sentence inserted into the no-match refusal, naming
|
|
13
|
+
* what the caller's flag selects
|
|
14
|
+
* @returns The matching manifest rows, de-duplicated, in request order
|
|
15
|
+
*/
|
|
16
|
+
export declare function selectPatchSubset(manifest: PatchesManifest, requested: readonly string[], refusalHint?: string): PatchMetadata[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* The `--patches <name…>` subset resolver shared by `lint --per-patch`,
|
|
4
|
+
* `status` and `verify`. One matcher, so a name that selects a patch in
|
|
5
|
+
* one gate selects the same patch in the others.
|
|
6
|
+
*/
|
|
7
|
+
import { GeneralError } from '../errors/base.js';
|
|
8
|
+
/** Every spelling an operator may use for one patch. */
|
|
9
|
+
function patchAliases(p) {
|
|
10
|
+
const aliases = new Set([p.filename, p.filename.replace(/\.patch$/, ''), p.name]);
|
|
11
|
+
// The bare order number, both as the manifest records it and as the
|
|
12
|
+
// filename spells it (`2` and `002`). The refusal message offers stems,
|
|
13
|
+
// and an operator reading `102-ui-canvas-tiles.patch` in a lint finding
|
|
14
|
+
// reaches for `102` first.
|
|
15
|
+
aliases.add(String(p.order));
|
|
16
|
+
const match = /^(\d+)-([a-z]+)-(.+)\.patch$/.exec(p.filename);
|
|
17
|
+
if (match?.[1])
|
|
18
|
+
aliases.add(match[1]);
|
|
19
|
+
if (match?.[2] && match[3]) {
|
|
20
|
+
aliases.add(`${match[2]}-${match[3]}`);
|
|
21
|
+
aliases.add(match[3]);
|
|
22
|
+
}
|
|
23
|
+
return aliases;
|
|
24
|
+
}
|
|
25
|
+
function matchesPatch(p, name) {
|
|
26
|
+
const aliases = patchAliases(p);
|
|
27
|
+
const stem = name.replace(/\.patch$/, '');
|
|
28
|
+
return aliases.has(name) || aliases.has(stem) || aliases.has(`${stem}.patch`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves a `--patches <name…>` subset against the manifest, matching each
|
|
32
|
+
* requested name tolerantly (exact filename, filename ± `.patch`, the bare
|
|
33
|
+
* order number, the manifest `name` field, or the slug). Accepts repeated
|
|
34
|
+
* values and comma lists. Throws listing the available patches when a
|
|
35
|
+
* requested name matches none, so a typo fails loud rather than silently
|
|
36
|
+
* judging nothing.
|
|
37
|
+
*
|
|
38
|
+
* @param manifest - The loaded patch manifest
|
|
39
|
+
* @param requested - Raw `--patches` values (each may be a comma list)
|
|
40
|
+
* @param refusalHint - Sentence inserted into the no-match refusal, naming
|
|
41
|
+
* what the caller's flag selects
|
|
42
|
+
* @returns The matching manifest rows, de-duplicated, in request order
|
|
43
|
+
*/
|
|
44
|
+
export function selectPatchSubset(manifest, requested, refusalHint = '') {
|
|
45
|
+
const normalizedRequests = requested
|
|
46
|
+
.flatMap((entry) => entry.split(','))
|
|
47
|
+
.map((entry) => entry.trim())
|
|
48
|
+
.filter((entry) => entry.length > 0);
|
|
49
|
+
const selected = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
for (const name of normalizedRequests) {
|
|
52
|
+
const found = manifest.patches.filter((p) => matchesPatch(p, name));
|
|
53
|
+
if (found.length === 0) {
|
|
54
|
+
const available = manifest.patches.map((p) => p.filename).join(', ');
|
|
55
|
+
throw new GeneralError(`No patch in the queue matches "${name}".${refusalHint ? ` ${refusalHint}` : ''} ` +
|
|
56
|
+
`Available patches: ${available}`);
|
|
57
|
+
}
|
|
58
|
+
for (const p of found) {
|
|
59
|
+
if (!seen.has(p.filename)) {
|
|
60
|
+
seen.add(p.filename);
|
|
61
|
+
selected.push(p);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return selected;
|
|
66
|
+
}
|
|
@@ -1,2 +1,22 @@
|
|
|
1
1
|
/** Waits for a busy browser or port without changing or terminating its owner. */
|
|
2
2
|
export declare function waitForPreflight(action: () => Promise<void>, seconds?: number): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Spends a `--wait-browser`/`--wait-port` budget BEFORE the engine session
|
|
5
|
+
* lock is taken, so a run queued behind a busy browser or port does not
|
|
6
|
+
* hold the lock (and every peer's build, test and re-export behind it)
|
|
7
|
+
* while it does nothing. The caller re-checks once under the lock.
|
|
8
|
+
*
|
|
9
|
+
* Never refuses on its own: when the budget runs out the run still joins
|
|
10
|
+
* the lock queue, and the single check under the lock decides. A peer's
|
|
11
|
+
* test run that holds the lock legitimately keeps its browser alive until
|
|
12
|
+
* it releases, and refusing here would turn that ordinary queue into a
|
|
13
|
+
* refusal the old in-lock wait never produced. Never terminates anything
|
|
14
|
+
* either: nothing is owned before the lock is won. A refusal whose note is
|
|
15
|
+
* in `clearNotes` (a stale browser the run is allowed to reap under the
|
|
16
|
+
* lock) counts as clear.
|
|
17
|
+
*
|
|
18
|
+
* @param action - The preflight probe, which throws `PreflightRefusalError` while busy
|
|
19
|
+
* @param seconds - The wait budget
|
|
20
|
+
* @param clearNotes - Refusal classes that do not need waiting out
|
|
21
|
+
*/
|
|
22
|
+
export declare function waitBeforeLock(action: () => Promise<void>, seconds: number | undefined, clearNotes?: readonly string[]): Promise<void>;
|
|
@@ -22,3 +22,41 @@ export async function waitForPreflight(action, seconds) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Spends a `--wait-browser`/`--wait-port` budget BEFORE the engine session
|
|
27
|
+
* lock is taken, so a run queued behind a busy browser or port does not
|
|
28
|
+
* hold the lock (and every peer's build, test and re-export behind it)
|
|
29
|
+
* while it does nothing. The caller re-checks once under the lock.
|
|
30
|
+
*
|
|
31
|
+
* Never refuses on its own: when the budget runs out the run still joins
|
|
32
|
+
* the lock queue, and the single check under the lock decides. A peer's
|
|
33
|
+
* test run that holds the lock legitimately keeps its browser alive until
|
|
34
|
+
* it releases, and refusing here would turn that ordinary queue into a
|
|
35
|
+
* refusal the old in-lock wait never produced. Never terminates anything
|
|
36
|
+
* either: nothing is owned before the lock is won. A refusal whose note is
|
|
37
|
+
* in `clearNotes` (a stale browser the run is allowed to reap under the
|
|
38
|
+
* lock) counts as clear.
|
|
39
|
+
*
|
|
40
|
+
* @param action - The preflight probe, which throws `PreflightRefusalError` while busy
|
|
41
|
+
* @param seconds - The wait budget
|
|
42
|
+
* @param clearNotes - Refusal classes that do not need waiting out
|
|
43
|
+
*/
|
|
44
|
+
export async function waitBeforeLock(action, seconds, clearNotes = []) {
|
|
45
|
+
try {
|
|
46
|
+
await waitForPreflight(async () => {
|
|
47
|
+
try {
|
|
48
|
+
await action();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof PreflightRefusalError && clearNotes.includes(error.note))
|
|
52
|
+
return;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}, seconds);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (!(error instanceof PreflightRefusalError))
|
|
59
|
+
throw error;
|
|
60
|
+
info(`Still busy after the ${seconds ?? 0}-second wait; joining the lock queue and re-checking once under the lock.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the "from" side of a `fireforge rebase` source move.
|
|
3
|
+
*
|
|
4
|
+
* The rebase target is the working `firefox.version` pin, which
|
|
5
|
+
* `fireforge source set` has already moved. The tree the queue was last
|
|
6
|
+
* applied to is the previous pin: the `firefox` block of `fireforge.json`
|
|
7
|
+
* as committed in the project repository. Patch `sourceVersion` stamps are
|
|
8
|
+
* a poor stand-in: patches untouched since an older hop keep their older
|
|
9
|
+
* stamp, so the oldest stamp can trail the real previous tree by several
|
|
10
|
+
* releases. The stamps are only the fallback, for a project that is not a
|
|
11
|
+
* git checkout or whose committed pin already equals the target.
|
|
12
|
+
*/
|
|
13
|
+
import type { FirefoxProduct } from '../types/config.js';
|
|
14
|
+
/** A `firefox.version` / `firefox.product` pair. */
|
|
15
|
+
export interface FirefoxPin {
|
|
16
|
+
version: string;
|
|
17
|
+
product?: FirefoxProduct;
|
|
18
|
+
}
|
|
19
|
+
/** Where the rebase's "from" label came from. */
|
|
20
|
+
export interface RebaseFromVersion extends FirefoxPin {
|
|
21
|
+
/** `committed-pin`: the project's committed `fireforge.json`; `patch-stamps`: the oldest stamp. */
|
|
22
|
+
origin: 'committed-pin' | 'patch-stamps';
|
|
23
|
+
}
|
|
24
|
+
/** Tally of the queue's `sourceVersion` stamps, oldest first. */
|
|
25
|
+
export interface PatchStampSpread {
|
|
26
|
+
versions: string[];
|
|
27
|
+
counts: Map<string, number>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads the `firefox` pin from `fireforge.json` as committed at the project
|
|
31
|
+
* repository's HEAD. `HEAD:./fireforge.json` resolves relative to the
|
|
32
|
+
* project root, so a project nested inside a larger repository works too.
|
|
33
|
+
* Returns `undefined` when there is no repository, no commit, no committed
|
|
34
|
+
* config, or no valid pin in it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readCommittedFirefoxPin(projectRoot: string): Promise<FirefoxPin | undefined>;
|
|
37
|
+
/** Counts the queue's stamps and orders them semantically (a lexical sort put 153.10.0esr before 153.2.0esr). */
|
|
38
|
+
export declare function tallyPatchStamps(stamps: readonly string[]): PatchStampSpread;
|
|
39
|
+
/** `"317 at 153.0esr, 5 at 153.3.0esr"`. */
|
|
40
|
+
export declare function formatPatchStampSpread(spread: PatchStampSpread): string;
|
|
41
|
+
/**
|
|
42
|
+
* Picks the rebase's "from": the committed pin when it differs from the
|
|
43
|
+
* target, otherwise the oldest patch stamp (`oldestStampProduct` is that
|
|
44
|
+
* stamp's recorded product, when any patch carries one).
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveRebaseFromVersion(target: string, committed: FirefoxPin | undefined, spread: PatchStampSpread, oldestStampProduct: FirefoxProduct | undefined): RebaseFromVersion;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the "from" side of a `fireforge rebase` source move.
|
|
4
|
+
*
|
|
5
|
+
* The rebase target is the working `firefox.version` pin, which
|
|
6
|
+
* `fireforge source set` has already moved. The tree the queue was last
|
|
7
|
+
* applied to is the previous pin: the `firefox` block of `fireforge.json`
|
|
8
|
+
* as committed in the project repository. Patch `sourceVersion` stamps are
|
|
9
|
+
* a poor stand-in: patches untouched since an older hop keep their older
|
|
10
|
+
* stamp, so the oldest stamp can trail the real previous tree by several
|
|
11
|
+
* releases. The stamps are only the fallback, for a project that is not a
|
|
12
|
+
* git checkout or whose committed pin already equals the target.
|
|
13
|
+
*/
|
|
14
|
+
import { exec } from '../utils/process.js';
|
|
15
|
+
import { compareFirefoxVersions, isObject, isValidFirefoxProduct, isValidFirefoxVersion, } from '../utils/validation.js';
|
|
16
|
+
/**
|
|
17
|
+
* Reads the `firefox` pin from `fireforge.json` as committed at the project
|
|
18
|
+
* repository's HEAD. `HEAD:./fireforge.json` resolves relative to the
|
|
19
|
+
* project root, so a project nested inside a larger repository works too.
|
|
20
|
+
* Returns `undefined` when there is no repository, no commit, no committed
|
|
21
|
+
* config, or no valid pin in it.
|
|
22
|
+
*/
|
|
23
|
+
export async function readCommittedFirefoxPin(projectRoot) {
|
|
24
|
+
const result = await exec('git', ['show', 'HEAD:./fireforge.json'], { cwd: projectRoot }).catch(() => undefined);
|
|
25
|
+
if (result?.exitCode !== 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(result.stdout);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const firefox = isObject(parsed) ? parsed['firefox'] : undefined;
|
|
35
|
+
if (!isObject(firefox))
|
|
36
|
+
return undefined;
|
|
37
|
+
const version = firefox['version'];
|
|
38
|
+
if (typeof version !== 'string' || !isValidFirefoxVersion(version))
|
|
39
|
+
return undefined;
|
|
40
|
+
const product = firefox['product'];
|
|
41
|
+
return typeof product === 'string' && isValidFirefoxProduct(product)
|
|
42
|
+
? { version, product }
|
|
43
|
+
: { version };
|
|
44
|
+
}
|
|
45
|
+
/** Counts the queue's stamps and orders them semantically (a lexical sort put 153.10.0esr before 153.2.0esr). */
|
|
46
|
+
export function tallyPatchStamps(stamps) {
|
|
47
|
+
const counts = new Map();
|
|
48
|
+
for (const stamp of stamps)
|
|
49
|
+
counts.set(stamp, (counts.get(stamp) ?? 0) + 1);
|
|
50
|
+
return { versions: [...counts.keys()].sort(compareFirefoxVersions), counts };
|
|
51
|
+
}
|
|
52
|
+
/** `"317 at 153.0esr, 5 at 153.3.0esr"`. */
|
|
53
|
+
export function formatPatchStampSpread(spread) {
|
|
54
|
+
return spread.versions.map((v) => `${spread.counts.get(v)} at ${v}`).join(', ');
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Picks the rebase's "from": the committed pin when it differs from the
|
|
58
|
+
* target, otherwise the oldest patch stamp (`oldestStampProduct` is that
|
|
59
|
+
* stamp's recorded product, when any patch carries one).
|
|
60
|
+
*/
|
|
61
|
+
export function resolveRebaseFromVersion(target, committed, spread, oldestStampProduct) {
|
|
62
|
+
if (committed !== undefined && committed.version !== target) {
|
|
63
|
+
return { ...committed, origin: 'committed-pin' };
|
|
64
|
+
}
|
|
65
|
+
const oldest = spread.versions[0] ?? target;
|
|
66
|
+
return {
|
|
67
|
+
version: oldest,
|
|
68
|
+
...(oldestStampProduct !== undefined ? { product: oldestStampProduct } : {}),
|
|
69
|
+
origin: 'patch-stamps',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -43,6 +43,26 @@ export declare function getRunLogDir(projectRoot: string): string;
|
|
|
43
43
|
* @returns The sink, or undefined when no log could be opened
|
|
44
44
|
*/
|
|
45
45
|
export declare function openRunLog(projectRoot: string, command: string, now?: Date): Promise<RunLog | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* Tees a run log into a caller-chosen copy (`--keep-log`,
|
|
48
|
+
* {@link LOG_KEEP_ENV_VAR}) that retention never prunes.
|
|
49
|
+
*
|
|
50
|
+
* `.fireforge/logs/` keeps twenty logs per command kind for EVERY session
|
|
51
|
+
* on the checkout, so on a shared checkout a log named by a verdict line
|
|
52
|
+
* can be gone within the hour, and a failure recorded only by its log path
|
|
53
|
+
* cannot be read later. The copy is written live, line for line with the
|
|
54
|
+
* same redaction, rather than copied at the end: a killed run keeps what it
|
|
55
|
+
* had written, like the primary log does.
|
|
56
|
+
*
|
|
57
|
+
* The primary path stays the one `log=` names. When the primary could not
|
|
58
|
+
* be opened the copy stands in for it.
|
|
59
|
+
*
|
|
60
|
+
* @param log - The primary run log, if one opened
|
|
61
|
+
* @param flag - The `--keep-log` value, if any
|
|
62
|
+
* @param command - Command kind, naming a copy that has no primary
|
|
63
|
+
* @returns A sink writing to both, or whichever one opened
|
|
64
|
+
*/
|
|
65
|
+
export declare function keepRunLogCopy(log: RunLog | undefined, flag: string | undefined, command: string): Promise<RunLog | undefined>;
|
|
46
66
|
/** Installs (or clears, with `undefined`) the run log for this process. */
|
|
47
67
|
export declare function setActiveRunLog(log: RunLog | undefined): void;
|
|
48
68
|
/** Absolute path of the active run log, or undefined when none is open. */
|
package/dist/src/core/run-log.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* exactly the behaviour that existed before this module.
|
|
21
21
|
*/
|
|
22
22
|
import { createWriteStream } from 'node:fs';
|
|
23
|
-
import { mkdir, readdir, unlink } from 'node:fs/promises';
|
|
24
|
-
import { join } from 'node:path';
|
|
23
|
+
import { mkdir, readdir, stat, unlink } from 'node:fs/promises';
|
|
24
|
+
import { basename as basenameOf, dirname, join, resolve } from 'node:path';
|
|
25
25
|
import { Writable } from 'node:stream';
|
|
26
26
|
import { toError } from '../utils/errors.js';
|
|
27
27
|
import { verbose } from '../utils/logger.js';
|
|
@@ -107,11 +107,27 @@ async function pruneRunLogs(dir, command) {
|
|
|
107
107
|
*/
|
|
108
108
|
export async function openRunLog(projectRoot, command, now = new Date()) {
|
|
109
109
|
const dir = getRunLogDir(projectRoot);
|
|
110
|
-
let stream;
|
|
111
110
|
const path = join(dir, `${command}-${fileSafeTimestamp(now)}.log`);
|
|
112
111
|
try {
|
|
113
112
|
await mkdir(dir, { recursive: true });
|
|
114
113
|
await pruneRunLogs(dir, command);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
verbose(`Run log unavailable (${path}): ${toError(error).message}`);
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return openRunLogSink(path);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Opens one redacting log file at `path`, creating its directory.
|
|
123
|
+
*
|
|
124
|
+
* @param path - Absolute file path
|
|
125
|
+
* @returns The sink, or undefined when the file could not be opened
|
|
126
|
+
*/
|
|
127
|
+
async function openRunLogSink(path) {
|
|
128
|
+
let stream;
|
|
129
|
+
try {
|
|
130
|
+
await mkdir(dirname(path), { recursive: true });
|
|
115
131
|
stream = createWriteStream(path, { flags: 'a' });
|
|
116
132
|
}
|
|
117
133
|
catch (error) {
|
|
@@ -172,6 +188,76 @@ export async function openRunLog(projectRoot, command, now = new Date()) {
|
|
|
172
188
|
},
|
|
173
189
|
};
|
|
174
190
|
}
|
|
191
|
+
/** Environment variable naming where every run's log is also kept. */
|
|
192
|
+
const LOG_KEEP_ENV_VAR = 'FIREFORGE_LOG_KEEP';
|
|
193
|
+
/**
|
|
194
|
+
* Where a run's kept copy goes: `--keep-log <path>` when given, else
|
|
195
|
+
* {@link LOG_KEEP_ENV_VAR}, else nowhere. A value that ends in a path
|
|
196
|
+
* separator or names an existing directory receives the log under its own
|
|
197
|
+
* `.fireforge/logs/` basename, so a session-wide directory collects one
|
|
198
|
+
* file per run. Any other value is the file itself. Relative values resolve
|
|
199
|
+
* against the caller's working directory.
|
|
200
|
+
*
|
|
201
|
+
* @param flag - The `--keep-log` value, if any
|
|
202
|
+
* @param basename - The run log's own file name
|
|
203
|
+
* @returns The absolute destination, or undefined when none was asked for
|
|
204
|
+
*/
|
|
205
|
+
async function resolveRunLogKeepPath(flag, basename) {
|
|
206
|
+
const raw = flag ?? process.env[LOG_KEEP_ENV_VAR]?.trim();
|
|
207
|
+
if (raw === undefined || raw.length === 0)
|
|
208
|
+
return undefined;
|
|
209
|
+
const target = resolve(raw);
|
|
210
|
+
let directory = /[\\/]$/.test(raw);
|
|
211
|
+
if (!directory) {
|
|
212
|
+
try {
|
|
213
|
+
directory = (await stat(target)).isDirectory();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
directory = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return directory ? join(target, basename) : target;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Tees a run log into a caller-chosen copy (`--keep-log`,
|
|
223
|
+
* {@link LOG_KEEP_ENV_VAR}) that retention never prunes.
|
|
224
|
+
*
|
|
225
|
+
* `.fireforge/logs/` keeps twenty logs per command kind for EVERY session
|
|
226
|
+
* on the checkout, so on a shared checkout a log named by a verdict line
|
|
227
|
+
* can be gone within the hour, and a failure recorded only by its log path
|
|
228
|
+
* cannot be read later. The copy is written live, line for line with the
|
|
229
|
+
* same redaction, rather than copied at the end: a killed run keeps what it
|
|
230
|
+
* had written, like the primary log does.
|
|
231
|
+
*
|
|
232
|
+
* The primary path stays the one `log=` names. When the primary could not
|
|
233
|
+
* be opened the copy stands in for it.
|
|
234
|
+
*
|
|
235
|
+
* @param log - The primary run log, if one opened
|
|
236
|
+
* @param flag - The `--keep-log` value, if any
|
|
237
|
+
* @param command - Command kind, naming a copy that has no primary
|
|
238
|
+
* @returns A sink writing to both, or whichever one opened
|
|
239
|
+
*/
|
|
240
|
+
export async function keepRunLogCopy(log, flag, command) {
|
|
241
|
+
const basename = log === undefined ? `${command}-${fileSafeTimestamp(new Date())}.log` : basenameOf(log.path);
|
|
242
|
+
const keepPath = await resolveRunLogKeepPath(flag, basename);
|
|
243
|
+
if (keepPath === undefined || keepPath === log?.path)
|
|
244
|
+
return log;
|
|
245
|
+
const copy = await openRunLogSink(keepPath);
|
|
246
|
+
if (copy === undefined)
|
|
247
|
+
return log;
|
|
248
|
+
if (log === undefined)
|
|
249
|
+
return copy;
|
|
250
|
+
return {
|
|
251
|
+
path: log.path,
|
|
252
|
+
write: (chunk) => {
|
|
253
|
+
log.write(chunk);
|
|
254
|
+
copy.write(chunk);
|
|
255
|
+
},
|
|
256
|
+
close: async () => {
|
|
257
|
+
await Promise.all([log.close(), copy.close()]);
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
175
261
|
/**
|
|
176
262
|
* The run log for the command currently executing, if any.
|
|
177
263
|
*
|
|
@@ -17,6 +17,13 @@ export interface RunProfileRequest {
|
|
|
17
17
|
profile?: string | undefined;
|
|
18
18
|
/** `--temp-profile`: launch with a fresh directory removed afterwards. */
|
|
19
19
|
tempProfile?: boolean | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a named `profile` launches with `-purgecaches` (default `true`).
|
|
22
|
+
* A UI build does not change the build id the profile's startup cache is
|
|
23
|
+
* keyed on, so without it a kept profile runs the chrome scripts an
|
|
24
|
+
* earlier build cached. Temporary profiles start without a cache.
|
|
25
|
+
*/
|
|
26
|
+
purgeCaches?: boolean | undefined;
|
|
20
27
|
/**
|
|
21
28
|
* Whether this is a `--smoke-exit` run. A smoke run with neither flag gets
|
|
22
29
|
* a temporary profile: the step exists to prove what the build does on a
|
|
@@ -68,7 +68,13 @@ export async function resolveRunProfile(request) {
|
|
|
68
68
|
}
|
|
69
69
|
if (request.profile !== undefined) {
|
|
70
70
|
const dir = resolve(request.profile);
|
|
71
|
-
|
|
71
|
+
const purge = request.purgeCaches === false ? [] : ['-purgecaches'];
|
|
72
|
+
return {
|
|
73
|
+
args: ['-profile', dir, ...purge],
|
|
74
|
+
dir,
|
|
75
|
+
temporary: false,
|
|
76
|
+
dispose: NO_PROFILE.dispose,
|
|
77
|
+
};
|
|
72
78
|
}
|
|
73
79
|
if (request.tempProfile !== true && !request.smoke) {
|
|
74
80
|
return NO_PROFILE;
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* allowlisted hits separately from raw error matches for its summary.
|
|
18
18
|
*/
|
|
19
19
|
export declare function matchesSmokeError(line: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Returns `true` when `line` is console output from the browser rather than
|
|
22
|
+
* mach's own timestamped log. A blank line is neither.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isBrowserConsoleLine(line: string): boolean;
|
|
20
25
|
/**
|
|
21
26
|
* One compiled allowlist entry with its provenance retained so the smoke
|
|
22
27
|
* summary can attribute hits per entry. An entry that silently
|
|
@@ -56,6 +56,19 @@ export function matchesSmokeError(line) {
|
|
|
56
56
|
}
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Mach's own log lines carry an elapsed-time prefix (` 0:00.42 <message>`),
|
|
61
|
+
* the first of which echoes the browser command line. They prove mach ran,
|
|
62
|
+
* not that the browser printed anything.
|
|
63
|
+
*/
|
|
64
|
+
const MACH_LOG_LINE = /^\s*\d+:\d{2}\.\d{2} /;
|
|
65
|
+
/**
|
|
66
|
+
* Returns `true` when `line` is console output from the browser rather than
|
|
67
|
+
* mach's own timestamped log. A blank line is neither.
|
|
68
|
+
*/
|
|
69
|
+
export function isBrowserConsoleLine(line) {
|
|
70
|
+
return line.trim() !== '' && !MACH_LOG_LINE.test(line);
|
|
71
|
+
}
|
|
59
72
|
/** `# max-hits: N` on its own comment line, case-insensitive. */
|
|
60
73
|
const MAX_HITS_DIRECTIVE = /^#\s*max-hits:\s*(\S+)\s*$/i;
|
|
61
74
|
function parseMaxHitsDirective(raw, at) {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* reporting phantom test failures (or phantom passes).
|
|
23
23
|
*/
|
|
24
24
|
export type { HarnessCrashSignature } from './test-harness-signature.js';
|
|
25
|
-
import type { HarnessCrashSignature } from './test-harness-signature.js';
|
|
25
|
+
import type { HarnessCrashSignature, IncompleteRunEvidence } from './test-harness-signature.js';
|
|
26
26
|
export { headedDisplayAsleepVerdictNote, headedNoOutputTimeoutHint } from './test-stall-triage.js';
|
|
27
27
|
/** How a completed harness run should be interpreted. */
|
|
28
28
|
export type HarnessRunClassification = 'tests-ran-ok' | 'test-failures' | 'harness-crash' | 'harness-arguments' | 'no-tests';
|
|
@@ -71,6 +71,12 @@ export interface HarnessRunVerdict extends HarnessSummaryCounts {
|
|
|
71
71
|
* only. The status and reason keys are unaffected.
|
|
72
72
|
*/
|
|
73
73
|
note?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Set when a mach-exit-0 run was refused its pass because it is
|
|
76
|
+
* incomplete or reported unexpected results. See
|
|
77
|
+
* `test-run-completeness.ts`.
|
|
78
|
+
*/
|
|
79
|
+
incomplete?: IncompleteRunEvidence;
|
|
74
80
|
}
|
|
75
81
|
/** Why a green-looking embedded summary was not trusted. */
|
|
76
82
|
export interface GreenSummaryRejection {
|
|
@@ -81,6 +87,7 @@ export interface GreenSummaryRejection {
|
|
|
81
87
|
/** Files whose `TEST_START` has no matching end marker. */
|
|
82
88
|
neverEnded: string[];
|
|
83
89
|
}
|
|
90
|
+
export declare const SHUTDOWN_REENTRY_PATTERN: RegExp;
|
|
84
91
|
/**
|
|
85
92
|
* Strips non-signal noise from captured output before crash-signature
|
|
86
93
|
* matching: resource-monitor degradation warnings, and traceback blocks
|
|
@@ -108,6 +115,16 @@ export declare function findCrashMarkerLine(output: string): string | undefined;
|
|
|
108
115
|
* Exported for direct unit testing.
|
|
109
116
|
*/
|
|
110
117
|
export declare function hasCompletedGreenSummary(output: string): boolean;
|
|
118
|
+
/** Basename shape of a mozbuild-manifest test implementation file. */
|
|
119
|
+
export declare const TEST_FILE_BASENAME_PATTERN: RegExp;
|
|
120
|
+
/**
|
|
121
|
+
* Start/end execution markers, both log dialects: the structured mozlog
|
|
122
|
+
* `TEST_START`/`TEST_END` pair and the human browser-chrome
|
|
123
|
+
* `TEST-START`/`TEST-OK` pair. `TEST-UNEXPECTED-*` end shapes are
|
|
124
|
+
* irrelevant here, since any such line already fails the green-summary check
|
|
125
|
+
* before completeness is consulted.
|
|
126
|
+
*/
|
|
127
|
+
export declare const START_MARKER_PATTERN: RegExp;
|
|
111
128
|
/**
|
|
112
129
|
* Cross-checks run completeness against the requested test files: every
|
|
113
130
|
* requested file must produce a start marker, and every started test must
|
|
@@ -76,7 +76,7 @@ const FAIL_LINE_PATTERN = /^.*\bTEST-(?!KNOWN-FAIL\b)[A-Z-]*FAIL\b.*$/gm;
|
|
|
76
76
|
* test failure, pointing the reader at the wrong line.
|
|
77
77
|
*/
|
|
78
78
|
const ASSERTION_LINE_PATTERN = /^.*(?:Assertion failure:|MOZ_ASSERT\(|###!!! ASSERTION).*$/gm;
|
|
79
|
-
const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
|
|
79
|
+
export const SHUTDOWN_REENTRY_PATTERN = /Application shut down \(without crashing\) in the middle of a test/i;
|
|
80
80
|
const FOCUS_STALL_PATTERN = /must wait for focus/i;
|
|
81
81
|
const TRACEBACK_PATTERN = /Traceback \(most recent call last\)/;
|
|
82
82
|
const NO_OUTPUT_TIMEOUT_PATTERN = /timed out after \d+ seconds with no output/i;
|
|
@@ -248,7 +248,7 @@ export function hasCompletedGreenSummary(output) {
|
|
|
248
248
|
return hasGreenShapedSummary(output) && findCrashMarkerLine(output) === undefined;
|
|
249
249
|
}
|
|
250
250
|
/** Basename shape of a mozbuild-manifest test implementation file. */
|
|
251
|
-
const TEST_FILE_BASENAME_PATTERN = /^(?:browser|test)_.*\.m?js$/;
|
|
251
|
+
export const TEST_FILE_BASENAME_PATTERN = /^(?:browser|test)_.*\.m?js$/;
|
|
252
252
|
/**
|
|
253
253
|
* Start/end execution markers, both log dialects: the structured mozlog
|
|
254
254
|
* `TEST_START`/`TEST_END` pair and the human browser-chrome
|
|
@@ -256,7 +256,7 @@ const TEST_FILE_BASENAME_PATTERN = /^(?:browser|test)_.*\.m?js$/;
|
|
|
256
256
|
* irrelevant here, since any such line already fails the green-summary check
|
|
257
257
|
* before completeness is consulted.
|
|
258
258
|
*/
|
|
259
|
-
const START_MARKER_PATTERN = /\bTEST[-_]START\b[:| ]*\s*(\S+)?/;
|
|
259
|
+
export const START_MARKER_PATTERN = /\bTEST[-_]START\b[:| ]*\s*(\S+)?/;
|
|
260
260
|
const END_MARKER_PATTERN = /\bTEST[-_]END\b|\bTEST-OK\b/;
|
|
261
261
|
/**
|
|
262
262
|
* Cross-checks run completeness against the requested test files: every
|
|
@@ -660,12 +660,14 @@ export function formatFireforgeVerdictLine(verdict, shards) {
|
|
|
660
660
|
const counts = (verdict.checks !== undefined ? ` checks=${verdict.checks}` : '') +
|
|
661
661
|
(verdict.unexpected !== undefined ? ` unexpected=${verdict.unexpected}` : '');
|
|
662
662
|
const shardSuffix = shards ? ` shards=${shards.passed}/${shards.total}` : '';
|
|
663
|
+
const { started: n, requested: m, firstUnstarted: first } = verdict.incomplete ?? {};
|
|
664
|
+
const truncation = first === undefined ? '' : ` started=${n}/${m} first-unstarted=${first}`;
|
|
663
665
|
const note = verdict.note !== undefined ? ` (${verdict.note})` : '';
|
|
664
666
|
if (verdict.kind === 'tests-ran-ok') {
|
|
665
667
|
return `FIREFORGE-VERDICT: PASS${counts}${shardSuffix}${note}`;
|
|
666
668
|
}
|
|
667
669
|
const reason = verdict.kind === 'harness-crash' ? 'crash' : verdict.kind;
|
|
668
|
-
return `FIREFORGE-VERDICT: FAIL reason=${reason}${counts}${shardSuffix}${note}`;
|
|
670
|
+
return `FIREFORGE-VERDICT: FAIL reason=${reason}${counts}${truncation}${shardSuffix}${note}`;
|
|
669
671
|
}
|
|
670
672
|
/**
|
|
671
673
|
* Builds the operator-facing failure message for a green-looking summary
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* graph rather than the emitted code, and a shared leaf is a truer
|
|
9
9
|
* description of the relationship than either direction of the cycle was:
|
|
10
10
|
* neither module owns this type more than the other.
|
|
11
|
+
*
|
|
12
|
+
* {@link IncompleteRunEvidence} lives here for the same reason:
|
|
13
|
+
* `test-run-completeness.ts` produces it from helpers in
|
|
14
|
+
* `test-harness-crash.ts`, whose verdict type carries it.
|
|
11
15
|
*/
|
|
12
16
|
/** A recognized harness-crash shape with its evidence line. */
|
|
13
17
|
export interface HarnessCrashSignature {
|
|
@@ -16,3 +20,16 @@ export interface HarnessCrashSignature {
|
|
|
16
20
|
/** The concrete output line the recognition was based on. */
|
|
17
21
|
line: string;
|
|
18
22
|
}
|
|
23
|
+
/** Why an exit-0 run that would have read as a pass was failed instead. */
|
|
24
|
+
export interface IncompleteRunEvidence {
|
|
25
|
+
/** Requested test files that produced a start (or skip) marker. */
|
|
26
|
+
started: number;
|
|
27
|
+
/** Requested test files the run could account for (test-file-shaped paths). */
|
|
28
|
+
requested: number;
|
|
29
|
+
/** First requested file, in dispatch order, that never started. */
|
|
30
|
+
firstUnstarted?: string;
|
|
31
|
+
/** The harness's "shut down in the middle of a test" line, when present. */
|
|
32
|
+
shutdownLine?: string;
|
|
33
|
+
/** The summary's non-zero `Unexpected results:` count, when it printed one. */
|
|
34
|
+
unexpected?: number;
|
|
35
|
+
}
|
|
@@ -9,5 +9,9 @@
|
|
|
9
9
|
* graph rather than the emitted code, and a shared leaf is a truer
|
|
10
10
|
* description of the relationship than either direction of the cycle was:
|
|
11
11
|
* neither module owns this type more than the other.
|
|
12
|
+
*
|
|
13
|
+
* {@link IncompleteRunEvidence} lives here for the same reason:
|
|
14
|
+
* `test-run-completeness.ts` produces it from helpers in
|
|
15
|
+
* `test-harness-crash.ts`, whose verdict type carries it.
|
|
12
16
|
*/
|
|
13
17
|
export {};
|